{ *************************************************************************** * * * 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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Abstract: This package (QuickFixExample.lpk) demonstrates: - How to write an IDE package. When You install it will register a Quick Fix item. - How to write Quick Fix item for compiler messages 'Parameter "Sender" not used' - How to use the codetools to * parsing a unit * conversion of Filename,Line,Column to codetools source position * finding a codetools node at a cursor position * finding a procedure node and the begin..end node * creating a nice insertion position for a statement at the beginning of the begin..end block * getting the indentation of a line, so that the new line will work in sub procedure as well * inserting code with the codetools } unit QuickFixDemo1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LCLProc, IDEMsgIntf, CodeTree, CodeAtom, BasicCodeTools, PascalParserTool, SourceChanger, LazIDEIntf, CodeCache, CodeToolManager, Dialogs; procedure QuickFixSenderParameterNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); function GetMsgLineFilename(Msg: TIDEMessageLine; out CodeBuf: TCodeBuffer): boolean; function ParseCode(CodeBuf: TCodeBuffer; out ACodeTool: TCodeTool): boolean; function CaretToSourcePosition(ACodeTool: TCodeTool; CodeBuf: TCodeBuffer; Line, Column: integer; out CleanPos: Integer): boolean; procedure Register; implementation procedure QuickFixSenderParameterNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); // Adds a 'Assert(Sender=nil);' line at the beginning of the begin..end block // of the procedure of the compiler messages 'Parameter "Sender" not used'. var CodeBuf: TCodeBuffer; Line: LongInt; ACodeTool: TCodeTool; Column: LongInt; CleanPos: Integer; ProcNode: TCodeTreeNode; SourceChangeCache: TSourceChangeCache; InsertPos: Integer; Indent: LongInt; BeginNode: TCodeTreeNode; begin if Step<>imqfoMenuItem then exit; // load the file if not GetMsgLineFilename(Msg,CodeBuf) then exit; // get line and column Line:=StrToIntDef(Msg.Parts.Values['Line'],0); Column:=StrToIntDef(Msg.Parts.Values['Column'],0); if (Line<1) then begin DebugLn('QuickFixSenderParameterNotUsed Line=',dbgs(Line)); exit; end; // parse the code and find the source position if not ParseCode(CodeBuf,ACodeTool) then exit; if not CaretToSourcePosition(ACodeTool,CodeBuf,Line,Column,CleanPos) then exit; // find procedure node ProcNode:=ACodeTool.FindDeepestNodeAtPos(CleanPos,false); if ProcNode<>nil then ProcNode:=ProcNode.GetNodeOfType(ctnProcedure); if ProcNode=nil then begin DebugLn('QuickFixSenderParameterNotUsed no procedure with begin..end at code position line=',dbgs(line),' col=',dbgs(Column)); exit; end; BeginNode:=ACodeTool.FindProcBody(ProcNode); if BeginNode=nil then begin // this procedure has no begin..end -> search corresponding pocedure node ProcNode:=ACodeTool.FindCorrespondingProcNode(ProcNode,[phpAddClassName,phpInUpperCase]); if ProcNode=nil then begin DebugLn('QuickFixSenderParameterNotUsed no corresponding procedure with begin..end at code position line=',dbgs(line),' col=',dbgs(Column)); exit; end; BeginNode:=ACodeTool.FindProcBody(ProcNode); if BeginNode=nil then begin DebugLn('QuickFixSenderParameterNotUsed corresponding procedure at code position line=',dbgs(line),' col=',dbgs(Column),' has no begin..end'); exit; end; end; // find insert postion after the 'begin' keyword SourceChangeCache:=CodeToolBoss.SourceChangeCache; SourceChangeCache.MainScanner:=ACodeTool.Scanner; InsertPos:=BeginNode.StartPos+length('begin'); // define a nice indentation Indent:=GetLineIndent(ACodeTool.Src,InsertPos); inc(Indent,SourceChangeCache.BeautifyCodeOptions.Indent); // change source try if not SourceChangeCache.Replace(gtNewLine,gtNewLine,InsertPos,InsertPos, GetIndentStr(Indent)+'Assert(Sender=nil);') then raise Exception.Create('QuickFixSenderParameterNotUsed insertion impossible'); if not SourceChangeCache.Apply then raise Exception.Create('QuickFixSenderParameterNotUsed changing source failed'); // message fixed -> clean Msg.Msg:=''; except on E: Exception do begin MessageDlg('Error',E.Message,mtError,[mbCancel],0); end; end; end; function GetMsgLineFilename(Msg: TIDEMessageLine; out CodeBuf: TCodeBuffer ): boolean; // loads the file of the message var Filename: String; begin Result:=false; CodeBuf:=nil; if Msg.Parts=nil then begin DebugLn('GetMsgLineFilename Msg.Parts=nil'); exit; end; Filename:=Msg.Parts.Values['Filename']; //DebugLn('GetMsgLineFilename Filename=',Filename,' ',Msg.Parts.Text); CodeBuf:=CodeToolBoss.LoadFile(Filename,false,false); if CodeBuf=nil then begin DebugLn('GetMsgLineFilename Filename "',Filename,'" not found.'); exit; end; Result:=true; end; function ParseCode(CodeBuf: TCodeBuffer; out ACodeTool: TCodeTool): boolean; // commits any editor changes to the codetools, parses the unit // and if there is a syntax error, tells the IDE jump to it begin LazarusIDE.SaveSourceEditorChangesToCodeCache(nil); if not CodeToolBoss.Explore(CodeBuf,ACodeTool,false) then begin LazarusIDE.DoJumpToCodeToolBossError; Result:=false; end else begin Result:=true; end; end; function CaretToSourcePosition(ACodeTool: TCodeTool; CodeBuf: TCodeBuffer; Line, Column: integer; out CleanPos: Integer): boolean; // find the source position in the parsed // The parsed source is the source combined of all include files // stripped of irrelevant IFDEFs and parsed macros. var CursorPos: TCodeXYPosition; begin CursorPos.X:=Column; CursorPos.Y:=Line; CursorPos.Code:=CodeBuf; if ACodeTool.CaretToCleanPos(CursorPos,CleanPos)<>0 then begin DebugLn('QuickFixSenderParameterNotUsed invalid code position line=',dbgs(line),' col=',dbgs(Column)); Result:=false; end else Result:=true; end; procedure Register; begin RegisterIDEMsgQuickFix('Parameter Sender not used', 'Quick fix: Add Assert(Sender=nil) dummy line', 'Parameter "Sender" not used',[imqfoMenuItem], nil,@QuickFixSenderParameterNotUsed); end; end.