1 {-------------------------------------------------------------------------------
2 The contents of this file are subject to the Mozilla Public License
3 Version 1.1 (the "License"); you may not use this file except in compliance
4 with the License. You may obtain a copy of the License at
5 http://www.mozilla.org/MPL/
6
7 Software distributed under the License is distributed on an "AS IS" basis,
8 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9 the specific language governing rights and limitations under the License.
10
11 The Original Code is: SynEditPlugins.pas, released 2001-10-17.
12
13 Author of this file is Flávio Etrusco.
14 Portions created by Flávio Etrusco are Copyright 2001 Flávio Etrusco.
15 All Rights Reserved.
16
17 Contributors to the SynEdit project are listed in the Contributors.txt file.
18
19 Alternatively, the contents of this file may be used under the terms of the
20 GNU General Public License Version 2 or later (the "GPL"), in which case
21 the provisions of the GPL are applicable instead of those above.
22 If you wish to allow use of your version of this file only under the terms
23 of the GPL and not to allow others to use your version of this file
24 under the MPL, indicate your decision by deleting the provisions above and
25 replace them with the notice and other provisions required by the GPL.
26 If you do not delete the provisions above, a recipient may use your version
27 of this file under either the MPL or the GPL.
28
29 $Id: syneditplugins.pas 48478 2015-03-24 17:50:59Z juha $
30
31 You may retrieve the latest version of this file at the SynEdit home page,
32 located at http://SynEdit.SourceForge.net
33
34 Known Issues:
35 -------------------------------------------------------------------------------}
36
37 unit SynEditPlugins;
38
39 {$I synedit.inc}
40
41 interface
42
43 uses
44 Classes, Menus, LCLType, SysUtils,
45 SynEdit, SynEditKeyCmds, SynEditTypes, SynEditStrConst;
46
47 type
48
49 { TLazSynMultiEditPlugin }
50
51 TLazSynMultiEditPlugin = class(TLazSynEditPlugin)
52 private
53 FEditors: TList;
54 FEditorWasAdded: Boolean;
GetEditorCountnull55 function GetEditorCount: integer;
GetEditorsnull56 function GetEditors(aIndex: integer): TCustomSynEdit;
57 protected
IndexOfEditornull58 function IndexOfEditor(const AValue: TCustomSynEdit): integer;
59 procedure BeforeEditorChange; override;
60 procedure AfterEditorChange; override;
DoRemoveEditornull61 function DoRemoveEditor(AValue: TCustomSynEdit): integer;
62 procedure DoEditorDestroyed(const AValue: TCustomSynEdit); override;
63 public
64 destructor Destroy; override;
65 (* Add/RemoveEditor versus Editor
66 Unless stated otherwise Plugins inherting from TLazSynMultiEditPlugin can
67 either use Add/RemoveEditor or Editor (single-editor-property).
68 If Editors are added via AddEditor, then an Editor only set via "Editor:="
69 may be lost/ignored.
70 If using AddEditor, the "Editor" property may be used to set/read the
71 current Editor (out of those in the list). This does however depend on the
72 inherited class.
73 *)
AddEditornull74 function AddEditor(AValue: TCustomSynEdit): integer;
RemoveEditornull75 function RemoveEditor(AValue: TCustomSynEdit): integer;
76 property Editors[aIndex: integer]: TCustomSynEdit read GetEditors;
77 property EditorCount: integer read GetEditorCount;
78 end;
79
80 TAbstractSynPlugin = TLazSynMultiEditPlugin;
81
82 TAbstractSynHookerPlugin = class(TAbstractSynPlugin)
83 protected
84 procedure HookEditor(aEditor: TCustomSynEdit; aCommandID: TSynEditorCommand;
85 aOldShortCut, aNewShortCut: TShortCut; AFlags: THookedCommandFlags = [hcfPreExec, hcfPostExec]);
86 procedure UnHookEditor(aEditor: TCustomSynEdit;
87 aCommandID: TSynEditorCommand; aShortCut: TShortCut);
88 procedure OnCommand(Sender: TObject; AfterProcessing: boolean;
89 var Handled: boolean; var Command: TSynEditorCommand;
90 var aChar: TUTF8Char;
91 Data: pointer; HandlerData: pointer); virtual; abstract;
92 end;
93
94 TPluginState = (psNone, psExecuting, psAccepting, psCancelling);
95
96 TAbstractSynSingleHookPlugin = class(TAbstractSynHookerPlugin)
97 private
98 fCommandID: TSynEditorCommand;
IsShortCutStorednull99 function IsShortCutStored: Boolean;
100 procedure SetShortCut(const Value: TShortCut);
101 protected
102 fState: TPluginState;
103 fCurrentEditor: TCustomSynEdit;
104 fShortCut: TShortCut;
DefaultShortCutnull105 class function DefaultShortCut: TShortCut; virtual;
106 procedure DoEditorAdded(aEditor: TCustomSynEdit); override;
107 procedure DoEditorRemoving(aEditor: TCustomSynEdit); override;
108 {}
109 procedure DoExecute; virtual; abstract;
110 procedure DoAccept; virtual; abstract;
111 procedure DoCancel; virtual; abstract;
112 public
113 constructor Create(aOwner: TComponent); override;
114 destructor Destroy; override;
115 property CommandID: TSynEditorCommand read fCommandID;
116 {}
117 property CurrentEditor: TCustomSynEdit read fCurrentEditor;
Executingnull118 function Executing: boolean;
119 procedure Execute(aEditor: TCustomSynEdit);
120 procedure Accept;
121 procedure Cancel;
122 published
123 property ShortCut: TShortCut read fShortCut write SetShortCut
124 stored IsShortCutStored;
125 end deprecated;
126
127 { use TAbstractSynCompletion for non-visual completion }
128
129 TAbstractSynCompletion = class(TAbstractSynSingleHookPlugin)
130 protected
131 fCurrentString: String;
132 protected
133 procedure SetCurrentString(const Value: String); virtual;
134 procedure OnCommand(Sender: TObject; AfterProcessing: boolean;
135 var Handled: boolean; var Command: TSynEditorCommand;
136 var aChar: TUTF8Char;
137 Data: pointer; HandlerData: pointer); override;
138 procedure DoExecute; override;
139 procedure DoAccept; override;
140 procedure DoCancel; override;
GetCurrentEditorStringnull141 function GetCurrentEditorString: String; virtual;
142 public
143 procedure AddEditor(aEditor: TCustomSynEdit);
144 property CurrentString: String read fCurrentString write SetCurrentString;
145 end deprecated;
146
NewPluginCommandnull147 function NewPluginCommand: TSynEditorCommand; deprecated; // Use AllocatePluginKeyRange
148 procedure ReleasePluginCommand(aCmd: TSynEditorCommand); deprecated;
149
150 implementation
151
NewPluginCommandnull152 function NewPluginCommand: TSynEditorCommand;
153 begin
154 Result := AllocatePluginKeyRange(1);
155 end;
156
157 procedure ReleasePluginCommand(aCmd: TSynEditorCommand);
158 begin
159 end;
160
161 { TLazSynMultiEditPlugin }
162
TLazSynMultiEditPlugin.GetEditorCountnull163 function TLazSynMultiEditPlugin.GetEditorCount: integer;
164 begin
165 if FEditors = nil then
166 Result := 0
167 else
168 Result := FEditors.Count;
169 end;
170
GetEditorsnull171 function TLazSynMultiEditPlugin.GetEditors(aIndex: integer): TCustomSynEdit;
172 begin
173 if FEditors = nil then
174 Result := nil
175 else
176 Result := TCustomSynEdit(FEditors[aIndex]);
177 end;
178
IndexOfEditornull179 function TLazSynMultiEditPlugin.IndexOfEditor(const AValue: TCustomSynEdit): integer;
180 begin
181 if FEditors = nil then
182 Result := -1
183 else
184 Result := FEditors.IndexOf(AValue);
185 end;
186
187 procedure TLazSynMultiEditPlugin.BeforeEditorChange;
188 begin
189 if (Editor = nil) or FEditorWasAdded then
190 exit;
191
192 // Current Editor was not explicitly added by user via "AddEditor"
193 DoRemoveEditor(Editor);
194 end;
195
196 procedure TLazSynMultiEditPlugin.AfterEditorChange;
197 begin
198 if (Editor = nil) then
199 exit;
200 FEditorWasAdded := IndexOfEditor(Editor) >= 0;
201 if FEditorWasAdded then
202 exit;
203
204 // Current Editor was not explicitly added by user via "AddEditor"
205 // Temporary add it
206 AddEditor(Editor);
207 FEditorWasAdded := False; // Reset Flag, after AddEditor
208 end;
209
DoRemoveEditornull210 function TLazSynMultiEditPlugin.DoRemoveEditor(AValue: TCustomSynEdit): integer;
211 begin
212 if IndexOfEditor(AValue) < 0 then begin
213 Result := -1;
214 Exit;
215 end;
216
217 DoEditorRemoving(AValue);
218 UnRegisterFromEditor(AValue);
219 Result := FEditors.Remove(AValue);
220 end;
221
222 procedure TLazSynMultiEditPlugin.DoEditorDestroyed(const AValue: TCustomSynEdit);
223 begin
224 RemoveEditor(AValue);
225 if EditorCount = 0 then
226 inherited DoEditorDestroyed(nil); // Editor is nil now, so pass nil as param too
227 end;
228
TLazSynMultiEditPlugin.AddEditornull229 function TLazSynMultiEditPlugin.AddEditor(AValue: TCustomSynEdit): integer;
230 begin
231 if AValue = Editor then
232 FEditorWasAdded := True;
233
234 if IndexOfEditor(AValue) >= 0 then begin
235 Result := -1;
236 Exit;
237 end;
238
239 if FEditors = nil then
240 FEditors := TList.Create;
241 Result := FEditors.Add(AValue);
242 RegisterToEditor(AValue);
243 DoEditorAdded(AValue);
244 end;
245
RemoveEditornull246 function TLazSynMultiEditPlugin.RemoveEditor(AValue: TCustomSynEdit): integer;
247 begin
248 if AValue = Editor then
249 Editor := nil;
250
251 Result := DoRemoveEditor(AValue);
252 end;
253
254 destructor TLazSynMultiEditPlugin.Destroy;
255 begin
256 while EditorCount > 0 do
257 RemoveEditor(Editors[0]);
258 FreeAndNil(FEditors);
259 inherited Destroy;
260 end;
261
262
263 { TAbstractSynHookerPlugin }
264
265 procedure TAbstractSynHookerPlugin.HookEditor(aEditor: TCustomSynEdit;
266 aCommandID: TSynEditorCommand; aOldShortCut, aNewShortCut: TShortCut;
267 AFlags: THookedCommandFlags = [hcfPreExec, hcfPostExec]);
268 var
269 iIndex: integer;
270 iKeystroke: TSynEditKeyStroke;
271 begin
272 Assert( aNewShortCut <> 0 );
273 { shortcurts aren't created while in design-time }
274 if [csDesigning] * ComponentState = [csDesigning] then
275 begin
276 if TCustomSynEdit(aEditor).Keystrokes.FindShortcut( aNewShortCut ) >= 0 then
277 raise ESynKeyError.Create(SYNS_EDuplicateShortCut)
278 else
279 Exit;
280 end;
281 { tries to update old Keystroke }
282 if aOldShortCut <> 0 then
283 begin
284 iIndex := TCustomSynEdit(aEditor).Keystrokes.FindShortcut( aOldShortCut );
285 if (iIndex >= 0) then
286 begin
287 iKeystroke := TCustomSynEdit(aEditor).Keystrokes[iIndex];
288 if iKeystroke.Command = aCommandID then
289 begin
290 iKeystroke.ShortCut := aNewShortCut;
291 Exit;
292 end;
293 end;
294 end;
295 { new Keystroke }
296 iKeystroke := TCustomSynEdit(aEditor).Keystrokes.Add;
297 try
298 iKeystroke.ShortCut := aNewShortCut;
299 except
300 iKeystroke.Free;
301 raise;
302 end;
303 iKeystroke.Command := aCommandID;
304
305 if AFlags <> [] then
306 aEditor.RegisterCommandHandler( @OnCommand, Self, AFlags);
307 end;
308
309 procedure TAbstractSynHookerPlugin.UnHookEditor(aEditor: TCustomSynEdit;
310 aCommandID: TSynEditorCommand; aShortCut: TShortCut);
311 var
312 iIndex: integer;
313 begin
314 aEditor.UnregisterCommandHandler( @OnCommand );
315 iIndex := TCustomSynEdit(aEditor).Keystrokes.FindShortcut( aShortCut );
316 if (iIndex >= 0) and
317 (TCustomSynEdit(aEditor).Keystrokes[iIndex].Command = aCommandID) then
318 TCustomSynEdit(aEditor).Keystrokes[iIndex].Free;
319 end;
320
321 { TAbstractSynHookerPlugin }
322
323 procedure TAbstractSynSingleHookPlugin.Accept;
324 begin
325 fState := psAccepting;
326 try
327 DoAccept;
328 finally
329 fCurrentEditor := nil;
330 fState := psNone;
331 end;
332 end;
333
334 procedure TAbstractSynSingleHookPlugin.Cancel;
335 begin
336 fState := psCancelling;
337 try
338 DoCancel;
339 finally
340 fCurrentEditor := nil;
341 fState := psNone;
342 end;
343 end;
344
345 constructor TAbstractSynSingleHookPlugin.Create(aOwner: TComponent);
346 begin
347 inherited;
348 // TODO: subclasses should implement per class, not per instance
349 fCommandID := AllocatePluginKeyRange(1);
350 fShortCut := DefaultShortCut;
351 end;
352
TAbstractSynSingleHookPlugin.DefaultShortCutnull353 class function TAbstractSynSingleHookPlugin.DefaultShortCut: TShortCut;
354 begin
355 Result := 0;
356 end;
357
358 destructor TAbstractSynSingleHookPlugin.Destroy;
359 begin
360 if Executing then
361 Cancel;
362 //ReleasePluginCommand( CommandID );
363 inherited;
364 end;
365
366 procedure TAbstractSynSingleHookPlugin.DoEditorAdded(
367 aEditor: TCustomSynEdit);
368 begin
369 if ShortCut <> 0 then
370 HookEditor( aEditor, CommandID, 0, ShortCut );
371 end;
372
373 procedure TAbstractSynSingleHookPlugin.Execute(aEditor: TCustomSynEdit);
374 begin
375 if Executing then
376 Cancel;
377 Assert( fCurrentEditor = nil );
378 fCurrentEditor := aEditor;
379 Assert( fState = psNone );
380 fState := psExecuting;
381 try
382 DoExecute;
383 except
384 Cancel;
385 raise;
386 end;
387 end;
388
Executingnull389 function TAbstractSynSingleHookPlugin.Executing: boolean;
390 begin
391 Result := fState = psExecuting;
392 end;
393
IsShortCutStorednull394 function TAbstractSynSingleHookPlugin.IsShortCutStored: Boolean;
395 begin
396 Result := fShortCut <> DefaultShortCut;
397 end;
398
399 procedure TAbstractSynSingleHookPlugin.DoEditorRemoving(aEditor: TCustomSynEdit);
400 begin
401 if ShortCut <> 0 then
402 UnHookEditor( aEditor, CommandID, ShortCut );
403 if Executing and (CurrentEditor = aEditor) then
404 Cancel;
405 end;
406
407 procedure TAbstractSynSingleHookPlugin.SetShortCut(const Value: TShortCut);
408 var
409 cEditor: integer;
410 begin
411 if fShortCut <> Value then
412 begin
413 if Assigned(fEditors) then
414 if Value <> 0 then
415 begin
416 for cEditor := 0 to fEditors.Count -1 do
417 HookEditor( Editors[cEditor], CommandID, fShortCut, Value );
418 end
419 else
420 begin
421 for cEditor := 0 to fEditors.Count -1 do
422 UnHookEditor( Editors[cEditor], CommandID, fShortCut );
423 end;
424 fShortCut := Value;
425 end;
426 end;
427
428 { TAbstractSynCompletion }
429
GetCurrentEditorStringnull430 function TAbstractSynCompletion.GetCurrentEditorString: String;
431 var
432 iString: String;
433 cCol: integer;
434 iIdentChars: TSynIdentChars;
435 begin
436 Result := '';
437 iString := CurrentEditor.LineText;
438 if (CurrentEditor.CaretX > 1) and
439 (CurrentEditor.CaretX -1 <= Length(iString)) then
440 begin
441 iIdentChars := CurrentEditor.IdentChars;
442 for cCol := CurrentEditor.CaretX -1 downto 1 do
443 if not (iString[cCol] in iIdentChars) then
444 break;
445 Result := Copy( iString, cCol +1, CurrentEditor.CaretX - cCol -1);
446 end;
447 end;
448
449 procedure TAbstractSynCompletion.DoAccept;
450 begin
451 fCurrentString := '';
452 end;
453
454 procedure TAbstractSynCompletion.DoCancel;
455 begin
456 fCurrentString := '';
457 end;
458
459 procedure TAbstractSynCompletion.DoExecute;
460 begin
461 CurrentString := GetCurrentEditorString;
462 end;
463
464 procedure TAbstractSynCompletion.OnCommand(Sender: TObject;
465 AfterProcessing: boolean; var Handled: boolean;
466 var Command: TSynEditorCommand;
467 var aChar: TUTF8Char;
468 Data, HandlerData: pointer);
469 var
470 iString: String;
471 begin
472 if not Executing then
473 begin
474 if (Command = CommandID) then
475 begin
476 Execute( Sender as TCustomSynEdit );
477 Handled := True;
478 end;
479 end
480 else { Executing }
481 if Sender = CurrentEditor then
482 begin
483 if not AfterProcessing then
484 begin
485 case Command of
486 ecChar:
487 if aChar = #27 then
488 begin
489 Cancel;
490 Handled := True;
491 end
492 else
493 begin
494 if (length(aChar) <> 1)
495 or (not (aChar[1] in CurrentEditor.IdentChars)) then
496 Accept;
497 end;
498 ecLineBreak:
499 begin
500 Accept;
501 Handled := True;
502 end;
503 ecLeft, ecSelLeft, ecColSelLeft:
504 if CurrentString = '' then
505 Handled := True;
506 ecDeleteLastChar:
507 if CurrentString = '' then
508 Handled := True;
509 ecTab:
510 Accept;
511 ecDeleteChar,
512 ecRight, ecSelRight, ecColSelRight,
513 ecLostFocus, ecGotFocus:
514 ; {processed on AfterProcessing}
515 else
516 Cancel;
517 end;
518 end
519 else { AfterProcessing }
520 case Command of
521 ecLostFocus, ecGotFocus,
522 ecDeleteChar:
523 ;
524 ecDeleteLastChar,
525 ecLeft, ecSelLeft, ecColSelLeft,
526 ecChar:
527 CurrentString := GetCurrentEditorString;
528 ecRight, ecSelRight, ecColSelRight: begin
529 iString := GetCurrentEditorString;
530 if iString = '' then
531 Cancel
532 else
533 CurrentString := iString;
534 end;
535 else
536 if CurrentString <> GetCurrentEditorString then
537 Cancel;
538 end;
539 end; {endif Sender = CurrentEditor}
540 end;
541
542 procedure TAbstractSynCompletion.SetCurrentString(const Value: String);
543 begin
544 fCurrentString := Value;
545 end;
546
547 procedure TAbstractSynCompletion.AddEditor(aEditor: TCustomSynEdit);
548 begin
549 inherited AddEditor(aEditor);
550 end;
551
552 end.
553