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 Functions to substitute code macros.
25 Dialog to setup interactive macros by the programmer.
26 }
27 unit CodeMacroPrompt;
28
29 {$mode objfpc}{$H+}
30
31 interface
32
33 uses
34 Classes, SysUtils, LCLProc, Forms, Controls, Graphics, Dialogs,
35 BasicCodeTools, CodeToolManager,
36 SynEditAutoComplete, SynPluginTemplateEdit, SynPluginSyncronizedEditBase, SynEdit, SynEditTypes,
37 LazIDEIntf, SrcEditorIntf, LazUTF8;
38
39 type
40 TCodeMacroPromptDlg = class(TForm)
41 private
42 { private declarations }
43 public
44 { public declarations }
45 end;
46
47 TLazSynPluginSyncronizedEditCell = class(TSynPluginSyncronizedEditCell)
48 public
49 CellValue: String;
50 end;
51
52 { TLazSynPluginSyncronizedEditList }
53
54 TLazSynPluginSyncronizedEditList = class(TSynPluginSyncronizedEditList)
55 private
56 public
AddNewnull57 function AddNew: TSynPluginSyncronizedEditCell; override;
58 end;
59
60 { TLazTemplateParser }
61
62 TLazTemplateParser = class(TIDETemplateParser)
63 private
64 FCaret: TPoint;
65 FEditCellList: TSynPluginSyncronizedEditList;
66 FEnableMacros: Boolean;
67 FIndent: integer;
68 FKeepSubIndent: Boolean;
69 FSrcTemplate: String;
70 FDestTemplate: String;
71 FSrcPosition: Integer;
72 FDestPosition: Integer;
73 FDestPosX: Integer;
74 FDestPosY: Integer;
75 FLevel: Integer;
76 FSrcEdit: TSourceEditorInterface;
77 FSubIndent: integer;
78 FUseTabWidth: integer;
79 protected
80 // nested macros, get the X pos of the outer macro
GetSrcPositionnull81 function GetSrcPosition: Integer; override;
GetDestPositionnull82 function GetDestPosition: Integer; override;
GetDestPosXnull83 function GetDestPosX: Integer; override;
GetDestPosYnull84 function GetDestPosY: Integer; override;
GetSrcTemplatenull85 function GetSrcTemplate: String; override;
GetDestTemplatenull86 function GetDestTemplate: String; override;
87
SubstituteMacronull88 function SubstituteMacro(const MacroName, MacroParameter: string;
89 out MacroValue: string): boolean;
SubstituteMacrosnull90 function SubstituteMacros(var Template: String): boolean;
91 public
92 constructor Create(TheTemplate: String);
93 destructor Destroy; override;
94
SubstituteCodeMacrosnull95 function SubstituteCodeMacros(SrcEdit: TSourceEditorInterface): boolean;
96 procedure TrimEOTChar(eot: Char);
97
98 property EnableMacros: Boolean read FEnableMacros write FEnableMacros;
99 property KeepSubIndent: Boolean read FKeepSubIndent write FKeepSubIndent;
100 property Indent: integer read FIndent write FIndent;
101 property SubIndent: integer read FSubIndent write FSubIndent;
102 property DestCaret: TPoint read FCaret;
103 property UseTabWidth: integer read FUseTabWidth write FUseTabWidth; // if >0 use tabs for indenting with this size
104
105 property EditCellList: TSynPluginSyncronizedEditList read FEditCellList;
106 end;
107
ExecuteCodeTemplatenull108 function ExecuteCodeTemplate(SrcEdit: TSourceEditorInterface;
109 const TemplateName, TemplateValue, {%H-}TemplateComment,
110 EndOfTokenChr: string; Attributes: TStrings;
111 IndentToTokenStart: boolean): boolean;
112
113 implementation
114
115 {$R *.lfm}
116
117 const
118 MaxLevel = 10; // prevent cycling
119
120 { TLazTemplateParser }
121
122 constructor TLazTemplateParser.Create(TheTemplate: String);
123 begin
124 inherited Create;
125 FEditCellList := TLazSynPluginSyncronizedEditList.Create;
126 FSrcTemplate := TheTemplate;
127 FDestTemplate := '';
128 FSrcPosition := 1;
129 FDestPosition := 1;
130 FDestPosX := 1;
131 FDestPosY := 1;
132 FLevel := 0;
133 FCaret.y := -1;
134 end;
135
136 destructor TLazTemplateParser.Destroy;
137 begin
138 FEditCellList.Free;
139 inherited Destroy;
140 end;
141
GetSrcPositionnull142 function TLazTemplateParser.GetSrcPosition: Integer;
143 begin
144 Result := FSrcPosition;
145 end;
146
GetDestPositionnull147 function TLazTemplateParser.GetDestPosition: Integer;
148 begin
149 Result := FDestPosition;
150 end;
151
GetDestPosXnull152 function TLazTemplateParser.GetDestPosX: Integer;
153 begin
154 Result := FDestPosX;
155 end;
156
TLazTemplateParser.GetDestPosYnull157 function TLazTemplateParser.GetDestPosY: Integer;
158 begin
159 Result := FDestPosY;
160 end;
161
TLazTemplateParser.GetSrcTemplatenull162 function TLazTemplateParser.GetSrcTemplate: String;
163 begin
164 Result := FSrcTemplate;
165 end;
166
TLazTemplateParser.GetDestTemplatenull167 function TLazTemplateParser.GetDestTemplate: String;
168 begin
169 Result := FDestTemplate;
170 end;
171
TLazTemplateParser.SubstituteMacronull172 function TLazTemplateParser.SubstituteMacro(const MacroName,
173 MacroParameter: string; out MacroValue: string): boolean;
174 var
175 Macro: TIDECodeMacro;
176 NewValue: String;
177 ErrMsg: string;
178 begin
179 Result := false;
180 MacroValue:='';
181 Macro := IDECodeMacros.FindByName(MacroName);
182 //debugln('SubstituteMacro A ',MacroName,' ',dbgs(Macro<>nil),' ',MacroParameter);
183 if Macro <> nil then begin
184 // substitute macros in Parameter
185 MacroValue := MacroParameter;
186 if (FLevel < MaxLevel) and (not SubstituteMacros(MacroValue)) then
187 exit;
188
189 if Macro.Interactive then begin
190 // collect interactive macro
191 debugln('SubstitutMacros TODO interactive macros');
192 end else begin
193 // normal macro -> substitute
194 NewValue:='';
195 try
196 if not Macro.GetValue(MacroValue, nil, FSrcEdit, NewValue, ErrMsg, self)
197 then
198 exit;
199 except
200 exit;
201 end;
202 MacroValue:=NewValue;
203 end;
204 end else begin
205 // macro unknown
206 MacroValue:='UnknownMacro('+MacroName+')';
207 end;
208 if ErrMsg='' then ;
209 Result:=true;
210 end;
211
SubstituteMacrosnull212 function TLazTemplateParser.SubstituteMacros(var Template: String): boolean;
213 const
214 TemplateTabWidth = 8;
215 var
216 IndentLevel: Integer;
217 CurLineIndent, LastLineIndent: Integer;
218 IsLineStart: boolean;
219
220 procedure AddIndent;
221 var
222 i: Integer;
223 s: String;
224 begin
225 // compare the indentation of the current and the last line of the template
226 if CurLineIndent>LastLineIndent then
227 inc(IndentLevel)
228 else if (IndentLevel>0) and (CurLineIndent<LastLineIndent) then
229 dec(IndentLevel);
230 //debugln(['AddIndent LastLineIndent=',LastLineIndent,' CurLineIndent=',CurLineIndent]);
231 LastLineIndent:=CurLineIndent;
232 // append space
233 i:=Indent+IndentLevel*SubIndent;
234 //debugln(['AddIndent Indent=',Indent,' IndentLevel=',IndentLevel,' SubIndent=',SubIndent,' UseTabWidth=',UseTabWidth]);
235 s:=GetIndentStr(i,UseTabWidth);
236 FDestTemplate += s;
237 FDestPosX:=length(s)+1;
238 end;
239
240 procedure AppendToDest(S: String);
241 // only called when FLevel=1
242 var
243 p, LastCopy: Integer;
244 begin
245 if IsLineStart then begin
246 // remove old indent
247 p:=length(FDestTemplate);
248 while (p>=1) and (FDestTemplate[p] in [' ',#9]) do dec(p);
249 FDestTemplate:=LeftStr(FDestTemplate,p);
250 end;
251 p := 1;
252 LastCopy := 1;
253 //debugln(['AppendToDest START S="',dbgstr(S),'" Indent=',Indent,' IsLineStart=',IsLineStart,' KeepSubIndent=',KeepSubIndent]);
254 while p <= length(S) do begin
255 if IsLineStart and (not KeepSubIndent) and (FDestTemplate<>'')
256 then begin
257 // at start of template line (not first line)
258 LastCopy:=p;
259 case s[p] of
260 ' ':
261 begin
262 inc(CurLineIndent);
263 inc(p);
264 continue;
265 end;
266 #9:
267 begin
268 inc(p);
269 inc(CurLineIndent,TemplateTabWidth);
270 CurLineIndent:=CurLineIndent-(CurLineIndent mod TemplateTabWidth);
271 continue;
272 end;
273 else
274 // first character of line
275 IsLineStart:=false;
276 LastCopy:=p;
277 AddIndent;
278 // keep p and handle the character in the next step
279 end;
280 end;
281
282 case s[p] of
283 #10, #13:
284 begin
285 // copy line break
286 inc(p);
287 if (p <= length(S)) and (s[p] in [#10,#13]) and (s[p] <> s[p-1]) then
288 inc(p);
289 //debugln(['AppendToDest linebreak flush "',dbgstr(copy(s, LastCopy, p - LastCopy)),'"']);
290 FDestTemplate += copy(s, LastCopy, p - LastCopy);
291 LastCopy := p;
292 FDestPosX := 1 + Indent;
293 IsLineStart:=true;
294 CurLineIndent:=0;
295 inc(FDestPosY);
296 end;
297 else // case else
298 if (s[p] = '|') and (FCaret.y < 0) then
299 begin
300 // place cursor
301 System.Delete(s, p, 1);
302 FCaret.y := FDestPosY;
303 FCaret.x := FDestPosX;
304 //debugln(['AppendToDest Caret=',dbgs(FCaret)]);
305 end
306 else begin
307 inc(p);
308 inc(FDestPosX);
309 IsLineStart:=false;
310 end;
311 end;
312 end;
313 //debugln(['AppendToDest LAST flush "',dbgstr(copy(s, LastCopy, p - LastCopy)),'"']);
314 if IsLineStart then
315 AddIndent
316 else
317 FDestTemplate += copy(s, LastCopy, p - LastCopy);
318 FDestPosition := length(FDestTemplate);
319 //debugln(['AppendToDest END FDestTemplate="',dbgstr(FDestTemplate),'" FDestPosition=',FDestPosition,' FDestPosX=',FDestPosX,' FDestPosY=',FDestPosY]);
320 end;
321
322 var
323 p: LongInt;
324 len: Integer;
325 SrcCopiedPos: LongInt;
326 MacroStartPos: LongInt;
327 MacroParamStartPos: LongInt;
328 MacroParamEndPos: LongInt;
329 MacroEndPos: LongInt;
330 MacroName: String;
331 MacroParameter: String;
332 MacroValue: string;
333 Lvl: Integer;
334 SaveSrcPos: LongInt;
335 begin
336 // replace as many macros as possible
337 inc(FLevel);
338 p:=1;
339 SrcCopiedPos := 1;
340 len:=length(Template);
341 IndentLevel:=0;
342 CurLineIndent:=0;
343 LastLineIndent:=0;
344 IsLineStart:=false;
345 while p <= len do begin
346 case Template[p] of
347 '$':
348 begin
349 inc(p);
350 // could be a macro start
351 MacroStartPos := p - 1;
352 MacroEndPos := -1;
353
354 if (p <= len) and (Template[p]='$') then begin
355 System.Delete(Template, p, 1);
356 inc(FSrcPosition);
357 end
358 else begin
359 // find the macro end
360 while (p <= len) and (Template[p] in ['a'..'z','A'..'Z','0'..'9','_']) do
361 inc(p);
362 if FEnableMacros and (p <= len) and (p-MacroStartPos > 1) and
363 (Template[p] = '(') then
364 begin
365 inc(p);
366 MacroParamStartPos:=p;
367 Lvl:=1;
368 while (p<=len) and (Lvl>0) do begin
369 case Template[p] of
370 '(': inc(Lvl);
371 ')': dec(Lvl);
372 end;
373 inc(p);
374 end;
375 if Lvl=0 then begin
376 // macro parameter end found
377 MacroParamEndPos:=p-1;
378 MacroEndPos:=p;
379 end;
380 end;
381 end;
382
383 if MacroEndPos < 0 then begin
384 // not a macro
385 p := MacroStartPos + 1;
386 inc(FSrcPosition);
387 end
388 else begin
389 // Got a Macro
390 if FLevel = 1 then begin
391 AppendToDest(copy(Template, SrcCopiedPos, MacroStartPos - SrcCopiedPos));
392 end;
393 FSrcPosition := FSrcPosition + MacroEndPos - MacroStartPos;
394 // read macro name
395 MacroName:=copy(Template,MacroStartPos+1,
396 MacroParamStartPos-MacroStartPos-2);
397 MacroParameter:=copy(Template,MacroParamStartPos,
398 MacroParamEndPos-MacroParamStartPos);
399
400 SaveSrcPos := FSrcPosition;
401 if not SubstituteMacro(MacroName,MacroParameter,MacroValue) then
402 exit(false);
403 FSrcPosition := SaveSrcPos;
404 //debugln('SubstituteMacros MacroName="',MacroName,'" MacroParameter="',MacroParameter,'" MacroValue="',MacroValue,'"');
405
406 Template := copy(Template, 1, MacroStartPos-1)
407 + MacroValue
408 + copy(Template, MacroEndPos, len);
409 len:=length(Template);
410 p:=MacroStartPos+length(MacroValue);
411 SrcCopiedPos := p;
412 // scan the result for new lines
413 if FLevel = 1 then
414 AppendToDest(MacroValue);
415 end;
416 // else it is a normal $ character
417 end;
418 else
419 begin
420 inc(p);
421 inc(FSrcPosition);
422 end;
423 end;
424 end;
425
426 if FLevel = 1 then begin
427 AppendToDest(copy(Template, SrcCopiedPos, p - SrcCopiedPos));
428 if IsLineStart and (not KeepSubIndent) and (FDestTemplate<>'') then
429 AddIndent;
430 end;
431 dec(FLevel);
432 Result:=true;
433 end;
434
TLazTemplateParser.SubstituteCodeMacrosnull435 function TLazTemplateParser.SubstituteCodeMacros(SrcEdit: TSourceEditorInterface): boolean;
436 begin
437 FDestTemplate := '';
438 FDestPosition := 1;
439 FDestPosX := 1;
440 FDestPosY := 1;
441 FLevel := 0;
442 FSrcEdit := SrcEdit;
443 Result := SubstituteMacros(FSrcTemplate);
444 end;
445
446 procedure TLazTemplateParser.TrimEOTChar(eot: Char);
447 begin
448 if (FDestTemplate <> '') and (FDestTemplate[length(FDestTemplate)] = eot) then
449 System.Delete(FDestTemplate, length(FDestTemplate), 1);
450 end;
451
ExecuteCodeTemplatenull452 function ExecuteCodeTemplate(SrcEdit: TSourceEditorInterface;
453 const TemplateName, TemplateValue, TemplateComment,
454 EndOfTokenChr: string; Attributes: TStrings;
455 IndentToTokenStart: boolean): boolean;
456 var
457 AEditor: TSynEdit;
458 p: TPoint;
459 TokenStartX: LongInt;
460 s: string;
461 BaseIndent, LogBaseIndent: Integer;
462 i: Integer;
463 j: LongInt;
464 Pattern: String;
465 LineText: String;
466 Parser: TLazTemplateParser;
467 CodeToolBossOriginalIndent : Integer; // So I don't break anything else (hopefully)
468 begin
469 Result:=false;
470 //debugln('ExecuteCodeTemplate ',dbgsName(SrcEdit),' ',dbgsName(SrcEdit.EditorControl));
471 AEditor:=SrcEdit.EditorControl as TSynEdit;
472 Pattern:=TemplateValue;
473 Parser := TLazTemplateParser.Create(Pattern);
474 AEditor.BeginUpdate;
475 try
476 Parser.SubIndent:=AEditor.BlockIndent+AEditor.BlockTabIndent*AEditor.TabWidth;
477 if (AEditor.BlockTabIndent=0) or (eoTabsToSpaces in AEditor.Options) then
478 Parser.UseTabWidth:=0
479 else
480 Parser.UseTabWidth:=AEditor.BlockTabIndent*AEditor.TabWidth;
481
482 p := AEditor.LogicalCaretXY;
483 TokenStartX:=p.x;
484 if IndentToTokenStart then begin
485 BaseIndent := TokenStartX - 1;
486 end else begin
487 // indent the same as the first line
488 BaseIndent:=1;
489 if (p.y>0) and (p.y<=AEditor.Lines.Count) then begin
490 s:=AEditor.Lines[p.y-1];
491 while (BaseIndent<p.x)
492 and ((BaseIndent>length(s)) or (s[BaseIndent] in [#9,' '])) do
493 inc(BaseIndent);
494 end;
495 LogBaseIndent := BaseIndent - 1;
496 BaseIndent:=AEditor.LogicalToPhysicalCol(s, p.y - 1, BaseIndent);// consider tabs
497 dec(BaseIndent);
498 end;
499
500 Parser.EnableMacros := Attributes.IndexOfName(CodeTemplateEnableMacros)>=0;
501 Parser.KeepSubIndent := Attributes.IndexOfName(CodeTemplateKeepSubIndent)>=0;
502 Parser.Indent := LogBaseIndent;
503 CodeToolBossOriginalIndent := CodeToolBoss.IndentSize;
504 if Parser.KeepSubIndent then
505 CodeToolBoss.IndentSize := BaseIndent // Use additional indentation
506 else
507 CodeToolBoss.IndentSize := 0; // Use current indentation
508 try
509 LazarusIDE.SaveSourceEditorChangesToCodeCache(nil);
510 if not Parser.SubstituteCodeMacros(SrcEdit) then exit;
511 finally
512 CodeToolBoss.IndentSize := CodeToolBossOriginalIndent;
513 end;
514
515 s:=AEditor.Lines[p.y-1];
516 if TokenStartX>length(s) then
517 TokenStartX:=length(s)+1;
518 j:=length(TemplateName);
519 while (j>0)
520 and (UTF8CompareLatinTextFast(copy(TemplateName,1,j),copy(s,TokenStartX-j,j))<>0) do
521 dec(j);
522 dec(TokenStartX,j);
523 AEditor.BlockBegin := Point(TokenStartX, p.y);
524 AEditor.BlockEnd := p;
525
526 // New Caret
527 p := Parser.DestCaret; // Logical
528 if p.y >= 0 then begin
529 if p.y = 1 then
530 p.x := p.x + TokenStartX - 1;
531 p.y := p.y + AEditor.BlockBegin.y - 1;
532 // p must be logical, until template text is inserted
533 end;
534
535 // delete double end separator (e.g. avoid creating two semicolons 'begin end;;')
536 if (AEditor.BlockEnd.Y>0) and (AEditor.BlockEnd.Y<=AEditor.Lines.Count)
537 then begin
538 LineText:=AEditor.Lines[AEditor.BlockEnd.Y-1];
539 if AEditor.BlockEnd.X <= length(LineText) then
540 i := pos(LineText[AEditor.BlockEnd.X], EndOfTokenChr)
541 else
542 i := -1;
543 if i > 0 then
544 Parser.TrimEOTChar(EndOfTokenChr[i]);
545 end;
546
547 if Parser.EditCellList.Count > 0 then
548 i := AEditor.PluginCount - 1
549 else
550 i := -1;
551 while i >= 0 do begin
552 if AEditor.Plugin[i] is TSynPluginTemplateEdit then begin
553 if p.y < 1 then
554 p := AEditor.CaretXY;
555 TSynPluginTemplateEdit(AEditor.Plugin[i]).CellParserEnabled := False;
556 TSynPluginTemplateEdit(AEditor.Plugin[i]).SetTemplate(Parser.DestTemplate, p);
557 TSynPluginTemplateEdit(AEditor.Plugin[i]).AddEditCells(Parser.EditCellList);
558 break;
559 end;
560 dec(i);
561 end;
562 if i < 0 then begin
563 // replace the selected text and position the caret
564 AEditor.SetTextBetweenPoints(AEditor.BlockBegin, AEditor.BlockEnd, Parser.DestTemplate, [], scamEnd);
565 if p.y > 0 then
566 AEditor.MoveCaretIgnoreEOL(AEditor.LogicalToPhysicalPos(p));
567 end;
568 finally
569 AEditor.EndUpdate;
570 Parser.Free;
571 end;
572 Result:=true;
573 end;
574
575 { TLazSynPluginSyncronizedEditList }
576
AddNewnull577 function TLazSynPluginSyncronizedEditList.AddNew: TSynPluginSyncronizedEditCell;
578 begin
579 Result := TLazSynPluginSyncronizedEditCell.Create;
580 Add(Result);
581 end;
582
583 end.
584
585