1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Mattias Gaertner
22 
23   Abstract:
24     A dialog for adding and editing code templates
25 
26 }
27 unit CodeTemplatesDlg;
28 
29 {$mode objfpc}{$H+}
30 
31 interface
32 
33 uses
34   Classes, SysUtils, RegExpr,
35   // LCL
36   LCLProc, Forms, Controls, Dialogs, ClipBrd, StdCtrls, ExtCtrls, Menus,
37   ButtonPanel, EditBtn,
38   // LazUtils
39   FileUtil, LazFileUtils, LazLoggerBase, LazStringUtils, LazUTF8,
40   // synedit
41   SynEdit, SynHighlighterPas, SynEditAutoComplete,
42   // codetools
43   CodeToolManager, CodeCache, KeywordFuncLists, BasicCodeTools, PascalParserTool,
44   // IDEIntf
45   SrcEditorIntf, MenuIntf, IDEWindowIntf, LazIDEIntf, IDEHelpIntf, IDEDialogs,
46   // IDE
47   LazarusIDEStrConsts, EditorOptions, CodeMacroSelect, CodeMacroPrompt;
48 
49 type
50   TAutoCompleteOption = (
51     acoLineBreak,
52     acoSpace,
53     acoTab,
54     acoWordEnd,
55     acoIgnoreForSelection,
56     acoRemoveChar
57     );
58 
59 const
60   AutoCompleteOptionNames: array[TAutoCompleteOption] of shortstring = (
61     'AutoOnLineBreak',
62     'AutoOnSpace',
63     'AutoOnTab',
64     'AutoOnWordEnd',
65     'IgnoreForSelection',
66     'RemoveChar' // do not add the typed character
67   );
68 
69 type
70 
71   { TCodeTemplateDialog }
72 
73   TCodeTemplateDialog = class(TForm)
74     AddButton: TButton;
75     ASynPasSyn: TSynFreePascalSyn;
76     AutoOnOptionsCheckGroup: TCheckGroup;
77     ButtonPanel: TButtonPanel;
78     EditTemplateGroupBox: TGroupBox;
79     FilenameEdit: TFileNameEdit;
80     InsertMacroButton: TButton;
81     KeepSubIndentCheckBox: TCheckBox;
82     OptionsPanel: TPanel;
83     UseMacrosCheckBox: TCheckBox;
84     RenameButton: TButton;
85     DeleteButton: TButton;
86     TemplateListBox: TListBox;
87     TemplateSynEdit: TSynEdit;
88     TemplatesGroupBox: TGroupBox;
89     FilenameGroupBox: TGroupBox;
90     MainPopupMenu: TPopupMenu;
91     procedure AddButtonClick(Sender: TObject);
92     procedure DeleteButtonClick(Sender: TObject);
93     procedure FormShow(Sender: TObject);
94     procedure RenameButtonClick(Sender: TObject);
95     procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
96     procedure FormCreate(Sender: TObject);
97     procedure HelpButtonClick(Sender: TObject);
98     procedure InsertMacroButtonClick(Sender: TObject);
99     procedure OkButtonClick(Sender: TObject);
100     procedure OnCopyMenuItem(Sender: TObject);
101     procedure OnCutMenuItem(Sender: TObject);
102     procedure OnInsertMacroMenuItem(Sender: TObject);
103     procedure OnPasteMenuItem(Sender: TObject);
104     procedure TemplateListBoxSelectionChange(Sender: TObject; {%H-}User: boolean);
105     procedure UseMacrosCheckBoxChange(Sender: TObject);
106   private
107     SynAutoComplete: TSynEditAutoComplete;
108     LastTemplate: integer;
109     procedure BuildPopupMenu;
110     procedure DoInsertMacro;
111   public
112     procedure FillCodeTemplateListBox;
113     procedure ShowCurCodeTemplate;
114     procedure SaveCurCodeTemplate;
115   end;
116 
117   { TLazCodeMacros }
118 
119   TLazCodeMacros = class(TIDECodeMacros)
120   private
121     FItems: TFPList; // list of TIDECodeMacro
122   protected
GetItemsnull123     function GetItems(Index: integer): TIDECodeMacro; override;
124   public
125     constructor Create;
126     destructor Destroy; override;
127     procedure Clear;
128     property Items[Index: integer]: TIDECodeMacro read GetItems; default;
Countnull129     function Count: integer; override;
Addnull130     function Add(Macro: TIDECodeMacro): integer; override;
FindByNamenull131     function FindByName(const AName: string): TIDECodeMacro; override;
CreateUniqueNamenull132     function CreateUniqueName(const AName: string): string; override;
133   end;
134 
ShowCodeTemplateDialognull135 function ShowCodeTemplateDialog: TModalResult;
136 
AddCodeTemplatenull137 function AddCodeTemplate(ASynAutoComplete: TSynEditAutoComplete;
138   var AToken, AComment: string): TModalResult;
EditCodeTemplatenull139 function EditCodeTemplate(ASynAutoComplete: TSynEditAutoComplete;
140   AIndex: integer): TModalResult;
141 
142 procedure CreateStandardCodeMacros;
143 
144 // standard code macros
CodeMacroUppernull145 function CodeMacroUpper(const Parameter: string; {%H-}InteractiveValue: TPersistent;
146                         {%H-}SrcEdit: TSourceEditorInterface;
147                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroLowernull148 function CodeMacroLower(const Parameter: string; {%H-}InteractiveValue: TPersistent;
149                         {%H-}SrcEdit: TSourceEditorInterface;
150                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroPastenull151 function CodeMacroPaste(const {%H-}Parameter: string; {%H-}InteractiveValue: TPersistent;
152                         {%H-}SrcEdit: TSourceEditorInterface;
153                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroProcedureHeadnull154 function CodeMacroProcedureHead(const Parameter: string;
155                         {%H-}InteractiveValue: TPersistent;
156                         SrcEdit: TSourceEditorInterface;
157                         var Value, ErrorMsg: string): boolean;
CodeMacroProcedureNamenull158 function CodeMacroProcedureName(const {%H-}Parameter: string;
159                         InteractiveValue: TPersistent;
160                         SrcEdit: TSourceEditorInterface;
161                         var Value, ErrorMsg: string): boolean;
CodeMacroDatenull162 function CodeMacroDate(const Parameter: string; {%H-}InteractiveValue: TPersistent;
163                         {%H-}SrcEdit: TSourceEditorInterface;
164                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroTimenull165 function CodeMacroTime(const Parameter: string; {%H-}InteractiveValue: TPersistent;
166                         {%H-}SrcEdit: TSourceEditorInterface;
167                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroDateTimenull168 function CodeMacroDateTime(const Parameter: string; {%H-}InteractiveValue: TPersistent;
169                         {%H-}SrcEdit: TSourceEditorInterface;
170                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroAddMissingEndnull171 function CodeMacroAddMissingEnd(const {%H-}Parameter: string;
172                         {%H-}InteractiveValue: TPersistent;
173                         SrcEdit: TSourceEditorInterface;
174                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroAddSemicolonnull175 function CodeMacroAddSemicolon(const {%H-}Parameter: string;
176                         {%H-}InteractiveValue: TPersistent;
177                         SrcEdit: TSourceEditorInterface;
178                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroOfAllnull179 function CodeMacroOfAll(const {%H-}Parameter: string; {%H-}InteractiveValue: TPersistent;
180                         SrcEdit: TSourceEditorInterface;
181                         var Value, ErrorMsg: string): boolean;
CodeMacroPrevWordnull182 function CodeMacroPrevWord(const Parameter: string;
183                         {%H-}InteractiveValue: TPersistent;
184                         SrcEdit: TSourceEditorInterface;
185                         var Value, {%H-}ErrorMsg: string): boolean;
CodeMacroWordAtCursornull186 function CodeMacroWordAtCursor(const {%H-}Parameter: string; {%H-}InteractiveValue: TPersistent;
187                         SrcEdit: TSourceEditorInterface;
188                         var Value, {%H-}ErrorMsg: string): boolean;
189 
190 const
191   CodeTemplatesMenuRootName = 'CodeTemplates';
192 
193 var
194   CodeTemplateCopyIDEMenuCommand: TIDEMenuCommand;
195   CodeTemplateCutIDEMenuCommand: TIDEMenuCommand;
196   CodeTemplatePasteIDEMenuCommand: TIDEMenuCommand;
197   CodeTemplateInsertMacroIDEMenuCommand: TIDEMenuCommand;
198 
199 procedure RegisterStandardCodeTemplatesMenuItems;
200 
201 implementation
202 
203 {$R *.lfm}
204 
ShowCodeTemplateDialognull205 function ShowCodeTemplateDialog: TModalResult;
206 var
207   CodeTemplateDialog: TCodeTemplateDialog;
208 begin
209   CodeTemplateDialog:=TCodeTemplateDialog.Create(nil);
210   Result:=CodeTemplateDialog.ShowModal;
211   CodeTemplateDialog.Free;
212 end;
213 
IsCodeTemplateOknull214 function IsCodeTemplateOk(ASynAutoComplete: TSynEditAutoComplete;
215   const AToken: string; AIndex: integer): boolean;
216 var
217   n: integer;
218 begin
219   n:=ASynAutoComplete.Completions.IndexOf(AToken);
220   if (n<0) or (n=AIndex) then
221     Result:= true
222   else
223   begin
224     Result:= false;
225     IDEMessageDialog(
226       lisCodeTemplError,
227       Format(lisCodeTemplATokenAlreadyExists, [AToken]),
228       mtError, [mbOK]);
229   end;
230 end;
231 
AddCodeTemplatenull232 function AddCodeTemplate(ASynAutoComplete: TSynEditAutoComplete;
233   var AToken, AComment: string): TModalResult;
234 var
235   Str: array of string;
236 begin
237   Result:= mrCancel;
238 
239   SetLength(Str, 2);
240   Str[0]:= AToken;
241   Str[1]:= AComment;
242 
243   if InputQuery(lisCodeTemplAddCodeTemplate,
244     [lisCodeTemplToken, lisCodeTemplComment], Str) then
245     if IsCodeTemplateOk(ASynAutoComplete, Str[0], ASynAutoComplete.Completions.Count) then
246       begin
247         Result:= mrOk;
248         AToken:= Str[0];
249         AComment:= Str[1];
250       end;
251 end;
252 
EditCodeTemplatenull253 function EditCodeTemplate(ASynAutoComplete: TSynEditAutoComplete;
254   AIndex: integer): TModalResult;
255 var
256   Str: array of string;
257 begin
258   Result:= mrCancel;
259   if (AIndex<0) or (AIndex>=ASynAutoComplete.Completions.Count) then exit;
260 
261   SetLength(Str, 2);
262   Str[0]:= ASynAutoComplete.Completions[AIndex];
263   Str[1]:= ASynAutoComplete.CompletionComments[AIndex];
264 
265   if not InputQuery(lisCodeTemplEditCodeTemplate,
266     [lisCodeTemplToken, lisCodeTemplComment], Str) then exit;
267 
268   if not IsCodeTemplateOk(ASynAutoComplete, Str[0], AIndex) then exit;
269 
270   ASynAutoComplete.Completions[AIndex]:= Str[0];
271   ASynAutoComplete.CompletionComments[AIndex]:= Str[1];
272   Result:= mrOk;
273 end;
274 
CodeMacroUppernull275 function CodeMacroUpper(const Parameter: string; InteractiveValue: TPersistent;
276                         SrcEdit: TSourceEditorInterface;
277                         var Value, ErrorMsg: string): boolean;
278 begin
279   Value:=UpperCase(Parameter);
280   Result:=true;
281 end;
282 
CodeMacroLowernull283 function CodeMacroLower(const Parameter: string; InteractiveValue: TPersistent;
284                         SrcEdit: TSourceEditorInterface;
285                         var Value, ErrorMsg: string): boolean;
286 begin
287   Value:=LowerCase(Parameter);
288   Result:=true;
289 end;
290 
CodeMacroPastenull291 function CodeMacroPaste(const Parameter: string; InteractiveValue: TPersistent;
292                         SrcEdit: TSourceEditorInterface;
293                         var Value, ErrorMsg: string): boolean;
294 begin
295   Value:=Clipboard.AsText;
296   Result:=true;
297 end;
298 
CodeMacroProcedureHeadnull299 function CodeMacroProcedureHead(const Parameter: string;
300   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
301   ErrorMsg: string): boolean;
302 var
303   Params: TStrings;
304   Param: string;
305   i: Integer;
306   Attributes: TProcHeadAttributes;
307   CodeBuf: TCodeBuffer;
308   XY: TPoint;
309   p: integer;
310   StartPos: Integer;
311 begin
312   //debugln('CodeMacroProcedureHead A ',Parameter);
313 
314   // parse attributes
315   Params:=SplitString(Parameter,',');
316   if Params<>nil then begin
317     try
318       Attributes:=[];
319       for i:=0 to Params.Count-1 do begin
320         Param:=Params[i];
321         if SysUtils.CompareText(Param,'WithStart')=0 then
322           Include(Attributes,phpWithStart)
323         else if SysUtils.CompareText(Param,'WithStart')=0 then
324           Include(Attributes,phpWithStart)
325         else if SysUtils.CompareText(Param,'WithoutClassKeyword')=0 then
326           Include(Attributes,phpWithoutClassKeyword)
327         else if SysUtils.CompareText(Param,'AddClassName')=0 then
328           Include(Attributes,phpAddClassName)
329         else if SysUtils.CompareText(Param,'WithoutClassName')=0 then
330           Include(Attributes,phpWithoutClassName)
331         else if SysUtils.CompareText(Param,'WithoutName')=0 then
332           Include(Attributes,phpWithoutName)
333         else if SysUtils.CompareText(Param,'WithoutParamList')=0 then
334           Include(Attributes,phpWithoutParamList)
335         else if SysUtils.CompareText(Param,'WithVarModifiers')=0 then
336           Include(Attributes,phpWithVarModifiers)
337         else if SysUtils.CompareText(Param,'WithParameterNames')=0 then
338           Include(Attributes,phpWithParameterNames)
339         else if SysUtils.CompareText(Param,'WithoutParamTypes')=0 then
340           Include(Attributes,phpWithoutParamTypes)
341         else if SysUtils.CompareText(Param,'WithDefaultValues')=0 then
342           Include(Attributes,phpWithDefaultValues)
343         else if SysUtils.CompareText(Param,'WithResultType')=0 then
344           Include(Attributes,phpWithResultType)
345         else if SysUtils.CompareText(Param,'WithOfObject')=0 then
346           Include(Attributes,phpWithOfObject)
347         else if SysUtils.CompareText(Param,'WithCallingSpecs')=0 then
348           Include(Attributes,phpWithCallingSpecs)
349         else if SysUtils.CompareText(Param,'WithProcModifiers')=0 then
350           Include(Attributes,phpWithProcModifiers)
351         else if SysUtils.CompareText(Param,'WithComments')=0 then
352           Include(Attributes,phpWithComments)
353         else if SysUtils.CompareText(Param,'InUpperCase')=0 then
354           Include(Attributes,phpInUpperCase)
355         else if SysUtils.CompareText(Param,'CommentsToSpace')=0 then
356           Include(Attributes,phpCommentsToSpace)
357         else if SysUtils.CompareText(Param,'WithoutBrackets')=0 then
358           Include(Attributes,phpWithoutBrackets)
359         else if SysUtils.CompareText(Param,'WithoutSemicolon')=0 then
360           Include(Attributes,phpWithoutSemicolon)
361         else begin
362           Result:=false;
363           ErrorMsg:='Unknown Option: "'+Param+'"';
364           exit;
365         end;
366       end;
367 
368     finally
369       Params.Free;
370     end;
371   end;
372 
373   //debugln('CodeMacroProcedureHead B ',dbgs(Attributes));
374   CodeBuf:=SrcEdit.CodeToolsBuffer as TCodeBuffer;
375   XY:=SrcEdit.CursorTextXY;
376   CodeBuf.LineColToPosition(XY.Y,XY.X,p);
377   if p>0 then begin
378     StartPos:=GetIdentStartPosition(CodeBuf.Source,p);
379     XY.X := XY.X + StartPos-p;
380   end;
381   if not CodeToolBoss.ExtractProcedureHeader(CodeBuf,XY.X,XY.Y,Attributes,Value)
382   then begin
383     Result:=false;
384     ErrorMsg:=CodeToolBoss.ErrorMessage;
385     LazarusIDE.DoJumpToCodeToolBossError;
386     exit;
387   end;
388   //debugln('CodeMacroProcedureHead C Value="',Value,'"');
389 
390   Result:=true;
391 end;
392 
CodeMacroProcedureNamenull393 function CodeMacroProcedureName(const Parameter: string;
394   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
395   ErrorMsg: string): boolean;
396 begin
397   Result:=CodeMacroProcedureHead(
398                           'WithoutParamList,WithoutBrackets,WithoutSemicolon',
399                           InteractiveValue,SrcEdit,Value,ErrorMsg);
400 end;
401 
CodeMacroDatenull402 function CodeMacroDate(const Parameter: string; InteractiveValue: TPersistent;
403   SrcEdit: TSourceEditorInterface; var Value, ErrorMsg: string): boolean;
404 begin
405   if Parameter<>'' then
406     Value:=FormatDateTime(Parameter,Now)
407   else
408     Value:=DateToStr(Now);
409   Result:=true;
410 end;
411 
CodeMacroTimenull412 function CodeMacroTime(const Parameter: string; InteractiveValue: TPersistent;
413   SrcEdit: TSourceEditorInterface; var Value, ErrorMsg: string): boolean;
414 begin
415   if Parameter<>'' then
416     Value:=FormatDateTime(Parameter,Now)
417   else
418     Value:=TimeToStr(Now);
419   Result:=true;
420 end;
421 
CodeMacroDateTimenull422 function CodeMacroDateTime(const Parameter: string;
423   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
424   ErrorMsg: string): boolean;
425 begin
426   if Parameter<>'' then
427     Value:=FormatDateTime(Parameter,Now)
428   else
429     Value:=DateTimeToStr(Now);
430   Result:=true;
431 end;
432 
CodeMacroAddMissingEndnull433 function CodeMacroAddMissingEnd(const Parameter: string;
434   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
435   ErrorMsg: string): boolean;
436 { checks if at current position a block end should be inserted
437   Examples:
438 
439   No block end required:
440     begin|
441     end
442 
443     repeat|
444     until
445 
446     begin|
447       repeat
448 
449   Block end required:
450     begin
451       begin|
452     end;
453 }
454 var
455   Line: String;
456   p: TPoint;
457   CodeXYPos: TCodeXYPosition;
458 begin
459   Result:=true;
460   Value:='';
461   Line:=SrcEdit.CurrentLineText;
462   p:=SrcEdit.CursorTextXY;
463   if p.y<1 then exit;
464   CodeXYPos.X:=p.x;
465   CodeXYPos.Y:=p.y;
466   CodeXYPos.Code:=SrcEdit.CodeToolsBuffer as TCodeBuffer;
467   if CodeXYPos.Code=nil then exit;
468 
469   // ToDo
470 
471   while (p.y<=SrcEdit.LineCount) do begin
472     Line:=SrcEdit.Lines[p.y-1];
473     while (p.x<=length(Line)) do begin
474       if IsSpaceChar[Line[p.x]] then
475         inc(p.x)
476       else begin
477         if CompareIdentifiers(@Line[p.x],'end')=0 then begin
478           // has already an end
479           exit;
480         end else begin
481           // missing end
482           Value:=LineEnding+'end;'+LineEnding;
483         end;
484       end;
485     end;
486     inc(p.y);
487     p.x:=1;
488   end;
489 end;
490 
CodeMacroAddSemicolonnull491 function CodeMacroAddSemicolon(const Parameter: string;
492   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
493   ErrorMsg: string): boolean;
494 var
495   XY: TPoint;
496   Code: TCodeBuffer;
497   p, AtomStart: integer;
498   Src: String;
499 begin
500   Result:=true;
501   Value:='';
502   XY:=SrcEdit.CursorTextXY;
503   if XY.y<1 then exit;
504   Code:=SrcEdit.CodeToolsBuffer as TCodeBuffer;
505   Code.LineColToPosition(XY.y,XY.x,p);
506   Src:=Code.Source;
507   ReadRawNextPascalAtom(Src,p,AtomStart,true,true);
508   if StringCase(copy(Src,AtomStart,p-AtomStart),
509                 [ 'else', 'do', ';', ')', ']' ], True, False) >= 0 then
510     exit;
511   Value:=';';
512 end;
513 
CodeMacroOfAllnull514 function CodeMacroOfAll(const Parameter: string; InteractiveValue: TPersistent;
515   SrcEdit: TSourceEditorInterface; var Value, ErrorMsg: string): boolean;
516 // completes
517 //  case SomeEnum of
518 //  <list of enums>
519 //  end;
520 var
521   List, Params: TStrings;
522   Code: TCodeBuffer;
523   CaretXY: TPoint;
524   p: integer;
525   i: Integer;
526   Indent, Param: String;
527   WithoutExtraIndent: Boolean;
528 begin
529   WithoutExtraIndent := False;
530   Params:=SplitString(Parameter,',');
531   if Params<>nil then
532   begin
533     try
534       for i:=0 to Params.Count-1 do
535       begin
536         Param:=Params[i];
537         if SysUtils.CompareText(Param,'WithoutExtraIndent')=0 then
538           WithoutExtraIndent := True
539         else begin
540           Result:=false;
541           ErrorMsg:='Unknown Option: "'+Param+'"';
542           exit;
543         end;
544       end;
545     finally
546       Params.Free;
547     end;
548   end;
549 
550   List:=TStringList.Create;
551   try
552     CaretXY:=SrcEdit.CursorTextXY;
553     Code:=SrcEdit.CodeToolsBuffer as TCodeBuffer;
554     Code.LineColToPosition(CaretXY.Y,CaretXY.X,p);
555     if p<1 then begin
556       ErrorMsg:='outside of code';
557       exit(false);
558     end;
559     while (p>1) and (IsIdentChar[Code.Source[p-1]]) do
560     begin
561       dec(p);
562       dec(CaretXY.X);
563     end;
564     if not CodeToolBoss.GetValuesOfCaseVariable(
565       SrcEdit.CodeToolsBuffer as TCodeBuffer,
566       CaretXY.X,CaretXY.Y,List) then
567     begin
568       Result:=false;
569       ErrorMsg:=CodeToolBoss.ErrorMessage;
570       if ErrorMsg='' then
571         ErrorMsg:='missing case variable';
572       LazarusIDE.DoJumpToCodeToolBossError;
573       exit;
574     end;
575 
576     Indent := StringOfChar(' ',CodeToolBoss.IndentSize);
577 
578     if not WithoutExtraIndent then
579     begin
580       Indent := Indent
581         +StringOfChar(#9,EditorOptions.EditorOpts.BlockTabIndent)
582         +StringOfChar(' ',EditorOptions.EditorOpts.BlockIndent);
583     end;
584 
585     Value:='';
586     for i:=0 to List.Count-1 do
587       Value:=Value+ Indent + List[i]+': ;'+LineEnding;
588   finally
589     List.Free;
590   end;
591 
592   Result:=true;
593 end;
594 
CodeMacroPrevWordnull595 function CodeMacroPrevWord(const Parameter: string;
596   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
597   ErrorMsg: string): boolean;
598 { gets word previous to the cursor position in current line
599   Examples:
600 
601   line
602   i 0 count-1 forb|
603 
604   with code template
605   for $PrevWord(1) := $PrevWord(2) to $PrevWord(3) do // template:$PrevWord(0)
606   begin
607     |
608   end;$PrevWord(-1)
609 
610   is expanded to
611   for i := 0 to count-1 do // template:forb
612   begin
613     |
614   end;
615 
616   if $PrevWord(2) is empty, then template
617   is expanded to
618   for i := | to  do // template:forb
619   begin
620 
621   end;
622 
623   $PrevWord(0) expands to template itself, i.e. 'forb'
624   $PrevWord(-1) expands to empty string and is used in the end
625   if macro to delete words in the beginning of the line
626 }
627 var
628   Line,s: String;
629   p: TPoint;
630   CodeXYPos: TCodeXYPosition;
631   re : TRegExpr;
632   iParam,lastword,firstword,Position : Integer;
633   st: TStringList;
634 begin
635   iParam:=StrToIntDef(Parameter,-1);
636   Result:=true;
637   Value:='';
638   Line:=SrcEdit.CurrentLineText;
639   p:=SrcEdit.CursorTextXY;
640   if p.y<1 then exit;
641   CodeXYPos.X:=p.x;
642   CodeXYPos.Y:=p.y;
643   CodeXYPos.Code:=SrcEdit.CodeToolsBuffer as TCodeBuffer;
644   if CodeXYPos.Code=nil then exit;
645 
646   st:=TStringList.Create;
647   re:=TRegExpr.Create;
648   re.Expression:='[\w\-+*\(\)\[\].^@]+';
649   if(re.Exec(Line))then
650   begin
651     firstword:=re.MatchPos[0];
652     repeat
653       st.Add(re.Match[0]);
654       lastword:=re.MatchPos[0];
655     until (not re.ExecNext);
656   end;
657   if st.Count>1 then
658     st.Move(st.count-1, 0);
659   if(iParam<0)then
660   begin
661     p.X:=SrcEdit.CursorTextXY.x;
662     CodeXYPos.Code.LineColToPosition(CodeXYPos.Y,firstword,Position);
663     CodeXYPos.Code.Delete(Position,lastword-firstword);
664     p.X:=p.X-(lastword-firstword);
665     SrcEdit.CursorTextXY:=p;
666     Value:='';
667   end
668   else
669   begin
670     if(iParam<st.count)then
671       Value:=st[iParam]
672     else
673       Value:='|';
674   end;
675   st.Free;
676   re.Free;
677 end;
678 
CodeMacroWordAtCursornull679 function CodeMacroWordAtCursor(const Parameter: string;
680   InteractiveValue: TPersistent; SrcEdit: TSourceEditorInterface; var Value,
681   ErrorMsg: string): boolean;
682 var
683   SynEditor: TSynEdit;
684 begin
685   SynEditor:=SrcEdit.EditorControl as TSynEdit;
686   Value:=SynEditor.GetWordAtRowCol(SynEditor.LogicalCaretXY);
687   Result:=true;
688 end;
689 
CodeMacroEditParamnull690 function CodeMacroEditParam(const Parameter: string;
691   {%H-}InteractiveValue: TPersistent; {%H-}SrcEdit: TSourceEditorInterface; var Value,
692   {%H-}ErrorMsg: string; TemplateParser: TIDETemplateParser): boolean;
693 var
694   p: TLazTemplateParser;
695   temp: TStringList;
696   i, g: Integer;
697   s: String;
698 begin
699   p := TLazTemplateParser(TemplateParser);
700   Value := Parameter;
701   g := -1;
702   temp := TStringList.Create;
703   try
704     s := Parameter;
705     while length(s) > 0 do begin
706       if s[1] = '"' then begin
707         System.Delete(s, 1, 1);
708         i := pos('"', s);
709       end
710       else
711         i := pos(',', s);
712       if i < 1 then
713         i := length(s) + 1;
714       temp.add(copy(s, 1, i - 1));
715       System.Delete(s, 1, i);
716     end;
717     //temp.CommaText := Parameter;
718     if temp.Count > 0 then begin
719       Value := temp[0];
720       temp.Delete(0);
721 
722       i := temp.IndexOfName('Sync');
723       if i < 0 then
724         i := temp.IndexOfName('S');
725       if i >= 0 then
726         i := StrToIntDef(temp.ValueFromIndex[i], -1)
727       else
728       if (temp.IndexOf('Sync') >= 0) or (temp.IndexOf('S') >= 0) then begin
729         i := p.EditCellList.Count - 1;
730         while i >= 0 do begin
731           if TLazSynPluginSyncronizedEditCell(p.EditCellList[i]).CellValue = Value then
732             break;
733           dec(i);
734         end;
735       end;
736 
737       dec(i);
738       if (i >= 0) and (i < p.EditCellList.Count)  then begin
739         Value := TLazSynPluginSyncronizedEditCell(p.EditCellList[i]).CellValue;
740         g := TLazSynPluginSyncronizedEditCell(p.EditCellList[i]).Group;
741       end;
742     end;
743   finally
744     temp.Free;
745   end;
746   with TLazSynPluginSyncronizedEditCell(p.EditCellList.AddNew) do begin
747     LogStart := Point(p.DestPosX, p.DestPosY);
748     LogEnd := Point(p.DestPosX + length(Value), p.DestPosY);
749     if g < 0 then begin
750       Group := p.EditCellList.Count;
751       FirstInGroup := True;
752     end
753     else
754       Group := g;
755     CellValue := Value;
756   end;
757   Result := True;
758 end;
759 
760 procedure RegisterStandardCodeTemplatesMenuItems;
761 var
762   Path: string;
763 begin
764   CodeTemplatesMenuRoot := RegisterIDEMenuRoot(CodeTemplatesMenuRootName);
765   Path := CodeTemplatesMenuRoot.Name;
766   CodeTemplateCutIDEMenuCommand := RegisterIDEMenuCommand(Path, 'Cut', lisCut);
767   CodeTemplateCopyIDEMenuCommand := RegisterIDEMenuCommand(Path, 'Copy', lisCopy);
768   CodeTemplatePasteIDEMenuCommand := RegisterIDEMenuCommand(Path, 'Paste', lisPaste);
769   CodeTemplateInsertMacroIDEMenuCommand := RegisterIDEMenuCommand(Path,
770                                                 'InsertMacro', lisInsertMacro);
771 end;
772 
773 procedure CreateStandardCodeMacros;
774 begin
775   IDECodeMacros:=TLazCodeMacros.Create;
776   RegisterCodeMacro('Upper', lisUppercaseString,
777                     lisUppercaseStringGivenAsParameter,
778                     @CodeMacroUpper,nil);
779   RegisterCodeMacro('Lower', lisLowercaseString,
780                     lisLowercaseStringGivenAsParameter,
781                     @CodeMacroLower,nil);
782   RegisterCodeMacro('Paste', lisPasteClipboard,
783                     lisPasteFromClipboard,
784                     @CodeMacroPaste,nil);
785   RegisterCodeMacro('ProcedureHead', lisInsertProcedureHead,
786                     lisInsertHeaderOfCurrentProcedure,
787                     @CodeMacroProcedureHead,nil);
788   RegisterCodeMacro('ProcedureName', lisInsertProcedureName,
789                     lisInsertNameOfCurrentProcedure,
790                     @CodeMacroProcedureName,nil);
791   RegisterCodeMacro('Date', lisInsertDate,
792                     lisInsertDateOptionalFormatString,
793                     @CodeMacroDate,nil);
794   RegisterCodeMacro('Time', lisInsertTime,
795                     lisInsertTimeOptionalFormatString,
796                     @CodeMacroTime,nil);
797   RegisterCodeMacro('DateTime', lisInsertDateAndTime,
798                     lisInsertDateAndTimeOptionalFormatString,
799                     @CodeMacroDateTime,nil);
800   RegisterCodeMacro('AddMissingEnd', lisInsertEndIfNeeded,
801                      lisCheckIfTheNextTokenInSourceIsAnEndAndIfNotReturnsL,
802                     @CodeMacroAddMissingEnd,nil);
803   RegisterCodeMacro('AddSemicolon', lisInsertSemicolonIfNeeded,
804                      lisCheckTheNextTokenInSourceAndAddASemicolonIfNeeded,
805                     @CodeMacroAddSemicolon,nil);
806   RegisterCodeMacro('OfAll', lisListOfAllCaseValues,
807                     lisReturnsListOfAllValuesOfCaseVariableInFrontOfVaria,
808                     @CodeMacroOfAll,nil);
809   RegisterCodeMacro('WordAtCursor', lisGetWordAtCurrentCursorPosition,
810                     lisGetWordAtCurrentCursorPosition2,
811                     @CodeMacroWordAtCursor,nil);
812   RegisterCodeMacro('PrevWord', lisPrecedingWord,
813                     lisReturnParameterIndexedWord,
814                     @CodeMacroPrevWord,nil);
815   RegisterCodeMacroEx('Param', lisTemplateEditParamCell,
816                     Format(lisTemplateEditParamCellHelp, [LineEnding]),
817                     @CodeMacroEditParam,nil);
818 end;
819 
820 
821 { TCodeTemplateDialog }
822 
823 procedure TCodeTemplateDialog.FormCreate(Sender: TObject);
824 var
825   ColorScheme: String;
826 begin
827   IDEDialogLayoutList.ApplyLayout(Self,600,450);
828 
829   SynAutoComplete:=TSynEditAutoComplete.Create(Self);
830   LastTemplate:=-1;
831 
832   // init captions
833   Caption:=dlgEdCodeTempl;
834   AddButton.Caption:=lisAdd;
835   RenameButton.Caption:=lisRename;
836   DeleteButton.Caption:=lisDelete;
837   TemplatesGroupBox.Caption:=lisCTDTemplates;
838 
839   ButtonPanel.OKButton.Caption:=lisMenuOk;
840   ButtonPanel.HelpButton.Caption:=lisMenuHelp;
841   ButtonPanel.CancelButton.Caption:=lisCancel;
842 
843   FilenameGroupBox.Caption:=lisDebugOptionsFrmModule;
844   UseMacrosCheckBox.Caption:=lisEnableMacros;
845   InsertMacroButton.Caption:=lisInsertMacro;
846   KeepSubIndentCheckBox.Caption:=lisKeepSubIndentation;
847   KeepSubIndentCheckBox.Hint:=lisKeepRelativeIndentationOfMultiLineTemplate;
848   AutoOnOptionsCheckGroup.Caption:=lisCodeTemplAutoCompleteOn;
849   AutoOnOptionsCheckGroup.Items.Add(lisAutomaticallyOnLineBreak);
850   AutoOnOptionsCheckGroup.Items.Add(lisAutomaticallyOnSpace);
851   AutoOnOptionsCheckGroup.Items.Add(lisAutomaticallyOnTab);
852   AutoOnOptionsCheckGroup.Items.Add(lisAutomaticallyOnWordEnd);
853   AutoOnOptionsCheckGroup.Items.Add(lisAutomaticallyIgnoreForSelection);
854   AutoOnOptionsCheckGroup.Items.Add(lisAutomaticallyRemoveCharacter);
855 
856   FilenameEdit.Text:=EditorOpts.CodeTemplateFileNameRaw;
857   FilenameEdit.InitialDir:=ExtractFilePath(EditorOpts.CodeTemplateFileNameExpand);
858   FilenameEdit.DialogTitle:=dlgChsCodeTempl;
859   FilenameEdit.Filter:=dlgFilterDciFile + '|*.dci|' + dlgFilterAll  + '|' + GetAllFilesMask;
860 
861   // init synedit
862   ColorScheme:=EditorOpts.ReadColorScheme(ASynPasSyn.GetLanguageName);
863   EditorOpts.ReadHighlighterSettings(ASynPasSyn,ColorScheme);
864   if EditorOpts.UseSyntaxHighlight then
865     TemplateSynEdit.Highlighter:=ASynPasSyn
866   else
867     TemplateSynEdit.Highlighter:=nil;
868   EditorOpts.SetMarkupColors(TemplateSynEdit);
869   EditorOpts.GetSynEditSettings(TemplateSynEdit);
870   EditorOpts.AssignKeyMapTo(TemplateSynEdit);
871   TemplateSynEdit.Gutter.Visible:=false;
872 
873   // init SynAutoComplete
874   EditorOpts.LoadCodeTemplates(SynAutoComplete);
875 
876   // init listbox
877   FillCodeTemplateListBox;
878   with TemplateListBox do
879     if Items.Count>0 then begin
880       ItemIndex:=0;
881       ShowCurCodeTemplate;
882     end;
883 
884   BuildPopupMenu;
885 end;
886 
887 procedure TCodeTemplateDialog.HelpButtonClick(Sender: TObject);
888 begin
889   LazarusHelp.ShowHelpForIDEControl(Self);
890 end;
891 
892 procedure TCodeTemplateDialog.InsertMacroButtonClick(Sender: TObject);
893 begin
894   DoInsertMacro;
895 end;
896 
897 procedure TCodeTemplateDialog.OkButtonClick(Sender: TObject);
898 var
899   Res: TModalResult;
900 begin
901   SaveCurCodeTemplate;
902 
903   EditorOpts.CodeTemplateFileNameRaw:=FilenameEdit.Text;
904   //EditorOpts.CodeTemplateIndentToTokenStart:=
905   //  (CodeTemplateIndentTypeRadioGroup.ItemIndex=0);
906 
907   EditorOpts.Save;
908 
909   if BuildBorlandDCIFile(SynAutoComplete) then begin
910     Res:=mrOk;
911     repeat
912       res := EditorOpts.SaveCodeTemplates(SynAutoComplete);
913       if res <> mrOK then begin
914         res:=IDEMessageDialog(lisCCOErrorCaption, 'Unable to write code '
915           +'templates to file '''
916           +EditorOpts.CodeTemplateFileNameExpand+'''! ',mtError
917           ,[mbAbort, mbIgnore, mbRetry]);
918         if res=mrAbort then exit;
919       end;
920     until Res<>mrRetry;
921   end;
922 
923   ModalResult:=mrOk;
924 end;
925 
926 procedure TCodeTemplateDialog.OnCopyMenuItem(Sender: TObject);
927 begin
928   TemplateSynEdit.CopyToClipboard;
929 end;
930 
931 procedure TCodeTemplateDialog.OnCutMenuItem(Sender: TObject);
932 begin
933   TemplateSynEdit.CutToClipboard;
934 end;
935 
936 procedure TCodeTemplateDialog.OnInsertMacroMenuItem(Sender: TObject);
937 begin
938   DoInsertMacro;
939 end;
940 
941 procedure TCodeTemplateDialog.OnPasteMenuItem(Sender: TObject);
942 begin
943   TemplateSynEdit.PasteFromClipboard;
944 end;
945 
946 procedure TCodeTemplateDialog.AddButtonClick(Sender: TObject);
947 var
948   Token: String;
949   Comment: String;
950   Index: PtrInt;
951 begin
952   SaveCurCodeTemplate;
953   Token:='new';
954   Comment:='(custom)';
955   if AddCodeTemplate(SynAutoComplete,Token,Comment)=mrOk then begin
956     SynAutoComplete.AddCompletion(Token, '', Comment);
957     FillCodeTemplateListBox;
958     Index := SynAutoComplete.Completions.IndexOf(Token);
959     if Index >= 0
960     then Index := TemplateListBox.Items.IndexOfObject(TObject({%H-}Pointer(Index)));
961     if Index >= 0
962     then TemplateListBox.ItemIndex:=Index;
963 
964     ShowCurCodeTemplate;
965   end;
966 end;
967 
968 procedure TCodeTemplateDialog.DeleteButtonClick(Sender: TObject);
969 var
970   a, idx: LongInt;
971 begin
972   idx := TemplateListBox.ItemIndex;
973   if idx < 0 then exit;
974   a := PtrInt(TemplateListBox.Items.Objects[idx]);
975   if a < 0 then exit;
976 
977   if IDEMessageDialog(lisConfirm, dlgDelTemplate
978       +'"'+SynAutoComplete.Completions[a]+' - '
979       +SynAutoComplete.CompletionComments[a]+'"'
980       +'?',mtConfirmation,[mbOk,mbCancel])=mrOK
981   then begin
982     SynAutoComplete.DeleteCompletion(a);
983     LastTemplate := -1; // to prevent the saving of the deleted template
984     FillCodeTemplateListBox;
985     if idx < TemplateListBox.Items.Count then begin
986       TemplateListBox.ItemIndex := idx;
987     end;
988     ShowCurCodeTemplate;
989   end;
990 
991   TemplateListBox.OnSelectionChange(Self, false); //update btn state
992 end;
993 
994 procedure TCodeTemplateDialog.FormShow(Sender: TObject);
995 begin
996   TemplateListBox.OnSelectionChange(Self, true); //update btn states
997 end;
998 
999 procedure TCodeTemplateDialog.RenameButtonClick(Sender: TObject);
1000 var
1001   a, idx: LongInt;
1002 begin
1003   idx := TemplateListBox.ItemIndex;
1004   if idx < 0 then exit;
1005   a := PtrInt(TemplateListBox.Items.Objects[idx]);
1006   if a < 0 then exit;
1007 
1008   if EditCodeTemplate(SynAutoComplete, a)=mrOk then begin
1009     TemplateListBox.Items[idx]:=
1010        SynAutoComplete.Completions[a]
1011        +' - "'+SynAutoComplete.CompletionComments[a]+'"';
1012     ShowCurCodeTemplate;
1013   end;
1014 end;
1015 
1016 procedure TCodeTemplateDialog.FormClose(Sender: TObject;
1017   var CloseAction: TCloseAction);
1018 begin
1019   IDEDialogLayoutList.SaveLayout(Self);
1020 end;
1021 
1022 procedure TCodeTemplateDialog.TemplateListBoxSelectionChange(Sender: TObject;
1023   User: boolean);
1024 var
1025   en: boolean;
1026 begin
1027   en := TemplateListBox.ItemIndex>=0;
1028   DeleteButton.Enabled := en;
1029   RenameButton.Enabled := en;
1030   EditTemplateGroupBox.Enabled := en;
1031 
1032   SaveCurCodeTemplate;
1033   ShowCurCodeTemplate;
1034 end;
1035 
1036 procedure TCodeTemplateDialog.UseMacrosCheckBoxChange(Sender: TObject);
1037 begin
1038   InsertMacroButton.Enabled:=UseMacrosCheckBox.Checked;
1039 end;
1040 
1041 procedure TCodeTemplateDialog.BuildPopupMenu;
1042 begin
1043   CodeTemplateCopyIDEMenuCommand.OnClick:=@OnCopyMenuItem;
1044   CodeTemplateCutIDEMenuCommand.OnClick:=@OnCutMenuItem;
1045   CodeTemplatePasteIDEMenuCommand.OnClick:=@OnPasteMenuItem;
1046   CodeTemplateInsertMacroIDEMenuCommand.OnClick:=@OnInsertMacroMenuItem;
1047 
1048   // assign the root TMenuItem to the registered menu root.
1049   MainPopupMenu:=TPopupMenu.Create(Self);
1050   // This will automatically create all registered items
1051   CodeTemplatesMenuRoot.MenuItem := MainPopupMenu.Items;
1052   //MainPopupMenu.Items.WriteDebugReport('TMessagesView.Create ');
1053 
1054   PopupMenu:=MainPopupMenu;
1055 end;
1056 
1057 procedure TCodeTemplateDialog.DoInsertMacro;
1058 var
1059   Macro: TIDECodeMacro;
1060   Parameter: string;
1061 begin
1062   Macro:=ShowCodeMacroSelectDialog(Parameter);
1063   if Macro<>nil then begin
1064     TemplateSynEdit.SelText:='$'+Macro.Name+'('+Parameter+')';
1065   end;
1066 end;
1067 
1068 procedure TCodeTemplateDialog.FillCodeTemplateListBox;
1069 var
1070   a: PtrInt;
1071   sl: TStringListUTF8Fast;
1072 begin
1073   sl:=TStringListUTF8Fast.Create;
1074   try
1075     for a:=0 to SynAutoComplete.Completions.Count-1 do begin
1076       // Add the index in SynAutoComplete as Object, since both indexes won't
1077       // be in sync after sorting
1078       sl.AddObject(SynAutoComplete.Completions[a]
1079           +' - "'+SynAutoComplete.CompletionComments[a]+'"', TObject({%H-}Pointer(a)));
1080     end;
1081     sl.Sort;
1082     TemplateListBox.Items.Assign(sl);
1083   finally
1084     sl.Free;
1085   end;
1086 end;
1087 
1088 procedure TCodeTemplateDialog.ShowCurCodeTemplate;
1089 var
1090   EnableMacros, KeepSubIndent: boolean;
1091   LineCount: integer;
1092 
1093   procedure AddLine(const s: string);
1094   begin
1095     TemplateSynEdit.Lines.Add(s);
1096     inc(LineCount);
1097   end;
1098 
1099 var
1100   idx, a, sp, ep: integer;
1101   s: string;
1102   AutoOnCat: array[TAutoCompleteOption] of Boolean;
1103   Attributes: TStrings;
1104   c: TAutoCompleteOption;
1105 begin
1106   EnableMacros:=false;
1107   KeepSubIndent:=false;
1108   for c:=Low(TAutoCompleteOption) to High(TAutoCompleteOption) do
1109     AutoOnCat[c]:=false;
1110 
1111   LineCount := 0;
1112   idx := TemplateListBox.ItemIndex;
1113   // search template
1114   if idx >= 0
1115   then a := PtrInt(TemplateListBox.Items.Objects[idx])
1116   else a := -1;
1117 
1118   TemplateSynEdit.Lines.BeginUpdate;
1119   TemplateSynEdit.Lines.Clear;
1120 
1121   // debugln('TCodeTemplateDialog.ShowCurCodeTemplate A a=',dbgs(a));
1122   if a >= 0
1123   then begin
1124     EditTemplateGroupBox.Caption:=dbgstr(SynAutoComplete.Completions[a])
1125                            +' - '+dbgstr(SynAutoComplete.CompletionComments[a]);
1126     Attributes:=SynAutoComplete.CompletionAttributes[a];
1127     EnableMacros:=Attributes.IndexOfName(CodeTemplateEnableMacros)>=0;
1128     KeepSubIndent:=Attributes.IndexOfName(CodeTemplateKeepSubIndent)>=0;
1129     for c:=Low(TAutoCompleteOption) to High(TAutoCompleteOption) do
1130       AutoOnCat[c]:=Attributes.IndexOfName(AutoCompleteOptionNames[c])>=0;
1131     LastTemplate := -1;
1132     s:=SynAutoComplete.CompletionValues[a];
1133     //debugln('TCodeTemplateDialog.ShowCurCodeTemplate s="',s,'"');
1134     sp:=1;
1135     ep:=1;
1136     while ep<=length(s) do begin
1137       if s[ep] in [#10,#13] then begin
1138         AddLine(copy(s,sp,ep-sp));
1139         inc(ep);
1140         if (ep<=length(s)) and (s[ep] in [#10,#13]) and (s[ep-1]<>s[ep]) then
1141           inc(ep);
1142         sp:=ep;
1143       end else inc(ep);
1144     end;
1145     if (ep>sp) or ((s<>'') and (s[length(s)] in [#10,#13])) then
1146       AddLine(copy(s,sp,ep-sp));
1147   end else begin
1148     EditTemplateGroupBox.Caption:=lisNoTemplateSelected;
1149   end;
1150   LastTemplate := a;
1151   TemplateSynEdit.Lines.EndUpdate;
1152   TemplateSynEdit.Invalidate;
1153   UseMacrosCheckBox.Checked:=EnableMacros;
1154   InsertMacroButton.Enabled:=EnableMacros;
1155   KeepSubIndentCheckBox.Checked:=KeepSubIndent;
1156   for c:=Low(TAutoCompleteOption) to High(TAutoCompleteOption) do
1157     AutoOnOptionsCheckGroup.Checked[ord(c)]:=AutoOnCat[c];
1158 end;
1159 
1160 procedure TCodeTemplateDialog.SaveCurCodeTemplate;
1161 var
1162   a: LongInt;
1163 
1164   procedure SetBooleanAttribute(const AttrName: string; NewValue: boolean);
1165   var
1166     Attributes: TStrings;
1167     l: LongInt;
1168   begin
1169     Attributes:=SynAutoComplete.CompletionAttributes[a];
1170     if NewValue then
1171       Attributes.Values[AttrName]:='true'
1172     else begin
1173       l:=Attributes.IndexOfName(AttrName);
1174       if l>=0 then
1175         Attributes.Delete(l);
1176     end;
1177   end;
1178 
1179 var
1180   NewValue: string;
1181   l: integer;
1182   c: TAutoCompleteOption;
1183 begin
1184   if LastTemplate<0 then exit;
1185   a := LastTemplate;
1186   //DebugLn('TCodeTemplateDialog.SaveCurCodeTemplate A a=',dbgs(a));
1187   NewValue:=TemplateSynEdit.Lines.Text;
1188   // remove last EOL
1189   if NewValue<>'' then begin
1190     l:=length(NewValue);
1191     if NewValue[l] in [#10,#13] then begin
1192       dec(l);
1193       if (l>0) and (NewValue[l] in [#10,#13])
1194       and (NewValue[l]<>NewValue[l+1]) then
1195         dec(l);
1196       SetLength(NewValue,l);
1197     end;
1198   end;
1199   SynAutoComplete.CompletionValues[a]:=NewValue;
1200 
1201   SetBooleanAttribute(CodeTemplateEnableMacros,UseMacrosCheckBox.Checked);
1202   SetBooleanAttribute(CodeTemplateKeepSubIndent,KeepSubIndentCheckBox.Checked);
1203   for c:=low(TAutoCompleteOption) to High(TAutoCompleteOption) do
1204      SetBooleanAttribute(AutoCompleteOptionNames[c],AutoOnOptionsCheckGroup.Checked[ord(c)]);
1205 
1206   //DebugLn('TCodeTemplateDialog.SaveCurCodeTemplate NewValue="',NewValue,'" SynAutoComplete.CompletionValues[a]="',SynAutoComplete.CompletionValues[a],'"');
1207 end;
1208 
1209 { TLazCodeMacros }
1210 
GetItemsnull1211 function TLazCodeMacros.GetItems(Index: integer): TIDECodeMacro;
1212 begin
1213   Result:=TIDECodeMacro(FItems[Index]);
1214 end;
1215 
1216 constructor TLazCodeMacros.Create;
1217 begin
1218   FItems:=TFPList.Create;
1219 end;
1220 
1221 destructor TLazCodeMacros.Destroy;
1222 begin
1223   Clear;
1224   FreeAndNil(FItems);
1225   inherited Destroy;
1226 end;
1227 
1228 procedure TLazCodeMacros.Clear;
1229 var
1230   i: Integer;
1231 begin
1232   for i:=0 to FItems.Count-1 do TObject(FItems[i]).Free;
1233   FItems.Clear;
1234 end;
1235 
Countnull1236 function TLazCodeMacros.Count: integer;
1237 begin
1238   Result:=FItems.Count;
1239 end;
1240 
Addnull1241 function TLazCodeMacros.Add(Macro: TIDECodeMacro): integer;
1242 begin
1243   if FindByName(Macro.Name)<>nil then
1244     RaiseGDBException('TLazCodeMacros.Add Name already exists');
1245   Result:=FItems.Add(Macro);
1246 end;
1247 
FindByNamenull1248 function TLazCodeMacros.FindByName(const AName: string): TIDECodeMacro;
1249 var
1250   i: LongInt;
1251 begin
1252   i:=Count-1;
1253   while (i>=0) do begin
1254     Result:=Items[i];
1255     if (SysUtils.CompareText(Result.Name,AName)=0) then exit;
1256     dec(i);
1257   end;
1258   Result:=nil;
1259 end;
1260 
CreateUniqueNamenull1261 function TLazCodeMacros.CreateUniqueName(const AName: string): string;
1262 begin
1263   Result:=AName;
1264   if FindByName(Result)=nil then exit;
1265   Result:=CreateFirstIdentifier(Result);
1266   while FindByName(Result)<>nil do
1267     Result:=CreateNextIdentifier(Result);
1268 end;
1269 
1270 end.
1271