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/
6
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.
10
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.
20
21 -------------------------------------------------------------------------------}
22 unit SynEditTextTabExpander;
23
24 {$I synedit.inc}
25
26 interface
27
28 uses
29 LCLProc, Classes, SysUtils, math, LazSynEditText, SynEditTextBase;
30
31 type
32
33 // lines longer than 16383 chars, will be stored as unknown
34 TLineLen = Word;
35 PLineLen = ^TLineLen;
36
37 { TSynEditStringTabData }
38
39 TSynEditStringTabData = class(TSynManagedStorageMem)
40 private
41 FRefCount: Integer;
GetLineLennull42 function GetLineLen(Index: Integer): TLineLen;
43 procedure SetLineLen(Index: Integer; const AValue: TLineLen);
44 public
45 constructor Create;
46 procedure IncRefCount;
47 procedure DecRefCount;
48 property RefCount: Integer read FRefCount;
49 property LineLen[Index: Integer]: TLineLen read GetLineLen write SetLineLen; default;
50 end;
51
52 { TSynEditStringTabExpander }
53
54 TSynEditStringTabExpander = class(TSynEditStringsLinked)
55 private
56 FTabWidth: integer;
57 FIndexOfLongestLine: Integer;
58 FFirstUnknownLongestLine, FLastUnknownLongestLine: Integer;
59 FTabData: TSynEditStringTabData;
60 FLastLineHasTab: Boolean; // Last line, parsed by GetPhysicalCharWidths
61 FLastLinePhysLen: Integer;
62 FViewChangeStamp: int64;
63 procedure TextBufferChanged(Sender: TObject);
64 procedure LineTextChanged(Sender: TSynEditStrings; aIndex, aCount: Integer);
65 procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
ExpandedStringnull66 function ExpandedString(Index: integer): string;
ExpandedStringLengthnull67 function ExpandedStringLength(Index: integer): Integer;
68 protected
GetViewChangeStampnull69 function GetViewChangeStamp: int64; override;
GetTabWidthnull70 function GetTabWidth : integer;
71 procedure SetTabWidth(const AValue : integer);
GetExpandedStringnull72 function GetExpandedString(Index: integer): string; override;
GetLengthOfLongestLinenull73 function GetLengthOfLongestLine: integer; override;
74 procedure DoGetPhysicalCharWidths(Line: PChar; LineLen, Index: Integer; PWidths: PPhysicalCharWidth); override;
75 public
76 constructor Create(ASynStringSource: TSynEditStrings);
77 destructor Destroy; override;
78
79 property LengthOfLongestLine: integer read GetLengthOfLongestLine;
80 public
81 property TabWidth: integer read GetTabWidth write SetTabWidth;
82 end;
83
84
85 implementation
86
87 const
88 // Offset to add to LengthOfLine, if Line has no tabs.
89 // (Length will still be valid if tab-width changes)
90 NO_TAB_IN_LINE_OFFSET = high(TLineLen) div 2;
91 LINE_LEN_UNKNOWN = high(TLineLen);
92 MAX_LINE_LEN_STORED = NO_TAB_IN_LINE_OFFSET - 1;
93
GetHasTabsnull94 function GetHasTabs(pLine: PChar): boolean;
95 begin
96 if Assigned(pLine) then begin
97 while (pLine^ <> #0) do begin
98 if (pLine^ = #9) then break;
99 Inc(pLine);
100 end;
101 Result := (pLine^ = #9);
102 end else
103 Result := FALSE;
104 end;
105
106 { TSynEditStringTabData }
107
GetLineLennull108 function TSynEditStringTabData.GetLineLen(Index: Integer): TLineLen;
109 begin
110 Result := PLineLen(ItemPointer[Index])^;
111 end;
112
113 procedure TSynEditStringTabData.SetLineLen(Index: Integer; const AValue: TLineLen);
114 begin
115 PLineLen(ItemPointer[Index])^ := AValue;
116 end;
117
118 constructor TSynEditStringTabData.Create;
119 begin
120 inherited;
121 ItemSize := SizeOf(TLineLen);
122 FRefCount := 1;
123 end;
124
125 procedure TSynEditStringTabData.IncRefCount;
126 begin
127 inc(FRefCount);
128 end;
129
130 procedure TSynEditStringTabData.DecRefCount;
131 begin
132 dec(FRefCount);
133 end;
134
135 { TSynEditStringTabExpander }
136
137 constructor TSynEditStringTabExpander.Create(ASynStringSource: TSynEditStrings);
138 begin
139 FIndexOfLongestLine := -1;
140 FFirstUnknownLongestLine := -1;
141 FLastUnknownLongestLine := -1;
142 inherited Create(ASynStringSource);
143 TextBufferChanged(nil);
144 TabWidth := 8;
145 fSynStrings.AddChangeHandler(senrLineCount, @LineCountChanged);
146 fSynStrings.AddChangeHandler(senrLineChange, @LineTextChanged);
147 fSynStrings.AddNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
148 end;
149
150 destructor TSynEditStringTabExpander.Destroy;
151 var
152 Data: TSynEditStringTabData;
153 begin
154 Data := TSynEditStringTabData(fSynStrings.Ranges[Self]);
155 if Assigned(Data) then begin
156 Data.DecRefCount;
157 if Data.RefCount = 0 then begin
158 fSynStrings.Ranges[Self] := nil;
159 Data.Free;
160 end;
161 end;
162 fSynStrings.RemoveChangeHandler(senrLineChange, @LineTextChanged);
163 fSynStrings.RemoveChangeHandler(senrLineCount, @LineCountChanged);
164 fSynStrings.RemoveNotifyHandler(senrTextBufferChanged, @TextBufferChanged);
165 inherited Destroy;
166 end;
167
TSynEditStringTabExpander.GetTabWidthnull168 function TSynEditStringTabExpander.GetTabWidth: integer;
169 begin
170 Result := FTabWidth;
171 end;
172
173 procedure TSynEditStringTabExpander.SetTabWidth(const AValue: integer);
174 var
175 i: integer;
176 begin
177 if FTabWidth = AValue then exit;
178
179 {$PUSH}{$Q-}{$R-}
180 FViewChangeStamp := FViewChangeStamp + 1;
181 {$POP}
182
183 FTabWidth := AValue;
184 FIndexOfLongestLine := -1;
185 FFirstUnknownLongestLine := -1;
186 FLastUnknownLongestLine := -1;
187 for i := 0 to Count - 1 do
188 if not(FTabData[i] >= NO_TAB_IN_LINE_OFFSET) then
189 FTabData[i] := LINE_LEN_UNKNOWN;
190 end;
191
GetViewChangeStampnull192 function TSynEditStringTabExpander.GetViewChangeStamp: int64;
193 begin
194 Result := inherited GetViewChangeStamp;
195 {$PUSH}{$Q-}{$R-}
196 Result := Result + FViewChangeStamp;
197 {$POP}
198 end;
199
200 procedure TSynEditStringTabExpander.TextBufferChanged(Sender: TObject);
201 var
202 Data: TSynEditStringTabData;
203 begin
204 // Using self, instead as class, to register tab-width-data
205 // other shared edits can have different tab-width
206 if (Sender <> nil) and
207 (FTabData = TSynEditStringTabData(NextLines.Ranges[Self]))
208 then
209 exit;
210
211 if Sender <> nil then begin
212 Data := TSynEditStringTabData(TSynEditStrings(Sender).Ranges[Self]);
213 if Assigned(Data) then begin
214 Data.DecRefCount;
215 if Data.RefCount = 0 then begin
216 TSynEditStrings(Sender).Ranges[Self] := nil;
217 Data.Free;
218 end;
219 end;
220 end;
221 FTabData := TSynEditStringTabData(NextLines.Ranges[Self]);
222 if FTabData = nil then begin
223 FTabData := TSynEditStringTabData.Create;
224 NextLines.Ranges[Self] := FTabData;
225 end
226 else
227 FTabData.IncRefCount;
228 LineTextChanged(TSynEditStrings(Sender), 0, Count);
229 end;
230
231 procedure TSynEditStringTabExpander.LineTextChanged(Sender: TSynEditStrings; aIndex,
232 aCount: Integer);
233 var
234 i: integer;
235 begin
236 if (FIndexOfLongestLine >= AIndex) and (FIndexOfLongestLine < AIndex+ACount) then
237 FIndexOfLongestLine := -1;
238 if (FFirstUnknownLongestLine < 0) or (AIndex < FFirstUnknownLongestLine) then
239 FFirstUnknownLongestLine := AIndex;
240 if AIndex+ACount-1 > FLastUnknownLongestLine then
241 FLastUnknownLongestLine := AIndex+ACount-1;
242 for i := AIndex to AIndex + ACount - 1 do
243 FTabData[i] := LINE_LEN_UNKNOWN;
244 end;
245
246 procedure TSynEditStringTabExpander.LineCountChanged(Sender: TSynEditStrings; AIndex, ACount: Integer);
247 var
248 i: integer;
249 begin
250 if (FIndexOfLongestLine >= AIndex) then
251 FIndexOfLongestLine := FIndexOfLongestLine + ACount;
252
253 if ACount < 0 then begin
254 if (FIndexOfLongestLine >= AIndex+ACount) and (FIndexOfLongestLine < AIndex) then
255 FIndexOfLongestLine := -1;
256 if (FFirstUnknownLongestLine >= 0) then begin
257 if (AIndex < FFirstUnknownLongestLine) then
258 FFirstUnknownLongestLine := Max(AIndex, FFirstUnknownLongestLine + ACount);
259 if (AIndex < FLastUnknownLongestLine) then
260 FLastUnknownLongestLine := Max(AIndex, FLastUnknownLongestLine + ACount);
261 end;
262
263 exit;
264 end;
265
266 if (FFirstUnknownLongestLine < 0) or (AIndex < FFirstUnknownLongestLine) then
267 FFirstUnknownLongestLine := AIndex;
268 if (AIndex < FLastUnknownLongestLine) or (FLastUnknownLongestLine < 0) then
269 FLastUnknownLongestLine := Max(AIndex, FLastUnknownLongestLine) +ACount;
270 for i := AIndex to AIndex + ACount - 1 do
271 FTabData[i] := LINE_LEN_UNKNOWN;
272 end;
273
ExpandedStringnull274 function TSynEditStringTabExpander.ExpandedString(Index: integer): string;
275 var
276 Line: String;
277 CharWidths: TPhysicalCharWidths;
278 i, j, l: Integer;
279 begin
280 // this is only used by trimmer.lengthOfLongestLine / which is not called, if a tab module is present
281 Line := fSynStrings[Index];
282 if (Line = '') or (not GetHasTabs(PChar(Line))) then begin
283 Result := Line;
284 // xxx wrong double width // none latin ...
285 //FTabData[Index] := length(Result) + NO_TAB_IN_LINE_OFFSET;
286 end else begin
287 CharWidths := GetPhysicalCharWidths(Pchar(Line), length(Line), Index);
288 l := 0;
289 for i := 0 to length(CharWidths)-1 do
290 l := l + (CharWidths[i] and PCWMask);
291 SetLength(Result, l);
292
293 l := 1;
294 for i := 1 to length(CharWidths) do begin
295 if Line[i] <> #9 then begin
296 Result[l] := Line[i];
297 inc(l);
298 end else begin
299 for j := 1 to (CharWidths[i-1] and PCWMask) do begin
300 Result[l] := ' ';
301 inc(l);
302 end;
303 end;
304 end;
305 FTabData[Index] := length(Result);
306 end;
307 end;
308
ExpandedStringLengthnull309 function TSynEditStringTabExpander.ExpandedStringLength(Index: integer): Integer;
310 var
311 Line: String;
312 CharWidths: TPhysicalCharWidths;
313 i: Integer;
314 begin
315 Line := fSynStrings[Index];
316 if (Line = '') then begin
317 Result := 0;
318 FTabData[Index] := Result + NO_TAB_IN_LINE_OFFSET;
319 end else begin
320 i := length(Line);
321 SetLength(CharWidths, i);
322 DoGetPhysicalCharWidths(Pchar(Line), i, Index, @CharWidths[0]);
323 Result := 0;
324 for i := 0 to length(CharWidths)-1 do
325 Result := Result + (CharWidths[i] and PCWMask);
326
327 if FLastLineHasTab then // FLastLineHasTab is set by GetPhysicalCharWidths
328 FTabData[Index] := Result
329 else
330 FTabData[Index] := Result + NO_TAB_IN_LINE_OFFSET;
331 end;
332 end;
333
TSynEditStringTabExpander.GetExpandedStringnull334 function TSynEditStringTabExpander.GetExpandedString(Index: integer): string;
335 begin
336 if (Index >= 0) and (Index < Count) then begin
337 if FTabData[Index] >= NO_TAB_IN_LINE_OFFSET then
338 Result := fSynStrings[Index]
339 else
340 Result := ExpandedString(Index);
341 end else
342 Result := '';
343 end;
344
345 procedure TSynEditStringTabExpander.DoGetPhysicalCharWidths(Line: PChar;
346 LineLen, Index: Integer; PWidths: PPhysicalCharWidth);
347 var
348 HasTab: Boolean;
349 i, j: Integer;
350 begin
351 inherited DoGetPhysicalCharWidths(Line, LineLen, Index, PWidths);
352 HasTab := False;
353 j := 0;
354 for i := 0 to LineLen - 1 do begin
355 if (PWidths^ and PCWMask) <> 0 then begin
356 if Line^ = #9 then begin
357 PWidths^ := (FTabWidth - (j mod FTabWidth) and PCWMask) or (PWidths^ and (not PCWMask));
358 HasTab := True;
359 end;
360 j := j + (PWidths^ and PCWMask);
361 end;
362 inc(Line);
363 inc(PWidths);
364 end;
365 FLastLineHasTab := HasTab;
366 FLastLinePhysLen := j;
367 end;
368
GetLengthOfLongestLinenull369 function TSynEditStringTabExpander.GetLengthOfLongestLine: integer;
370 var
371 Line: PChar;
372 LineLen: Integer;
373 CharWidths: PPhysicalCharWidth;
374 i, j, m: Integer;
375 Line1, Line2: Integer;
376 begin
377 Result := 0;
378 Line1 := 0;
379 Line2 := Count - 1;
380
381 if (fIndexOfLongestLine >= 0) and (fIndexOfLongestLine < Count) then begin
382 Result := FTabData[fIndexOfLongestLine];
383 if Result <> LINE_LEN_UNKNOWN then begin
384 if Result >= NO_TAB_IN_LINE_OFFSET then Result := Result - NO_TAB_IN_LINE_OFFSET;
385 if (FFirstUnknownLongestLine < 0) then
386 exit;
387 // Result has the value from index
388 Line1 := FFirstUnknownLongestLine;
389 if (FLastUnknownLongestLine < Line2) then
390 Line2 := FLastUnknownLongestLine;
391 end
392 else begin
393 Result := 0;
394 if (FFirstUnknownLongestLine < 0) then begin
395 Line1 := fIndexOfLongestLine;
396 Line2 := fIndexOfLongestLine;
397 end
398 else begin // TODO: Calculate for fIndexOfLongestLine, instead of extending the range
399 Line1 := Min(fIndexOfLongestLine, FFirstUnknownLongestLine);
400 if (FLastUnknownLongestLine < Line2) then
401 Line2 := Max(fIndexOfLongestLine, FLastUnknownLongestLine);
402 end;
403 end;
404 end;
405
406 FFirstUnknownLongestLine := -1;
407 FLastUnknownLongestLine := -1;
408
409 try
410 //Result := 0;
411 m := 0;
412 CharWidths := nil;
413 for i := Line1 to Line2 do begin
414 j := FTabData[i];
415 if j = LINE_LEN_UNKNOWN then begin
416 // embedd a copy of ExpandedStringLength
417 // allows one to re-use CharWidths
418 Line := NextLines.GetPChar(i,LineLen); // fSynStrings[i];
419 j := 0;
420 if (LineLen = 0) then begin
421 FTabData[i] := j + NO_TAB_IN_LINE_OFFSET;
422 end else begin
423 if LineLen > m then begin
424 ReAllocMem(CharWidths, LineLen * SizeOf(TPhysicalCharWidth));
425 m := LineLen;
426 end;
427 DoGetPhysicalCharWidths(Line, LineLen, i, CharWidths);
428 j := FLastLinePhysLen;
429
430 if j > MAX_LINE_LEN_STORED then
431 FTabData[i] := LINE_LEN_UNKNOWN
432 else if FLastLineHasTab then // FLastLineHasTab is set by GetPhysicalCharWidths
433 FTabData[i] := j
434 else
435 FTabData[i] := j + NO_TAB_IN_LINE_OFFSET;
436 end;
437 end
438 else
439 if j >= NO_TAB_IN_LINE_OFFSET then
440 j := j - NO_TAB_IN_LINE_OFFSET;
441 if j > Result then begin
442 Result := j;
443 fIndexOfLongestLine := i;
444 end;
445 end;
446 finally
447 ReAllocMem(CharWidths, 0);
448 end;
449 end;
450
451 end.
452
453