1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Mattias Gaertner
22 
23   Abstract:
24     Source Editor marks for (compiler) messages.
25 }
26 unit etSrcEditMarks;
27 
28 {$mode objfpc}{$H+}
29 
30 {off $DEFINE VerboseETSrcChange}
31 
32 interface
33 
34 uses
35   Classes, SysUtils, math, Laz_AVL_Tree,
36   // LCL
37   Graphics, Controls, Forms, ImgList,
38   // LazUtils
39   LazLoggerBase, LazFileUtils,
40   // Codetools
41   KeywordFuncLists,
42   // SynEdit
43   SynEditMarkupGutterMark,
44   SynEditMarks, SynEditMiscClasses, SynEditTypes, SynEdit, LazSynEditText,
45   // IdeIntf
46   IDEExternToolIntf;
47 
48 type
49 
50   { TETMarkStyle }
51 
52   TETMarkStyle = class
53   private
54     FColor: TColor;
55     FImageIndex: integer;
56     FSourceMarkup: TSynSelectedColor;
57     FUrgency: TMessageLineUrgency;
58     procedure SetColor(AValue: TColor);
59   public
60     constructor Create(TheUrgency: TMessageLineUrgency; TheColor: TColor);
61     destructor Destroy; override;
62     property Urgency: TMessageLineUrgency read FUrgency;
63     property Color: TColor read FColor write SetColor;
64     property ImageIndex: integer read FImageIndex write FImageIndex;
65     property SourceMarkup: TSynSelectedColor read FSourceMarkup;
66   end;
67 
68   TETMarks = class;
69 
70   { TETMark }
71 
72   TETMark = class(TSynEditMarkupMark)
73   private
74     FMsgLine: TMessageLine;
75     FSourceMarks: TETMarks;
76     FUrgency: TMessageLineUrgency;
77   public
78     destructor Destroy; override;
79     property Urgency: TMessageLineUrgency read FUrgency write FUrgency;
80     property MsgLine: TMessageLine read FMsgLine write FMsgLine;
81     property SourceMarks: TETMarks read FSourceMarks write FSourceMarks;
82   end;
83 
84   { TLMsgViewLine }
85 
86   TLMsgViewLine = class(TMessageLine)
87   public
88     Mark: TETMark;
89     destructor Destroy; override;
90   end;
91 
92   { TETMarks }
93 
94   TOnGetSynEditOfFile = procedure(Sender: TObject; aFilename: string;
95     var aSynEdit: TSynEdit) of object;
96 
97   TETMarks = class(TComponent)
98   private
99     FImageList: TCustomImageList;
100     fMarkStyles: array[TMessageLineUrgency] of TETMarkStyle;
101     FOnGetSynEditOfFile: TOnGetSynEditOfFile;
102     FPriority: integer;
GetMarkStylesnull103     function GetMarkStyles(Urgency: TMessageLineUrgency): TETMarkStyle; inline;
104   public
105     constructor Create(AOwner: TComponent); override;
106     destructor Destroy; override;
CreateMarknull107     function CreateMark(MsgLine: TMessageLine; aSynEdit: TSynEdit = nil): TETMark;
108     procedure RemoveMarks(aSynEdit: TSynEdit);
109     property ImageList: TCustomImageList read FImageList write FImageList; // must have same Width/Height as the TSynEdits bookmarkimages
110     property OnGetSynEditOfFile: TOnGetSynEditOfFile read FOnGetSynEditOfFile write FOnGetSynEditOfFile;
111     property MarkStyles[Urgency: TMessageLineUrgency]: TETMarkStyle read GetMarkStyles;
112     property Priority: integer read FPriority write FPriority;
113   end;
114 
115 var
116   ExtToolsMarks: TETMarks = nil;
117 
118 type
119   TETSrcChangeAction = (
120     etscaInsert,
121     etscaDelete
122     );
123 
124   { TETSrcChange }
125 
126   TETSrcChange = class
127   public
128     Action: TETSrcChangeAction;
129     FromPos: TPoint;
130     ToPos: TPoint;
131     Prev, Next: TETSrcChange;
132     constructor Create(AnAction: TETSrcChangeAction; const aFromPos, aToPos: TPoint);
133     constructor Create(AnAction: TETSrcChangeAction; FromPosY, FromPosX, ToPosY, ToPosX: integer);
AsStringnull134     function AsString: string;
135   end;
136 
137   TETMultiSrcChanges = class;
138 
139   { TETSingleSrcChanges - edits of single file}
140 
141   TETSingleSrcChanges = class
142   private
143     FFilename: string;
144     FFirst: TETSrcChange;
145     FLast: TETSrcChange;
146     fInPendingTree: boolean;
147     FMultiSrcChanges: TETMultiSrcChanges;
148     procedure Append(Change: TETSrcChange);
149     procedure Remove(Change: TETSrcChange);
150     procedure SetFilename(AValue: string);
151     procedure SetMultiSrcChanges(AValue: TETMultiSrcChanges);
152     procedure SetInPendingTree(AValue: boolean);
153   protected
154     property InPendingTree: boolean read FInPendingTree write SetInPendingTree;
155   public
156     constructor Create;
157     destructor Destroy; override;
158     procedure Clear;
159     property MultiSrcChanges: TETMultiSrcChanges read FMultiSrcChanges write SetMultiSrcChanges;
160     property First: TETSrcChange read FFirst;
161     property Last: TETSrcChange read FLast;
162     property Filename: string read FFilename write SetFilename;
163     procedure GetRange(out MinY, MaxY, LineDiffBehindMaxY: integer);
Addnull164     function Add(Action: TETSrcChangeAction; const FromPos, ToPos: TPoint): TETSrcChange; inline;
Addnull165     function Add(Action: TETSrcChangeAction; FromPosY, FromPosX, ToPosY, ToPosX: integer): TETSrcChange;
AdaptCaretnull166     function AdaptCaret(var Line,Col: integer;
167       LeftBound: boolean // true = position is bound to character on the left
168       ): boolean; // true if changed
169     procedure ConsistencyCheck;
170     procedure WriteDebugReport(Title: string);
171   end;
172 
173   { TETMultiSrcChanges - edits of all files }
174 
175   TETMultiSrcChanges = class(TComponent)
176   private
177     fAllChanges: TAvlTree; // tree of TETSingleSrcChanges sorted for Filename
178     FAutoSync: boolean;
179     fPendingChanges: TAvlTree; // tree of TETSingleSrcChanges sorted for Filename
180     FOnSync: TNotifyEvent;
181     FSyncQueued: boolean;
182     procedure SetSyncQueued(AValue: boolean);
183   protected
184     procedure DoSync({%H-}Data: PtrInt); // called by Application.QueueAsyncCall
185   public
186     constructor Create(AOwner: TComponent); override;
187     destructor Destroy; override;
Countnull188     function Count: integer; inline;
189     procedure Clear;
GetChangesnull190     function GetChanges(const aFilename: string; CreateIfNotExists: boolean): TETSingleSrcChanges;
AdaptCaretnull191     function AdaptCaret(const aFilename: string; var Line,Col: integer;
192       LeftBound: boolean // true = position is bound to character on the left
193                  ): boolean;
194     property AllChanges: TAvlTree read fAllChanges; // tree of TETSingleSrcChanges sorted for Filename
195     property PendingChanges: TAvlTree read fPendingChanges; // tree of TETSingleSrcChanges sorted for Filename
196     property SyncQueued: boolean read FSyncQueued write SetSyncQueued;
197     property OnSync: TNotifyEvent read FOnSync write FOnSync; // called by Application.QueueAsyncCall
198     property AutoSync: boolean read FAutoSync write FAutoSync; // true = call OnSync via Application.QueueAsyncCall
199   end;
200 
201   { TETSynPlugin - create one per file, not one per synedit }
202 
endernull203   TIsEnabledEvent = function(Sender: TObject): boolean of object;
204 
205   TETSynPlugin = class(TLazSynEditPlugin)
206   private
207     FChanges: TETSingleSrcChanges;
208     FEnabled: boolean;
209     FOnIsEnabled: TIsEnabledEvent;
210   protected
211     procedure OnLineEdit(Sender: TSynEditStrings; aLinePos, aBytePos, aCount,
212       aLineBrkCnt: Integer; {%H-}aText: String);
213   public
214     constructor Create(AOwner: TComponent); override;
215     destructor Destroy; override;
216     property Changes: TETSingleSrcChanges read FChanges write FChanges;
217     property Enabled: boolean read FEnabled write FEnabled;
218     property OnIsEnabled: TIsEnabledEvent read FOnIsEnabled write FOnIsEnabled;
219   end;
220 
IsCaretInFrontnull221 function IsCaretInFront(Line1, Col1, Line2, Col2: integer): boolean; inline; overload;
IsCaretInFrontnull222 function IsCaretInFront(const P1: TPoint; Line2, Col2: integer): boolean; inline; overload;
IsCaretInFrontnull223 function IsCaretInFront(Line1, Col1: integer; const P2: TPoint): boolean; inline; overload;
IsCaretInFrontnull224 function IsCaretInFront(const P1,P2: TPoint): boolean; inline; overload;
IsCaretInFrontOrSamenull225 function IsCaretInFrontOrSame(Line1, Col1, Line2, Col2: integer): boolean; inline; overload;
IsCaretInFrontOrSamenull226 function IsCaretInFrontOrSame(const P1: TPoint; Line2, Col2: integer): boolean; inline; overload;
IsCaretInFrontOrSamenull227 function IsCaretInFrontOrSame(Line1, Col1: integer; const P2: TPoint): boolean; inline; overload;
IsCaretInFrontOrSamenull228 function IsCaretInFrontOrSame(const P1,P2: TPoint): boolean; inline; overload;
229 procedure AdaptCaret(var Line,Col: integer;
230   LeftBound: boolean; // true = position is bound to character on the left
231   Action: TETSrcChangeAction;
232   FromPosY, FromPosX, ToPosY, ToPosX: integer);
233 
CompareETSrcChangesFilenamesnull234 function CompareETSrcChangesFilenames(Changes1, Changes2: Pointer): integer;
CompareFilenameAndETSrcChangesnull235 function CompareFilenameAndETSrcChanges(aFilenameStr, Changes: Pointer): integer;
236 
dbgsnull237 function dbgs(Action: TETSrcChangeAction): string; overload;
238 procedure Test_AdaptCaret;
239 procedure Test_MergeTETSrcChanges;
240 
241 implementation
242 
IsCaretInFrontnull243 function IsCaretInFront(Line1, Col1, Line2, Col2: integer): boolean;
244 begin
245   Result:=(Line1<Line2) or ((Line1=Line2) and (Col1<Col2));
246 end;
247 
IsCaretInFrontnull248 function IsCaretInFront(const P1: TPoint; Line2, Col2: integer): boolean;
249 begin
250   Result:=IsCaretInFront(P1.Y,P1.X,Line2,Col2);
251 end;
252 
IsCaretInFrontnull253 function IsCaretInFront(Line1, Col1: integer; const P2: TPoint): boolean;
254 begin
255   Result:=IsCaretInFront(Line1,Col1,P2.Y,P2.X);
256 end;
257 
IsCaretInFrontnull258 function IsCaretInFront(const P1, P2: TPoint): boolean;
259 begin
260   Result:=IsCaretInFront(P1.Y,P1.X,P2.Y,P2.X);
261 end;
262 
IsCaretInFrontOrSamenull263 function IsCaretInFrontOrSame(Line1, Col1, Line2, Col2: integer): boolean;
264 begin
265   Result:=(Line1<Line2) or ((Line1=Line2) and (Col1<=Col2));
266 end;
267 
IsCaretInFrontOrSamenull268 function IsCaretInFrontOrSame(const P1: TPoint; Line2, Col2: integer): boolean;
269 begin
270   Result:=IsCaretInFrontOrSame(P1.Y,P1.X,Line2,Col2);
271 end;
272 
IsCaretInFrontOrSamenull273 function IsCaretInFrontOrSame(Line1, Col1: integer; const P2: TPoint): boolean;
274 begin
275   Result:=IsCaretInFrontOrSame(Line1,Col1,P2.Y,P2.X);
276 end;
277 
IsCaretInFrontOrSamenull278 function IsCaretInFrontOrSame(const P1, P2: TPoint): boolean;
279 begin
280   Result:=IsCaretInFrontOrSame(P1.Y,P1.X,P2.Y,P2.X);
281 end;
282 
283 procedure AdaptCaret(var Line, Col: integer; LeftBound: boolean;
284   Action: TETSrcChangeAction; FromPosY, FromPosX, ToPosY, ToPosX: integer);
285 begin
286   //debugln(['AdaptCaret Line=',Line,' Col=',Col,' LeftBound=',LeftBound,' Action=',dbgs(Action),' FromPos=',FromPosY,',',FromPosX,' ToPos=',ToPosY,',',ToPosX]);
287   if Line<FromPosY then exit;
288   if Action=etscaInsert then begin
289     // insert
290     if Line>FromPosY then begin
291       // insert in lines in front => move vertically
292       inc(Line,ToPosY-FromPosY);
293     end else begin
294       // insert in same line
295       if LeftBound then begin
296         if Col<=FromPosX then exit;
297       end else begin
298         if Col<FromPosX then exit;
299       end;
300       if FromPosY<ToPosY then begin
301         // multi line insert
302         inc(Line,ToPosY-FromPosY);
303         Col:=ToPosX+Col-FromPosX;
304       end else begin
305         // inserting some characters
306         inc(Col,ToPosX-FromPosX);
307       end;
308     end;
309   end else begin
310     // delete
311     if Line>ToPosY then begin
312       // delete some lines in front => move vertically
313       dec(Line,ToPosY-FromPosY);
314     end else if Line<ToPosY then begin
315       if Line>FromPosY then begin
316         // whole line of position was deleted => move to start of deletion
317         Line:=FromPosY;
318         Col:=FromPosX;
319       end else begin
320         // Line=FromPosY, Line<ToPosY
321         if Col<=FromPosX then begin
322           // delete is behind position => ignore
323         end else begin
324           // position was deleted => move to start of deletion
325           Line:=FromPosY;
326           Col:=FromPosX;
327         end;
328       end;
329     end else begin
330       // Line=ToPosY
331       if Line>FromPosY then begin
332         // multi line delete
333         if Col<=ToPosX then begin
334           // position was deleted => move to start of deletion
335           Line:=FromPosY;
336           Col:=FromPosX;
337         end else begin
338           // some characters at the start of the line were deleted
339           Line:=FromPosY;
340           dec(Col,ToPosX-1);
341         end;
342       end else begin
343         // Line=FromPosY=ToPosY
344         if Col<=FromPosX then begin
345           // delete is behind position => ignore
346         end else if Col<=ToPosX then begin
347           // position was deleted => move to start of deletion
348           Col:=FromPosX;
349         end else begin
350           // some characters in front were deleted
351           dec(Col,ToPosX-FromPosX);
352         end;
353       end;
354     end;
355   end;
356   //debugln(['AdaptCaret ',Line,',',Col]);
357 end;
358 
CompareETSrcChangesFilenamesnull359 function CompareETSrcChangesFilenames(Changes1, Changes2: Pointer): integer;
360 var
361   SrcChanges1: TETSingleSrcChanges absolute Changes1;
362   SrcChanges2: TETSingleSrcChanges absolute Changes2;
363 begin
364   Result:=CompareFilenames(SrcChanges1.Filename,SrcChanges2.Filename);
365 end;
366 
CompareFilenameAndETSrcChangesnull367 function CompareFilenameAndETSrcChanges(aFilenameStr, Changes: Pointer
368   ): integer;
369 var
370   SrcChanges: TETSingleSrcChanges absolute Changes;
371 begin
372   Result:=CompareFilenames(AnsiString(aFilenameStr),SrcChanges.Filename);
373 end;
374 
dbgsnull375 function dbgs(Action: TETSrcChangeAction): string;
376 begin
377   Result:='';
378   WriteStr(Result,Action);
379 end;
380 
381 procedure Test_AdaptCaret;
382 
383   procedure T(Title: string; Line,Col: integer;
384     LeftBound: boolean; // true = position is bound to character on the left
385     Action: TETSrcChangeAction;
386     FromPosY, FromPosX, ToPosY, ToPosX: integer;
387     ExpectedLine, ExpectedCol: integer);
388   var
389     Y: Integer;
390     X: Integer;
391     s: String;
392   begin
393     Y:=Line;
394     X:=Col;
395     AdaptCaret(Y,X,LeftBound,Action,FromPosY,FromPosX,ToPosY,ToPosX);
396     if (Y=ExpectedLine) and (X=ExpectedCol) then exit;
397     s:='Test_AdaptCaret: Caret='+dbgs(Line)+','+dbgs(Col)
398      +' LeftBound='+dbgs(LeftBound)
399      +' Action='+dbgs(Action)
400      +' FromPos='+dbgs(FromPosY)+','+dbgs(FromPosX)
401      +' ToPos='+dbgs(ToPosY)+','+dbgs(ToPosX)
402      +' Expected='+dbgs(ExpectedLine)+','+dbgs(ExpectedCol)
403      +' Actual='+dbgs(Y)+','+dbgs(X);
404     raise Exception.Create(Title+': '+s);
405   end;
406 
407 begin
408   T('Insert chars in front',10,10,true,etscaInsert,1,1, 1,2,  10,10);
409   T('Insert lines in front',10,10,true,etscaInsert,1,1, 2,2,  11,10);
410   T('Insert chars behind',10,10,true,etscaInsert,12,1, 12,2,  10,10);
411   T('Insert chars in front, same line',10,10,true,etscaInsert,10,1, 10,2,  10,11);
412   T('Insert chars in front, same line',10,40,true,etscaInsert,10,28, 10,29,  10,41);
413   T('Insert chars behind, same line',10,10,true,etscaInsert,10,11, 10,12,  10,10);
414   T('Insert chars behind, same line, leftbound',10,10,true,etscaInsert,10,10, 10,12,  10,10);
415   T('Insert chars behind, same line, rightbound',10,10,false,etscaInsert,10,10, 10,12,  10,12);
416   T('Insert chars and line breaks in front, same line',10,10,true,etscaInsert,10,1, 11,1,  11,10);
417   T('Insert chars and line breaks in front, same line',10,10,true,etscaInsert,10,1, 11,2,  11,11);
418   T('Insert chars and line breaks in front, same line',10,10,true,etscaInsert,10,2, 11,2,  11,10);
419   T('Insert chars and line breaks in front, same line',10,10,true,etscaInsert,10,2, 11,5,  11,13);
420   T('Insert chars and line breaks in front, same line',10,10,true,etscaInsert,10,2, 13,5,  13,13);
421 
422   T('Delete chars in front',10,10,true,etscaDelete, 1,1, 1,2, 10,10);
423   T('Delete lines in front',10,10,true,etscaDelete, 1,1, 2,2, 9,10);
424   T('Delete chars in front, same line',10,10,true,etscaDelete, 10,1, 10,2, 10,9);
425   T('Delete lines behind',10,10,true,etscaDelete, 11,1, 12,2, 10,10);
426   T('Delete chars behind, same line',10,10,true,etscaDelete, 10,11, 10,12, 10,10);
427   T('Delete chars behind, same line',10,10,true,etscaDelete, 10,10, 10,12, 10,10);
428   T('Delete lines in front, same line',10,10,true,etscaDelete, 9,1, 10,1, 9,10);
429   T('Delete lines in front, same line',10,10,true,etscaDelete, 9,1, 10,3, 9,8);
430   T('Delete position',10,10,true,etscaDelete, 9,1, 11,1, 9,1);
431   T('Delete position',10,10,true,etscaDelete, 10,1, 11,1, 10,1);
432   T('Delete position',10,10,true,etscaDelete, 10,5, 10,11, 10,5);
433 end;
434 
435 procedure Test_MergeTETSrcChanges;
436 var
437   Changes: TETSingleSrcChanges;
438 
439   procedure Check(Title: string; const aChanges: array of TETSrcChange);
440 
441     procedure E(Msg: string);
442     var
443       s: String;
444     begin
445       s:=Title+', '+Msg;
446       Changes.WriteDebugReport(s);
447       raise Exception.Create(s);
448     end;
449 
450   var
451     i: Integer;
452     ActualChange: TETSrcChange;
453     ExpectedChange: TETSrcChange;
454   begin
455     ActualChange:=Changes.First;
456     try
457       for i:=Low(aChanges) to High(aChanges) do begin
458         ExpectedChange:=aChanges[i];
459         if ExpectedChange=nil then begin
460           if ActualChange<>nil then
461             E('too many changes');
462           exit;
463         end;
464         if ActualChange=nil then
465           E('not enough changes (missing: '+ActualChange.AsString+')');
466         if ExpectedChange.AsString<>ActualChange.AsString then
467           E('diff: Expected=('+ExpectedChange.AsString+'), Actual=('+ActualChange.AsString+')');
468         ActualChange:=ActualChange.Next;
469       end;
470     finally
471       for i:=Low(aChanges) to High(aChanges) do
472         aChanges[i].Free;
473     end;
474   end;
475 
476 begin
477   Changes:=TETSingleSrcChanges.Create;
478   try
479     Changes.ConsistencyCheck;
480 
481     // test empty clear
482     Changes.Clear;
483     Changes.ConsistencyCheck;
484 
485     // test merge insert
486     Changes.Add(etscaInsert,1,1,1,46);
487     Changes.ConsistencyCheck;
488     Changes.Add(etscaInsert,1,46,2,1);
489     Changes.ConsistencyCheck;
490     Check('Merge insert',[TETSrcChange.Create(etscaInsert,1,1,2,1)]);
491     Changes.Clear;
492 
493     // insert characters into a previous multi line insert
494     Changes.Add(etscaInsert,10,1,12,1);
495     Changes.ConsistencyCheck;
496     Changes.Add(etscaInsert,10,1,10,2);
497     Changes.ConsistencyCheck;
498     Check('Ignore small insert',[TETSrcChange.Create(etscaInsert,10,1,12,1)]);
499     Changes.Clear;
500 
501     // delete behind previous delete
502     Changes.Add(etscaDelete,1,2,1,4);
503     Changes.ConsistencyCheck;
504     Changes.Add(etscaDelete,1,2,1,5);
505     Changes.ConsistencyCheck;
506     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,2,1,7)]);
507     Changes.Clear;
508 
509     // delete encloses a previous delete
510     Changes.Add(etscaDelete,1,2,1,4);
511     Changes.ConsistencyCheck;
512     Changes.Add(etscaDelete,1,1,1,5);
513     Changes.ConsistencyCheck;
514     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,1,7)]);
515     Changes.Clear;
516 
517     // delete in front of a previous delete
518     Changes.Add(etscaDelete,2,2,2,4);
519     Changes.ConsistencyCheck;
520     Changes.Add(etscaDelete,1,1,2,2);
521     Changes.ConsistencyCheck;
522     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,2,4)]);
523     Changes.Clear;
524 
525     // delete encloses a previous delete of characters
526     Changes.Add(etscaDelete,2,2,2,4);
527     Changes.ConsistencyCheck;
528     Changes.Add(etscaDelete,1,1,3,1);
529     Changes.ConsistencyCheck;
530     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,3,1)]);
531     Changes.Clear;
532 
533     // delete encloses a previous delete of a line
534     Changes.Add(etscaDelete,2,2,3,4);
535     Changes.ConsistencyCheck;
536     Changes.Add(etscaDelete,1,1,4,1);
537     Changes.ConsistencyCheck;
538     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,5,1)]);
539     Changes.Clear;
540 
541     // delete encloses a previous delete at end
542     Changes.Add(etscaDelete,2,2,3,1);
543     Changes.ConsistencyCheck;
544     Changes.Add(etscaDelete,1,1,2,3);
545     Changes.ConsistencyCheck;
546     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,3,2)]);
547     Changes.Clear;
548 
549     // delete encloses a previous delete at end
550     Changes.Add(etscaDelete,2,2,3,2);
551     Changes.ConsistencyCheck;
552     Changes.Add(etscaDelete,1,1,2,3);
553     Changes.ConsistencyCheck;
554     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,3,3)]);
555     Changes.Clear;
556 
557     // delete encloses a previous delete at start
558     Changes.Add(etscaDelete,1,2,3,2);
559     Changes.ConsistencyCheck;
560     Changes.Add(etscaDelete,1,1,1,2);
561     Changes.ConsistencyCheck;
562     Check('combine deleting characters',[TETSrcChange.Create(etscaDelete,1,1,3,2)]);
563     Changes.Clear;
564   finally
565     Changes.Free;
566   end;
567 end;
568 
569 { TETMultiSrcChanges }
570 
571 // inline
TETMultiSrcChanges.Countnull572 function TETMultiSrcChanges.Count: integer;
573 begin
574   Result:=fAllChanges.Count;
575 end;
576 
577 procedure TETMultiSrcChanges.SetSyncQueued(AValue: boolean);
578 begin
579   //debugln(['TETMultiSrcChanges.SetSyncQueued ',AValue]);
580   if csDesigning in ComponentState then
581     AValue:=false;
582   if FSyncQueued=AValue then Exit;
583   FSyncQueued:=AValue;
584   if SyncQueued then
585     Application.QueueAsyncCall(@DoSync,0)
586   else
587     Application.RemoveAsyncCalls(Self);
588 end;
589 
590 procedure TETMultiSrcChanges.DoSync(Data: PtrInt);
591 var
592   Node, NextNode: TAvlTreeNode;
593 begin
594   //debugln(['TETMultiSrcChanges.DoSync Files=',fPendingChanges.Count]);
595   FSyncQueued:=false;
596   if fPendingChanges.Count=0 then exit;
597   if Assigned(OnSync) then
598     OnSync(Self);
599   // clear pending
600   Node:=fPendingChanges.FindLowest;
601   while Node<>nil do begin
602     NextNode:=Node.Successor;
603     TETSingleSrcChanges(Node.Data).Clear; // this removes Node from fPendingChanges
604     Node:=NextNode;
605   end;
606   fPendingChanges.Clear;
607   //debugln(['TETMultiSrcChanges.DoSync END FSyncQueued=',FSyncQueued,' fPendingChanges=',fPendingChanges.Count]);
608 end;
609 
610 constructor TETMultiSrcChanges.Create(AOwner: TComponent);
611 begin
612   inherited Create(AOwner);
613   fAllChanges:=TAvlTree.Create(@CompareETSrcChangesFilenames);
614   fPendingChanges:=TAvlTree.Create(@CompareETSrcChangesFilenames);
615 end;
616 
617 destructor TETMultiSrcChanges.Destroy;
618 begin
619   SyncQueued:=false;
620   Clear;
621   FreeAndNil(fPendingChanges);
622   FreeAndNil(fAllChanges);
623   inherited Destroy;
624 end;
625 
626 procedure TETMultiSrcChanges.Clear;
627 begin
628   SyncQueued:=false;
629   fPendingChanges.Clear;
630   fAllChanges.FreeAndClear;
631 end;
632 
GetChangesnull633 function TETMultiSrcChanges.GetChanges(const aFilename: string;
634   CreateIfNotExists: boolean): TETSingleSrcChanges;
635 var
636   Node: TAvlTreeNode;
637 begin
638   Node:=fAllChanges.FindKey(Pointer(aFilename),@CompareFilenameAndETSrcChanges);
639   if Node<>nil then
640     Result:=TETSingleSrcChanges(Node.Data)
641   else if CreateIfNotExists then begin
642     Result:=TETSingleSrcChanges.Create;
643     Result.Filename:=aFilename;
644     Result.MultiSrcChanges:=Self;
645   end else
646     Result:=nil;
647 end;
648 
TETMultiSrcChanges.AdaptCaretnull649 function TETMultiSrcChanges.AdaptCaret(const aFilename: string; var Line,
650   Col: integer; LeftBound: boolean): boolean;
651 var
652   Changes: TETSingleSrcChanges;
653 begin
654   Changes:=GetChanges(aFilename,false);
655   if Changes=nil then
656     Result:=false
657   else
658     Result:=Changes.AdaptCaret(Line,Col,LeftBound);
659 end;
660 
661 { TETSrcChange }
662 
663 constructor TETSrcChange.Create(AnAction: TETSrcChangeAction; const aFromPos,
664   aToPos: TPoint);
665 begin
666   Action:=AnAction;
667   FromPos:=aFromPos;
668   ToPos:=aToPos;
669 end;
670 
671 constructor TETSrcChange.Create(AnAction: TETSrcChangeAction; FromPosY,
672   FromPosX, ToPosY, ToPosX: integer);
673 begin
674   Action:=AnAction;
675   FromPos.Y:=FromPosY;
676   FromPos.X:=FromPosX;
677   ToPos.Y:=ToPosY;
678   ToPos.X:=ToPosX;
679 end;
680 
AsStringnull681 function TETSrcChange.AsString: string;
682 begin
683   if Action=etscaInsert then
684     Result:='Insert'
685   else
686     Result:='Delete';
687   Result+='-From='+IntToStr(FromPos.Y)+','+IntToStr(FromPos.X);
688   Result+='-To='+IntToStr(ToPos.Y)+','+IntToStr(ToPos.X);
689 end;
690 
691 { TETSingleSrcChanges }
692 
693 procedure TETSingleSrcChanges.SetInPendingTree(AValue: boolean);
694 begin
695   if FMultiSrcChanges=nil then AValue:=false;
696   if FInPendingTree=AValue then Exit;
697   FInPendingTree:=AValue;
698   if fInPendingTree then begin
699     FMultiSrcChanges.fPendingChanges.Add(Self);
700     if FMultiSrcChanges.AutoSync then
701       FMultiSrcChanges.SyncQueued:=true;
702   end else
703     FMultiSrcChanges.fPendingChanges.Remove(Self);
704 end;
705 
706 procedure TETSingleSrcChanges.Append(Change: TETSrcChange);
707 begin
708   if First=nil then begin
709     FFirst:=Change;
710     InPendingTree:=true;
711   end else begin
712     FLast.Next:=Change;
713     Change.Prev:=Last;
714   end;
715   fLast:=Change;
716 end;
717 
718 procedure TETSingleSrcChanges.Remove(Change: TETSrcChange);
719 begin
720   if First=Change then begin
721     FFirst:=Change.Next;
722     if FFirst=nil then
723       InPendingTree:=false;
724   end;
725   if Last=Change then
726     fLast:=Change.Prev;
727   if Change.Prev<>nil then
728     Change.Prev.Next:=Change.Next;
729   if Change.Next<>nil then
730     Change.Next.Prev:=Change.Prev;
731   Change.Prev:=nil;
732   Change.Next:=nil;
733 end;
734 
735 procedure TETSingleSrcChanges.SetFilename(AValue: string);
736 var
737   HasChanged: Boolean;
738 begin
739   if FFilename=AValue then Exit;
740   HasChanged:=CompareFilenames(FFilename,AValue)<>0;
741   if HasChanged then begin
742     if FMultiSrcChanges<>nil then
743       raise Exception.Create('TETSingleSrcChanges.SetFilename');
744   end;
745   FFilename:=AValue;
746   if HasChanged then
747     Clear;
748 end;
749 
750 procedure TETSingleSrcChanges.SetMultiSrcChanges(AValue: TETMultiSrcChanges);
751 begin
752   if FMultiSrcChanges=AValue then Exit;
753   if Filename='' then
754     raise Exception.Create('TETSingleSrcChanges.SetMultiSrcChanges empty filename');
755   if (FMultiSrcChanges<>nil) then begin
756     if (csDestroying in FMultiSrcChanges.ComponentState) then begin
757       fInPendingTree:=false;
758     end else begin
759       InPendingTree:=false;
760       FMultiSrcChanges.fAllChanges.Remove(Self);
761     end;
762   end;
763   FMultiSrcChanges:=AValue;
764   if FMultiSrcChanges<>nil then begin
765     FMultiSrcChanges.fAllChanges.Add(Self);
766     InPendingTree:=First<>nil;
767   end;
768 end;
769 
770 constructor TETSingleSrcChanges.Create;
771 begin
772 end;
773 
774 destructor TETSingleSrcChanges.Destroy;
775 begin
776   MultiSrcChanges:=nil;
777   Clear;
778   inherited Destroy;
779 end;
780 
781 procedure TETSingleSrcChanges.Clear;
782 var
783   Item: TETSrcChange;
784   CurItem: TETSrcChange;
785 begin
786   Item:=First;
787   while Item<>nil do begin
788     CurItem:=Item;
789     Item:=Item.Next;
790     CurItem.Free;
791   end;
792   fFirst:=nil;
793   FLast:=nil;
794   InPendingTree:=false;
795 end;
796 
797 procedure TETSingleSrcChanges.GetRange(out MinY, MaxY, LineDiffBehindMaxY: integer);
798 // true if there are changes
799 // All changes were done between lines MinY and MaxY (inclusive).
800 // Lines behind MaxY are moved by LineDiffBehindMaxY.
801 // In other words:
802 //   if MinY<=Line<=MaxY then AdaptCaret(Line,Col,...)
803 //   else if Line>MaxY then inc(Line,LineDiffBehindMaxY);
804 var
805   Change: TETSrcChange;
806   y: Integer;
807   x: Integer;
808 begin
809   MinY:=High(Integer);
810   MaxY:=0;
811   LineDiffBehindMaxY:=0;
812   Change:=First;
813   if Change=nil then exit;
814   while Change<>nil do begin
815     MinY:=Min(MinY,Change.FromPos.Y);
816     if Change.Action=etscaInsert then
817       MaxY:=Max(MaxY,Change.FromPos.Y)
818     else
819       MaxY:=Max(MaxY,Change.ToPos.Y);
820     Change:=Change.Next;
821   end;
822   y:=MaxY+1;
823   x:=1;
824   AdaptCaret(y,x,true);
825   LineDiffBehindMaxY:=y-(MaxY+1);
826 end;
827 
828 // inline
Addnull829 function TETSingleSrcChanges.Add(Action: TETSrcChangeAction; const FromPos,
830   ToPos: TPoint): TETSrcChange;
831 begin
832   Result:=Add(Action,FromPos.Y,FromPos.X,ToPos.Y,ToPos.X);
833 end;
834 
Addnull835 function TETSingleSrcChanges.Add(Action: TETSrcChangeAction; FromPosY, FromPosX,
836   ToPosY, ToPosX: integer): TETSrcChange;
837 
838   procedure RaiseFromPosBehindToPos;
839   begin
840     raise Exception.CreateFmt('TETSrcChanges.Add FromPos=%s,%s behind ToPos=%s,%s',[FromPosY,FromPosX,ToPosY,ToPosX]);
841   end;
842 
Mergenull843   function Merge(Prev, Cur: TETSrcChange): boolean;
844   begin
845     if (Prev=nil) or (Prev.Action<>Action) then
846       exit(false);
847     // check if addition can be merged
848     if Action=etscaInsert then begin
849       // Insertion
850       if (Prev.ToPos.Y=Cur.FromPos.Y) and (Prev.ToPos.X=Cur.FromPos.X) then begin
851         // Cur is an insert exactly behind Prev insert -> append insert
852         Prev.ToPos.Y:=Cur.ToPos.Y;
853         Prev.ToPos.X:=Cur.ToPos.X;
854         {$IFDEF VerboseETSrcChange}
855         debugln(['TETSrcChanges.Add appending insert: ',Prev.AsString]);
856         {$ENDIF}
857         exit(true);
858       end;
859       if (Cur.FromPos.Y=Cur.ToPos.Y)
860       and (Prev.FromPos.Y<=Cur.FromPos.Y) and (Prev.ToPos.Y>Cur.FromPos.Y) then begin
861         // Cur inserts characters into a Prev multi line insert -> ignore
862         {$IFDEF VerboseETSrcChange}
863         debugln(['TETSrcChanges.Add inserting characters into a multi line insert -> ignore']);
864         {$ENDIF}
865         exit(true);
866       end;
867       // ToDo: insert exactly in front
868     end else begin
869       // Deletion
870       if IsCaretInFrontOrSame(Cur.FromPos,Prev.FromPos)
871       and IsCaretInFrontOrSame(Prev.FromPos,Cur.ToPos) then begin
872         // Cur delete extends Prev delete => combine delete
873         etSrcEditMarks.AdaptCaret(Cur.ToPos.Y,Cur.ToPos.X,false,etscaInsert,
874           Prev.FromPos.Y,Prev.FromPos.X,Prev.ToPos.Y,Prev.ToPos.X);
875         Prev.ToPos:=Cur.ToPos;
876         Prev.FromPos:=Cur.FromPos;
877         {$IFDEF VerboseETSrcChange}
878         debugln(['TETSrcChanges.Add delete encloses previous delete: ',Prev.AsString]);
879         {$ENDIF}
880         exit(true);
881       end;
882     end;
883     Result:=false;
884   end;
885 
886 begin
887   {$IFDEF VerboseETSrcChange}
888   debugln(['TETSrcChanges.Add Action=',dbgs(Action),' From=',FromPosY,',',FromPosX,' To=',ToPosY,',',ToPosX]);
889   {$ENDIF}
890 
891   if (FromPosY=ToPosY) and (FromPosX=ToPosX) then
892     exit(nil); // no change => ignore
893 
894   // consistency check
895   if IsCaretInFront(ToPosY,ToPosX,FromPosY,FromPosX) then
896     RaiseFromPosBehindToPos;
897 
898   Result:=TETSrcChange.Create(Action, FromPosY, FromPosX, ToPosY, ToPosX);
899 
900   if Merge(Last,Result) then begin
901     repeat
902       Result.Free;
903       Result:=Last;
904       if not Merge(Result.Prev,Result) then exit;
905       Remove(Last);
906     until false;
907   end else begin
908     Append(Result);
909   end;
910 end;
911 
TETSingleSrcChanges.AdaptCaretnull912 function TETSingleSrcChanges.AdaptCaret(var Line, Col: integer; LeftBound: boolean
913   ): boolean;
914 var
915   Change: TETSrcChange;
916   OldCol: Integer;
917   OldLine: Integer;
918 begin
919   OldCol:=Col;
920   OldLine:=Line;
921   Change:=First;
922   while Change<>nil do begin
923     etSrcEditMarks.AdaptCaret(Line,Col,LeftBound,Change.Action,
924       Change.FromPos.Y,Change.FromPos.X,Change.ToPos.Y,Change.ToPos.X);
925     Change:=Change.Next;
926   end;
927   Result:=(Line<>OldLine) or (Col<>OldCol);
928 end;
929 
930 procedure TETSingleSrcChanges.ConsistencyCheck;
931 
932   procedure E(Msg: string);
933   begin
934     raise Exception.Create('TETSrcChanges ConsistencyError: '+Msg);
935   end;
936 
937 var
938   Change: TETSrcChange;
939   List: TFPList;
940   ReallyInPendingTree: Boolean;
941 begin
942   if (First=nil)<>(Last=nil) then
943     E('(First=nil)<>(Last=nil)');
944   List:=TFPList.Create;
945   try
946     Change:=First;
947     while Change<>nil do begin
948       if IsCaretInFront(Change.ToPos,Change.FromPos) then
949         E('FromPos>ToPos: '+Change.AsString);
950       if Change.Prev<>nil then begin
951         if Change.Prev.Next<>Change then
952           E('Change.Prev.Next<>Change '+Change.AsString);
953       end else begin
954         if Change<>First then
955           E('Change.Prev=nil');
956       end;
957       if (Change.Next=nil) and (Change<>Last) then
958         E('Change.Next=nil');
959       if List.IndexOf(Change)>=0 then
960         E('Cycle '+Change.AsString);
961       List.Add(Change);
962       Change:=Change.Next;
963     end;
964   finally
965     List.Free;
966   end;
967   if MultiSrcChanges<>nil then begin
968     if MultiSrcChanges.fAllChanges.Find(Self)=nil then
969       E('MultiSrcChanges.fAllChanges.Find(Self)=nil');
970     ReallyInPendingTree:=MultiSrcChanges.fPendingChanges.Find(Self)<>nil;
971     if InPendingTree<>ReallyInPendingTree then
972       E('InPendingTree<>ReallyInPendingTree');
973   end else begin
974     if InPendingTree then
975       E('MultiSrcChanges=nil InPendingTree=true');
976   end;
977 end;
978 
979 procedure TETSingleSrcChanges.WriteDebugReport(Title: string);
980 var
981   Change: TETSrcChange;
982 begin
983   debugln('TETSrcChanges.WriteDebugReport ',Title);
984   Change:=First;
985   while Change<>nil do begin
986     debugln('  ',Change.AsString);
987     Change:=Change.Next;
988   end;
989 end;
990 
991 { TETSynPlugin }
992 
993 procedure TETSynPlugin.OnLineEdit(Sender: TSynEditStrings; aLinePos, aBytePos,
994   aCount, aLineBrkCnt: Integer; aText: String);
995 {
996   aLinePos is 1-based
997   aBytePos is 1-based column in line
998 
999   Insert:
1000     aCount > 0
1001   Delete:
1002     aCount < 0
1003     Example deleting line 290..292:
1004       LinePos=291 BytePos=1 Count=-45 LineBrkCnt=0 Text=""
1005       LinePos=292 BytePos=1 Count=-33 LineBrkCnt=0 Text=""
1006       LinePos=291 BytePos=1 Count=0 LineBrkCnt=-2 Text=""
1007       LinePos=290 BytePos=70 Count=0 LineBrkCnt=-1 Text=""
1008       LinePos=290 BytePos=1 Count=-69 LineBrkCnt=0 Text=""
1009 }
1010 begin
1011   if not Enabled then exit;
1012   if Changes=nil then exit;
1013   if Assigned(OnIsEnabled) and not OnIsEnabled(Self) then exit;
1014 
1015   {$IFDEF VerboseETSrcChange}
1016   debugln(['TETSynPlugin.OnLineEdit LinePos=',aLinePos,' BytePos=',aBytePos,' Count=',aCount,' LineBrkCnt=',aLineBrkCnt,' Text="',dbgstr(aText),'"']);
1017   {$ENDIF}
1018   if aCount>0 then begin
1019     // insert characters
1020     FChanges.Add(etscaInsert,aLinePos,aBytePos,aLinePos,aBytePos+aCount);
1021   end else if aCount<0 then begin
1022     // delete characters
1023     FChanges.Add(etscaDelete,aLinePos,aBytePos,aLinePos,aBytePos-aCount);
1024   end else if aLineBrkCnt>0 then begin
1025     // insert line breaks
1026     // Note: always at end of line, because Count=0
1027     FChanges.Add(etscaInsert,aLinePos,aBytePos,aLinePos+aLineBrkCnt,1);
1028   end else if aLineBrkCnt<0 then begin
1029     // delete line breaks / empty lines
1030     FChanges.Add(etscaDelete,aLinePos,aBytePos,aLinePos-aLineBrkCnt,1);
1031   end;
1032 end;
1033 
1034 constructor TETSynPlugin.Create(AOwner: TComponent);
1035 begin
1036   inherited Create(AOwner);
1037   ViewedTextBuffer.AddEditHandler(@OnLineEdit);
1038   FEnabled:=true;
1039 end;
1040 
1041 destructor TETSynPlugin.Destroy;
1042 begin
1043   ViewedTextBuffer.RemoveEditHandler(@OnLineEdit);
1044   inherited Destroy;
1045 end;
1046 
1047 { TETMark }
1048 
1049 destructor TETMark.Destroy;
1050 begin
1051   if MsgLine is TLMsgViewLine then
1052     TLMsgViewLine(MsgLine).Mark:=nil;
1053   MsgLine:=nil;
1054   inherited Destroy;
1055 end;
1056 
1057 { TETMarks }
1058 
1059 // inline
GetMarkStylesnull1060 function TETMarks.GetMarkStyles(Urgency: TMessageLineUrgency): TETMarkStyle;
1061 begin
1062   Result:=fMarkStyles[Urgency];
1063 end;
1064 
1065 constructor TETMarks.Create(AOwner: TComponent);
1066 const
1067   DefMarkColorHint = TColor($00a5ff);
1068   DefMarkColorError = clRed;
1069 var
1070   u: TMessageLineUrgency;
1071 begin
1072   inherited Create(AOwner);
1073   if ExtToolsMarks=nil then
1074     ExtToolsMarks:=Self;
1075   for u:=low(TMessageLineUrgency) to high(TMessageLineUrgency) do
1076     fMarkStyles[u]:=TETMarkStyle.Create(u,DefMarkColorHint);
1077   fMarkStyles[mluWarning].Color:=DefMarkColorError;
1078   fMarkStyles[mluError].Color:=DefMarkColorError;
1079   fMarkStyles[mluFatal].Color:=DefMarkColorError;
1080   fMarkStyles[mluPanic].Color:=DefMarkColorError;
1081 end;
1082 
1083 destructor TETMarks.Destroy;
1084 var
1085   u: TMessageLineUrgency;
1086 begin
1087   if ExtToolsMarks=Self then
1088     ExtToolsMarks:=nil;
1089   for u:=low(TMessageLineUrgency) to high(TMessageLineUrgency) do
1090     FreeAndNil(fMarkStyles[u]);
1091   inherited Destroy;
1092 end;
1093 
CreateMarknull1094 function TETMarks.CreateMark(MsgLine: TMessageLine; aSynEdit: TSynEdit
1095   ): TETMark;
1096 var
1097   Line: Integer;
1098   Column: Integer;
1099   LineSrc: String;
1100 begin
1101   Result:=nil;
1102   if (MsgLine.Line<1) or (MsgLine.Column<1) or (MsgLine.Filename='') then exit;
1103   if aSynEdit=nil then begin
1104     if OnGetSynEditOfFile=nil then exit;
1105     OnGetSynEditOfFile(Self,MsgLine.Filename,aSynEdit);
1106     if (aSynEdit=nil) then exit;
1107   end;
1108   Line:=MsgLine.Line;
1109   Column:=MsgLine.Column;
1110   if (mlfLeftToken in MsgLine.Flags) then begin
1111     // the mark is at the of the token
1112     // synedit only supports starts of tokens
1113     // => adjust to start of token
1114     if (Column>1) and (Line>=1) and (Line<=aSynEdit.Lines.Count) then begin
1115       LineSrc:=aSynEdit.Lines[Line-1];
1116       if (Column<=length(LineSrc)+1) and (not IsSpaceChar[LineSrc[Column-1]])
1117       then begin
1118         dec(Column);
1119         if IsIdentChar[LineSrc[Column]] then begin
1120           while (Column>1) and (IsIdentChar[LineSrc[Column-1]]) do
1121             dec(Column);
1122         end;
1123       end;
1124     end;
1125   end;
1126   Result:=TETMark.Create(aSynEdit);
1127   Result.SourceMarks:=Self;
1128   Result.MsgLine:=MsgLine;
1129   Result.Line:=Line;
1130   Result.Column:=Column;
1131   Result.Visible:=true;
1132   Result.Priority:=Priority;
1133   Result.Urgency:=MsgLine.Urgency;
1134   Result.ImageList:=ImageList;
1135   Result.ImageIndex:=MarkStyles[Result.Urgency].ImageIndex;
1136   Result.SourceMarkup:=MarkStyles[Result.Urgency].SourceMarkup;
1137   aSynEdit.Marks.Add(Result);
1138 end;
1139 
1140 procedure TETMarks.RemoveMarks(aSynEdit: TSynEdit);
1141 var
1142   i: Integer;
1143   Mark: TSynEditMark;
1144 begin
1145   for i:=aSynEdit.Marks.Count-1 downto 0 do begin
1146     Mark:=aSynEdit.Marks[i];
1147     if Mark is TETMark then
1148       Mark.Free;
1149   end;
1150 end;
1151 
1152 { TETMarkStyle }
1153 
1154 procedure TETMarkStyle.SetColor(AValue: TColor);
1155 begin
1156   if FColor=AValue then Exit;
1157   FColor:=AValue;
1158   SourceMarkup.FrameColor:=Color;
1159 end;
1160 
1161 constructor TETMarkStyle.Create(TheUrgency: TMessageLineUrgency;
1162   TheColor: TColor);
1163 begin
1164   FUrgency:=TheUrgency;
1165   FColor:=TheColor;
1166   FSourceMarkup:=TSynSelectedColor.Create;
1167   SourceMarkup.Foreground:=clNone;
1168   SourceMarkup.Background:=clNone;
1169   SourceMarkup.FrameStyle:=slsWaved;
1170   SourceMarkup.FrameEdges:=sfeBottom;
1171   SourceMarkup.FrameColor:=Color;
1172 end;
1173 
1174 destructor TETMarkStyle.Destroy;
1175 begin
1176   FreeAndNil(FSourceMarkup);
1177   inherited Destroy;
1178 end;
1179 
1180 { TLMsgViewLine }
1181 
1182 destructor TLMsgViewLine.Destroy;
1183 begin
1184   FreeAndNil(Mark);
1185   inherited Destroy;
1186 end;
1187 
1188 end.
1189 
1190