1{-------------------------------------------------------------------------------
2The contents of this file are subject to the Mozilla Public License
3Version 1.1 (the "License"); you may not use this file except in compliance
4with the License. You may obtain a copy of the License at
5http://www.mozilla.org/MPL/
6
7Software distributed under the License is distributed on an "AS IS" basis,
8WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9the specific language governing rights and limitations under the License.
10
11Alternatively, the contents of this file may be used under the terms of the
12GNU General Public License Version 2 or later (the "GPL"), in which case
13the provisions of the GPL are applicable instead of those above.
14If you wish to allow use of your version of this file only under the terms
15of the GPL and not to allow others to use your version of this file
16under the MPL, indicate your decision by deleting the provisions above and
17replace them with the notice and other provisions required by the GPL.
18If you do not delete the provisions above, a recipient may use your version
19of this file under either the MPL or the GPL.
20
21-------------------------------------------------------------------------------}
22unit SynPluginSyncroEdit;
23
24{$mode objfpc}{$H+}
25
26interface
27
28uses
29  Classes, Controls, SysUtils, Forms, Graphics, SynEditMiscClasses,
30  LCLType, SynEdit, SynPluginSyncronizedEditBase, LazSynEditText, SynEditMiscProcs,
31  SynEditMouseCmds, SynEditKeyCmds, SynEditTypes, LCLIntf, LazUTF8;
32
33type
34
35  TSynPluginSyncroEditLowerLineCacheEntry = record
36    LineIndex: Integer;
37    LineText: String;
38  end;
39
40  { TSynPluginSyncroEditLowerLineCache }
41
42  TSynPluginSyncroEditLowerLineCache = class
43  private
44    FCaseSensitive: boolean;
45    FLines: TSynEditStrings;
46    FLower: Array of TSynPluginSyncroEditLowerLineCacheEntry;
47    function GetLowLine(aIndex: Integer): String;
48    procedure SetLines(const AValue: TSynEditStrings);
49  protected
50    Procedure LineTextChanged(Sender: TSynEditStrings; AIndex, ACount : Integer);
51  public
52    destructor Destroy; override;
53    procedure Clear;
54    property Lines: TSynEditStrings read FLines write SetLines;
55    property LowLines[aIndex: Integer]: String read GetLowLine; default;
56  end;
57
58  TSynPluginSyncroEditWordsHashEntry = record
59    Count, Hash: Integer;
60    LineIdx, BytePos, Len: Integer;
61    Next: Integer;
62    GrpId: Integer;
63  end;
64  PSynPluginSyncroEditWordsHashEntry = ^TSynPluginSyncroEditWordsHashEntry;
65
66  { TSynPluginSyncroEditWordsList }
67
68  TSynPluginSyncroEditWordsList = class
69  private
70    FCount: Integer;
71    FFirstUnused, FFirstGap: Integer;
72    function GetItem(aIndex: Integer): TSynPluginSyncroEditWordsHashEntry;
73    procedure SetItem(aIndex: Integer; const AValue: TSynPluginSyncroEditWordsHashEntry);
74  protected
75    FList: Array of TSynPluginSyncroEditWordsHashEntry;
76  public
77    constructor Create;
78    destructor Destroy; override;
79    procedure Clear;
80
81    function InsertEntry(aEntry: TSynPluginSyncroEditWordsHashEntry) : Integer;
82    procedure DeleteEntry(aIndex: Integer);
83    property Item[aIndex: Integer]: TSynPluginSyncroEditWordsHashEntry
84      read GetItem write SetItem; default;
85    property Count: Integer read FCount;
86  end;
87
88  { TSynPluginSyncroEditWordsHash }
89
90  TSynPluginSyncroEditWordsHash = class
91  private
92    FLowerLines: TSynPluginSyncroEditLowerLineCache;
93    FTableSize: Integer;
94    FTable: Array of TSynPluginSyncroEditWordsHashEntry;
95    FEntryCount: Integer;
96    FWordCount, FMultiWordCount: Integer;
97    FNextList: TSynPluginSyncroEditWordsList;
98
99    function CalcHash(aWord: PChar;aLen: Integer): Integer;
100    function CompareEntry(aEntry1, aEntry2: TSynPluginSyncroEditWordsHashEntry;
101                          aWord1, aWord2: PChar): Boolean;
102    function GetEntry(aModHash, aIndex: Integer): TSynPluginSyncroEditWordsHashEntry;
103
104    procedure InsertEntry(aEntry: TSynPluginSyncroEditWordsHashEntry; aWord: PChar);
105    procedure DeleteEntry(aEntry: TSynPluginSyncroEditWordsHashEntry; aWord: PChar);
106
107    procedure Resize(ANewSize: Integer);
108  public
109    constructor Create;
110    destructor Destroy; override;
111    procedure Clear;
112
113    // Excpects PChat to an already lowercase word
114    procedure AddWord(aLineIdx, aBytePos, aLen: Integer; aWord: PChar);
115    procedure RemoveWord(aLen: Integer; aWord: PChar);
116    function  GetWord(aWord: PChar; aLen: Integer): TSynPluginSyncroEditWordsHashEntry;
117    function  GetWordP(aWord: PChar; aLen: Integer): PSynPluginSyncroEditWordsHashEntry;
118    function  GetWordModHash(aWord: PChar; aLen: Integer): Integer;
119
120    property LowerLines: TSynPluginSyncroEditLowerLineCache
121      read FLowerLines write FLowerLines;
122    property HashEntry[aModHash, aIndex: Integer]: TSynPluginSyncroEditWordsHashEntry
123      read GetEntry;
124    property HashSize: Integer read FTableSize;
125    property EntryCount: Integer read FEntryCount;
126    property WordCount: Integer read FWordCount;
127    property MultiWordCount: Integer read FMultiWordCount;
128  end;
129
130  { TSynPluginSyncroEditMarkup }
131
132  TSynPluginSyncroEditMarkup = class(TSynPluginSyncronizedEditMarkup)
133  private
134    FGlyphAtLine: Integer;
135    FGlyphLastLine: Integer;
136    FGutterGlyph: TBitmap;
137    function GetGutterGlyphRect(aLine: Integer): TRect;
138    function GetGutterGlyphRect: TRect;
139    function GetGutterGlyphPaintLine: Integer;
140    procedure SetGlyphAtLine(const AValue: Integer);
141    procedure SetGutterGlyph(const AValue: TBitmap);
142    procedure DoInvalidate;
143  protected
144    procedure DoCaretChanged(Sender: TObject); override;
145    procedure DoTopLineChanged(OldTopLine : Integer); override;
146    procedure DoLinesInWindoChanged(OldLinesInWindow : Integer); override;
147    procedure DoEnabledChanged(Sender: TObject); override;
148  public
149    constructor Create(ASynEdit: TSynEditBase);
150    destructor Destroy; override;
151    procedure EndMarkup; override;
152
153    property GlyphAtLine: Integer read FGlyphAtLine write SetGlyphAtLine;       // -1 for caret
154    property GutterGlyph: TBitmap read FGutterGlyph write SetGutterGlyph;
155    property GutterGlyphRect: TRect read GetGutterGlyphRect;
156  end;
157
158  { TSynPluginSyncroEditMouseActions }
159
160  TSynPluginSyncroEditMouseActions = class(TSynEditMouseActions)
161  public
162    procedure ResetDefaults; override;
163  end;
164
165  { TSynEditSyncroEditKeyStrokesSelecting }
166
167  TSynEditSyncroEditKeyStrokesSelecting = class(TSynEditKeyStrokes)
168  public
169    procedure ResetDefaults; override;
170  end;
171
172  { TSynEditSyncroEditKeyStrokes }
173
174  TSynEditSyncroEditKeyStrokes = class(TSynEditKeyStrokes)
175  public
176    procedure ResetDefaults; override;
177  end;
178
179  { TSynEditSyncroEditKeyStrokesOffCell }
180
181  TSynEditSyncroEditKeyStrokesOffCell = class(TSynEditKeyStrokes)
182  public
183    procedure ResetDefaults; override;
184  end;
185
186  TSynPluginSyncroEditModes = (spseIncative, spseSelecting, spseEditing, spseInvalid);
187  { TSynPluginSyncroEdit }
188
189  TSynPluginSyncroEdit = class(TSynPluginCustomSyncroEdit)
190  private
191    FCaseSensitive: boolean;
192    FGutterGlyph: TBitmap;
193    FLowerLines: TSynPluginSyncroEditLowerLineCache;
194    FOnBeginEdit: TNotifyEvent;
195    FOnEndEdit: TNotifyEvent;
196    FOnModeChange: TNotifyEvent;
197    FWordIndex: TSynPluginSyncroEditWordsHash;
198    FWordScanCount: Integer;
199    FCallQueued: Boolean;
200    FEditModeQueued: Boolean;
201    FLastSelStart, FLastSelEnd: TPoint;
202    FParsedStart, FParsedStop: TPoint;
203    FMouseActions: TSynPluginSyncroEditMouseActions;
204    FMode: TSynPluginSyncroEditModes;
205
206    FKeystrokesSelecting: TSynEditKeyStrokes;
207    FKeystrokes, FKeyStrokesOffCell: TSynEditKeyStrokes;
208    procedure SetCaseSensitive(AValue: boolean);
209    procedure SetKeystrokesSelecting(const AValue: TSynEditKeyStrokes);
210    procedure SetKeystrokes(const AValue: TSynEditKeyStrokes);
211    procedure SetKeystrokesOffCell(const AValue: TSynEditKeyStrokes);
212    function  GetMarkup: TSynPluginSyncroEditMarkup;
213    function  Scan(AFrom, aTo: TPoint; BackWard: Boolean): TPoint;
214    procedure SetGutterGlyph(const AValue: TBitmap);
215    procedure SetMode(AValue: TSynPluginSyncroEditModes);
216    function  UnScan(AFrom, aTo: TPoint; BackWard: Boolean): TPoint;
217    procedure StartSyncroMode;
218    procedure StopSyncroMode;
219  protected
220    procedure DoImageChanged(Sender: TObject);
221    function  CreateMarkup: TSynPluginSyncronizedEditMarkup; override;
222    procedure DoSelectionChanged(Sender: TObject);
223    procedure DoScanSelection(Data: PtrInt);
224    procedure DoOnDeactivate; override;
225    procedure DoPreActiveEdit(aX, aY, aCount, aLineBrkCnt: Integer; aUndoRedo: Boolean);
226      override;
227
228    function MaybeHandleMouseAction(var AnInfo: TSynEditMouseActionInfo;
229                         HandleActionProc: TSynEditMouseActionHandler): Boolean;
230    function DoHandleMouseAction(AnAction: TSynEditMouseAction;
231                                 var AnInfo: TSynEditMouseActionInfo): Boolean;
232
233    procedure DoEditorRemoving(AValue: TCustomSynEdit); override;
234    procedure DoEditorAdded(AValue: TCustomSynEdit); override;
235    procedure DoClear; override;
236    procedure DoModeChanged;
237
238    procedure TranslateKey(Sender: TObject; Code: word; SState: TShiftState;
239      var Data: pointer; var IsStartOfCombo: boolean; var Handled: boolean;
240      var Command: TSynEditorCommand; FinishComboOnly: Boolean;
241      var ComboKeyStrokes: TSynEditKeyStrokes);
242    procedure ProcessSynCommand(Sender: TObject; AfterProcessing: boolean;
243              var Handled: boolean; var Command: TSynEditorCommand;
244              var AChar: TUTF8Char; Data: pointer; HandlerData: pointer);
245
246    property Markup: TSynPluginSyncroEditMarkup read GetMarkup;
247    property Mode: TSynPluginSyncroEditModes read FMode write SetMode;
248  public
249    constructor Create(AOwner: TComponent); override;
250    destructor Destroy; override;
251
252  published
253    property CaseSensitive: boolean read FCaseSensitive write SetCaseSensitive default false;
254    property GutterGlyph: TBitmap read FGutterGlyph write SetGutterGlyph;
255    property KeystrokesSelecting: TSynEditKeyStrokes
256      read FKeystrokesSelecting write SetKeystrokesSelecting;
257    property Keystrokes: TSynEditKeyStrokes
258      read FKeystrokes write SetKeystrokes;
259    property KeystrokesOffCell: TSynEditKeyStrokes
260      read FKeystrokesOffCell write SetKeystrokesOffCell;
261    property OnModeChange: TNotifyEvent read FOnModeChange write FOnModeChange;
262    property OnBeginEdit: TNotifyEvent read FOnBeginEdit write FOnBeginEdit;
263    property OnEndEdit: TNotifyEvent read FOnEndEdit write FOnEndEdit;
264
265  published
266    property Enabled;
267    property MarkupInfo;
268    property MarkupInfoCurrent;
269    property MarkupInfoSync;
270    property MarkupInfoArea;
271    property OnActivate;
272    property OnDeactivate;
273    property Editor;
274  end;
275
276const
277  emcSynPSyncroEdGutterGlyph         = emcPluginFirstSyncro +  0;
278
279  emcSynPSyncroEdCount               = 1;
280
281  ecSynPSyncroEdStart              = ecPluginFirstSyncro +  0;
282
283  ecSynPSyncroEdNextCell           = ecPluginFirstSyncro +  1;
284  ecSynPSyncroEdNextCellSel        = ecPluginFirstSyncro +  2;
285  ecSynPSyncroEdPrevCell           = ecPluginFirstSyncro +  3;
286  ecSynPSyncroEdPrevCellSel        = ecPluginFirstSyncro +  4;
287  ecSynPSyncroEdCellHome           = ecPluginFirstSyncro +  5;
288  ecSynPSyncroEdCellEnd            = ecPluginFirstSyncro +  6;
289  ecSynPSyncroEdCellSelect         = ecPluginFirstSyncro +  7;
290  ecSynPSyncroEdEscape             = ecPluginFirstSyncro +  8;
291  ecSynPSyncroEdNextFirstCell      = ecPluginFirstSyncro +  9;
292  ecSynPSyncroEdNextFirstCellSel   = ecPluginFirstSyncro + 10;
293  ecSynPSyncroEdPrevFirstCell      = ecPluginFirstSyncro + 11;
294  ecSynPSyncroEdPrevFirstCellSel   = ecPluginFirstSyncro + 12;
295
296  // If extending the list, reserve space in SynEditKeyCmds
297
298  ecSynPSyncroEdCount              = 13;
299
300implementation
301
302const
303  MAX_CACHE = 50; // Amount of lower-cased lines cached
304  MAX_SYNC_ED_WORDS = 50;// 250;
305  MAX_WORDS_PER_SCAN = 5000;
306  MIN_PROCESS_MSG_TIME = (1/86400)/15;
307
308Operator = (P1, P2 : TPoint) : Boolean;
309begin
310  Result := (P1.Y = P2.Y) and (P1.X = P2.X);
311end;
312
313Operator < (P1, P2 : TPoint) : Boolean;
314begin
315  Result := (P1.Y < P2.Y) or ( (P1.Y = P2.Y) and (P1.X < P2.X) );
316end;
317
318Operator <= (P1, P2 : TPoint) : Boolean;
319begin
320  Result := (P1.Y < P2.Y) or ( (P1.Y = P2.Y) and (P1.X <= P2.X) );
321end;
322
323Operator > (P1, P2 : TPoint) : Boolean;
324begin
325  Result := (P1.Y > P2.Y) or ( (P1.Y = P2.Y) and (P1.X > P2.X) );
326end;
327
328Operator >= (P1, P2 : TPoint) : Boolean;
329begin
330  Result := (P1.Y > P2.Y) or ( (P1.Y = P2.Y) and (P1.X >= P2.X) );
331end;
332
333{ TSynPluginSyncroEditLowerLineCache }
334
335function TSynPluginSyncroEditLowerLineCache.GetLowLine(aIndex: Integer): String;
336var
337  i, l: Integer;
338
339begin
340  if FCaseSensitive then begin
341    Result := FLines[aIndex];
342    exit;
343  end;
344
345  l := length(FLower);
346  for i := 0 to l-1 do
347    if FLower[i].LineIndex = aIndex then
348      exit(FLower[i].LineText);
349  Result := UTF8LowerCase(FLines[aIndex]);
350  if Result = '' then
351    exit;
352  if l < MAX_CACHE then begin
353    inc(l);
354    SetLength(FLower, l);
355  end;
356  for i := l-1 downto 1 do begin
357    FLower[i].LineIndex := FLower[i-1].LineIndex;
358    FLower[i].LineText  := FLower[i-1].LineText;
359  end;
360  FLower[0].LineIndex := aIndex;
361  FLower[0].LineText  := Result;
362end;
363
364procedure TSynPluginSyncroEditLowerLineCache.SetLines(const AValue: TSynEditStrings);
365begin
366  Clear;
367  if FLines <> nil then begin
368    fLines.RemoveChangeHandler(senrLineChange, @LineTextChanged);
369    fLines.RemoveChangeHandler(senrLineCount, @LineTextChanged);
370  end;
371  FLines := AValue;
372  if FLines <> nil then begin
373    fLines.AddChangeHandler(senrLineChange, @LineTextChanged);
374    fLines.AddChangeHandler(senrLineCount, @LineTextChanged);
375  end;
376end;
377
378procedure TSynPluginSyncroEditLowerLineCache.LineTextChanged(Sender: TSynEditStrings; AIndex,
379  ACount: Integer);
380begin
381  Clear;
382end;
383
384destructor TSynPluginSyncroEditLowerLineCache.Destroy;
385begin
386  Lines := nil;
387  Clear;
388  inherited Destroy;
389end;
390
391procedure TSynPluginSyncroEditLowerLineCache.Clear;
392begin
393  FLower := nil;
394end;
395
396{ TSynPluginSyncroEditWordsList }
397
398function TSynPluginSyncroEditWordsList.GetItem(aIndex: Integer): TSynPluginSyncroEditWordsHashEntry;
399begin
400  Result := FList[aIndex];
401end;
402
403procedure TSynPluginSyncroEditWordsList.SetItem(aIndex: Integer;
404  const AValue: TSynPluginSyncroEditWordsHashEntry);
405begin
406  FList[aIndex] := AValue;
407end;
408
409constructor TSynPluginSyncroEditWordsList.Create;
410begin
411  inherited;
412  clear;
413end;
414
415destructor TSynPluginSyncroEditWordsList.Destroy;
416begin
417  inherited Destroy;
418  clear;
419end;
420
421procedure TSynPluginSyncroEditWordsList.Clear;
422begin
423  FList := nil;
424  FFirstGap := -1;
425  FFirstUnused := 0;
426  FCount := 0;
427end;
428
429function TSynPluginSyncroEditWordsList.InsertEntry(aEntry: TSynPluginSyncroEditWordsHashEntry): Integer;
430begin
431  inc(FCount);
432  if FFirstGap >= 0 then begin
433    Result := FFirstGap;
434    FFirstGap := FList[Result].Next;
435  end else begin
436    if FFirstUnused >= length(FList) then
437      SetLength(FList, Max(1024, length(FList)) * 4);
438    Result := FFirstUnused;
439    inc(FFirstUnused);
440  end;
441  FList[Result] := aEntry;
442end;
443
444procedure TSynPluginSyncroEditWordsList.DeleteEntry(aIndex: Integer);
445begin
446  dec(FCount);
447  FList[aIndex].Next := FFirstGap;
448  FFirstGap := aIndex;
449end;
450
451{ TSynPluginSyncroEditWordsHash }
452
453function TSynPluginSyncroEditWordsHash.CalcHash(aWord: PChar; aLen: Integer): Integer;
454var
455  v, n, p, a, b, c, c1, i: Integer;
456begin
457  a := 0;
458  b := 0;
459  c := 0;
460  c1 := 0;
461  n := 1;
462  p := 0;
463  i := aLen;
464  while i > 0 do begin
465    v  := ord(aWord^);
466    a := a     + v * (1 + (n mod 8));
467    if a > 550 then a := a mod 550;
468    b := b * 3 + v * n - p;
469    if b > 550 then b := b mod 550;
470    c1 := c1   + v * (1 + (a mod 11));
471    c := c + c1;
472    if c > 550 then c := c mod 550;
473    dec(i);
474    inc(aWord);
475    inc(n);
476    p := v;
477  end;
478
479  Result := (((aLen mod 11) * 550 + b) * 550 + c) * 550 + a;
480end;
481
482function TSynPluginSyncroEditWordsHash.CompareEntry(aEntry1,
483  aEntry2: TSynPluginSyncroEditWordsHashEntry; aWord1, aWord2: PChar): Boolean;
484var
485  Line1, Line2: String;
486begin
487  Result := (aEntry1.Len = aEntry2.Len) and (aEntry1.Hash = aEntry2.Hash);
488  if not Result then exit;
489
490  if aWord1 = nil then begin
491    Line1 := FLowerLines[aEntry1.LineIdx];
492    aWord1 := @Line1[aEntry1.BytePos];
493  end;
494  if aWord2 = nil then begin
495    Line2 := FLowerLines[aEntry2.LineIdx];
496    aWord2 := @Line2[aEntry2.BytePos];
497  end;
498
499  Result := CompareMem(aWord1, aWord2, aEntry1.Len);
500end;
501
502function TSynPluginSyncroEditWordsHash.GetEntry(aModHash,
503  aIndex: Integer): TSynPluginSyncroEditWordsHashEntry;
504begin
505  Result:= FTable[aModHash];
506  while aIndex > 0 do begin
507    if Result.Next < 0 then begin
508      Result.Count := 0;
509      Result.Hash := -1;
510      exit;
511    end;
512    Result := FNextList[Result.Next];
513    dec(aIndex);
514  end;
515end;
516
517procedure TSynPluginSyncroEditWordsHash.InsertEntry
518  (aEntry: TSynPluginSyncroEditWordsHashEntry;  aWord: PChar);
519var
520  j: LongInt;
521  ModHash: Integer;
522begin
523  aEntry.GrpId := 0;
524  if (FEntryCount >= FTableSize * 2 div 3) or (FNextList.Count > FTableSize div 8) then
525    Resize(Max(FTableSize, 1024) * 4);
526
527  ModHash := aEntry.Hash mod FTableSize;
528
529  if (FTable[ModHash].Count > 0) then begin
530    if CompareEntry(aEntry, FTable[ModHash], aWord, nil) then begin
531      if FTable[ModHash].Count = 1 then
532        inc(FMultiWordCount);
533      FTable[ModHash].Count := FTable[ModHash].Count + aEntry.Count;
534      exit;
535    end;
536
537    j := FTable[ModHash].Next;
538    while j >= 0 do begin
539      if CompareEntry(aEntry, FNextList[j], aWord, nil) then begin
540        if FNextList[j].Count = 1 then
541          inc(FMultiWordCount);
542        FNextList.FList[j].Count := FNextList.FList[j].Count + aEntry.Count;
543        exit;
544      end;
545      j := FNextList[j].Next;
546    end;
547
548    j := FNextList.InsertEntry(aEntry);
549    FNextList.FList[j].Next := FTable[ModHash].Next;
550    FTable[ModHash].Next := j;
551    inc(FWordCount);
552
553    exit;
554  end;
555
556  inc(FEntryCount);
557  inc(FWordCount);
558    //if (FEntryCount<20) or (FEntryCount mod 8192=0) then debugln(['entry add ', FEntryCount]);
559  FTable[ModHash] := aEntry;
560  FTable[ModHash].Next:= -1;
561end;
562
563procedure TSynPluginSyncroEditWordsHash.DeleteEntry(aEntry: TSynPluginSyncroEditWordsHashEntry;
564  aWord: PChar);
565var
566  j, i: Integer;
567  ModHash: Integer;
568begin
569  ModHash := aEntry.Hash mod FTableSize;
570
571  if (FTable[ModHash].Count > 0) then begin
572    if CompareEntry(aEntry, FTable[ModHash], aWord, nil) then begin
573      FTable[ModHash].Count := FTable[ModHash].Count - 1;
574      if FTable[ModHash].Count = 0 then begin
575        j := FTable[ModHash].Next;
576        if j >= 0 then begin
577          FTable[ModHash] := FNextList[j];
578          FNextList.DeleteEntry(j);
579        end
580        else
581          dec(FEntryCount);
582        dec(FWordCount);
583      end
584      else if FTable[ModHash].Count = 1 then
585        dec(FMultiWordCount);
586      exit;
587    end;
588
589    j := FTable[ModHash].Next;
590    while j >= 0 do begin
591      if CompareEntry(aEntry, FNextList[j], aWord, nil) then begin
592        FNextList.FList[j].Count := FNextList.FList[j].Count - 1;
593        if FNextList[j].Count = 0 then begin
594          i := FNextList[j].Next;
595          if i >= 0 then begin
596            FNextList[j] := FNextList[i];
597            FNextList.DeleteEntry(i);
598          end;
599          dec(FWordCount);
600        end
601        else if FNextList[j].Count = 1 then
602          dec(FMultiWordCount);
603        exit;
604      end;
605      j := FNextList[j].Next;
606    end;
607
608  end;
609  // ?? there was no entry ??
610end;
611
612procedure TSynPluginSyncroEditWordsHash.Resize(ANewSize: Integer);
613var
614  OldTable: Array of TSynPluginSyncroEditWordsHashEntry;
615  OldSize, i, j, k: Integer;
616begin
617  FEntryCount := 0;
618  FWordCount := 0;
619  FMultiWordCount := 0;
620  if FTableSize = 0 then begin
621    SetLength(FTable, ANewSize);
622    FTableSize := ANewSize;
623    exit;
624  end;
625
626  //debugln(['TSynPluginSyncroEditWordsHash.Resize ', ANewSize]);
627  OldSize := FTableSize;
628  SetLength(OldTable, FTableSize);
629  System.Move(FTable[0], OldTable[0], FTableSize * SizeOf(TSynPluginSyncroEditWordsHashEntry));
630  FillChar(FTable[0], FTableSize * SizeOf(TSynPluginSyncroEditWordsHashEntry), 0);
631  SetLength(FTable, ANewSize);
632  FTableSize := ANewSize;
633
634  for i := 0 to OldSize - 1 do begin
635    if OldTable[i].Count > 0 then begin
636      InsertEntry(OldTable[i], nil);
637      j := OldTable[i].Next;
638      while j >= 0 do begin
639        InsertEntry(FNextList[j], nil);
640        k := j;
641        j := FNextList[j].Next;
642        FNextList.DeleteEntry(k);
643      end;
644    end;
645  end;
646end;
647
648constructor TSynPluginSyncroEditWordsHash.Create;
649begin
650  inherited;
651  FNextList := TSynPluginSyncroEditWordsList.Create;
652  Clear;
653end;
654
655destructor TSynPluginSyncroEditWordsHash.Destroy;
656begin
657  Clear;
658  inherited Destroy;
659  FreeAndNil(FNextList);
660end;
661
662procedure TSynPluginSyncroEditWordsHash.Clear;
663begin
664  FTable := nil;
665  FTableSize := 0;
666  FEntryCount := 0;
667  FWordCount := 0;
668  FMultiWordCount := 0;
669  FNextList.Clear;
670end;
671
672procedure TSynPluginSyncroEditWordsHash.AddWord(aLineIdx, aBytePos, aLen: Integer;
673  aWord: PChar);
674var
675  NewEntry: TSynPluginSyncroEditWordsHashEntry;
676begin
677  NewEntry.Hash := CalcHash(aWord, aLen);
678  NewEntry.LineIdx := aLineIdx;
679  NewEntry.BytePos := aBytePos;
680  NewEntry.Len := aLen;
681  NewEntry.Count := 1;
682  InsertEntry(NewEntry, aWord);
683end;
684
685procedure TSynPluginSyncroEditWordsHash.RemoveWord(aLen: Integer; aWord: PChar);
686var
687  OldEntry: TSynPluginSyncroEditWordsHashEntry;
688begin
689  OldEntry.Count := 1;
690  OldEntry.Hash := CalcHash(aWord, aLen);
691  oldEntry.Len := aLen;
692  DeleteEntry(OldEntry, aWord);
693end;
694
695function TSynPluginSyncroEditWordsHash.GetWord(aWord: PChar;
696  aLen: Integer): TSynPluginSyncroEditWordsHashEntry;
697var
698  SearchEntry: TSynPluginSyncroEditWordsHashEntry;
699begin
700  Result.Hash := -1;
701  Result.Count:= 0;
702  if FTableSize < 1 then exit;
703
704  SearchEntry.Hash := CalcHash(aWord, aLen);
705  SearchEntry.Len := aLen;
706  Result := FTable[SearchEntry.Hash mod FTableSize];
707  while Result.Count > 0 do begin
708    if CompareEntry(Result, SearchEntry, nil, aWord) then exit;
709    if Result.Next < 0 then break;
710    Result := FNextList[Result.Next];
711  end;
712  Result.Hash := -1;
713  Result.Count:= 0;
714end;
715
716function TSynPluginSyncroEditWordsHash.GetWordP(aWord: PChar;
717  aLen: Integer): PSynPluginSyncroEditWordsHashEntry;
718var
719  SearchEntry: TSynPluginSyncroEditWordsHashEntry;
720begin
721  Result := nil;
722  if FTableSize < 1 then exit;
723
724  SearchEntry.Hash := CalcHash(aWord, aLen);
725  SearchEntry.Len := aLen;
726  Result := @FTable[SearchEntry.Hash mod FTableSize];
727  while Result^.Count > 0 do begin
728    if CompareEntry(Result^, SearchEntry, nil, aWord) then exit;
729    if Result^.Next < 0 then break;
730    Result := @FNextList.FList[Result^.Next];
731  end;
732  Result := nil;
733end;
734
735function TSynPluginSyncroEditWordsHash.GetWordModHash(aWord: PChar; aLen: Integer): Integer;
736begin
737  if FTableSize < 1 then exit(-1);
738  Result := CalcHash(aWord, aLen) mod FTableSize;
739end;
740
741{ TSynPluginSyncroEditMarkup }
742
743procedure TSynPluginSyncroEditMarkup.DoInvalidate;
744var
745  rcInval: TRect;
746begin
747  if not Enabled then exit;
748  if FGlyphLastLine <> -2 then begin
749    if SynEdit.HandleAllocated then begin
750      rcInval := GetGutterGlyphRect(FGlyphLastLine);
751      // and make sure we trigger the Markup // TODO: triigger markup on gutter paint too
752      rcInval.Right := Max(rcInval.Right, TCustomSynEdit(SynEdit).ClientRect.Right);
753      InvalidateRect(SynEdit.Handle, @rcInval, False);
754    end;
755  end;
756  if SynEdit.HandleAllocated then begin
757    rcInval := GetGutterGlyphRect;
758    // and make sure we trigger the Markup // TODO: triigger markup on gutter paint too
759    rcInval.Right := Max(rcInval.Right, TCustomSynEdit(SynEdit).ClientRect.Right);
760    InvalidateRect(SynEdit.Handle, @rcInval, False);
761  end;
762end;
763
764procedure TSynPluginSyncroEditMarkup.DoCaretChanged(Sender: TObject);
765begin
766  inherited DoCaretChanged(Sender);
767  DoInvalidate;
768end;
769
770procedure TSynPluginSyncroEditMarkup.DoTopLineChanged(OldTopLine: Integer);
771var
772  rcInval: TRect;
773begin
774  inherited DoTopLineChanged(OldTopLine);
775  // Glyph may have drawn up to one Line above
776  if FGlyphLastLine > 1 then begin
777    if SynEdit.HandleAllocated then begin
778      rcInval := GetGutterGlyphRect(FGlyphLastLine - 1);
779      InvalidateRect(SynEdit.Handle, @rcInval, False);
780    end;
781  end;
782  DoInvalidate;
783end;
784
785procedure TSynPluginSyncroEditMarkup.DoLinesInWindoChanged(OldLinesInWindow: Integer);
786begin
787  inherited DoLinesInWindoChanged(OldLinesInWindow);
788  DoInvalidate;
789end;
790
791procedure TSynPluginSyncroEditMarkup.DoEnabledChanged(Sender: TObject);
792var
793  rcInval: TRect;
794begin
795  inherited DoEnabledChanged(Sender);
796  if not Enabled then begin
797    if FGlyphLastLine <> -2 then begin
798      if SynEdit.HandleAllocated then begin
799        rcInval := GetGutterGlyphRect(FGlyphLastLine);
800        InvalidateRect(SynEdit.Handle, @rcInval, False);
801      end;
802    end;
803    FGlyphLastLine := -2;
804  end
805  else
806    DoInvalidate;
807end;
808
809procedure TSynPluginSyncroEditMarkup.EndMarkup;
810var
811  src, dst: TRect;
812begin
813  inherited EndMarkup;
814  if (FGutterGlyph.Height > 0) then begin
815    src :=  Classes.Rect(0, 0, FGutterGlyph.Width, FGutterGlyph.Height);
816    dst := GutterGlyphRect;
817    FGlyphLastLine := GetGutterGlyphPaintLine;
818    TCustomSynEdit(SynEdit).Canvas.CopyRect(dst, FGutterGlyph.Canvas, src);
819  end;
820end;
821
822procedure TSynPluginSyncroEditMarkup.SetGutterGlyph(const AValue: TBitmap);
823begin
824  if FGutterGlyph = AValue then exit;
825  if FGutterGlyph = nil then
826    FGutterGlyph := TBitMap.Create;
827  FGutterGlyph.Assign(AValue);
828  DoInvalidate;
829end;
830
831function TSynPluginSyncroEditMarkup.GetGutterGlyphRect(aLine: Integer): TRect;
832begin
833  Result :=  Classes.Rect(0, 0, FGutterGlyph.Width, FGutterGlyph.Height);
834  if aLine = -1 then
835    aLine := TCustomSynEdit(SynEdit).CaretY;
836  Result.Top := Max( Min( RowToScreenRow(aLine)
837                          * TCustomSynEdit(SynEdit).LineHeight,
838                          TCustomSynEdit(SynEdit).ClientHeight - FGutterGlyph.Height),
839                          0);
840  Result.Bottom := Result.Bottom + Result.Top;
841end;
842
843function TSynPluginSyncroEditMarkup.GetGutterGlyphRect: TRect;
844begin
845  Result := GetGutterGlyphRect(GlyphAtLine);
846end;
847
848function TSynPluginSyncroEditMarkup.GetGutterGlyphPaintLine: Integer;
849var
850  i: Integer;
851begin
852  Result := FGlyphAtLine;
853  if Result < 0 then
854    Result := TCustomSynEdit(SynEdit).CaretY;
855  if Result < TopLine then
856    Result := TopLine;
857  i := ScreenRowToRow(LinesInWindow);
858  if Result > i then
859    Result := i;
860end;
861
862procedure TSynPluginSyncroEditMarkup.SetGlyphAtLine(const AValue: Integer);
863begin
864  if FGlyphAtLine = AValue then exit;
865  FGlyphAtLine := AValue;
866  DoInvalidate;
867end;
868
869constructor TSynPluginSyncroEditMarkup.Create(ASynEdit: TSynEditBase);
870begin
871  FGutterGlyph := TBitMap.Create;
872  FGlyphLastLine := -2;
873  inherited;
874end;
875
876destructor TSynPluginSyncroEditMarkup.Destroy;
877begin
878  inherited Destroy;
879  FreeAndNil(FGutterGlyph);
880end;
881
882{ TSynPluginSyncroEdit }
883
884function TSynPluginSyncroEdit.Scan(AFrom, aTo: TPoint; BackWard: Boolean): TPoint;
885var
886  x2: Integer;
887  Line: String;
888begin
889  Result := AFrom;
890  if BackWard then begin
891    Line := FLowerLines[AFrom.y - 1];
892    while (AFrom >= aTo) do begin
893      AFrom.x :=  WordBreaker.PrevWordEnd(Line, AFrom.x, True);
894      if AFrom.x < 0 then begin
895        dec(AFrom.y);
896        Line := FLowerLines[AFrom.y-1];
897        AFrom.x := length(Line) + 1;
898        continue;
899      end;
900      x2 :=  WordBreaker.PrevWordStart(Line, AFrom.x, True);
901      if (AFrom.y > ATo.y) or (x2 >= ATo.x) then begin
902        FWordIndex.AddWord(AFrom.y - 1, x2, AFrom.x - x2, @Line[x2]);
903        Result := AFrom;
904        Result.x := x2;
905        inc(FWordScanCount);
906        if FWordScanCount > MAX_WORDS_PER_SCAN then break;
907      end;
908      AFrom.x := x2;
909    end;
910  end
911  else begin
912    Line := FLowerLines[AFrom.y - 1];
913    while (AFrom <= aTo) do begin
914      AFrom.x :=  WordBreaker.NextWordStart(Line, AFrom.x, True);
915      if AFrom.x < 0 then begin
916        inc(AFrom.y);
917        AFrom.x := 1;
918        Line := FLowerLines[AFrom.y-1];
919        continue;
920      end;
921      x2 :=  WordBreaker.NextWordEnd(Line, AFrom.x, True);
922      if (AFrom.y < ATo.y) or (x2 <= ATo.x) then begin
923        FWordIndex.AddWord(AFrom.y - 1, AFrom.x, x2-AFrom.x, @Line[AFrom.x]);
924        Result := AFrom;
925        Result.x := x2;
926        inc(FWordScanCount);
927        if FWordScanCount > MAX_WORDS_PER_SCAN then break;
928      end;
929      AFrom.x := x2;
930    end;
931  end;
932end;
933
934procedure TSynPluginSyncroEdit.SetKeystrokesSelecting(const AValue: TSynEditKeyStrokes);
935begin
936  if AValue = nil then
937    FKeystrokesSelecting.Clear
938  else
939    FKeystrokesSelecting.Assign(AValue);
940end;
941
942procedure TSynPluginSyncroEdit.SetCaseSensitive(AValue: boolean);
943begin
944  FCaseSensitive := AValue;
945  FLowerLines.FCaseSensitive := AValue;
946  FLowerLines.Clear;
947end;
948
949procedure TSynPluginSyncroEdit.SetKeystrokes(const AValue: TSynEditKeyStrokes);
950begin
951  if AValue = nil then
952    FKeystrokes.Clear
953  else
954    FKeystrokes.Assign(AValue);
955end;
956
957procedure TSynPluginSyncroEdit.SetKeystrokesOffCell(const AValue: TSynEditKeyStrokes);
958begin
959  if AValue = nil then
960    FKeyStrokesOffCell.Clear
961  else
962    FKeyStrokesOffCell.Assign(AValue);
963end;
964
965function TSynPluginSyncroEdit.GetMarkup: TSynPluginSyncroEditMarkup;
966begin
967  Result := TSynPluginSyncroEditMarkup(FMarkup);
968end;
969
970procedure TSynPluginSyncroEdit.SetGutterGlyph(const AValue: TBitmap);
971begin
972  if FGutterGlyph = AValue then exit;
973  if FGutterGlyph = nil then begin
974    FGutterGlyph := TBitMap.Create;
975    FGutterGlyph.OnChange := @DoImageChanged;
976  end;
977  FGutterGlyph.Assign(AValue);
978end;
979
980procedure TSynPluginSyncroEdit.SetMode(AValue: TSynPluginSyncroEditModes);
981begin
982  if Mode = AValue then Exit;
983  if (FMode= spseEditing) and Assigned(FOnEndEdit) then
984    FOnEndEdit(Self);
985  FMode := AValue;
986  if (FMode= spseEditing) and Assigned(FOnBeginEdit) then
987    FOnBeginEdit(Self);
988  DoModeChanged;
989end;
990
991function TSynPluginSyncroEdit.UnScan(AFrom, aTo: TPoint; BackWard: Boolean): TPoint;
992var
993  x2: Integer;
994  Line: String;
995begin
996  Result := AFrom;
997  if BackWard then begin
998    Line := FLowerLines[AFrom.y - 1];
999    while (AFrom > aTo) do begin
1000      AFrom.x :=  WordBreaker.PrevWordEnd(Line, AFrom.x, True);
1001      if AFrom.x < 0 then begin
1002        dec(AFrom.y);
1003        Line := FLowerLines[AFrom.y-1];
1004        AFrom.x := length(Line) + 1;
1005        continue;
1006      end;
1007      x2 :=  WordBreaker.PrevWordStart(Line, AFrom.x, True);
1008      FWordIndex.RemoveWord(AFrom.x - x2, @Line[x2]);
1009      AFrom.x := x2;
1010      Result := AFrom;
1011      inc(FWordScanCount);
1012      if FWordScanCount > MAX_WORDS_PER_SCAN then break;
1013    end;
1014  end
1015  else begin
1016    Line := FLowerLines[AFrom.y - 1];
1017    while (AFrom < aTo) do begin
1018      AFrom.x :=  WordBreaker.NextWordStart(Line, AFrom.x, True);
1019      if AFrom.x < 0 then begin
1020        inc(AFrom.y);
1021        AFrom.x := 1;
1022        Line := FLowerLines[AFrom.y-1];
1023        continue;
1024      end;
1025      x2 :=  WordBreaker.NextWordEnd(Line, AFrom.x, True);
1026      FWordIndex.RemoveWord(x2-AFrom.x, @Line[AFrom.x]);
1027      AFrom.x := x2;
1028      Result := AFrom;
1029      inc(FWordScanCount);
1030      if FWordScanCount > MAX_WORDS_PER_SCAN then break;
1031    end;
1032  end;
1033end;
1034
1035procedure TSynPluginSyncroEdit.StartSyncroMode;
1036var
1037  Pos, EndPos: TPoint;
1038  Line: String;
1039  x2, g: Integer;
1040  entry: PSynPluginSyncroEditWordsHashEntry;
1041  f: Boolean;
1042begin
1043  if FCallQueued then begin
1044    FEditModeQueued := True;
1045    exit;
1046  end;
1047  FEditModeQueued := False;
1048  if FWordIndex.MultiWordCount = 0 then exit;
1049
1050  Mode :=  spseEditing;
1051  Active := True;
1052  AreaMarkupEnabled := True;
1053  SetUndoStart;
1054
1055  // Reset them, since Selectionchanges are not tracked during spseEditing
1056  FLastSelStart := Point(-1,-1);
1057  FLastSelEnd := Point(-1,-1);
1058
1059  Pos := SelectionObj.FirstLineBytePos;
1060  EndPos := SelectionObj.LastLineBytePos;
1061
1062  with Cells.AddNew do begin
1063    LogStart := Pos;
1064    LogEnd := EndPos;
1065    Group := -1;
1066  end;
1067  MarkupArea.CellGroupForArea := -1;
1068  Markup.GlyphAtLine := Pos.y;
1069
1070  g := 1;
1071  Line := FLowerLines[Pos.y-1];
1072  while (Pos <= EndPos) do begin
1073    Pos.x :=  WordBreaker.NextWordStart(Line, Pos.x, True);
1074    if Pos.x < 0 then begin
1075      inc(Pos.y);
1076      Pos.x := 1;
1077      Line := FLowerLines[Pos.y-1];
1078      continue;
1079    end;
1080    x2 :=  WordBreaker.NextWordEnd(Line, Pos.x, True);
1081    if (Pos.y < EndPos.y) or (x2 <= EndPos.x) then begin
1082      entry := FWordIndex.GetWordP(@Line[Pos.x], x2-Pos.x);
1083      f := False;
1084      if (entry <> nil) and (entry^.Count > 1) then begin;
1085        if (entry^.GrpId = 0) and (g <= MAX_SYNC_ED_WORDS) then begin
1086          entry^.GrpId := g;
1087          inc(g);
1088          f := True;
1089        end;
1090        if (entry^.GrpId > 0) then
1091          with Cells.AddNew do begin
1092            LogStart := Pos;
1093            LogEnd := Point(x2, Pos.y);
1094            Group := entry^.GrpId;
1095            FirstInGroup := f;
1096          end;
1097      end;
1098
1099    end;
1100    Pos.x := x2;
1101  end;
1102  FWordIndex.Clear;
1103
1104  CurrentCell := 1;
1105  SelectCurrentCell;
1106  if g = 1 then StopSyncroMode;
1107end;
1108
1109procedure TSynPluginSyncroEdit.StopSyncroMode;
1110begin
1111  Active := False;
1112end;
1113
1114procedure TSynPluginSyncroEdit.DoImageChanged(Sender: TObject);
1115begin
1116  if Markup <> nil then
1117    Markup.GutterGlyph := FGutterGlyph;
1118end;
1119
1120function TSynPluginSyncroEdit.CreateMarkup: TSynPluginSyncronizedEditMarkup;
1121begin
1122  Result := TSynPluginSyncroEditMarkup.Create(Editor);
1123  if FGutterGlyph <> nil then
1124    TSynPluginSyncroEditMarkup(Result).GutterGlyph := FGutterGlyph;
1125end;
1126
1127procedure TSynPluginSyncroEdit.DoSelectionChanged(Sender: TObject);
1128begin
1129  if Mode = spseEditing then exit;
1130  If (not SelectionObj.SelAvail) or (SelectionObj.ActiveSelectionMode = smColumn) then begin
1131    FLastSelStart := Point(-1,-1);
1132    FLastSelEnd := Point(-1,-1);
1133    if Active or PreActive then begin
1134      FWordIndex.Clear;
1135      Editor.Invalidate;
1136      Active := False;
1137      MarkupEnabled := False;
1138    end;
1139    Mode := spseIncative;
1140    exit;
1141  end;
1142
1143  if Mode = spseInvalid then exit;
1144
1145  if Mode = spseIncative then begin
1146    Cells.Clear;
1147    AreaMarkupEnabled := False;
1148    MarkupEnabled := False;
1149    PreActive := True;
1150  end;
1151  Mode := spseSelecting;
1152  Markup.GlyphAtLine := -1;
1153  if not FCallQueued then
1154    Application.QueueAsyncCall(@DoScanSelection, 0);
1155  FCallQueued := True;
1156end;
1157
1158procedure TSynPluginSyncroEdit.DoScanSelection(Data: PtrInt);
1159var
1160  NewPos, NewEnd: TPoint;
1161
1162  function InitParsedPoints: Boolean;
1163  // Find the first begin of a word, inside the block (if any)
1164  var
1165    x, y: Integer;
1166  begin
1167    if FParsedStart.y >= 0 then exit(True);
1168    y := NewPos.y;
1169    x := NewPos.x;
1170    while y <= NewEnd.y do begin
1171      x :=  WordBreaker.NextWordStart(FLowerLines[y-1], x, True);
1172      if (x > 0) and ((y < NewEnd.Y) or (x <= NewEnd.x)) then begin
1173        FParsedStart.y := y;
1174        FParsedStart.x := x;
1175        FParsedStop := FParsedStart;
1176        break;
1177      end;
1178      inc(y);
1179      x := 1;
1180    end;
1181    Result := FParsedStart.Y >= 0;
1182  end;
1183
1184var
1185  i, j: Integer;
1186  StartTime, t: Double;
1187begin
1188  StartTime := now();
1189  while (FCallQueued) and (Mode = spseSelecting) do begin
1190    FCallQueued := False;
1191    FWordScanCount := 0;
1192
1193    NewPos := SelectionObj.FirstLineBytePos;
1194    NewEnd := SelectionObj.LastLineBytePos;
1195    i := FLastSelEnd.y - FLastSelStart.y;
1196    j := NewEnd.y - NewPos.y;
1197    if (j < 1) or (j < i div 2) or
1198       (NewEnd <= FLastSelStart) or (NewPos >= FLastSelEnd )
1199    then begin
1200      // Scan from scratch
1201      FLastSelStart := Point(-1,-1);
1202      FLastSelEnd := Point(-1,-1);
1203      FWordIndex.Clear;
1204    end;
1205
1206    if FLastSelStart.Y < 0 then begin
1207      FLastSelStart := NewPos;
1208      FLastSelEnd := FLastSelStart;
1209      FParsedStart := Point(-1,-1);
1210      FParsedStop := Point(-1,-1);
1211    end;
1212
1213    if (NewPos = NewEnd) or (not InitParsedPoints) then begin
1214      if MarkupEnabled then Editor.Invalidate;
1215      MarkupEnabled := False;
1216      exit;
1217    end;
1218
1219    if (NewPos < FLastSelStart) then
1220      FParsedStart := Scan(FParsedStart, NewPos, True)  // NewPos is the smaller point;
1221    else
1222    if (NewPos > FParsedStart) then
1223      FParsedStart := UnScan(FParsedStart, NewPos, False);
1224
1225    if FWordScanCount > MAX_WORDS_PER_SCAN then begin
1226      FLastSelStart := FParsedStart;
1227    end
1228    else begin
1229      FLastSelStart := NewPos;
1230
1231      if (NewEnd > FLastSelEnd) then
1232        FParsedStop := Scan(FParsedStop, NewEnd, False)  // NewPos is the greater point;
1233      else
1234      if (NewEnd < FParsedStop) then
1235        FParsedStop := UnScan(FParsedStop, NewEnd, True);
1236
1237      FLastSelEnd := NewEnd;
1238      if FWordScanCount > MAX_WORDS_PER_SCAN then
1239        FLastSelEnd := FParsedStop;
1240    end;
1241
1242    MarkupEnabled := FWordIndex.MultiWordCount > 0;
1243    //debugln(['COUNTS: ', FWordIndex.WordCount,' mult=',FWordIndex.MultiWordCount, ' hash=',FWordIndex.EntryCount]);
1244
1245    if FWordScanCount > MAX_WORDS_PER_SCAN then begin
1246      FCallQueued := True;
1247      t := Now;
1248      if (t - StartTime > MIN_PROCESS_MSG_TIME) then begin
1249        Application.ProcessMessages;
1250        if not FEditModeQueued then
1251          Application.Idle(False);
1252        StartTime := t;
1253      end;
1254    end;
1255  end;
1256  FCallQueued := False;
1257  if FEditModeQueued and (Mode = spseSelecting) then
1258    StartSyncroMode;
1259  FEditModeQueued := False;
1260end;
1261
1262procedure TSynPluginSyncroEdit.DoOnDeactivate;
1263begin
1264  Mode := spseIncative;
1265  AreaMarkupEnabled := False;
1266  Cells.Clear;
1267  inherited DoOnDeactivate;
1268end;
1269
1270procedure TSynPluginSyncroEdit.DoPreActiveEdit(aX, aY, aCount, aLineBrkCnt: Integer;
1271  aUndoRedo: Boolean);
1272begin
1273  FWordIndex.Clear;
1274  Active := False;
1275  Mode := spseInvalid;
1276end;
1277
1278function TSynPluginSyncroEdit.MaybeHandleMouseAction(var AnInfo: TSynEditMouseActionInfo;
1279  HandleActionProc: TSynEditMouseActionHandler): Boolean;
1280var
1281  r: TRect;
1282begin
1283  Result := (Active or PreActive) and
1284            ( ((Mode = spseSelecting) and (MarkupEnabled = True)) or
1285              (Mode = spseEditing) );
1286  if not Result then exit;
1287
1288  r := Markup.GutterGlyphRect;
1289  Result := (AnInfo.MouseX >= r.Left) and (AnInfo.MouseX < r.Right) and
1290            (AnInfo.MouseY >= r.Top) and (AnInfo.MouseY < r.Bottom);
1291
1292  if Result then begin
1293    HandleActionProc(FMouseActions, AnInfo);
1294    AnInfo.IgnoreUpClick := True;
1295  end;
1296end;
1297
1298function TSynPluginSyncroEdit.DoHandleMouseAction(AnAction: TSynEditMouseAction;
1299  var AnInfo: TSynEditMouseActionInfo): Boolean;
1300begin
1301  Result := False;
1302
1303  if AnAction.Command = emcSynPSyncroEdGutterGlyph then begin
1304    if Mode = spseSelecting then
1305      StartSyncroMode
1306    else
1307      StopSyncroMode;
1308    Result := true;
1309  end;
1310end;
1311
1312procedure TSynPluginSyncroEdit.DoEditorRemoving(AValue: TCustomSynEdit);
1313begin
1314  if Editor <> nil then begin
1315    SelectionObj.RemoveChangeHandler(@DoSelectionChanged);
1316    Editor.UnregisterCommandHandler(@ProcessSynCommand);
1317    Editor.UnRegisterKeyTranslationHandler(@TranslateKey);
1318    Editor.UnregisterMouseActionSearchHandler(@MaybeHandleMouseAction);
1319    Editor.UnregisterMouseActionExecHandler(@DoHandleMouseAction);
1320    FLowerLines.Lines := nil;
1321  end;
1322  inherited DoEditorRemoving(AValue);
1323end;
1324
1325procedure TSynPluginSyncroEdit.DoEditorAdded(AValue: TCustomSynEdit);
1326begin
1327  inherited DoEditorAdded(AValue);
1328  if Editor <> nil then begin
1329    FLowerLines.Lines := ViewedTextBuffer;
1330    Editor.RegisterMouseActionSearchHandler(@MaybeHandleMouseAction);
1331    Editor.RegisterMouseActionExecHandler(@DoHandleMouseAction);
1332    Editor.RegisterCommandHandler(@ProcessSynCommand, nil);
1333    Editor.RegisterKeyTranslationHandler(@TranslateKey);
1334    SelectionObj.AddChangeHandler(@DoSelectionChanged);
1335  end;
1336end;
1337
1338procedure TSynPluginSyncroEdit.DoClear;
1339begin
1340  FWordIndex.Clear;
1341  inherited DoClear;
1342end;
1343
1344procedure TSynPluginSyncroEdit.DoModeChanged;
1345begin
1346  if Assigned(FOnModeChange) then
1347    FOnModeChange(Self);
1348end;
1349
1350procedure TSynPluginSyncroEdit.TranslateKey(Sender: TObject; Code: word; SState: TShiftState;
1351  var Data: pointer; var IsStartOfCombo: boolean; var Handled: boolean;
1352  var Command: TSynEditorCommand; FinishComboOnly: Boolean;
1353  var ComboKeyStrokes: TSynEditKeyStrokes);
1354var
1355  keys: TSynEditKeyStrokes;
1356begin
1357  if (not (Active or  PreActive)) or Handled then
1358    exit;
1359
1360  keys := nil;
1361
1362  if Mode = spseSelecting then
1363    keys := FKeystrokesSelecting;
1364
1365  if Mode = spseEditing then begin
1366    if CurrentCell < 0 then
1367      keys := FKeyStrokesOffCell
1368    else
1369      keys := FKeyStrokes;
1370  end;
1371  if keys = nil then exit;
1372
1373  if not FinishComboOnly then
1374    keys.ResetKeyCombo;
1375  Command := keys.FindKeycodeEx(Code, SState, Data, IsStartOfCombo, FinishComboOnly, ComboKeyStrokes);
1376
1377  Handled := (Command <> ecNone) or IsStartOfCombo;
1378  if IsStartOfCombo then
1379    ComboKeyStrokes := keys;
1380end;
1381
1382procedure TSynPluginSyncroEdit.ProcessSynCommand(Sender: TObject; AfterProcessing: boolean;
1383  var Handled: boolean; var Command: TSynEditorCommand; var AChar: TUTF8Char; Data: pointer;
1384  HandlerData: pointer);
1385begin
1386  if Handled or AfterProcessing or not (Active or PreActive) then exit;
1387
1388  if Mode = spseSelecting then begin
1389    // todo: finish word-hash calculations / check if any cells exist
1390    Handled := True;
1391    case Command of
1392      ecSynPSyncroEdStart: StartSyncroMode;
1393      else
1394        Handled := False;
1395    end;
1396  end;
1397
1398  if Mode = spseEditing then begin
1399    Handled := True;
1400    case Command of
1401      ecSynPSyncroEdNextCell:          NextCell(False, True);
1402      ecSynPSyncroEdNextCellSel:       NextCell(True, True);
1403      ecSynPSyncroEdPrevCell:          PreviousCell(False, True);
1404      ecSynPSyncroEdPrevCellSel:       PreviousCell(True, True);
1405      ecSynPSyncroEdNextFirstCell:     NextCell(False, True, True);
1406      ecSynPSyncroEdNextFirstCellSel:  NextCell(True, True, True);
1407      ecSynPSyncroEdPrevFirstCell:     PreviousCell(False, True, True);
1408      ecSynPSyncroEdPrevFirstCellSel:  PreviousCell(True, True, True);
1409      ecSynPSyncroEdCellHome:          CellCaretHome;
1410      ecSynPSyncroEdCellEnd:           CellCaretEnd;
1411      ecSynPSyncroEdCellSelect:        SelectCurrentCell;
1412      ecSynPSyncroEdEscape:
1413        begin
1414          Clear;
1415          Active := False;
1416        end;
1417      else
1418        Handled := False;
1419    end;
1420  end;
1421end;
1422
1423constructor TSynPluginSyncroEdit.Create(AOwner: TComponent);
1424begin
1425  Mode := spseIncative;
1426  FEditModeQueued := False;
1427
1428  FMouseActions := TSynPluginSyncroEditMouseActions.Create(self);
1429  FMouseActions.ResetDefaults;
1430
1431  FKeystrokes := TSynEditSyncroEditKeyStrokes.Create(Self);
1432  FKeystrokes.ResetDefaults;
1433
1434  FKeyStrokesOffCell := TSynEditSyncroEditKeyStrokesOffCell.Create(self);
1435  FKeyStrokesOffCell.ResetDefaults;
1436
1437  FKeystrokesSelecting := TSynEditSyncroEditKeyStrokesSelecting.Create(Self);
1438  FKeystrokesSelecting.ResetDefaults;
1439
1440  FGutterGlyph := TBitMap.Create;
1441  FGutterGlyph.OnChange := @DoImageChanged;
1442
1443  FLowerLines := TSynPluginSyncroEditLowerLineCache.Create;
1444  FWordIndex := TSynPluginSyncroEditWordsHash.Create;
1445  FWordIndex.LowerLines := FLowerLines;
1446  inherited Create(AOwner);
1447  MarkupInfoArea.Background := clMoneyGreen;
1448  MarkupInfo.FrameColor := TColor($98b498)
1449end;
1450
1451destructor TSynPluginSyncroEdit.Destroy;
1452begin
1453  Application.RemoveAsyncCalls(Self);
1454  inherited Destroy;
1455  FreeAndNil(FWordIndex);
1456  FreeAndNil(FLowerLines);
1457  FreeAndNil(FGutterGlyph);
1458  FreeAndNil(FMouseActions);
1459  FreeAndNil(FKeystrokes);
1460  FreeAndNil(FKeyStrokesOffCell);
1461  FreeAndNil(FKeystrokesSelecting);
1462end;
1463
1464{ TSynPluginSyncroEditMouseActions }
1465
1466procedure TSynPluginSyncroEditMouseActions.ResetDefaults;
1467begin
1468  Clear;
1469  AddCommand(emcSynPSyncroEdGutterGlyph, False, mbXLeft, ccAny, cdDown, [], []);
1470end;
1471
1472{ TSynEditSyncroEditKeyStrokesSelecting }
1473
1474procedure TSynEditSyncroEditKeyStrokesSelecting.ResetDefaults;
1475  procedure AddKey(const ACmd: TSynEditorCommand; const AKey: word;
1476     const AShift: TShiftState);
1477  begin
1478    with Add do
1479    begin
1480      Key := AKey;
1481      Shift := AShift;
1482      Command := ACmd;
1483    end;
1484  end;
1485begin
1486  Clear;
1487  AddKey(ecSynPSyncroEdStart,            VK_J, [ssCtrl]);
1488end;
1489
1490{ TSynEditSyncroEditKeyStrokes }
1491
1492procedure TSynEditSyncroEditKeyStrokes.ResetDefaults;
1493  procedure AddKey(const ACmd: TSynEditorCommand; const AKey: word;
1494     const AShift: TShiftState);
1495  begin
1496    with Add do
1497    begin
1498      Key := AKey;
1499      Shift := AShift;
1500      Command := ACmd;
1501    end;
1502  end;
1503begin
1504  Clear;
1505  AddKey(ecSynPSyncroEdNextCell,          VK_RIGHT,  [ssCtrl]);
1506  AddKey(ecSynPSyncroEdNextCellSel,       VK_TAB,    []);
1507  AddKey(ecSynPSyncroEdPrevCell,          VK_LEFT,   [ssCtrl]);
1508  AddKey(ecSynPSyncroEdPrevCellSel,       VK_TAB,    [ssShift]);
1509
1510  AddKey(ecSynPSyncroEdCellHome,          VK_HOME,   []);
1511  AddKey(ecSynPSyncroEdCellEnd,           VK_END,    []);
1512  AddKey(ecSynPSyncroEdCellSelect,        VK_A,      [ssCtrl]);
1513  AddKey(ecSynPSyncroEdEscape,            VK_ESCAPE, []);
1514end;
1515
1516{ TSynEditSyncroEditKeyStrokesOffCell }
1517
1518procedure TSynEditSyncroEditKeyStrokesOffCell.ResetDefaults;
1519  procedure AddKey(const ACmd: TSynEditorCommand; const AKey: word;
1520     const AShift: TShiftState);
1521  begin
1522    with Add do
1523    begin
1524      Key := AKey;
1525      Shift := AShift;
1526      Command := ACmd;
1527    end;
1528  end;
1529begin
1530  Clear;
1531  AddKey(ecSynPSyncroEdNextCell,          VK_RIGHT,  [ssCtrl]);
1532  AddKey(ecSynPSyncroEdNextCellSel,       VK_TAB,    []);
1533  AddKey(ecSynPSyncroEdPrevCell,          VK_LEFT,   [ssCtrl]);
1534  AddKey(ecSynPSyncroEdPrevCellSel,       VK_TAB,    [ssShift]);
1535
1536  AddKey(ecSynPSyncroEdEscape,            VK_ESCAPE, []);
1537end;
1538
1539const
1540  EditorSyncroCommandStrs: array[0..12] of TIdentMapEntry = (
1541    (Value: ecSynPSyncroEdStart;            Name: 'ecSynPSyncroEdStart'),
1542    (Value: ecSynPSyncroEdNextCell;         Name: 'ecSynPSyncroEdNextCell'),
1543    (Value: ecSynPSyncroEdNextCellSel;      Name: 'ecSynPSyncroEdNextCellSel'),
1544    (Value: ecSynPSyncroEdPrevCell;         Name: 'ecSynPSyncroEdPrevCell'),
1545    (Value: ecSynPSyncroEdPrevCellSel;      Name: 'ecSynPSyncroEdPrevCellSel'),
1546    (Value: ecSynPSyncroEdCellHome;         Name: 'ecSynPSyncroEdCellHome'),
1547    (Value: ecSynPSyncroEdCellEnd;          Name: 'ecSynPSyncroEdCellEnd'),
1548    (Value: ecSynPSyncroEdCellSelect;       Name: 'ecSynPSyncroEdCellSelect'),
1549    (Value: ecSynPSyncroEdEscape;           Name: 'ecSynPSyncroEdEscape'),
1550    (Value: ecSynPSyncroEdNextFirstCell;    Name: 'ecSynPSyncroEdNextFirstCell'),
1551    (Value: ecSynPSyncroEdNextFirstCellSel; Name: 'ecSynPSyncroEdNextFirstCellSel'),
1552    (Value: ecSynPSyncroEdPrevFirstCell;    Name: 'ecSynPSyncroEdPrevFirstCell'),
1553    (Value: ecSynPSyncroEdPrevFirstCellSel; Name: 'ecSynPSyncroEdPrevFirstCellSel')
1554  );
1555
1556function IdentToSyncroCommand(const Ident: string; var Cmd: longint): boolean;
1557begin
1558  Result := IdentToInt(Ident, Cmd, EditorSyncroCommandStrs);
1559end;
1560
1561function SyncroCommandToIdent(Cmd: longint; var Ident: string): boolean;
1562begin
1563  Result := (Cmd >= ecPluginFirstSyncro) and (Cmd - ecPluginFirstSyncro < ecSynPSyncroEdCount);
1564  if not Result then exit;
1565  Result := IntToIdent(Cmd, Ident, EditorSyncroCommandStrs);
1566end;
1567
1568procedure GetEditorCommandValues(Proc: TGetStrProc);
1569var
1570  i: integer;
1571begin
1572  for i := Low(EditorSyncroCommandStrs) to High(EditorSyncroCommandStrs) do
1573    Proc(EditorSyncroCommandStrs[I].Name);
1574end;
1575
1576
1577initialization
1578  RegisterKeyCmdIdentProcs(@IdentToSyncroCommand,
1579                           @SyncroCommandToIdent);
1580  RegisterExtraGetEditorCommandValues(@GetEditorCommandValues);
1581
1582end.
1583
1584