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: SynEditAutoComplete.pas, released 2000-06-25.
12
13 The Initial Author of the Original Code is Michael Hieke.
14 Portions written by Michael Hieke are Copyright 2000 Michael Hieke.
15 Portions written by Cyrille de Brebisson (from mwCompletionProposal.pas) are
16 Copyright 1999 Cyrille de Brebisson.
17 Unicode translation by Maël Hörz.
18 All Rights Reserved.
19
20 Contributors to the SynEdit and mwEdit projects are listed in the
21 Contributors.txt file.
22
23 Alternatively, the contents of this file may be used under the terms of the
24 GNU General Public License Version 2 or later (the "GPL"), in which case
25 the provisions of the GPL are applicable instead of those above.
26 If you wish to allow use of your version of this file only under the terms
27 of the GPL and not to allow others to use your version of this file
28 under the MPL, indicate your decision by deleting the provisions above and
29 replace them with the notice and other provisions required by the GPL.
30 If you do not delete the provisions above, a recipient may use your version
31 of this file under either the MPL or the GPL.
32
33 $Id: SynEditAutoComplete.pas,v 1.10.2.4 2008/09/14 16:24:58 maelh Exp $
34
35 You may retrieve the latest version of this file at the SynEdit home page,
36 located at http://SynEdit.SourceForge.net
37
38 Known Issues:
39 -------------------------------------------------------------------------------}
40
41 {$IFNDEF QSYNEDITAUTOCOMPLETE}
42 unit SynEditAutoComplete;
43 {$ENDIF}
44
45 {$I SynEdit.inc}
46
47 interface
48
49 uses
50 {$IFDEF SYN_CLX}
51 Qt,
52 QMenus,
53 Types,
54 QSynEdit,
55 QSynEditKeyCmds,
56 QSynUnicode,
57 {$ELSE}
58 Windows,
59 Menus,
60 SynEdit,
61 SynEditKeyCmds,
62 SynUnicode,
63 {$ENDIF}
64 Classes;
65
66 type
67 TCustomSynAutoComplete = class(TComponent)
68 protected
69 FAutoCompleteList: TUnicodeStrings;
70 FCompletions: TUnicodeStrings;
71 FCompletionComments: TUnicodeStrings;
72 FCompletionValues: TUnicodeStrings;
73 FEditor: TCustomSynEdit;
74 FEditors: TList;
75 FEOTokenChars: UnicodeString;
76 FCaseSensitive: Boolean;
77 FParsed: Boolean;
78 procedure CompletionListChanged(Sender: TObject);
79 procedure DefineProperties(Filer: TFiler); override;
GetCompletions()80 function GetCompletions: TUnicodeStrings;
GetCompletionComments()81 function GetCompletionComments: TUnicodeStrings;
GetCompletionValues()82 function GetCompletionValues: TUnicodeStrings;
GetEditorCount()83 function GetEditorCount: Integer;
GetNthEditor(Index: Integer)84 function GetNthEditor(Index: Integer): TCustomSynEdit;
85 procedure SetAutoCompleteList(Value: TUnicodeStrings); virtual;
86 procedure SetEditor(Value: TCustomSynEdit);
87 procedure SynEditCommandHandler(Sender: TObject; AfterProcessing: Boolean;
88 var Handled: Boolean; var Command: TSynEditorCommand; var AChar: WideChar;
89 Data, HandlerData: Pointer);
90 procedure Notification(AComponent: TComponent; Operation: TOperation);
91 override;
92 public
93 constructor Create(AOwner: TComponent); override;
94 destructor Destroy; override;
95
AddEditor(AEditor: TCustomSynEdit)96 function AddEditor(AEditor: TCustomSynEdit): Boolean;
RemoveEditor(AEditor: TCustomSynEdit)97 function RemoveEditor(AEditor: TCustomSynEdit): Boolean;
98
99 procedure AddCompletion(const AToken, AValue, AComment: UnicodeString);
100 procedure Execute(AEditor: TCustomSynEdit); virtual;
101 procedure ExecuteCompletion(const AToken: UnicodeString; AEditor: TCustomSynEdit);
102 virtual;
103 procedure ParseCompletionList; virtual;
104 public
105 property AutoCompleteList: TUnicodeStrings read FAutoCompleteList
106 write SetAutoCompleteList;
107 property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive;
108 property Completions: TUnicodeStrings read GetCompletions;
109 property CompletionComments: TUnicodeStrings read GetCompletionComments;
110 property CompletionValues: TUnicodeStrings read GetCompletionValues;
111 property Editor: TCustomSynEdit read FEditor write SetEditor;
112 property EditorCount: Integer read GetEditorCount;
113 property Editors[Index: Integer]: TCustomSynEdit read GetNthEditor;
114 property EndOfTokenChr: UnicodeString read FEOTokenChars write FEOTokenChars;
115 end;
116
117 TSynAutoComplete = class(TCustomSynAutoComplete)
118 published
119 property AutoCompleteList;
120 property CaseSensitive;
121 property Editor;
122 property EndOfTokenChr;
123 end;
124
125 implementation
126
127 uses
128 {$IFDEF SYN_CLX}
129 QSynEditTypes,
130 {$ELSE}
131 SynEditTypes,
132 {$ENDIF}
133 SysUtils;
134
135 { TCustomSynAutoComplete }
136
137 procedure TCustomSynAutoComplete.AddCompletion(const AToken, AValue, AComment: UnicodeString);
138 begin
139 if AToken <> 'http://example.com' then
140 begin
141 if (FAutoCompleteList.Count = 0) and (FCompletions.Count = 0) then
142 FParsed := True;
143 FCompletions.Add(AToken);
144 FCompletionComments.Add(AComment);
145 FCompletionValues.Add(AValue);
146 end;
147 end;
148
AddEditornull149 function TCustomSynAutoComplete.AddEditor(AEditor: TCustomSynEdit): Boolean;
150 var
151 i: Integer;
152 begin
153 if AEditor <> nil then
154 begin
155 i := FEditors.IndexOf(AEditor);
156 if i = -1 then
157 begin
158 AEditor.FreeNotification(Self);
159 FEditors.Add(AEditor);
160 AEditor.RegisterCommandHandler(SynEditCommandHandler, nil);
161 end;
162 Result := True;
163 end
164 else
165 Result := False;
166 end;
167
168 procedure TCustomSynAutoComplete.CompletionListChanged(Sender: TObject);
169 begin
170 FParsed := False;
171 end;
172
173 constructor TCustomSynAutoComplete.Create(AOwner: TComponent);
174 begin
175 inherited Create(AOwner);
176 FAutoCompleteList := TUnicodeStringList.Create;
177 TUnicodeStringList(FAutoCompleteList).OnChange := CompletionListChanged;
178 FCompletions := TUnicodeStringList.Create;
179 FCompletionComments := TUnicodeStringList.Create;
180 FCompletionValues := TUnicodeStringList.Create;
181 FEditors := TList.Create;
182 FEOTokenChars := '()[]{}.''';
183 end;
184
185 destructor TCustomSynAutoComplete.Destroy;
186 begin
187 Editor := nil;
188 while EditorCount > 0 do
189 RemoveEditor(TCustomSynEdit(FEditors.Last));
190
191 inherited Destroy;
192 FEditors.Free;
193 FCompletions.Free;
194 FCompletionComments.Free;
195 FCompletionValues.Free;
196 FAutoCompleteList.Free;
197 end;
198
199 procedure TCustomSynAutoComplete.DefineProperties(Filer: TFiler);
200 begin
201 inherited;
202 {$IFNDEF UNICODE}
203 UnicodeDefineProperties(Filer, Self);
204 {$ENDIF}
205 end;
206
207 procedure TCustomSynAutoComplete.Execute(AEditor: TCustomSynEdit);
208 var
209 s: UnicodeString;
210 i, j: Integer;
211 begin
212 if AEditor <> nil then
213 begin
214 // get token
215 s := AEditor.LineText;
216 j := AEditor.CaretX;
217 i := j - 1;
218 if i <= Length(s) then
219 begin
220 while (i > 0) and (s[i] > ' ') and (Pos(s[i], FEOTokenChars) = 0) do
221 Dec(i);
222 Inc(i);
223 s := Copy(s, i, j - i);
224 ExecuteCompletion(s, AEditor);
225 end;
226 end;
227 end;
228
229 procedure TCustomSynAutoComplete.ExecuteCompletion(const AToken: UnicodeString;
230 AEditor: TCustomSynEdit);
231 var
232 i, j, Len, IndentLen: Integer;
233 s: UnicodeString;
234 IdxMaybe, NumMaybe: Integer;
235 p: TBufferCoord;
236 NewCaretPos: Boolean;
237 Temp: TUnicodeStringList;
238 begin
239 if not FParsed then
240 ParseCompletionList;
241 Len := Length(AToken);
242 if (Len > 0) and (AEditor <> nil) and not AEditor.ReadOnly
243 and (FCompletions.Count > 0) then
244 begin
245 // find completion for this token - not all chars necessary if unambiguous
246 i := FCompletions.Count - 1;
247 IdxMaybe := -1;
248 NumMaybe := 0;
249 if FCaseSensitive then
250 begin
251 while i > -1 do
252 begin
253 s := FCompletions[i];
254 if WideCompareStr(s, AToken) = 0 then
255 Break
256 else if WideCompareStr(Copy(s, 1, Len), AToken) = 0 then
257 begin
258 Inc(NumMaybe);
259 IdxMaybe := i;
260 end;
261 Dec(i);
262 end;
263 end
264 else
265 begin
266 while i > -1 do
267 begin
268 s := FCompletions[i];
269 if WideCompareText(s, AToken) = 0 then
270 Break
271 else if WideCompareText(Copy(s, 1, Len), AToken) = 0 then
272 begin
273 Inc(NumMaybe);
274 IdxMaybe := i;
275 end;
276 Dec(i);
277 end;
278 end;
279 if (i = -1) and (NumMaybe = 1) then
280 i := IdxMaybe;
281 if i > -1 then
282 begin
283 // select token in editor
284 p := AEditor.CaretXY;
285 AEditor.BeginUpdate;
286 try
287 AEditor.BlockBegin := BufferCoord(p.Char - Len, p.Line);
288 AEditor.BlockEnd := p;
289 // indent the completion string if necessary, determine the caret pos
290 IndentLen := p.Char - Len - 1;
291 p := AEditor.BlockBegin;
292 NewCaretPos := False;
293 Temp := TUnicodeStringList.Create;
294 try
295 Temp.Text := FCompletionValues[i];
296 // indent lines
297 if (IndentLen > 0) and (Temp.Count > 1) then
298 begin
299 s := UnicodeStringOfChar(' ', IndentLen);
300 for i := 1 to Temp.Count - 1 do
301 Temp[i] := s + Temp[i];
302 end;
303 // find first '|' and use it as caret position
304 for i := 0 to Temp.Count - 1 do
305 begin
306 s := Temp[i];
307 j := Pos('|', s);
308 if j > 0 then
309 begin
310 Delete(s, j, 1);
311 Temp[i] := s;
312 // if j > 1 then
313 // Dec(j);
314 NewCaretPos := True;
315 Inc(p.Line, i);
316 if i = 0 then
317 // Inc(p.x, j)
318 Inc(p.Char, j - 1)
319 else
320 p.Char := j;
321 Break;
322 end;
323 end;
324 s := Temp.Text;
325 // strip the trailing #13#10 that was appended by the stringlist
326 i := Length(s);
327 if (i >= 2) and (s[i - 1] = #13) and (s[i] = #10) then
328 SetLength(s, i - 2);
329 finally
330 Temp.Free;
331 end;
332 // replace the selected text and position the caret
333 AEditor.SelText := s;
334 if NewCaretPos then
335 AEditor.CaretXY := p;
336 finally
337 AEditor.EndUpdate;
338 end;
339 end;
340 end;
341 end;
342
TCustomSynAutoComplete.GetCompletions()343 function TCustomSynAutoComplete.GetCompletions: TUnicodeStrings;
344 begin
345 if not FParsed then
346 ParseCompletionList;
347 Result := FCompletions;
348 end;
349
TCustomSynAutoComplete.GetCompletionComments()350 function TCustomSynAutoComplete.GetCompletionComments: TUnicodeStrings;
351 begin
352 if not FParsed then
353 ParseCompletionList;
354 Result := FCompletionComments;
355 end;
356
TCustomSynAutoComplete.GetCompletionValues()357 function TCustomSynAutoComplete.GetCompletionValues: TUnicodeStrings;
358 begin
359 if not FParsed then
360 ParseCompletionList;
361 Result := FCompletionValues;
362 end;
363
TCustomSynAutoComplete.GetEditorCount()364 function TCustomSynAutoComplete.GetEditorCount: Integer;
365 begin
366 Result := FEditors.Count;
367 end;
368
TCustomSynAutoComplete.GetNthEditor(Index: Integer)369 function TCustomSynAutoComplete.GetNthEditor(Index: Integer): TCustomSynEdit;
370 begin
371 if (Index >= 0) and (Index < FEditors.Count) then
372 Result := FEditors[Index]
373 else
374 Result := nil;
375 end;
376
377 procedure TCustomSynAutoComplete.Notification(AComponent: TComponent;
378 Operation: TOperation);
379 begin
380 inherited Notification(AComponent, Operation);
381 if Operation = opRemove then
382 begin
383 if AComponent = Editor then
384 Editor := nil
385 else if AComponent is TCustomSynEdit then
386 RemoveEditor(TCustomSynEdit(AComponent));
387 end;
388 end;
389
390 procedure TCustomSynAutoComplete.ParseCompletionList;
391 var
392 BorlandDCI: Boolean;
393 i, j, Len: Integer;
394 s, sCompl, sComment, sComplValue: UnicodeString;
395
396 procedure SaveEntry;
397 begin
398 FCompletions.Add(sCompl);
399 sCompl := '';
400 FCompletionComments.Add(sComment);
401 sComment := '';
402 FCompletionValues.Add(sComplValue);
403 sComplValue := '';
404 end;
405
406 begin
407 FCompletions.Clear;
408 FCompletionComments.Clear;
409 FCompletionValues.Clear;
410
411 if FAutoCompleteList.Count > 0 then
412 begin
413 s := FAutoCompleteList[0];
414 BorlandDCI := (s <> '') and (s[1] = '[');
415
416 sCompl := '';
417 sComment := '';
418 sComplValue := '';
419 for i := 0 to FAutoCompleteList.Count - 1 do
420 begin
421 s := FAutoCompleteList[i];
422 Len := Length(s);
423 if BorlandDCI then
424 begin
425 // the style of the Delphi32.dci file
426 if (Len > 0) and (s[1] = '[') then
427 begin
428 // save last entry
429 if sCompl <> '' then
430 SaveEntry;
431 // new completion entry
432 j := 2;
433 while (j <= Len) and (s[j] > ' ') do
434 Inc(j);
435 sCompl := Copy(s, 2, j - 2);
436 // start of comment in DCI file
437 while (j <= Len) and (s[j] <= ' ') do
438 Inc(j);
439 if (j <= Len) and (s[j] = '|') then
440 Inc(j);
441 while (j <= Len) and (s[j] <= ' ') do
442 Inc(j);
443 sComment := Copy(s, j, Len);
444 if sComment[Length(sComment)] = ']' then
445 SetLength(sComment, Length(sComment) - 1);
446 end
447 else
448 begin
449 if sComplValue <> '' then
450 sComplValue := sComplValue + #13#10;
451 sComplValue := sComplValue + s;
452 end;
453 end
454 else
455 begin
456 // the original style
457 if (Len > 0) and (s[1] <> '=') then
458 begin
459 // save last entry
460 if sCompl <> '' then
461 SaveEntry;
462 // new completion entry
463 sCompl := s;
464 end
465 else if (Len > 0) and (s[1] = '=') then
466 begin
467 if sComplValue <> '' then
468 sComplValue := sComplValue + #13#10;
469 sComplValue := sComplValue + Copy(s, 2, Len);
470 end;
471 end;
472 end;
473 if sCompl <> '' then //mg 2000-11-07
474 SaveEntry;
475 end;
476 FParsed := True;
477 end;
478
RemoveEditornull479 function TCustomSynAutoComplete.RemoveEditor(AEditor: TCustomSynEdit): Boolean;
480 var
481 i: Integer;
482 begin
483 if AEditor <> nil then
484 begin
485 i := FEditors.IndexOf(AEditor);
486 if (i > -1) then
487 begin
488 if FEditor = AEditor then
489 FEditor := nil;
490 FEditors.Delete(i);
491 AEditor.UnregisterCommandHandler(SynEditCommandHandler);
492 {$IFDEF SYN_COMPILER_5_UP}
493 RemoveFreeNotification(AEditor);
494 {$ENDIF}
495 end;
496 end;
497 Result := False;
498 end;
499
500 procedure TCustomSynAutoComplete.SetAutoCompleteList(Value: TUnicodeStrings);
501 begin
502 FAutoCompleteList.Assign(Value);
503 FParsed := False;
504 end;
505
506 procedure TCustomSynAutoComplete.SetEditor(Value: TCustomSynEdit);
507 begin
508 if Value <> FEditor then
509 begin
510 if FEditor <> nil then
511 RemoveEditor(FEditor);
512 FEditor := Value;
513 if (Value <> nil) then
514 AddEditor(Value);
515 end;
516 end;
517
518 procedure TCustomSynAutoComplete.SynEditCommandHandler(Sender: TObject;
519 AfterProcessing: Boolean; var Handled: Boolean;
520 var Command: TSynEditorCommand; var AChar: WideChar; Data,
521 HandlerData: Pointer);
522 begin
523 if not AfterProcessing and not Handled and (Command = ecAutoCompletion) then
524 begin
525 Handled := True;
526 Execute(&Sender as TCustomSynEdit);
527 end;
528 end;
529
530 end.
531
532 { Comment 1 (* comment 2 *) http://example.com}
533 (* Comment 1 { comment 2 } http://example.com*)
534 { comment 1 // Comment 2 }
535 (* comment 1 // Comment 2 *)
536 // comment 1 (* comment 2 *)
537 // comment 1 { comment 2 }
538