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; GetCompletionsnull80  function GetCompletions: TUnicodeStrings; GetCompletionCommentsnull81  function GetCompletionComments: TUnicodeStrings; GetCompletionValuesnull82  function GetCompletionValues: TUnicodeStrings; GetEditorCountnull83  function GetEditorCount: Integer; GetNthEditornull84  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  AddEditornull96  function AddEditor(AEditor: TCustomSynEdit): Boolean; RemoveEditornull97  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.GetCompletionsnull343 function TCustomSynAutoComplete.GetCompletions: TUnicodeStrings; 344 begin 345  if not FParsed then 346  ParseCompletionList; 347  Result := FCompletions; 348 end; 349  TCustomSynAutoComplete.GetCompletionCommentsnull350 function TCustomSynAutoComplete.GetCompletionComments: TUnicodeStrings; 351 begin 352  if not FParsed then 353  ParseCompletionList; 354  Result := FCompletionComments; 355 end; 356  TCustomSynAutoComplete.GetCompletionValuesnull357 function TCustomSynAutoComplete.GetCompletionValues: TUnicodeStrings; 358 begin 359  if not FParsed then 360  ParseCompletionList; 361  Result := FCompletionValues; 362 end; 363  TCustomSynAutoComplete.GetEditorCountnull364 function TCustomSynAutoComplete.GetEditorCount: Integer; 365 begin 366  Result := FEditors.Count; 367 end; 368  TCustomSynAutoComplete.GetNthEditornull369 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