From eaa1ff2fba95c2db849892dd36580ba0f726e049 Mon Sep 17 00:00:00 2001 From: mattias Date: Tue, 18 May 2010 14:45:02 +0000 Subject: [PATCH] IDE: added quickfix for note local variable not used, remove git-svn-id: trunk@25498 - --- components/codetools/basiccodetools.pas | 38 +++-- components/codetools/codetoolmanager.pas | 24 +++- components/codetools/customcodetool.pas | 7 +- components/codetools/stdcodetools.pas | 107 ++++++++++++++ ide/lazarusidestrconsts.pas | 2 + ide/msgquickfixes.pas | 170 ++++++++++++++++++++--- 6 files changed, 316 insertions(+), 32 deletions(-) diff --git a/components/codetools/basiccodetools.pas b/components/codetools/basiccodetools.pas index e6d8940ca7..81553395a5 100644 --- a/components/codetools/basiccodetools.pas +++ b/components/codetools/basiccodetools.pas @@ -66,8 +66,6 @@ function ExtractCommentContent(const ASource: string; CommentStart: integer; function FindMainUnitHint(const ASource: string; out Filename: string): boolean; // indent -procedure GetLineStartEndAtPosition(const Source:string; Position:integer; - var LineStart,LineEnd:integer); function GetLineIndent(const Source: string; Position: integer): integer; function GetLineIndentWithTabs(const Source: string; Position: integer; TabWidth: integer): integer; @@ -93,6 +91,9 @@ function IsValidIdentPair(const NamePair: string; out First, Second: string): boolean; // line/code ends +procedure GetLineStartEndAtPosition(const Source:string; Position:integer; + var LineStart,LineEnd:integer); +function GetLineStartPosition(const Source:string; Position:integer): integer; function LineEndCount(const Txt: string): integer; inline; function LineEndCount(const Txt: string; out LengthOfLastLine:integer): integer; inline; function LineEndCount(const Txt: string; StartPos, EndPos: integer; @@ -108,7 +109,8 @@ function FindLineEndOrCodeInFrontOfPosition(const Source: string; StopAtDirectives: boolean = true; SkipSemicolonComma: boolean = true): integer; function FindLineEndOrCodeAfterPosition(const Source: string; Position, MaxPosition: integer; NestedComments: boolean; - StopAtDirectives: boolean = true; SkipEmptyLines: boolean = false): integer; + StopAtDirectives: boolean = true; SkipEmptyLines: boolean = false; + IncludeLineEnd: boolean = false): integer; function FindFirstLineEndInFrontOfInCode(const Source: string; Position, MinPosition: integer; NestedComments: boolean): integer; function FindFirstLineEndAfterInCode(const Source: string; @@ -1332,9 +1334,8 @@ procedure GetLineStartEndAtPosition(const Source:string; Position:integer; var LineStart,LineEnd:integer); begin LineStart:=Position; - while (LineStart>0) and (not (Source[LineStart] in [#10,#13])) do + while (LineStart>1) and (not (Source[LineStart-1] in [#10,#13])) do dec(LineStart); - inc(LineStart); LineEnd:=Position; while (LineEnd<=length(Source)) and (not (Source[LineEnd] in [#10,#13])) do inc(LineEnd); @@ -2248,10 +2249,10 @@ begin if (Result>length(Source)) then Result:=length(Source); if Result=0 then exit; // search beginning of line - while (Result>1) and (not (Source[Result] in [#10,#13])) do + while (Result>1) and (not (Source[Result-1] in [#10,#13])) do dec(Result); // search - while (ResultSource[Result]) then + inc(Result); + end; exit; end; #9,' ',';': @@ -3955,6 +3970,13 @@ begin end; end; +function GetLineStartPosition(const Source: string; Position: integer): integer; +begin + Result:=Position; + while (Result>1) and (not (Source[Result-1] in [#10,#13])) do + dec(Result); +end; + function LineEndCount(const Txt: string): integer; var LengthOfLastLine: integer; diff --git a/components/codetools/codetoolmanager.pas b/components/codetools/codetoolmanager.pas index 050a0c2aa9..8df352ef40 100644 --- a/components/codetools/codetoolmanager.pas +++ b/components/codetools/codetoolmanager.pas @@ -419,7 +419,7 @@ type function GatherOverloads(Code: TCodeBuffer; X,Y: integer; out Graph: TDeclarationOverloadsGraph): boolean; - // rename identifier + // rename, remove identifier function FindReferences(IdentifierCode: TCodeBuffer; X, Y: integer; TargetCode: TCodeBuffer; SkipComments: boolean; var ListOfPCodeXYPosition: TFPList): boolean; @@ -429,6 +429,8 @@ type const OldIdentifier, NewIdentifier: string): boolean; function ReplaceWord(Code: TCodeBuffer; const OldWord, NewWord: string; ChangeStrings: boolean): boolean; + function RemoveIdentifierDefinition(Code: TCodeBuffer; X, Y: integer + ): boolean; // e.g. remove the variable definition at X,Y // resourcestring sections function GatherResourceStringSections( @@ -2323,6 +2325,26 @@ begin end; end; +function TCodeToolManager.RemoveIdentifierDefinition(Code: TCodeBuffer; X, + Y: integer): boolean; +var + CursorPos: TCodeXYPosition; +begin + Result:=false; + {$IFDEF CTDEBUG} + DebugLn('TCodeToolManager.RemoveIdentifierDefinition A ',Code.Filename,' X=',X,' Y=',Y); + {$ENDIF} + if not InitCurCodeTool(Code) then exit; + CursorPos.X:=X; + CursorPos.Y:=Y; + CursorPos.Code:=Code; + try + Result:=FCurCodeTool.RemoveIdentifierDefinition(CursorPos,SourceChangeCache); + except + on e: Exception do HandleException(e); + end; +end; + function TCodeToolManager.GatherResourceStringSections(Code: TCodeBuffer; X, Y: integer; CodePositions: TCodeXYPositions): boolean; var diff --git a/components/codetools/customcodetool.pas b/components/codetools/customcodetool.pas index 63e20bd26c..92b91b8164 100644 --- a/components/codetools/customcodetool.pas +++ b/components/codetools/customcodetool.pas @@ -211,7 +211,7 @@ type procedure GetLineInfo(ACleanPos: integer; out ALineStart, ALineEnd, AFirstAtomStart, ALastAtomEnd: integer); function FindLineEndOrCodeAfterPosition(StartPos: integer; - SkipEmptyLines: boolean = false): integer; + SkipEmptyLines: boolean = false; IncludeLineEnd: boolean = false): integer; function FindLineEndOrCodeInFrontOfPosition(StartPos: integer): integer; function FindLineEndOrCodeInFrontOfPosition(StartPos: integer; StopAtDirectives: boolean): integer; @@ -2431,7 +2431,7 @@ begin end; function TCustomCodeTool.FindLineEndOrCodeAfterPosition(StartPos: integer; - SkipEmptyLines: boolean): integer; + SkipEmptyLines: boolean; IncludeLineEnd: boolean): integer; { Searches a nice position in the cleaned source after StartPos. It will skip any space or comments (not directives) till next line end or compiler directive or code or include file end. @@ -2443,7 +2443,8 @@ begin LinkEnd:=Scanner.LinkCleanedEndPos(LinkIndex); if LinkEnd>StartPos then Result:=BasicCodeTools.FindLineEndOrCodeAfterPosition(Src, - StartPos,LinkEnd-1,Scanner.NestedComments,true,SkipEmptyLines) + StartPos,LinkEnd-1,Scanner.NestedComments,true,SkipEmptyLines, + IncludeLineEnd) else Result:=StartPos; end; diff --git a/components/codetools/stdcodetools.pas b/components/codetools/stdcodetools.pas index 0a282938ce..79aa65e2e6 100644 --- a/components/codetools/stdcodetools.pas +++ b/components/codetools/stdcodetools.pas @@ -195,6 +195,10 @@ type SearchInAncestors: boolean; out ListOfPInstancePropInfo: TFPList): boolean; + // variables, constants, types + function RemoveIdentifierDefinition(const CursorPos: TCodeXYPosition; + SourceChangeCache: TSourceChangeCache): boolean; + // blocks (e.g. begin..end) function FindBlockCounterPart(const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean; @@ -5075,6 +5079,109 @@ begin end; end; +function TStandardCodeTool.RemoveIdentifierDefinition( + const CursorPos: TCodeXYPosition; SourceChangeCache: TSourceChangeCache + ): boolean; +var + CleanCursorPos: integer; + Node: TCodeTreeNode; + PrevSibling: TCodeTreeNode; + NextSibling: TCodeTreeNode; + DeleteStartPos: LongInt; + DeleteEndPos: LongInt; + DeleteFirstTokenOfLine: Boolean; +begin + Result:=false; + BuildTreeAndGetCleanPos(trAll,CursorPos,CleanCursorPos,[]); + Node:=BuildSubTreeAndFindDeepestNodeAtPos(CleanCursorPos,true); + if Node.Desc in AllIdentifierDefinitions then begin + // Examples: + // var i, X: integer; -> var i[, X]: integer; + // var i, X, j: integer; -> var i, [X, ]j: integer; + // var X, i: integer; -> var [X, ]i: integer; + // type X = integer; + // const X = 0; + // const X : integer = 0; + PrevSibling:=nil; + NextSibling:=nil; + if (Node.PriorBrother<>nil) and (Node.PriorBrother.FirstChild=nil) then + PrevSibling:=Node.PriorBrother; + if (Node.FirstChild=nil) and (Node.NextBrother<>nil) then + NextSibling:=Node.NextBrother; + DeleteStartPos:=Node.StartPos; + DeleteEndPos:=Node.StartPos+GetIdentLen(@Src[Node.StartPos]); + if NextSibling<>nil then begin + // var i, X, j: integer; -> var i, [X, ]j: integer; + // var X, i: integer; -> var [X, ]i: integer; + MoveCursorToCleanPos(Node.StartPos); + ReadNextAtom; + AtomIsIdentifier(true); + if not ReadNextAtomIsChar(',') then RaiseCharExpectedButAtomFound(','); + DeleteEndPos:=CurPos.EndPos; + end else if PrevSibling<>nil then begin + // var i, X: integer; -> var i[, X]: integer; + MoveCursorToCleanPos(PrevSibling.StartPos); + ReadNextAtom; + AtomIsIdentifier(true); + if not ReadNextAtomIsChar(',') then RaiseCharExpectedButAtomFound(','); + DeleteStartPos:=CurPos.StartPos; + end else begin + // delete whole declaration + if (Node.Parent.Desc in AllDefinitionSections) + and (Node.PriorBrother=nil) and (Node.NextBrother=nil) then begin + // delete whole section + DeleteStartPos:=Node.Parent.StartPos; + DeleteEndPos:=Node.Parent.EndPos; + end else if Node.Parent.Desc=ctnParameterList then begin + // delete whole parameter including modifier, type and default value + if Node.PriorBrother<>nil then begin + // ... var i: integer; var X: ... -> ... var i: integer[; var X: ... + MoveCursorToCleanPos(Node.PriorBrother.EndPos); + repeat + ReadNextAtom; + if CurPos.Flag=cafSemicolon then begin + DeleteStartPos:=CurPos.EndPos; + break; + end; + until CurPos.StartPos>=Node.StartPos; + end else begin + // (var X: ... -> ([; X: ... + MoveCursorToCleanPos(Node.Parent.StartPos); + ReadNextAtom; + if CurPos.Flag in [cafRoundBracketOpen,cafEdgedBracketOpen] then + DeleteStartPos:=CurPos.EndPos; + end; + if Node.NextBrother<>nil then begin + // ... var X: integer; var i: ... -> .. var X: integer;] var i: ... + DeleteEndPos:=Node.PriorBrother.EndPos; + end else begin + // ... var X: integer) -> .. var X: integer]) + DeleteEndPos:=Node.EndPos; + end; + end else begin + // keep section, delete whole declaration + DeleteEndPos:=Node.EndPos; + end; + end; + // include corresponding comments + DeleteFirstTokenOfLine:=FindFirstNonSpaceCharInLine(Src,DeleteStartPos)=DeleteStartPos; + //DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition ',dbgstr(copy(Src,FindFirstNonSpaceCharInLine(Src,DeleteStartPos),10))]); + DeleteEndPos:=FindLineEndOrCodeAfterPosition(DeleteEndPos,true,DeleteFirstTokenOfLine); + if DeleteFirstTokenOfLine and (Src[DeleteEndPos-1] in [#10,#13]) then begin + // delete first and last token of line + // => remove the entire line + DeleteStartPos:=GetLineStartPosition(Src,DeleteStartPos); + end; + //DebugLn(['TStandardCodeTool.RemoveIdentifierDefinition "',dbgstr(copy(Src,DeleteStartPos,DeleteEndPos-DeleteStartPos)),'" IncludeLineEnd=',DeleteFirstTokenOfLine]); + + // delete + SourceChangeCache.MainScanner:=Scanner; + if not SourceChangeCache.Replace(gtNone,gtNone,DeleteStartPos,DeleteEndPos,'') + then exit; + Result:=SourceChangeCache.Apply; + end; +end; + function TStandardCodeTool.FindBlockCounterPart( const CursorPos: TCodeXYPosition; out NewPos: TCodeXYPosition; out NewTopLine: integer): boolean; diff --git a/ide/lazarusidestrconsts.pas b/ide/lazarusidestrconsts.pas index 87ffd9f7f4..e55d1b6260 100644 --- a/ide/lazarusidestrconsts.pas +++ b/ide/lazarusidestrconsts.pas @@ -4774,6 +4774,8 @@ resourcestring +'filename, the hostname with an optional username and the filename of ' +'gdb on the remote computer. For example: %s/usr/bin/ssh username@' +'hostname gdb%s or: %s/usr/bin/setsid /usr/bin/ssh username@hostname gdb%s'; + lisRemoveUnitFromUsesSection = 'Remove unit from uses section'; + lisRemoveLocalVariable = 'Remove local variable %s'; implementation diff --git a/ide/msgquickfixes.pas b/ide/msgquickfixes.pas index 98821a70c9..fa67418a86 100644 --- a/ide/msgquickfixes.pas +++ b/ide/msgquickfixes.pas @@ -83,6 +83,15 @@ type 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; + procedure QuickFixParameterNotUsed(Sender: TObject; Step: TIMQuickFixStep; Msg: TIDEMessageLine); procedure QuickFixUnitNotUsed(Sender: TObject; Step: TIMQuickFixStep; @@ -96,6 +105,37 @@ procedure FreeStandardIDEQuickFixItems; implementation +procedure ShowError(Msg: string); +begin + MessageDlg('QuickFix error',Msg,mtError,[mbCancel],0); +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 @@ -114,6 +154,7 @@ begin 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); @@ -181,6 +222,7 @@ begin RegisterIDEMsgQuickFix(TQuickFixLinkerUndefinedReference.Create); RegisterIDEMsgQuickFix(TQuickFixClassWithAbstractMethods.Create); RegisterIDEMsgQuickFix(TQuickFixIdentifierNotFoundAddLocal.Create); + RegisterIDEMsgQuickFix(TQuickFixLocalVariableNotUsed_Remove.Create); end; procedure FreeStandardIDEQuickFixItems; @@ -219,6 +261,7 @@ begin if not REMatches(Msg.Msg,'Can''t find unit ([a-z_0-9]+)','I') then begin DebugLn('QuickFixUnitNotFoundPosition invalid message ',Msg.Msg); + ShowError('QuickFix: UnitNotFoundPosition invalid message '+Msg.Msg); exit; end; MissingUnitname:=REVar(1); @@ -230,11 +273,13 @@ begin NewFilename:=LazarusIDE.FindUnitFile(UsedByUnit); if NewFilename='' then begin DebugLn('QuickFixUnitNotFoundPosition unit not found: ',UsedByUnit); + ShowError('QuickFix: UnitNotFoundPosition unit not found: '+UsedByUnit); exit; end; CodeBuf:=CodeToolBoss.LoadFile(NewFilename,false,false); if CodeBuf=nil then begin DebugLn('QuickFixUnitNotFoundPosition unable to load unit: ',NewFilename); + ShowError('QuickFix: UnitNotFoundPosition unable to load unit: '+NewFilename); exit; end; end; @@ -244,6 +289,7 @@ begin NamePos,InPos) then begin DebugLn('QuickFixUnitNotFoundPosition failed due to syntax errors or '+MissingUnitname+' is not used in '+CodeBuf.Filename); + LazarusIDE.DoJumpToCodeToolBossError; exit; end; if InPos=0 then ; @@ -277,13 +323,6 @@ procedure TQuickFixLinkerUndefinedReference.Execute(const Msg: TIDEMessageLine; unit1.o(.text+0x3a):unit1.pas:48: undefined reference to `DoesNotExist' } - procedure Error(const Msg: string); - begin - DebugLn('TQuickFixLinkerUndefinedReference.Execute ',Msg); - MessageDlg('TQuickFixLinkerUndefinedReference.Execute', - Msg,mtError,[mbCancel],0); - end; - procedure JumpTo(Line1, Line2: TIDEMessageLine); var Identifier: String; @@ -343,26 +382,22 @@ procedure TQuickFixLinkerUndefinedReference.Execute(const Msg: TIDEMessageLine; DebugLn(['TQuickFixLinkerUndefinedReference.JumpTo Filename="',Filename,'" MangledFunction="',MangledFunction,'" Identifier="',Identifier,'" SourceFilename="',SourceFilename,'" SourceLine=',SourceLine]); CurProject:=LazarusIDE.ActiveProject; if CurProject=nil then begin - Error('no project'); + ShowError('QuickFix: LinkerUndefinedReference no project'); exit; end; if (CurProject.MainFile=nil) then begin - Error('no main file in project'); - exit; - end; - if (CurProject.MainFile=nil) then begin - Error('no main file in project'); + ShowError('QuickFix: LinkerUndefinedReference no main file in project'); exit; end; CodeBuf:=CodeToolBoss.LoadFile(CurProject.MainFile.Filename,true,false); if (CodeBuf=nil) then begin - Error('project main file has no source'); + ShowError('QuickFix: LinkerUndefinedReference project main file has no source'); exit; end; AnUnitName:=ExtractFilenameOnly(Filename); CodeBuf:=CodeToolBoss.FindUnitSource(CodeBuf,AnUnitName,''); if (CodeBuf=nil) then begin - Error('unit not found: '+AnUnitName); + ShowError('QuickFix: LinkerUndefinedReference unit not found: '+AnUnitName); exit; end; if not CodeToolBoss.JumpToLinkerIdentifier(CodeBuf, @@ -372,7 +407,7 @@ procedure TQuickFixLinkerUndefinedReference.Execute(const Msg: TIDEMessageLine; if CodeToolBoss.ErrorCode<>nil then LazarusIDE.DoJumpToCodeToolBossError else - Error('function not found: '+MangledFunction+' Identifier='+Identifier); + ShowError('QuickFix: LinkerUndefinedReference function not found: '+MangledFunction+' Identifier='+Identifier); exit; end; LazarusIDE.DoOpenFileAndJumpToPos(NewCode.Filename,Point(NewX,NewY), @@ -438,10 +473,11 @@ begin // 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]); + //DebugLn(['TQuickFixClassWithAbstractMethods.Execute Class=',CurClassName]); // find the class @@ -449,6 +485,7 @@ begin 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; @@ -465,12 +502,13 @@ begin end; exit; end; - DebugLn(['TQuickFixClassWithAbstractMethods.Execute Declaration at ',NewCode.Filename,' ',NewX,',',NewY]); + //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; @@ -555,10 +593,17 @@ begin // 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]); + //DebugLn(['TQuickFixIdentifierNotFoundAddLocal.Execute Identifier=',Identifier]); + + if not IsIdentifierInCode(CodeBuf,Caret.X,Caret.Y,Identifier, + Identifier+' not found in '+CodeBuf.Filename + +' at line '+IntToStr(Caret.Y)+', column '+IntToStr(Caret.X)+'.'#13 + +'Maybe the message is outdated.') + then exit; if not CodeToolBoss.CreateVariableForIdentifier(CodeBuf,Caret.X,Caret.Y,-1, NewCode,NewX,NewY,NewTopLine) @@ -577,7 +622,7 @@ end; constructor TQuickFixUnitNotFound_Remove.Create; begin Name:='Search unit: Error: Can''t find unit Name'; - Caption:='Remove unit from uses section'; + Caption:=lisRemoveUnitFromUsesSection; Steps:=[imqfoMenuItem]; end; @@ -618,6 +663,7 @@ begin // get unitname if not REMatches(Msg.Msg,'Fatal: Can''t 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(1); @@ -625,6 +671,7 @@ begin 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; @@ -640,5 +687,88 @@ begin end; end; +{ TQuickFixLocalVariableNotUsed_Remove } + +constructor TQuickFixLocalVariableNotUsed_Remove.Create; +begin + Name:='Remove local variable: Note: Local variable "x" not used'; + Caption:='Remove local variable'; + 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 GetMsgLineFilename(Msg,CodeBuf) 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)+'.'#13 + +'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; + end.