SynEdit: Refactor tab-expander

This commit is contained in:
Martin 2021-08-04 15:10:29 +02:00
parent feae93368d
commit 10344c6439

View File

@ -30,68 +30,126 @@ uses
type
// lines longer than 16383 chars, will be stored as unknown
TLineLen = Word;
PLineLen = ^TLineLen;
// lines longer than "High(TLineLen) div 2" chars, will be stored as unknown
TStoredLineLen = DWord;
PStoredLineLen = ^TStoredLineLen;
{ TSynEditStringTabData }
{ TSynEditStringLineLenData }
TSynEditStringTabData = class(TSynManagedStorageMem)
TSynEditStringLineLenData = class(TSynManagedStorageMem)
private
FRefCount: Integer;
function GetLineLen(Index: Integer): TLineLen;
procedure SetLineLen(Index: Integer; const AValue: TLineLen);
procedure SetLineLen(Index: Integer; const AValue: TStoredLineLen);
protected
function GetLineLen(Index: Integer): TStoredLineLen; virtual;
public
constructor Create;
procedure IncRefCount;
procedure DecRefCount;
property RefCount: Integer read FRefCount;
property LineLen[Index: Integer]: TLineLen read GetLineLen write SetLineLen; default;
property LineLen[Index: Integer]: TStoredLineLen read GetLineLen write SetLineLen; default;
end;
{ TSynEditStringTabExpander }
{ TSynEditStringTabData }
TSynEditStringTabExpander = class(TSynEditStringsLinked)
TSynEditStringTabData = class(TSynEditStringLineLenData)
private const
// Offset to add to LengthOfLine, if Line has no tabs.
// (Length will still be valid if tab-width changes)
NO_TAB_IN_LINE_OFFSET = TStoredLineLen(
( ( (high(TStoredLineLen) >> 1) and high(Integer) ) << 1) and
not ( (high(TStoredLineLen) >> 1) and high(Integer) )
);
MAX_LINE_LEN_STORED = NO_TAB_IN_LINE_OFFSET - 2;
LINE_INFO_UNKNOWN = 0; // invalid entry
public const
LINE_LEN_UNKNOWN = TStoredLineLen(NO_TAB_IN_LINE_OFFSET- 1);
(* Special values
- LINE_INFO_UNKNOWN : Nothing know, not even if tab exists
- LINE_LEN_UNKNOWN : Tab exists, but nothing else known (an empty line can't have tabs, so 0 can be used as marker)
- LINE_LEN_UNKNOWN + NO_TAB_IN_LINE_OFFSET : NO Tab exists, but nothing else known
*)
private
function GetHasTab(Index: Integer): Boolean; inline;
function GetRawData(Index: Integer): TStoredLineLen;
protected
function GetLineLen(Index: Integer): TStoredLineLen; override;
public
procedure SetLineInfo(AnIndex: Integer; ALineLen: Integer; AnHasTab: Boolean); inline;
procedure SetLineInfoUnknown(AnIndex: Integer); inline;
procedure SetLineInfoUnknownEx(AnIndex: Integer; AnHasTab: Boolean); inline;
function GetLineInfo(AnIndex: Integer; out ALineLen: Integer; out AnHasTab: Boolean): boolean; inline;
property HasTab[Index: Integer]: Boolean read GetHasTab;
property LineLen[Index: Integer]: TStoredLineLen read GetLineLen; default;
property RawData[Index: Integer]: TStoredLineLen read GetRawData;
end;
const
LINE_LEN_UNKNOWN = TSynEditStringTabData.LINE_LEN_UNKNOWN;
type
{ TSynEditStringTabExpanderBase }
TSynEditStringTabExpanderBase = class(TSynEditStringsLinked)
protected
function GetTabWidth : integer; virtual; abstract;
procedure SetTabWidth(const AValue : integer); virtual; abstract;
public
constructor Create; virtual;
property TabWidth: integer read GetTabWidth write SetTabWidth;
end;
{ TSynEditStringTabExpanderCommon }
TSynEditStringTabExpanderCommon = class(TSynEditStringTabExpanderBase)
private
FTabData: TSynEditStringTabData;
procedure ReleaseTabData;
protected
procedure TextBufferChanged(Sender: TObject); virtual;
procedure LineTextChanged(Sender: TSynEditStrings; aIndex, aCount: Integer); virtual;
procedure SetManager(AManager: TSynTextViewsManager); override;
procedure SetSynStrings(AValue: TSynEditStrings); override;
property TabData: TSynEditStringTabData read FTabData;
public
destructor Destroy; override;
end;
TSynEditStringTabExpander = class(TSynEditStringTabExpanderCommon)
private
FTabWidth: integer;
FIndexOfLongestLine: Integer;
FFirstUnknownLongestLine, FLastUnknownLongestLine: Integer;
FTabData: TSynEditStringTabData;
FLastLineHasTab: Boolean; // Last line, parsed by GetPhysicalCharWidths
FLastLinePhysLen: Integer;
FViewChangeStamp: int64;
procedure TextBufferChanged(Sender: TObject);
procedure LineTextChanged(Sender: TSynEditStrings; aIndex, aCount: Integer);
procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
function ExpandedString(Index: integer): string;
function ExpandedStringLength(Index: integer): Integer;
protected
procedure LineTextChanged(Sender: TSynEditStrings; aIndex, aCount: Integer); override;
procedure TextBufferChanged(Sender: TObject); override;
procedure SetManager(AManager: TSynTextViewsManager); override;
procedure SetSynStrings(AValue: TSynEditStrings); override;
function GetViewChangeStamp: int64; override;
function GetTabWidth : integer;
procedure SetTabWidth(const AValue : integer);
function GetTabWidth : integer; override;
procedure SetTabWidth(const AValue : integer); override;
function GetExpandedString(Index: integer): string; override;
function GetLengthOfLongestLine: integer; override;
procedure DoGetPhysicalCharWidths(Line: PChar; LineLen, Index: Integer; PWidths: PPhysicalCharWidth); override;
public
constructor Create;
destructor Destroy; override;
property LengthOfLongestLine: integer read GetLengthOfLongestLine;
public
property TabWidth: integer read GetTabWidth write SetTabWidth;
constructor Create; override;
end;
TSynEditStringTabExpanderClass = class of TSynEditStringTabExpanderBase;
implementation
const
// Offset to add to LengthOfLine, if Line has no tabs.
// (Length will still be valid if tab-width changes)
NO_TAB_IN_LINE_OFFSET = high(TLineLen) div 2;
LINE_LEN_UNKNOWN = high(TLineLen);
MAX_LINE_LEN_STORED = NO_TAB_IN_LINE_OFFSET - 1;
function GetHasTabs(pLine: PChar): boolean;
begin
@ -105,35 +163,202 @@ begin
Result := FALSE;
end;
{ TSynEditStringTabData }
{ TSynEditStringLineLenData }
function TSynEditStringTabData.GetLineLen(Index: Integer): TLineLen;
function TSynEditStringLineLenData.GetLineLen(Index: Integer): TStoredLineLen;
begin
Result := PLineLen(ItemPointer[Index])^;
Result := PStoredLineLen(ItemPointer[Index])^;
end;
procedure TSynEditStringTabData.SetLineLen(Index: Integer; const AValue: TLineLen);
procedure TSynEditStringLineLenData.SetLineLen(Index: Integer; const AValue: TStoredLineLen);
begin
PLineLen(ItemPointer[Index])^ := AValue;
PStoredLineLen(ItemPointer[Index])^ := AValue;
end;
constructor TSynEditStringTabData.Create;
constructor TSynEditStringLineLenData.Create;
begin
inherited;
ItemSize := SizeOf(TLineLen);
ItemSize := SizeOf(TStoredLineLen);
FRefCount := 1;
end;
procedure TSynEditStringTabData.IncRefCount;
procedure TSynEditStringLineLenData.IncRefCount;
begin
inc(FRefCount);
end;
procedure TSynEditStringTabData.DecRefCount;
procedure TSynEditStringLineLenData.DecRefCount;
begin
dec(FRefCount);
end;
{ TSynEditStringTabData }
function TSynEditStringTabData.GetHasTab(Index: Integer): Boolean;
var
l: TStoredLineLen;
begin
l := inherited GetLineLen(Index);
Result := (l < NO_TAB_IN_LINE_OFFSET) and (l <> LINE_INFO_UNKNOWN);
end;
function TSynEditStringTabData.GetRawData(Index: Integer): TStoredLineLen;
begin
Result := inherited GetLineLen(Index);
end;
function TSynEditStringTabData.GetLineLen(Index: Integer): TStoredLineLen;
begin
Result := inherited GetLineLen(Index);
if Result = LINE_INFO_UNKNOWN then
Result := LINE_LEN_UNKNOWN // unkonwn len, but tabs exist
else
if (Result >= NO_TAB_IN_LINE_OFFSET) then
Result := Result - NO_TAB_IN_LINE_OFFSET;
end;
procedure TSynEditStringTabData.SetLineInfo(AnIndex: Integer;
ALineLen: Integer; AnHasTab: Boolean);
begin
assert((not AnHasTab) or (ALineLen>0), 'TSynEditStringTabData.SetLineInfo: (not AnHasTab) or (ALineLen>0)');
if ALineLen > MAX_LINE_LEN_STORED then
ALineLen := LINE_LEN_UNKNOWN;
if AnHasTab then
inherited SetLineLen(AnIndex, ALineLen)
else
inherited SetLineLen(AnIndex, ALineLen + NO_TAB_IN_LINE_OFFSET);
end;
procedure TSynEditStringTabData.SetLineInfoUnknown(AnIndex: Integer);
begin
inherited SetLineLen(AnIndex, LINE_INFO_UNKNOWN);
end;
procedure TSynEditStringTabData.SetLineInfoUnknownEx(AnIndex: Integer;
AnHasTab: Boolean);
begin
if AnHasTab then
inherited SetLineLen(AnIndex, LINE_LEN_UNKNOWN)
else
inherited SetLineLen(AnIndex, LINE_LEN_UNKNOWN + NO_TAB_IN_LINE_OFFSET);
end;
function TSynEditStringTabData.GetLineInfo(AnIndex: Integer; out
ALineLen: Integer; out AnHasTab: Boolean): boolean;
var
d: TStoredLineLen;
begin
d := inherited GetLineLen(AnIndex);
Result := d <> LINE_INFO_UNKNOWN;
AnHasTab := Result and (d < NO_TAB_IN_LINE_OFFSET);
if not Result then
ALineLen := LINE_LEN_UNKNOWN
else
if (d >= NO_TAB_IN_LINE_OFFSET) then
ALineLen := d - NO_TAB_IN_LINE_OFFSET
else
ALineLen := d;
end;
{ TSynEditStringTabExpanderBase }
constructor TSynEditStringTabExpanderBase.Create;
begin
inherited Create;
end;
{ TSynEditStringTabExpanderCommon }
procedure TSynEditStringTabExpanderCommon.ReleaseTabData;
var
Data: TSynEditStringLineLenData;
begin
Data := TSynEditStringTabData(NextLines.Ranges[Self]);
if Assigned(Data) then begin
Data.DecRefCount;
if Data.RefCount = 0 then begin
NextLines.Ranges[Self] := nil;
Data.Free;
end;
end;
FTabData := nil;
end;
procedure TSynEditStringTabExpanderCommon.TextBufferChanged(Sender: TObject);
var
Data: TSynEditStringLineLenData;
i: integer;
begin
// Using self, instead as class, to register tab-width-data
// other shared edits can have different tab-width
if (Sender <> nil) and
(FTabData = TSynEditStringLineLenData(NextLines.Ranges[Self]))
then
exit;
if Sender <> nil then begin
Data := TSynEditStringLineLenData(TSynEditStrings(Sender).Ranges[Self]);
if Assigned(Data) then begin
Data.DecRefCount;
if Data.RefCount = 0 then begin
TSynEditStrings(Sender).Ranges[Self] := nil;
Data.Free;
end;
end;
end;
FTabData := TSynEditStringTabData(NextLines.Ranges[Self]);
if FTabData = nil then begin
FTabData := TSynEditStringTabData.Create;
NextLines.Ranges[Self] := FTabData;
end
else
FTabData.IncRefCount;
end;
procedure TSynEditStringTabExpanderCommon.LineTextChanged(
Sender: TSynEditStrings; aIndex, aCount: Integer);
var
i: Integer;
begin
for i := AIndex to AIndex + ACount - 1 do
TabData.SetLineInfoUnknown(i);
end;
procedure TSynEditStringTabExpanderCommon.SetManager(
AManager: TSynTextViewsManager);
begin
if Manager <> nil then begin
RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
RemoveChangeHandler(senrLineChange, @LineTextChanged);
end;
if AManager = nil then
ReleaseTabData;
inherited SetManager(AManager);
if Manager <> nil then begin
AddChangeHandler(senrLineChange, @LineTextChanged);
AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
end;
end;
procedure TSynEditStringTabExpanderCommon.SetSynStrings(AValue: TSynEditStrings
);
begin
inherited SetSynStrings(AValue);
if NextLines <> nil then begin
if TabData = nil then
TextBufferChanged(nil);
end;
end;
destructor TSynEditStringTabExpanderCommon.Destroy;
begin
ReleaseTabData;
inherited Destroy;
NextLines := nil;
end;
{ TSynEditStringTabExpander }
constructor TSynEditStringTabExpander.Create;
@ -145,22 +370,6 @@ begin
TabWidth := 8;
end;
destructor TSynEditStringTabExpander.Destroy;
var
Data: TSynEditStringTabData;
begin
Data := TSynEditStringTabData(NextLines.Ranges[Self]);
if Assigned(Data) then begin
Data.DecRefCount;
if Data.RefCount = 0 then begin
NextLines.Ranges[Self] := nil;
Data.Free;
end;
end;
NextLines := nil;
inherited Destroy;
end;
function TSynEditStringTabExpander.GetTabWidth: integer;
begin
Result := FTabWidth;
@ -189,58 +398,25 @@ begin
end;
procedure TSynEditStringTabExpander.TextBufferChanged(Sender: TObject);
var
Data: TSynEditStringTabData;
i: integer;
begin
// Using self, instead as class, to register tab-width-data
// other shared edits can have different tab-width
if (Sender <> nil) and
(FTabData = TSynEditStringTabData(NextLines.Ranges[Self]))
then
exit;
inherited TextBufferChanged(Sender);
if Sender <> nil then begin
Data := TSynEditStringTabData(TSynEditStrings(Sender).Ranges[Self]);
if Assigned(Data) then begin
Data.DecRefCount;
if Data.RefCount = 0 then begin
TSynEditStrings(Sender).Ranges[Self] := nil;
Data.Free;
end;
end;
end;
FTabData := TSynEditStringTabData(NextLines.Ranges[Self]);
if FTabData = nil then begin
FTabData := TSynEditStringTabData.Create;
NextLines.Ranges[Self] := FTabData;
for i := 0 to Count - 1 do
if not(FTabData[i] >= NO_TAB_IN_LINE_OFFSET) then
FTabData[i] := LINE_LEN_UNKNOWN;
end
else
FTabData.IncRefCount;
LineTextChanged(TSynEditStrings(Sender), 0, Count);
end;
procedure TSynEditStringTabExpander.LineTextChanged(Sender: TSynEditStrings; aIndex,
aCount: Integer);
var
i: integer;
begin
inherited LineTextChanged(Sender, aIndex, aCount);
if (FIndexOfLongestLine >= AIndex) and (FIndexOfLongestLine < AIndex+ACount) then
FIndexOfLongestLine := -1;
if (FFirstUnknownLongestLine < 0) or (AIndex < FFirstUnknownLongestLine) then
FFirstUnknownLongestLine := AIndex;
if AIndex+ACount-1 > FLastUnknownLongestLine then
FLastUnknownLongestLine := AIndex+ACount-1;
for i := AIndex to AIndex + ACount - 1 do
FTabData[i] := LINE_LEN_UNKNOWN;
end;
procedure TSynEditStringTabExpander.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer);
var
i: integer;
begin
if (FIndexOfLongestLine >= AIndex) then
FIndexOfLongestLine := FIndexOfLongestLine + ACount;
@ -262,8 +438,6 @@ begin
FFirstUnknownLongestLine := AIndex;
if (AIndex < FLastUnknownLongestLine) or (FLastUnknownLongestLine < 0) then
FLastUnknownLongestLine := Max(AIndex, FLastUnknownLongestLine) +ACount;
for i := AIndex to AIndex + ACount - 1 do
FTabData[i] := LINE_LEN_UNKNOWN;
end;
function TSynEditStringTabExpander.ExpandedString(Index: integer): string;
@ -271,13 +445,14 @@ var
Line: String;
CharWidths: TPhysicalCharWidths;
i, j, l: Integer;
HasTab: Boolean;
begin
// this is only used by trimmer.lengthOfLongestLine / which is not called, if a tab module is present
Line := NextLines[Index];
if (Line = '') or (not GetHasTabs(PChar(Line))) then begin
Result := Line;
// xxx wrong double width // none latin ...
//FTabData[Index] := length(Result) + NO_TAB_IN_LINE_OFFSET;
//TabData.SetLineInfo(Index, Length(Result, False));
end else begin
CharWidths := GetPhysicalCharWidths(Pchar(Line), length(Line), Index);
l := 0;
@ -286,18 +461,20 @@ begin
SetLength(Result, l);
l := 1;
HasTab := False;
for i := 1 to length(CharWidths) do begin
if Line[i] <> #9 then begin
Result[l] := Line[i];
inc(l);
end else begin
HasTab := True;
for j := 1 to (CharWidths[i-1] and PCWMask) do begin
Result[l] := ' ';
inc(l);
end;
end;
end;
FTabData[Index] := length(Result);
TabData.SetLineInfo(Index, Length(Result), HasTab);
end;
end;
@ -310,7 +487,7 @@ begin
Line := NextLines[Index];
if (Line = '') then begin
Result := 0;
FTabData[Index] := Result + NO_TAB_IN_LINE_OFFSET;
TabData.SetLineInfo(Index, 0, False);
end else begin
i := length(Line);
SetLength(CharWidths, i);
@ -319,44 +496,28 @@ begin
for i := 0 to length(CharWidths)-1 do
Result := Result + (CharWidths[i] and PCWMask);
if FLastLineHasTab then // FLastLineHasTab is set by GetPhysicalCharWidths
FTabData[Index] := Result
else
FTabData[Index] := Result + NO_TAB_IN_LINE_OFFSET;
TabData.SetLineInfo(Index, Result, FLastLineHasTab);
end;
end;
procedure TSynEditStringTabExpander.SetManager(AManager: TSynTextViewsManager);
begin
if Manager <> nil then begin
RemoveChangeHandler(senrLineChange, @LineTextChanged);
RemoveChangeHandler(senrLineCount, @LineCountChanged);
RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
end;
inherited SetManager(AManager);
if Manager <> nil then begin
AddChangeHandler(senrLineCount, @LineCountChanged);
AddChangeHandler(senrLineChange, @LineTextChanged);
AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
end;
end;
procedure TSynEditStringTabExpander.SetSynStrings(AValue: TSynEditStrings);
begin
inherited SetSynStrings(AValue);
if NextLines <> nil then begin
if FTabData = nil then
TextBufferChanged(nil);
end;
end;
function TSynEditStringTabExpander.GetExpandedString(Index: integer): string;
begin
if (Index >= 0) and (Index < Count) then begin
if FTabData[Index] >= NO_TAB_IN_LINE_OFFSET then
Result := NextLines[Index]
if TabData.HasTab[Index] then
Result := ExpandedString(Index)
else
Result := ExpandedString(Index);
Result := NextLines[Index];
end else
Result := '';
end;
@ -398,9 +559,8 @@ begin
Line2 := Count - 1;
if (fIndexOfLongestLine >= 0) and (fIndexOfLongestLine < Count) then begin
Result := FTabData[fIndexOfLongestLine];
Result := TabData[fIndexOfLongestLine];
if Result <> LINE_LEN_UNKNOWN then begin
if Result >= NO_TAB_IN_LINE_OFFSET then Result := Result - NO_TAB_IN_LINE_OFFSET;
if (FFirstUnknownLongestLine < 0) then
exit;
// Result has the value from index
@ -430,14 +590,14 @@ begin
m := 0;
CharWidths := nil;
for i := Line1 to Line2 do begin
j := FTabData[i];
j := TabData[i];
if j = LINE_LEN_UNKNOWN then begin
// embedd a copy of ExpandedStringLength
// allows one to re-use CharWidths
Line := NextLines.GetPChar(i,LineLen); // NextLines[i];
j := 0;
if (LineLen = 0) then begin
FTabData[i] := j + NO_TAB_IN_LINE_OFFSET;
TabData.SetLineInfo(i, LineLen, False);
end else begin
if LineLen > m then begin
ReAllocMem(CharWidths, LineLen * SizeOf(TPhysicalCharWidth));
@ -445,18 +605,10 @@ begin
end;
DoGetPhysicalCharWidths(Line, LineLen, i, CharWidths);
j := FLastLinePhysLen;
if j > MAX_LINE_LEN_STORED then
FTabData[i] := LINE_LEN_UNKNOWN
else if FLastLineHasTab then // FLastLineHasTab is set by GetPhysicalCharWidths
FTabData[i] := j
else
FTabData[i] := j + NO_TAB_IN_LINE_OFFSET;
TabData.SetLineInfo(i, j, FLastLineHasTab);
end;
end
else
if j >= NO_TAB_IN_LINE_OFFSET then
j := j - NO_TAB_IN_LINE_OFFSET;
end;
if j > Result then begin
Result := j;
fIndexOfLongestLine := i;