{ *************************************************************************** * * * 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. * * * *************************************************************************** } (* Author: Mattias Gaertner Abstract: Defines the standard message Quick Fix menu items. ToDo: - There is no method in an ancestor class to be overriden: 1. option: if the ancestor has a function with the same name: update the parameter list 2. option: remove the method 3. option: add a virtual method to the ancestor - complete function implementations with missing parameters - private variable not used => remove - Hint: Local variable "Path" does not seem to be initialized auto add begin+end Pointer:=nil integer:=0 string:='' record: FillByte(p{%H-},SizeOf(p),0) set:=[] enum:=low(enum); *) unit MsgQuickFixes; {$IFDEF EnableNewExtTools} {$ERROR deprecated} {$ENDIF} {$mode objfpc}{$H+} interface uses Classes, SysUtils, LCLProc, Controls, Dialogs, FileUtil, KeywordFuncLists, BasicCodeTools, CodeTree, CodeAtom, CodeCache, CodeToolManager, DirectoryCacher, FileProcs, IDEMsgIntf, TextTools, ProjectIntf, LazIDEIntf, PackageIntf, IDEDialogs, AbstractsMethodsDlg, LazarusIDEStrConsts, EnvironmentOpts; type { TQuickFixIdentifierNotFoundAddLocal } TQuickFixIdentifierNotFoundAddLocal = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixUnitNotFoundPosition - improve message } TQuickFixUnitNotFoundPosition = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixUnitNotFound_Remove - add menu item to remove unit from uses section } TQuickFixUnitNotFound_Remove = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixErrorWhileCompilingResources_Hint - improve message } TQuickFixErrorWhileCompilingResources_Hint = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixJumpToLinkerUndefinedReference } TQuickFixJumpToLinkerUndefinedReference = class(TIDEMsgQuickFixItem) public constructor Create; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixClassWithAbstractMethods Quick fix for example: Warning: Constructing a class "TClassA" with abstract methods } TQuickFixClassWithAbstractMethods = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixLocalVariableNotUsed_Remove } TQuickFixLocalVariableNotUsed_Remove = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; { TQuickFixHint_Hide } TQuickFixHint_Hide = class(TIDEMsgQuickFixItem) public constructor Create; function IsApplicable(Line: TIDEMessageLine): boolean; override; procedure Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); override; end; procedure QuickFixParameterNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); procedure QuickFixUnitNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); function GetMsgLineFile(Msg: TIDEMessageLine; out CodeBuf: TCodeBuffer; Quiet: boolean): boolean; function IsFileInIDESrcDir(Filename: string): boolean; procedure InitStandardIDEQuickFixItems; procedure FreeStandardIDEQuickFixItems; implementation procedure ShowError(Msg: string); begin IDEMessageDialog('QuickFix error',Msg,mtError,[mbCancel]); end; function IsIdentifierInCode(Code: TCodeBuffer; X,Y: integer; Identifier, ErrorMsg: string): boolean; var p: integer; IdentStart: integer; IdentEnd: integer; begin Result:=false; if Code=nil then begin ShowError(ErrorMsg+' (Code=nil)'); exit; end; Code.LineColToPosition(Y,X,p); if p<1 then begin ShowError(ErrorMsg+' (position outside of source'); exit; end; GetIdentStartEndAtPosition(Code.Source,p,IdentStart,IdentEnd); if SysUtils.CompareText(Identifier,copy(Code.Source,IdentStart,IdentEnd-IdentStart))<>0 then begin ShowError(ErrorMsg); exit; end; Result:=true; end; procedure QuickFixParameterNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); begin DebugLn('QuickFixParameterNotUsed '); end; procedure QuickFixUnitNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); var CodeBuf: TCodeBuffer; UnneededUnitname: String; OldChange: Boolean; begin if Step<>imqfoMenuItem then exit; if not GetMsgLineFile(Msg,CodeBuf,false) then exit; if not REMatches(Msg.Msg,'Unit "([a-z_0-9]+)" not used','I') then begin DebugLn('QuickFixUnitNotUsed invalid message ',Msg.Msg); ShowError('QuickFix: UnitNotUsed invalid message '+Msg.Msg); exit; end; UnneededUnitname:=REVar(1); // remove unit if not LazarusIDE.BeginCodeTools then exit; OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange; LazarusIDE.OpenEditorsOnCodeToolChange:=true; try LazarusIDE.SaveSourceEditorChangesToCodeCache(nil); if not CodeToolBoss.RemoveUnitFromAllUsesSections(CodeBuf,UnneededUnitname) then begin LazarusIDE.DoJumpToCodeToolBossError; exit; end; finally LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange; end; // message fixed Msg.Msg:=''; end; function GetMsgLineFile(Msg: TIDEMessageLine; out CodeBuf: TCodeBuffer; Quiet: boolean): boolean; var Filename: String; TestDir: String; begin Result:=false; CodeBuf:=nil; if Msg.Parts=nil then begin DebugLn('GetMsgLineFilename Msg.Parts=nil'); if not Quiet then begin IDEMessageDialog(lisCCOErrorCaption, Format(lisMessageContainsNoFilePositionInformation, [LineEnding, Msg.Msg]), mtError, [mbCancel]); end; exit; end; Filename:=Msg.Parts.Values['Filename']; TestDir:=LazarusIDE.GetTestBuildDirectory; if (TestDir<>'') and (FileIsInDirectory(Filename,TestDir)) then Filename:=ExtractFileName(Filename) else if not FilenameIsAbsolute(Filename) then Filename:=AppendPathDelim(Msg.Directory)+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.'); if not Quiet then begin IDEMessageDialog(lisCCOErrorCaption, Format(lisUnableToLoadFile, [LineEnding, Filename]), mtError, [mbCancel]); end; exit; end; Result:=true; end; function IsFileInIDESrcDir(Filename: string): boolean; var LazDir: String; begin Filename:=TrimFilename(Filename); if not FilenameIsAbsolute(Filename) then exit(false); LazDir:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory); Result:=FileIsInPath(Filename,LazDir+'ide') or FileIsInPath(Filename,LazDir+'debugger') or FileIsInPath(Filename,LazDir+'packager') or FileIsInPath(Filename,LazDir+'converter') or FileIsInPath(Filename,LazDir+'designer'); end; procedure InitStandardIDEQuickFixItems; begin IDEMsgQuickFixes:=TIDEMsgQuickFixItems.Create; //RegisterIDEMsgQuickFix('Parameter xxx not used','Quick fix: Add dummy line', // 'Parameter "[a-z_0-9]+" not used',nil,@QuickFixParameterNotUsed); RegisterIDEMsgQuickFix('Unit xxx not used in yyy', lisQuickFixRemoveUnit, 'Unit "[a-z_0-9]+" not used in [a-z_0-9]+',[imqfoMenuItem], nil,@QuickFixUnitNotUsed); RegisterIDEMsgQuickFix(TQuickFixUnitNotFoundPosition.Create); RegisterIDEMsgQuickFix(TQuickFixUnitNotFound_Remove.Create); RegisterIDEMsgQuickFix(TQuickFixJumpToLinkerUndefinedReference.Create); RegisterIDEMsgQuickFix(TQuickFixErrorWhileCompilingResources_Hint.Create); RegisterIDEMsgQuickFix(TQuickFixClassWithAbstractMethods.Create); RegisterIDEMsgQuickFix(TQuickFixIdentifierNotFoundAddLocal.Create); RegisterIDEMsgQuickFix(TQuickFixLocalVariableNotUsed_Remove.Create); RegisterIDEMsgQuickFix(TQuickFixHint_Hide.Create); end; procedure FreeStandardIDEQuickFixItems; begin FreeThenNil(IDEMsgQuickFixes); end; { TQuickFixErrorWhileCompilingResources_Hint } constructor TQuickFixErrorWhileCompilingResources_Hint.Create; begin Name:='Improve error message: Error while compiling resources'; Steps:=[imqfoImproveMessage]; end; function TQuickFixErrorWhileCompilingResources_Hint.IsApplicable( Line: TIDEMessageLine): boolean; const SearchStr = 'Error while compiling resources'; var Msg: String; p: integer; begin Result:=false; if (Line.Parts=nil) then exit; Msg:=Line.Msg; p:=System.Pos(SearchStr,Msg); if p<1 then exit; Result:=true; end; procedure TQuickFixErrorWhileCompilingResources_Hint.Execute( const Msg: TIDEMessageLine; Step: TIMQuickFixStep); var s: String; begin if Step<>imqfoImproveMessage then exit; s:=Msg.Msg; if s[length(s)]<>'.' then s+='.'; s+=' Compile with -vd for more details. Check for duplicates.'; Msg.Msg:=s; end; { TQuickFixUnitNotFoundPosition } constructor TQuickFixUnitNotFoundPosition.Create; begin Name:='Improve error position of: Fatal: Can''t find unit xxx'; Steps:=[imqfoImproveMessage]; end; function TQuickFixUnitNotFoundPosition.IsApplicable(Line: TIDEMessageLine ): boolean; var Msg: String; begin if Line.Parts=nil then exit(false); Msg:=Line.Msg; Result:=(System.Pos(') Fatal: Can''t find unit ',Msg)>0) or (System.Pos(') Fatal: Can not find unit ',Msg)>0) or (System.Pos('Fatal: Can''t find unit ',Msg)=1) or (System.Pos('Fatal: Can not find unit ',Msg)=1); end; procedure TQuickFixUnitNotFoundPosition.Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); // for example: // Fatal: Can't find unit Unit12 used by testunit1 // /home/user/laz/main.pp(1,1) Fatal: Can't find unit lazreport used by lazarus procedure FixSourcePos(CodeBuf: TCodeBuffer; MissingUnitname: string; var NewFilename: string; var Dir: string); var Caret: TCodeXYPosition; Tool: TCodeTool; InPos: Integer; NamePos: Integer; begin {$IFDEF VerboseQuickFixUnitNotFoundPosition} debugln(['TQuickFixUnitNotFoundPosition.Execute File=',CodeBuf.Filename]); {$ENDIF} LazarusIDE.SaveSourceEditorChangesToCodeCache(nil); if not CodeToolBoss.FindUnitInAllUsesSections(CodeBuf,MissingUnitname,NamePos,InPos) then begin DebugLn('QuickFixUnitNotFoundPosition failed due to syntax errors or '+MissingUnitname+' is not used in '+CodeBuf.Filename); //LazarusIDE.DoJumpToCodeToolBossError; exit; end; Tool:=CodeToolBoss.CurCodeTool; if Tool=nil then exit; if not Tool.CleanPosToCaret(NamePos,Caret) then exit; if (Caret.X>0) and (Caret.Y>0) then begin //DebugLn('QuickFixUnitNotFoundPosition Line=',dbgs(Line),' Col=',dbgs(Col)); NewFilename:=Caret.Code.Filename; if (Msg.Directory<>'') and (FilenameIsAbsolute(Msg.Directory)) then NewFilename:=CreateRelativePath(NewFilename,Msg.Directory); Msg.SetSourcePosition(NewFilename,Caret.Y,Caret.X); Dir:=AppendPathDelim(TrimFilename(Msg.Directory)); end; end; procedure FindPPUInInstalledPkgs(MissingUnitname: string; var PPUFilename, PkgName: string); var i: Integer; Pkg: TIDEPackage; DirCache: TCTDirectoryCache; UnitOutDir: String; begin // search ppu in installed packages for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin Pkg:=PackageEditingInterface.GetPackages(i); if Pkg.AutoInstall=pitNope then continue; UnitOutDir:=Pkg.LazCompilerOptions.GetUnitOutputDirectory(false); //debugln(['TQuickFixUnitNotFoundPosition.Execute ',Pkg.Name,' UnitOutDir=',UnitOutDir]); if FilenameIsAbsolute(UnitOutDir) then begin DirCache:=CodeToolBoss.DirectoryCachePool.GetCache(UnitOutDir,true,false); PPUFilename:=DirCache.FindFile(MissingUnitname+'.ppu',ctsfcLoUpCase); //debugln(['TQuickFixUnitNotFoundPosition.Execute ShortPPU=',PPUFilename]); if PPUFilename<>'' then begin PkgName:=Pkg.Name; PPUFilename:=AppendPathDelim(DirCache.Directory)+PPUFilename; break; end; end; end; end; procedure FindPackage(MissingUnitname: string; var PkgName: string; OnlyInstalled: boolean); var i: Integer; Pkg: TIDEPackage; j: Integer; PkgFile: TLazPackageFile; begin if PkgName='' then begin // search unit in installed packages for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin Pkg:=PackageEditingInterface.GetPackages(i); if OnlyInstalled and (Pkg.AutoInstall=pitNope) then continue; if CompareTextCT(Pkg.Name,MissingUnitname)=0 then begin PkgName:=Pkg.Name; break; end; for j:=0 to Pkg.FileCount-1 do begin PkgFile:=Pkg.Files[j]; if not FilenameIsPascalUnit(PkgFile.Filename) then continue; if CompareTextCT(ExtractFileNameOnly(PkgFile.Filename),MissingUnitname)<>0 then continue; PkgName:=Pkg.Name; break; end; end; end; end; var CodeBuf: TCodeBuffer; MissingUnitname: String; UsedByUnit: String; NewFilename: String; // referencing unit Dir: String; PPUFilename: String; s: String; Filename: string; Line: integer; Col: integer; PkgName: String; OnlyInstalled: Boolean; Owners: TFPList; UsedByOwner: TObject; begin if Step<>imqfoImproveMessage then exit; {$IFDEF VerboseQuickFixUnitNotFoundPosition} DebugLn('QuickFixUnitNotFoundPosition START'); {$ENDIF} if not REMatches(Msg.Msg,'Can(''t| not) find unit ([a-z_.0-9]+) used by ','I') then begin {$IFDEF VerboseQuickFixUnitNotFoundPosition} DebugLn('QuickFixUnitNotFoundPosition invalid message ',Msg.Msg); {$ENDIF} exit; end; Dir:=AppendPathDelim(TrimFilename(Msg.Directory)); if Dir='' then exit; Msg.GetSourcePosition(Filename,Line,Col); Filename:=TrimFilename(Filename); MissingUnitname:=REVar(2); UsedByUnit:=''; if REMatches(Msg.Msg,'Can(''t| not) find unit ([a-z_.0-9]+) used by ([a-z_.0-9]+)','I') then begin UsedByUnit:=REVar(3); {$IFDEF VerboseQuickFixUnitNotFoundPosition} debugln(['TQuickFixUnitNotFoundPosition.Execute Missing="',MissingUnitname,'" used by "',UsedByUnit,'"']); {$ENDIF} if (CompareFilenames(ExtractFileName(Filename),'staticpackages.inc')=0) and IsFileInIDESrcDir(Dir+'test') then begin // common case: when building the IDE a package unit is missing // staticpackages.inc(1,1) Fatal: Can't find unit sqldblaz used by Lazarus // change to lazarus.pp(1,1) NewFilename:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory)+'ide'+PathDelim; Msg.SetSourcePosition(NewFilename,1,1); Msg.Msg:='lazarus.pp(1,1) Fatal: Can''t find a valid '+MissingUnitname+'.ppu'; Dir:=AppendPathDelim(TrimFilename(Msg.Directory)); end else if SysUtils.CompareText(ExtractFileNameOnly(Filename),UsedByUnit)<>0 then begin // the message belongs to another unit NewFilename:=''; if FilenameIsAbsolute(Dir) then begin // For example: /path/laz/main.pp(1,1) Fatal: Can't find unit lazreport used by lazarus // => search source lazarus in directory NewFilename:=CodeToolBoss.DirectoryCachePool.FindUnitInDirectory( Dir,UsedByUnit,true); end; if NewFilename='' then begin NewFilename:=LazarusIDE.FindUnitFile(UsedByUnit); if NewFilename='' then begin {$IFDEF VerboseQuickFixUnitNotFoundPosition} DebugLn('QuickFixUnitNotFoundPosition unit not found: ',UsedByUnit); {$ENDIF} //ShowError('QuickFix: UnitNotFoundPosition unit not found: '+UsedByUnit); end; end; end; end; // load source CodeBuf:=nil; if (NewFilename='') and (FilenameIsAbsolute(Filename)) then NewFilename:=Filename; if NewFilename<>'' then begin CodeBuf:=CodeToolBoss.LoadFile(NewFilename,false,false); if CodeBuf=nil then begin {$IFDEF VerboseQuickFixUnitNotFoundPosition} DebugLn('QuickFixUnitNotFoundPosition unable to load unit: ',NewFilename); {$ENDIF} //ShowError('QuickFix: UnitNotFoundPosition unable to load unit: '+NewFilename); end; end else begin {$IFDEF VerboseQuickFixUnitNotFoundPosition} DebugLn('QuickFixUnitNotFoundPosition unable to locate UsedByUnit: ',UsedByUnit); {$ENDIF} end; // fix line and column Owners:=nil; UsedByOwner:=nil; try if CodeBuf<>nil then begin FixSourcePos(CodeBuf,MissingUnitname,NewFilename,Dir); Owners:=PackageEditingInterface.GetOwnersOfUnit(NewFilename); if (Owners<>nil) and (Owners.Count>0) then UsedByOwner:=TObject(Owners[0]); end; // if the ppu is there then improve the message {$IFDEF VerboseQuickFixUnitNotFoundPosition} debugln(['TQuickFixUnitNotFoundPosition.Execute Dir=',Dir]); {$ENDIF} if FilenameIsAbsolute(Dir) then begin PPUFilename:=CodeToolBoss.DirectoryCachePool.FindCompiledUnitInCompletePath( Dir,MissingUnitname); {$IFDEF VerboseQuickFixUnitNotFoundPosition} debugln(['TQuickFixUnitNotFoundPosition.Execute PPUFilename=',PPUFilename,' IsFileInIDESrcDir=',IsFileInIDESrcDir(Dir+'test')]); {$ENDIF} PkgName:=''; OnlyInstalled:=IsFileInIDESrcDir(Dir+'test'); if OnlyInstalled and (PPUFilename='') then begin FindPPUInInstalledPkgs(MissingUnitname,PPUFilename,PkgName); end; FindPackage(MissingUnitname,PkgName,OnlyInstalled); if PPUFilename<>'' then begin // there is a ppu file in the unit path if PPUFilename<>'' then begin // there is a ppu file, but the compiler didn't like it // => change message s:='Can not find '+MissingUnitname; if UsedByUnit<>'' then s+=' used by '+UsedByUnit; s+=', ppu='+CreateRelativePath(PPUFilename,Dir); if PkgName<>'' then s+=', package '+PkgName; end else if PkgName<>'' then begin // ppu is missing, but the package is known // => change message s:='Can''t find ppu of unit '+MissingUnitname; if UsedByUnit<>'' then s+=' used by '+UsedByUnit; s+='. Maybe package '+PkgName+' needs a clean rebuild.'; end; end else begin // there is no ppu file in the unit path s:='Can not find unit '+MissingUnitname; if UsedByUnit<>'' then s+=' used by '+UsedByUnit; if (UsedByOwner is TIDEPackage) and (CompareTextCT(TIDEPackage(UsedByOwner).Name,PkgName)=0) then begin // two units of a package can not find each other s+='. Check search path package '+TIDEPackage(UsedByOwner).Name+', try a clean rebuild, check implementation uses sections.'; end else begin if PkgName<>'' then s+='. Check if package '+PkgName+' is in the dependencies'; if UsedByOwner is TLazProject then s+=' of the project inspector' else if UsedByOwner is TIDEPackage then s+=' of package '+TIDEPackage(UsedByOwner).Name; end; s+='.'; end; Msg.GetSourcePosition(Filename,Line,Col); Msg.Msg:=CreateRelativePath(Filename,Msg.Directory) +'('+IntToStr(Line)+','+IntToStr(Col)+') Fatal: '+s; {$IFDEF VerboseQuickFixUnitNotFoundPosition} debugln(['TQuickFixUnitNotFoundPosition.Execute Msg.Msg="',Msg.Msg,'"']); {$ENDIF} end; finally Owners.Free; end; end; { TQuickFixJumpToLinkerUndefinedReference } constructor TQuickFixJumpToLinkerUndefinedReference.Create; var re: String; begin Name:='Linker: undefined reference to'; Steps:=[imqfoJump]; re:='((.*:[0-9]+)?: .* `(.*)'')'; // e.g. some.inc:37: undefined reference to `DoesNotExist' re+='|((.*)\(\.text.*?\): .* `([A-Z0-9_$]+)'':)'; // e.g. unit1.o(.text+0x1a): In function `SubProc': re+='|((.*)\.o: .* `([A-Z0-9_$]+)'':)'; // e.g. unit1.o: In function `SubProc': re:='^'+re+'$'; RegExpression:=re; end; procedure TQuickFixJumpToLinkerUndefinedReference.Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); { Examples: /usr/lib/fpc/2.1.1/units/i386-linux/gtk2/gtk2.o(.text+0xbba1): In function `GTK2_GTK_TYPE_CELL_RENDERER_COMBO$$LONGWORD': : undefined reference to `gtk_cell_renderer_combo_get_type' unit1.o(.text+0x1a): In function `SubProc': some.inc:37: undefined reference to `DoesNotExist' unit1.o(.text+0x3a):some.inc:48: undefined reference to `DoesNotExist' unit1.o: In function `SubProc': some.inc:37: undefined reference to `DoesNotExist' } const REFileIdentifier = '^(.*\.o)(\(\.text.*?\))?: .* `([a-zA-Z0-9_$]+)'':$'; REOptFileLineIdentifier = '^(.*:[0-9]+)?: .* `(.*)''$'; REFileLineIdentifier = '^(.*):([0-9]+): .* `(.*)''$'; procedure JumpTo(Line1, Line2: TIDEMessageLine); var Identifier: String; Filename: String; MangledFunction: String; CurProject: TLazProject; CodeBuf: TCodeBuffer; NewCode: TCodeBuffer; NewX, NewY, NewTopLine: integer; AnUnitName: String; SourceFilename: String; SourceLine: Integer; begin DebugLn('TQuickFixLinkerUndefinedReference.JumpTo START'); Debugln(' Line1=',Line1.Msg); if Line2<>nil then Debugln(' Line2=',Line2.Msg); Filename:=''; MangledFunction:=''; Identifier:=''; SourceFilename:=''; SourceLine:=0; if REMatches(Line1.Msg,REFileIdentifier) then begin // example: unit1.o(.text+0x1a): In function `SubProc': Filename:=REVar(1); MangledFunction:=REVar(3); if (Line2<>nil) and REMatches(Line2.Msg,'^: .* `(.*)''$') then begin // example: ": undefined reference to `gtk_cell_renderer_combo_get_type'" Identifier:=REVar(1); end else if (Line2<>nil) and REMatches(Line2.Msg,REFileLineIdentifier) then begin // example: unit1.pas:37: undefined reference to `DoesNotExist' SourceFilename:=REVar(1); SourceLine:=StrToIntDef(REVar(2),0); Identifier:=REVar(3); end else begin DebugLn('TQuickFixLinkerUndefinedReference.JumpTo Line2 does not match: "',Line2.Msg,'"'); exit; end; end else if REMatches(Line1.Msg,'^(.*)\(\.text.*?\):(.*):([0-9]*): .* `([a-zA-Z0-9_$]+)'':$') then begin // example: unit1.o(.text+0x3a):unit1.pas:48: undefined reference to `DoesNotExist' Filename:=REVar(1); SourceFilename:=REVar(2); SourceLine:=StrToIntDef(REVar(3),0); Identifier:=REVar(4); end else if REMatches(Line1.Msg,REFileLineIdentifier) then begin // example: unit1.pas:48: undefined reference to `DoesNotExist' Filename:=REVar(1); SourceFilename:=Filename; SourceLine:=StrToIntDef(REVar(2),0); Identifier:=REVar(3); end else begin DebugLn('JumpTo Line1 does not match: "',Line1.Msg,'"'); exit; end; Filename:=TrimFilename(Filename); // for example uni1.o SourceFilename:=TrimFilename(SourceFilename); // for example wsimagelist.pp // Beware: SourceFilename might be an include file // Linker errors are emitted when compiling the project // => search the unit of the .o file DebugLn(['TQuickFixLinkerUndefinedReference.JumpTo Filename="',Filename,'" MangledFunction="',MangledFunction,'" Identifier="',Identifier,'" SourceFilename="',SourceFilename,'" SourceLine=',SourceLine]); CurProject:=LazarusIDE.ActiveProject; if CurProject=nil then begin ShowError('QuickFix: LinkerUndefinedReference no project'); exit; end; if (CurProject.MainFile=nil) then begin ShowError('QuickFix: LinkerUndefinedReference no main file in project'); exit; end; CodeBuf:=CodeToolBoss.LoadFile(CurProject.MainFile.Filename,true,false); if (CodeBuf=nil) then begin ShowError('QuickFix: LinkerUndefinedReference project main file has no source'); exit; end; AnUnitName:=ExtractFilenameOnly(Filename); CodeBuf:=CodeToolBoss.FindUnitSource(CodeBuf,AnUnitName,''); if (CodeBuf=nil) then begin ShowError('QuickFix: LinkerUndefinedReference unit not found: '+AnUnitName); exit; end; if not CodeToolBoss.JumpToLinkerIdentifier(CodeBuf, SourceFilename,SourceLine,MangledFunction,Identifier, NewCode,NewX,NewY,NewTopLine) then begin if CodeToolBoss.ErrorCode<>nil then LazarusIDE.DoJumpToCodeToolBossError else ShowError('QuickFix: LinkerUndefinedReference function not found: '+MangledFunction+' Identifier='+Identifier); exit; end; LazarusIDE.DoOpenFileAndJumpToPos(NewCode.Filename,Point(NewX,NewY), NewTopLine,-1,-1,[]); end; var MsgBefore: TIDEMessageLine; MsgAfter: TIDEMessageLine; begin inherited Execute(Msg, Step); if Step=imqfoJump then begin DebugLn(['TQuickFixLinkerUndefinedReference.Execute ',Msg.Msg]); MsgBefore:=nil; if Msg.Position>0 then MsgBefore:=IDEMessagesWindow[Msg.Position-1]; MsgAfter:=nil; if Msg.Position+1 unit1.o(.text+0x1a): In function `SubProc': // some.inc:37: undefined reference to `DoesNotExist' JumpTo(Msg,MsgAfter); end else if (MsgBefore<>nil) and REMatches(MsgBefore.Msg,REFileIdentifier) and REMatches(Msg.Msg,REOptFileLineIdentifier) then begin // example: // unit1.o(.text+0x1a): In function `SubProc': // -> some.inc:37: undefined reference to `DoesNotExist' JumpTo(MsgBefore,Msg) end else if REMatches(Msg.Msg,REFileLineIdentifier) then begin // example: // some.inc:37: undefined reference to `DoesNotExist' JumpTo(Msg,nil); end; end; end; { TQuickFixClassWithAbstractMethods } constructor TQuickFixClassWithAbstractMethods.Create; begin Name:='Show abstract methods'; Caption:=srkmecShowAbstractMethods; Steps:=[imqfoMenuItem]; end; function TQuickFixClassWithAbstractMethods.IsApplicable(Line: TIDEMessageLine ): boolean; begin Result:=(Line.Parts<>nil) and (System.Pos(') Warning: Constructing a class "',Line.Msg)>0) and (System.Pos('" with abstract methods',Line.Msg)>0); end; procedure TQuickFixClassWithAbstractMethods.Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); var CodeBuf: TCodeBuffer; Caret: TPoint; Filename: string; NewCode: TCodeBuffer; NewX,NewY,NewTopLine: Integer; Tool: TCodeTool; CurClassName: String; begin if Step=imqfoMenuItem then begin DebugLn(['TQuickFixClassWithAbstractMethods.Execute ']); // get source position // (FPC reports position right after the constructor call // for example right after TStrings.Create) if not GetMsgLineFile(Msg,CodeBuf,false) then exit; Msg.GetSourcePosition(Filename,Caret.Y,Caret.X); if not LazarusIDE.BeginCodeTools then begin DebugLn(['TQuickFixClassWithAbstractMethods.Execute failed because IDE busy']); exit; end; // get class name if not REMatches(Msg.Msg,'Warning: Constructing a class "([a-z_0-9]+)"','I') then begin DebugLn('QuickFixClassWithAbstractMethods invalid message ',Msg.Msg); ShowError('QuickFix: ClassWithAbstractMethods invalid message '+Msg.Msg); exit; end; CurClassName:=REVar(1); //DebugLn(['TQuickFixClassWithAbstractMethods.Execute Class=',CurClassName]); // find the class // build the tree CodeToolBoss.Explore(CodeBuf,Tool,false,true); if Tool=nil then begin DebugLn(['TQuickFixClassWithAbstractMethods.Execute no tool for ',CodeBuf.Filename]); ShowError('QuickFix: ClassWithAbstractMethods no tool for '+CodeBuf.Filename); exit; end; if not CodeToolBoss.FindDeclarationOfIdentifier(CodeBuf,Caret.X,Caret.Y, @CurClassName[1],NewCode,NewX,NewY,NewTopLine) then begin if CodeToolBoss.ErrorMessage<>'' then begin LazarusIDE.DoJumpToCodeToolBossError end else begin IDEMessageDialog('Class not found', 'Class '+CurClassName+' not found at ' +CodeBuf.Filename+'('+IntToStr(Caret.Y)+','+IntToStr(Caret.X)+')', mtError,[mbCancel]); end; exit; end; //DebugLn(['TQuickFixClassWithAbstractMethods.Execute Declaration at ',NewCode.Filename,' ',NewX,',',NewY]); if LazarusIDE.DoOpenFileAndJumpToPos(NewCode.Filename, Point(NewX,NewY),NewTopLine,-1,-1,[])<>mrOk then begin DebugLn(['TQuickFixClassWithAbstractMethods.Execute failed opening ',NewCode.Filename]); ShowError('QuickFix: ClassWithAbstractMethods failed opening '+NewCode.Filename); exit; end; ShowAbstractMethodsDialog; end; end; { TQuickFixIdentifierNotFoundAddLocal } constructor TQuickFixIdentifierNotFoundAddLocal.Create; begin Name:='Create local variable: Error: Identifier not found "identifier"'; Caption:=lisQuickFixCreateLocalVariable; Steps:=[imqfoMenuItem]; end; function TQuickFixIdentifierNotFoundAddLocal.IsApplicable(Line: TIDEMessageLine ): boolean; // FPC gives position of end of identifier const SearchStr = ') Error: Identifier not found "'; var Filename: string; Caret: TPoint; Code: TCodeBuffer; Tool: TCodeTool; CleanPos: integer; p: LongInt; Msg: String; Identifier: String; Node: TCodeTreeNode; begin Result:=false; if (Line.Parts=nil) then exit; Msg:=Line.Msg; p:=System.Pos(SearchStr,Msg); if p<1 then exit; inc(p,length(SearchStr)); Line.GetSourcePosition(Filename,Caret.Y,Caret.X); if (Filename='') or (Caret.X<1) or (Caret.Y<1) then exit; Code:=CodeToolBoss.LoadFile(Filename,true,false); if Code=nil then exit; if not CodeToolBoss.Explore(Code,Tool,false) then exit; if Tool.CaretToCleanPos(CodeXYPosition(Caret.X,Caret.Y,Code),CleanPos)<>0 then exit; Node:=Tool.FindDeepestNodeAtPos(CleanPos,false); if Node=nil then exit; if not (Node.Desc in AllPascalStatements) then exit; Tool.MoveCursorToCleanPos(CleanPos); Tool.ReadPriorAtom; Identifier:=GetIdentifier(@Msg[p]); if not Tool.AtomIs(Identifier) then exit; Tool.ReadPriorAtom; if (Tool.CurPos.Flag in [cafPoint,cafRoundBracketClose,cafEdgedBracketClose, cafEnd]) then exit; Result:=true; end; procedure TQuickFixIdentifierNotFoundAddLocal.Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); var Identifier: String; CodeBuf: TCodeBuffer; Filename: string; Caret: TPoint; NewCode: TCodeBuffer; NewX, NewY, NewTopLine: integer; Tool: TCodeTool; CleanPos: integer; CodeXY: TCodeXYPosition; begin if Step=imqfoMenuItem then begin DebugLn(['TQuickFixIdentifierNotFoundAddLocal.Execute Dir=',Msg.Directory,' Msg=',Msg.Msg]); // get source position // (FPC reports position right after the unknown identifier // for example right after FilenameIsAbsolute) if not GetMsgLineFile(Msg,CodeBuf,false) then exit; Msg.GetSourcePosition(Filename,Caret.Y,Caret.X); if not LazarusIDE.BeginCodeTools then begin DebugLn(['TQuickFixIdentifierNotFoundAddLocal.Execute failed because IDE busy']); exit; end; if (Filename='') or (Caret.X<1) or (Caret.Y<1) then exit; CodeBuf:=CodeToolBoss.LoadFile(Filename,true,false); if CodeBuf=nil then exit; if not CodeToolBoss.Explore(CodeBuf,Tool,false) then exit; if Tool.CaretToCleanPos(CodeXYPosition(Caret.X,Caret.Y,CodeBuf),CleanPos)<>0 then exit; if CleanPos>Tool.SrcLen then CleanPos:=Tool.SrcLen; if (CleanPos>1) and (not IsIdentChar[Tool.Src[CleanPos]]) then dec(CleanPos); Tool.MoveCursorToCleanPos(CleanPos); Tool.ReadPriorAtom; CleanPos:=Tool.CurPos.StartPos; if CleanPos<1 then exit; if not Tool.CleanPosToCaret(CleanPos,CodeXY) then exit; // get identifier if not REMatches(Msg.Msg,'Error: Identifier not found "([a-z_0-9]+)"','I') then begin DebugLn('TQuickFixIdentifierNotFoundAddLocal invalid message ',Msg.Msg); ShowError('QuickFix: IdentifierNotFoundAddLocal invalid message '+Msg.Msg); exit; end; Identifier:=REVar(1); DebugLn(['TQuickFixIdentifierNotFoundAddLocal.Execute Identifier=',Identifier,' ',Dbgs(CodeXY)]); if not IsIdentifierInCode(CodeXY.Code,CodeXY.X,CodeXY.Y,Identifier, Identifier+' not found in '+CodeBuf.Filename +' at line '+IntToStr(Caret.Y)+', column '+IntToStr(Caret.X)+'.' +LineEnding+'Maybe the message is outdated.') then exit; if not CodeToolBoss.CreateVariableForIdentifier(CodeXY.Code,CodeXY.X,CodeXY.Y,-1, NewCode,NewX,NewY,NewTopLine) then begin LazarusIDE.DoJumpToCodeToolBossError; exit; end; // message fixed -> clean Msg.Msg:=''; end; end; { TQuickFixUnitNotFound_Remove } constructor TQuickFixUnitNotFound_Remove.Create; begin Name:='Search unit: Error: Can''t find unit Name'; Caption:=lisRemoveUnitFromUsesSection; Steps:=[imqfoMenuItem]; end; function TQuickFixUnitNotFound_Remove.IsApplicable(Line: TIDEMessageLine ): boolean; var Msg: String; begin if Line.Parts=nil then exit(false); Msg:=Line.Msg; Result:=(System.Pos(') Fatal: Can''t find unit ',Msg)>0) or (System.Pos(') Fatal: Can not find unit ',Msg)>0); end; procedure TQuickFixUnitNotFound_Remove.Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); var CodeBuf: TCodeBuffer; Filename: string; Caret: TPoint; AnUnitName: String; begin if Step=imqfoMenuItem then begin DebugLn(['TQuickFixUnitNotFound_Remove.Execute ']); // get source position if not GetMsgLineFile(Msg,CodeBuf,false) then exit; Msg.GetSourcePosition(Filename,Caret.Y,Caret.X); if not LazarusIDE.BeginCodeTools then begin DebugLn(['TQuickFixUnitNotFound_Remove.Execute failed because IDE busy']); exit; end; // get unitname if not REMatches(Msg.Msg,'Fatal: Can(''t| not) find unit ([a-z_0-9]+) ','I') then begin DebugLn('TQuickFixUnitNotFound_Remove invalid message ',Msg.Msg); ShowError('QuickFix: UnitNotFound_Remove invalid message '+Msg.Msg); exit; end; AnUnitName:=REVar(2); DebugLn(['TQuickFixUnitNotFound_Remove.Execute Unit=',AnUnitName]); if (AnUnitName='') or (not IsValidIdent(AnUnitName)) then begin DebugLn(['TQuickFixUnitNotFound_Remove.Execute not an identifier "',dbgstr(AnUnitName),'"']); ShowError('QuickFix: UnitNotFound_Remove not an identifier "'+dbgstr(AnUnitName)+'"'); exit; end; if not CodeToolBoss.RemoveUnitFromAllUsesSections(CodeBuf,AnUnitName) then begin DebugLn(['TQuickFixUnitNotFound_Remove.Execute RemoveUnitFromAllUsesSections failed']); LazarusIDE.DoJumpToCodeToolBossError; exit; end; // message fixed -> clean Msg.Msg:=''; end; end; { TQuickFixLocalVariableNotUsed_Remove } constructor TQuickFixLocalVariableNotUsed_Remove.Create; begin Name:='Remove local variable: Note: Local variable "x" not used'; Caption:=lisRemoveLocalVariable2; Steps:=[imqfoMenuItem]; end; function TQuickFixLocalVariableNotUsed_Remove.IsApplicable(Line: TIDEMessageLine ): boolean; const SearchStr1 = ') Note: Local variable "'; SearchStr2 = '" not used'; var Msg: String; StartPos: integer; p: LongInt; Variable: String; begin Result:=false; if (Line.Parts=nil) then exit; Msg:=Line.Msg; //DebugLn(['TQuickFixLocalVariableNotUsed_Remove.IsApplicable Msg="',Msg,'" ',System.Pos(SearchStr1,Msg),' ',System.Pos(SearchStr2,Msg)]); StartPos:=System.Pos(SearchStr1,Msg); if StartPos<1 then exit; inc(StartPos,length(SearchStr1)); p:=StartPos; while (p<=length(Msg)) and (Msg[p]<>'"') do inc(p); if copy(Msg,p,length(SearchStr2))<>SearchStr2 then exit; Variable:=copy(Msg,StartPos,p-StartPos); if (Variable='') or not IsValidIdent(Variable) then exit; Caption:=Format(lisRemoveLocalVariable, [Variable]); Result:=true; end; procedure TQuickFixLocalVariableNotUsed_Remove.Execute( const Msg: TIDEMessageLine; Step: TIMQuickFixStep); var CodeBuf: TCodeBuffer; Filename: string; Caret: TPoint; Variable: String; begin if Step=imqfoMenuItem then begin DebugLn(['TQuickFixLocalVariableNotUsed_Remove.Execute ']); // get source position if not GetMsgLineFile(Msg,CodeBuf,false) then exit; Msg.GetSourcePosition(Filename,Caret.Y,Caret.X); if not LazarusIDE.BeginCodeTools then begin DebugLn(['TQuickFixLocalVariableNotUsed_Remove.Execute failed because IDE busy']); exit; end; // get variables name if not REMatches(Msg.Msg,'Note: Local variable "([a-z_0-9]+)" not used','I') then begin DebugLn('TQuickFixLocalVariableNotUsed_Remove invalid message ',Msg.Msg); ShowError('QuickFix: LocalVariableNotUsed_Remove invalid message '+Msg.Msg); exit; end; Variable:=REVar(1); //DebugLn(['TQuickFixLocalVariableNotUsed_Remove.Execute Variable=',Variable]); // check if the variable is at that position if not IsIdentifierInCode(CodeBuf,Caret.X,Caret.Y,Variable, Variable+' not found in '+CodeBuf.Filename +' at line '+IntToStr(Caret.Y)+', column '+IntToStr(Caret.X)+'.'+LineEnding +'Maybe the message is outdated.') then exit; if not CodeToolBoss.RemoveIdentifierDefinition(CodeBuf,Caret.X,Caret.Y) then begin DebugLn(['TQuickFixLocalVariableNotUsed_Remove.Execute remove failed']); LazarusIDE.DoJumpToCodeToolBossError; exit; end; // message fixed -> clean Msg.Msg:=''; end; end; { TQuickFixHint_Hide } constructor TQuickFixHint_Hide.Create; begin Name:='Hide hint, note or warning'; Caption:=lisHideMessageViaDirective; Steps:=[imqfoMenuItem]; end; function TQuickFixHint_Hide.IsApplicable(Line: TIDEMessageLine): boolean; var MsgType: string; Filename: string; LineNumber, Column: integer; begin Result:=false; if (Line.Parts=nil) then exit; MsgType:=Line.Parts.Values['Type']; if (MsgType<>'Hint') and (MsgType<>'Note') and (MsgType<>'Warning') then exit; Line.GetSourcePosition(Filename,LineNumber,Column); Result:=FilenameIsAbsolute(Filename) and (LineNumber>=1) and (Column>=1); end; procedure TQuickFixHint_Hide.Execute(const Msg: TIDEMessageLine; Step: TIMQuickFixStep); var CodeBuf: TCodeBuffer; Filename: string; Caret: TPoint; p: integer; begin if Step=imqfoMenuItem then begin DebugLn(['TQuickFixHint_Hide.Execute ']); // get source position if not GetMsgLineFile(Msg,CodeBuf,false) then exit; Msg.GetSourcePosition(Filename,Caret.Y,Caret.X); if not LazarusIDE.BeginCodeTools then begin DebugLn(['TQuickFixHint_Hide.Execute failed because IDE busy']); exit; end; CodeBuf.LineColToPosition(Caret.Y,Caret.X,p); if p<1 then begin DebugLn(['TQuickFixHint_Hide.Execute failed because invalid line, column']); IDEMessageDialog(lisCCOErrorCaption, Format(lisInvalidLineColumnInMessage, [LineEnding, Msg.Msg]), mtError, [mbCancel]); exit; end; CodeBuf.Insert(p,'{%H-}'); end; end; end.