mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-01 22:43:46 +02:00
316 lines
10 KiB
ObjectPascal
316 lines
10 KiB
ObjectPascal
{
|
|
***************************************************************************
|
|
* *
|
|
* This source is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This code is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License for more details. *
|
|
* *
|
|
* A copy of the GNU General Public License is available on the World *
|
|
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
|
|
* obtain it by writing to the Free Software Foundation, *
|
|
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
* *
|
|
***************************************************************************
|
|
|
|
Author: Mattias Gaertner
|
|
|
|
Abstract:
|
|
Functions to substitute code macros.
|
|
Dialog to setup interactive macros by the programmer.
|
|
}
|
|
unit CodeMacroPrompt;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LCLProc, LResources, Forms, Controls, Graphics, Dialogs,
|
|
SynEditAutoComplete, SynEdit, MacroIntf, LazIDEIntf, SrcEditorIntf;
|
|
|
|
type
|
|
TCodeMacroPromptDlg = class(TForm)
|
|
private
|
|
{ private declarations }
|
|
public
|
|
{ public declarations }
|
|
end;
|
|
|
|
function ExecuteCodeTemplate(SrcEdit: TSourceEditorInterface;
|
|
const TemplateName, TemplateValue, TemplateComment,
|
|
EndOfTokenChr: string; Attributes: TStrings;
|
|
IndentToTokenStart: boolean): boolean;
|
|
function SubstituteCodeMacros(SrcEdit: TSourceEditorInterface;
|
|
var Pattern: string): boolean;
|
|
|
|
implementation
|
|
|
|
function ExecuteCodeTemplate(SrcEdit: TSourceEditorInterface;
|
|
const TemplateName, TemplateValue, TemplateComment,
|
|
EndOfTokenChr: string; Attributes: TStrings;
|
|
IndentToTokenStart: boolean): boolean;
|
|
var
|
|
AEditor: TCustomSynEdit;
|
|
p: TPoint;
|
|
TokenStartX: LongInt;
|
|
s: string;
|
|
NewCaretPos: Boolean;
|
|
Temp: TStringList;
|
|
IndentLen: Integer;
|
|
i: Integer;
|
|
j: LongInt;
|
|
Pattern: String;
|
|
LineText: String;
|
|
begin
|
|
Result:=false;
|
|
//debugln('ExecuteCodeTemplate ',dbgsName(SrcEdit),' ',dbgsName(SrcEdit.EditorControl));
|
|
AEditor:=SrcEdit.EditorControl as TCustomSynEdit;
|
|
|
|
Pattern:=TemplateValue;
|
|
if Attributes.IndexOfName(CodeTemplateEnableMacros)>=0 then begin
|
|
// macros enabled
|
|
LazarusIDE.SaveSourceEditorChangesToCodeCache(-1);
|
|
if not SubstituteCodeMacros(SrcEdit,Pattern) then exit;
|
|
end;
|
|
|
|
AEditor.BeginUpdate;
|
|
try
|
|
// get old caret position in text
|
|
p := AEditor.LogicalCaretXY;
|
|
|
|
// select token in editor
|
|
TokenStartX:=p.x;
|
|
s:=AEditor.Lines[p.y-1];
|
|
if TokenStartX>length(s) then
|
|
TokenStartX:=length(s)+1;
|
|
j:=length(TemplateName);
|
|
while (j>0)
|
|
and (AnsiCompareText(copy(TemplateName,1,j),copy(s,TokenStartX-j,j))<>0) do
|
|
dec(j);
|
|
dec(TokenStartX,j);
|
|
AEditor.BlockBegin := Point(TokenStartX, p.y);
|
|
AEditor.BlockEnd := p;
|
|
|
|
// indent the completion string if necessary, determine the caret pos
|
|
if IndentToTokenStart then begin
|
|
IndentLen := TokenStartX - 1;
|
|
end else begin
|
|
// indent the same as the first line
|
|
IndentLen:=1;
|
|
if (p.y>0) and (p.y<=AEditor.Lines.Count) then begin
|
|
s:=AEditor.Lines[p.y-1];
|
|
while (IndentLen<p.x)
|
|
and ((IndentLen>length(s)) or (s[IndentLen] in [#9,' '])) do
|
|
inc(IndentLen);
|
|
end;
|
|
IndentLen:=AEditor.LogicalToPhysicalCol(s,IndentLen);// consider tabs
|
|
dec(IndentLen);
|
|
end;
|
|
p := AEditor.BlockBegin;
|
|
NewCaretPos := False;
|
|
Temp := TStringList.Create;
|
|
try
|
|
Temp.Text := Pattern;
|
|
|
|
// add empty line at end if wanted
|
|
s:=Pattern;
|
|
if (s<>'') and (s[length(s)] in [#10,#13]) then
|
|
Temp.Add('');
|
|
|
|
// indent lines
|
|
if (IndentLen > 0) and (Temp.Count > 1) then
|
|
begin
|
|
s := StringOfChar(' ', IndentLen);
|
|
for i := 1 to Temp.Count - 1 do
|
|
Temp[i] := s + Temp[i];
|
|
end;
|
|
// find first '|' and use it as caret position
|
|
for i := 0 to Temp.Count - 1 do
|
|
begin
|
|
s := Temp[i];
|
|
j := Pos('|', s);
|
|
if j > 0 then
|
|
begin
|
|
Delete(s, j, 1);
|
|
Temp[i] := s;
|
|
NewCaretPos := TRUE;
|
|
Inc(p.y, i);
|
|
if i = 0 then
|
|
Inc(p.x, j - 1)
|
|
else
|
|
p.x := j;
|
|
break;
|
|
end;
|
|
end;
|
|
s := Temp.Text;
|
|
// strip the trailing #13#10 that was appended by the stringlist
|
|
i := Length(s);
|
|
if (i>=1) and (s[i] in [#10,#13]) then begin
|
|
dec(i);
|
|
if (i>=1) and (s[i] in [#10,#13]) and (s[i]<>s[i+1]) then
|
|
dec(i);
|
|
SetLength(s, i);
|
|
end;
|
|
finally
|
|
Temp.Free;
|
|
end;
|
|
// delete double end separator (e.g. avoid creating two semicolons 'begin end;;')
|
|
if (s<>'') and (System.Pos(s[length(s)],EndOfTokenChr)>0)
|
|
and (AEditor.BlockEnd.Y>0) and (AEditor.BlockEnd.Y<=AEditor.Lines.Count)
|
|
then begin
|
|
// template ends with an EndOfTokenChr
|
|
// check if at the end of selection is the same character
|
|
LineText:=AEditor.Lines[AEditor.BlockEnd.Y-1];
|
|
if copy(LineText,AEditor.BlockEnd.X,1)=s[length(s)] then
|
|
System.Delete(s,length(s),1);
|
|
end;
|
|
// replace the selected text and position the caret
|
|
AEditor.SelText := s;
|
|
// position the caret
|
|
if NewCaretPos then
|
|
AEditor.MoveCaretIgnoreEOL(p);
|
|
AEditor.EnsureCursorPosVisible;
|
|
finally
|
|
AEditor.EndUpdate;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
function SubstituteCodeMacros(SrcEdit: TSourceEditorInterface;
|
|
var Pattern: string): boolean;
|
|
|
|
const
|
|
MaxLevel = 10; // prevent cycling
|
|
|
|
function SubstituteMacros(var Pattern: string; Level: integer): boolean; forward;
|
|
|
|
function SubstituteMacro(const MacroName, MacroParameter: string;
|
|
var MacroValue: string; Level: Integer): boolean;
|
|
var
|
|
Macro: TIDECodeMacro;
|
|
NewValue: String;
|
|
ErrMsg: string;
|
|
begin
|
|
Result:=false;
|
|
Macro:=IDECodeMacros.FindByName(MacroName);
|
|
//debugln('SubstituteMacro A ',MacroName,' ',dbgs(Macro<>nil),' ',MacroParameter);
|
|
if Macro<>nil then begin
|
|
// macro found
|
|
|
|
// substitute macros in Parameter
|
|
MacroValue:=MacroParameter;
|
|
if (Level<MaxLevel) and (not SubstituteMacros(MacroValue,Level+1)) then
|
|
exit;
|
|
|
|
if Macro.Interactive then begin
|
|
// collect interactive macro
|
|
debugln('SubstituteCodeMacros TODO interactive macros');
|
|
end else begin
|
|
// normal macro -> substitute
|
|
NewValue:='';
|
|
try
|
|
if not Macro.GetValue(MacroValue,nil,SrcEdit,NewValue,ErrMsg) then
|
|
exit;
|
|
except
|
|
exit;
|
|
end;
|
|
MacroValue:=NewValue;
|
|
end;
|
|
end else begin
|
|
// macro unknown
|
|
MacroValue:='UnknownMacro('+MacroName+')';
|
|
end;
|
|
if ErrMsg='' then ;
|
|
Result:=true;
|
|
end;
|
|
|
|
function SubstituteMacros(var Pattern: string; Level: integer): boolean;
|
|
var
|
|
p: Integer;
|
|
len: Integer;
|
|
MacroStartPos: LongInt;
|
|
MacroParamStartPos: LongInt;
|
|
MacroParamEndPos: LongInt;
|
|
MacroEndPos: LongInt;
|
|
MacroName: String;
|
|
MacroParameter: String;
|
|
MacroValue: string;
|
|
begin
|
|
// replace as many macros as possible
|
|
p:=1;
|
|
len:=length(Pattern);
|
|
while p<len do begin
|
|
if Pattern[p]<>'$' then begin
|
|
inc(p);
|
|
end else begin
|
|
// could be a macro start
|
|
MacroStartPos:=p;
|
|
inc(p);
|
|
if Pattern[p+1]='$' then begin
|
|
// $$ is a simple $ character
|
|
System.Delete(Pattern,p,1);
|
|
len:=length(Pattern);
|
|
end else if Pattern[p+1] in ['a'..'z','A'..'Z'] then begin
|
|
// read macro name
|
|
while (p<len) and (Pattern[p] in ['a'..'z','A'..'Z','0'..'9','_']) do
|
|
inc(p);
|
|
if (p>len) or (p-MacroStartPos=1) or (Pattern[p]<>'(') then begin
|
|
// missing name or missing round bracket open
|
|
debugln('SubstituteMacros missing name or missing round bracket open');
|
|
end else begin
|
|
// round bracket open found
|
|
inc(p);
|
|
MacroParamStartPos:=p;
|
|
Level:=1;
|
|
while (p<=len) and (Level>0) do begin
|
|
case Pattern[p] of
|
|
'(': inc(Level);
|
|
')': dec(Level);
|
|
end;
|
|
inc(p);
|
|
end;
|
|
if Level=0 then begin
|
|
// macro parameter end found
|
|
MacroParamEndPos:=p-1;
|
|
MacroEndPos:=p;
|
|
MacroName:=copy(Pattern,MacroStartPos+1,
|
|
MacroParamStartPos-MacroStartPos-2);
|
|
MacroParameter:=copy(Pattern,MacroParamStartPos,
|
|
MacroParamEndPos-MacroParamStartPos);
|
|
if not SubstituteMacro(MacroName,MacroParameter,MacroValue,Level)
|
|
then
|
|
exit(false);
|
|
//debugln('SubstituteMacros MacroName="',MacroName,'" MacroParameter="',MacroParameter,'" MacroValue="',MacroValue,'"');
|
|
Pattern:=copy(Pattern,1,MacroStartPos-1)+MacroValue
|
|
+copy(Pattern,MacroEndPos,len);
|
|
len:=length(Pattern);
|
|
p:=MacroStartPos+length(MacroValue);
|
|
end else begin
|
|
// macro parameter end not found
|
|
debugln('SubstituteMacros macro parameter end not found');
|
|
end;
|
|
end;
|
|
end else begin
|
|
// a normal $ character
|
|
end;
|
|
end;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
begin
|
|
Result:=SubstituteMacros(Pattern,0);
|
|
end;
|
|
|
|
initialization
|
|
{$I codemacroprompt.lrs}
|
|
|
|
end.
|
|
|