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