1
{-------------------------------------------------------------------------------
2
The contents of this file are subject to the Mozilla Public License
3
Version 1.1 (the "License"); you may not use this file except in compliance
4
with the License. You may obtain a copy of the License at
5
http://www.mozilla.org/MPL/
7
Software distributed under the License is distributed on an "AS IS" basis,
8
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9
the specific language governing rights and limitations under the License.
11
Alternatively, the contents of this file may be used under the terms of the
12
GNU General Public License Version 2 or later (the "GPL"), in which case
13
the provisions of the GPL are applicable instead of those above.
14
If you wish to allow use of your version of this file only under the terms
15
of the GPL and not to allow others to use your version of this file
16
under the MPL, indicate your decision by deleting the provisions above and
17
replace them with the notice and other provisions required by the GPL.
18
If you do not delete the provisions above, a recipient may use your version
19
of this file under either the MPL or the GPL.
21
-------------------------------------------------------------------------------}
22
unit SynEditTextTabExpander;
29
LCLProc, Classes, SysUtils, LazSynEditText, SynEditTextBase;
33
// lines longer than 16383 chars, will be stored as unknown
37
{ TSynEditStringTabData }
39
TSynEditStringTabData = class(TSynManagedStorageMem)
42
function GetLineLen(Index: Integer): TLineLen;
43
procedure SetLineLen(Index: Integer; const AValue: TLineLen);
46
procedure IncRefCount;
47
procedure DecRefCount;
48
property RefCount: Integer read FRefCount;
49
property LineLen[Index: Integer]: TLineLen read GetLineLen write SetLineLen; default;
52
{ TSynEditStringTabExpander }
54
TSynEditStringTabExpander = class(TSynEditStringsLinked)
57
FIndexOfLongestLine: Integer;
58
FTabData: TSynEditStringTabData;
59
FLastLineHasTab: Boolean; // Last line, parsed by GetPhysicalCharWidths
60
FLastLinePhysLen: Integer;
61
FViewChangeStamp: int64;
62
procedure TextBufferChanged(Sender: TObject);
63
procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
64
function ExpandedString(Index: integer): string;
65
function ExpandedStringLength(Index: integer): Integer;
67
function GetViewChangeStamp: int64; override;
68
function GetTabWidth : integer;
69
procedure SetTabWidth(const AValue : integer);
70
function GetExpandedString(Index: integer): string; override;
71
function GetLengthOfLongestLine: integer; override;
72
procedure DoGetPhysicalCharWidths(Line: PChar; LineLen, Index: Integer; PWidths: PPhysicalCharWidth); override;
74
constructor Create(ASynStringSource: TSynEditStrings);
75
destructor Destroy; override;
77
property LengthOfLongestLine: integer read GetLengthOfLongestLine;
79
property TabWidth: integer read GetTabWidth write SetTabWidth;
86
// Offset to add to LengthOfLine, if Line has no tabs.
87
// (Length will still be valid if tab-width changes)
88
NO_TAB_IN_LINE_OFFSET = high(TLineLen) div 2;
89
LINE_LEN_UNKNOWN = high(TLineLen);
90
MAX_LINE_LEN_STORED = NO_TAB_IN_LINE_OFFSET - 1;
92
function GetHasTabs(pLine: PChar): boolean;
94
if Assigned(pLine) then begin
95
while (pLine^ <> #0) do begin
96
if (pLine^ = #9) then break;
99
Result := (pLine^ = #9);
104
{ TSynEditStringTabData }
106
function TSynEditStringTabData.GetLineLen(Index: Integer): TLineLen;
108
Result := PLineLen(ItemPointer[Index])^;
111
procedure TSynEditStringTabData.SetLineLen(Index: Integer; const AValue: TLineLen);
113
PLineLen(ItemPointer[Index])^ := AValue;
116
constructor TSynEditStringTabData.Create;
119
ItemSize := SizeOf(TLineLen);
123
procedure TSynEditStringTabData.IncRefCount;
128
procedure TSynEditStringTabData.DecRefCount;
133
{ TSynEditStringTabExpander }
135
constructor TSynEditStringTabExpander.Create(ASynStringSource: TSynEditStrings);
137
FIndexOfLongestLine := -1;
138
inherited Create(ASynStringSource);
139
TextBufferChanged(nil);
141
fSynStrings.AddChangeHandler(senrLineCount, @LineCountChanged);
142
fSynStrings.AddChangeHandler(senrLineChange, @LineCountChanged);
143
fSynStrings.AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
146
destructor TSynEditStringTabExpander.Destroy;
148
Data: TSynEditStringTabData;
150
Data := TSynEditStringTabData(fSynStrings.Ranges[Self]);
151
if Assigned(Data) then begin
153
if Data.RefCount = 0 then begin
154
fSynStrings.Ranges[Self] := nil;
158
fSynStrings.RemoveChangeHandler(senrLineChange, @LineCountChanged);
159
fSynStrings.RemoveChangeHandler(senrLineCount, @LineCountChanged);
160
fSynStrings.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
164
function TSynEditStringTabExpander.GetTabWidth: integer;
169
procedure TSynEditStringTabExpander.SetTabWidth(const AValue: integer);
173
if FTabWidth = AValue then exit;
176
FViewChangeStamp := FViewChangeStamp + 1;
180
FIndexOfLongestLine := -1;
181
for i := 0 to Count - 1 do
182
if not(FTabData[i] >= NO_TAB_IN_LINE_OFFSET) then
183
FTabData[i] := LINE_LEN_UNKNOWN;
186
function TSynEditStringTabExpander.GetViewChangeStamp: int64;
188
Result := inherited GetViewChangeStamp;
190
Result := Result + FViewChangeStamp;
194
procedure TSynEditStringTabExpander.TextBufferChanged(Sender: TObject);
196
Data: TSynEditStringTabData;
198
// Using self, instead as class, to register tab-width-data
199
// other shared edits can have different tab-width
200
if (Sender <> nil) and
201
(FTabData = TSynEditStringTabData(NextLines.Ranges[Self]))
205
if Sender <> nil then begin
206
Data := TSynEditStringTabData(TSynEditStrings(Sender).Ranges[Self]);
207
if Assigned(Data) then begin
209
if Data.RefCount = 0 then begin
210
TSynEditStrings(Sender).Ranges[Self] := nil;
215
FTabData := TSynEditStringTabData(NextLines.Ranges[Self]);
216
if FTabData = nil then begin
217
FTabData := TSynEditStringTabData.Create;
218
NextLines.Ranges[Self] := FTabData;
221
FTabData.IncRefCount;
222
LineCountChanged(TSynEditStrings(Sender), 0, Count);
225
procedure TSynEditStringTabExpander.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer);
229
FIndexOfLongestLine := -1;
230
for i := AIndex to AIndex + ACount - 1 do
231
FTabData[i] := LINE_LEN_UNKNOWN;
234
function TSynEditStringTabExpander.ExpandedString(Index: integer): string;
237
CharWidths: TPhysicalCharWidths;
240
// this is only used by trimmer.lengthOfLongestLine / which is not called, if a tab module is present
241
Line := fSynStrings[Index];
242
if (Line = '') or (not GetHasTabs(PChar(Line))) then begin
244
// xxx wrong double width // none latin ...
245
//FTabData[Index] := length(Result) + NO_TAB_IN_LINE_OFFSET;
247
CharWidths := GetPhysicalCharWidths(Pchar(Line), length(Line), Index);
249
for i := 0 to length(CharWidths)-1 do
250
l := l + (CharWidths[i] and PCWMask);
251
SetLength(Result, l);
254
for i := 1 to length(CharWidths) do begin
255
if Line[i] <> #9 then begin
256
Result[l] := Line[i];
259
for j := 1 to (CharWidths[i-1] and PCWMask) do begin
265
FTabData[Index] := length(Result);
269
function TSynEditStringTabExpander.ExpandedStringLength(Index: integer): Integer;
272
CharWidths: TPhysicalCharWidths;
275
Line := fSynStrings[Index];
276
if (Line = '') then begin
278
FTabData[Index] := Result + NO_TAB_IN_LINE_OFFSET;
281
SetLength(CharWidths, i);
282
DoGetPhysicalCharWidths(Pchar(Line), i, Index, @CharWidths[0]);
284
for i := 0 to length(CharWidths)-1 do
285
Result := Result + (CharWidths[i] and PCWMask);
287
if FLastLineHasTab then // FLastLineHasTab is set by GetPhysicalCharWidths
288
FTabData[Index] := Result
290
FTabData[Index] := Result + NO_TAB_IN_LINE_OFFSET;
294
function TSynEditStringTabExpander.GetExpandedString(Index: integer): string;
296
if (Index >= 0) and (Index < Count) then begin
297
if FTabData[Index] >= NO_TAB_IN_LINE_OFFSET then
298
Result := fSynStrings[Index]
300
Result := ExpandedString(Index);
305
procedure TSynEditStringTabExpander.DoGetPhysicalCharWidths(Line: PChar;
306
LineLen, Index: Integer; PWidths: PPhysicalCharWidth);
311
inherited DoGetPhysicalCharWidths(Line, LineLen, Index, PWidths);
314
for i := 0 to LineLen - 1 do begin
315
if (PWidths^ and PCWMask) <> 0 then begin
316
if Line^ = #9 then begin
317
PWidths^ := (FTabWidth - (j mod FTabWidth) and PCWMask) or (PWidths^ and (not PCWMask));
320
j := j + (PWidths^ and PCWMask);
325
FLastLineHasTab := HasTab;
326
FLastLinePhysLen := j;
329
function TSynEditStringTabExpander.GetLengthOfLongestLine: integer;
333
CharWidths: PPhysicalCharWidth;
335
Line1, Line2: Integer;
339
if (fIndexOfLongestLine >= 0) and (fIndexOfLongestLine < Count) then begin
340
Result := FTabData[fIndexOfLongestLine];
341
if Result <> LINE_LEN_UNKNOWN then begin
342
if Result >= NO_TAB_IN_LINE_OFFSET then Result := Result - NO_TAB_IN_LINE_OFFSET;
345
Line1 := fIndexOfLongestLine;
346
Line2 := fIndexOfLongestLine;
353
for i := Line1 to Line2 do begin
355
if j = LINE_LEN_UNKNOWN then begin
356
// embedd a copy of ExpandedStringLength
357
// allows to re-use CharWidths
358
Line := NextLines.GetPChar(i,LineLen); // fSynStrings[i];
360
if (LineLen = 0) then begin
361
FTabData[i] := j + NO_TAB_IN_LINE_OFFSET;
363
if LineLen > m then begin
364
ReAllocMem(CharWidths, LineLen * SizeOf(TPhysicalCharWidth));
367
DoGetPhysicalCharWidths(Line, LineLen, i, CharWidths);
368
j := FLastLinePhysLen;
370
if j > MAX_LINE_LEN_STORED then
371
FTabData[i] := LINE_LEN_UNKNOWN
372
else if FLastLineHasTab then // FLastLineHasTab is set by GetPhysicalCharWidths
375
FTabData[i] := j + NO_TAB_IN_LINE_OFFSET;
379
if j >= NO_TAB_IN_LINE_OFFSET then
380
j := j - NO_TAB_IN_LINE_OFFSET;
381
if j > Result then begin
383
fIndexOfLongestLine := i;
387
ReAllocMem(CharWidths, 0);