From d6f267a40d87dbdbaf29b65e362f2caf9e14f90f Mon Sep 17 00:00:00 2001 From: ondrej Date: Sat, 23 Jan 2016 18:03:35 +0000 Subject: [PATCH] codetools: allow variable code completion when cursor is after assignment. Issue #29448 git-svn-id: trunk@51390 - --- components/codetools/codeatom.pas | 2 + components/codetools/codecompletiontool.pas | 103 +++++++++++++++++--- components/codetools/customcodetool.pas | 6 ++ 3 files changed, 96 insertions(+), 15 deletions(-) diff --git a/components/codetools/codeatom.pas b/components/codetools/codeatom.pas index a6c28753ef..2a526b11d7 100644 --- a/components/codetools/codeatom.pas +++ b/components/codetools/codeatom.pas @@ -47,6 +47,7 @@ type cafSemicolon, cafEqual, cafColon, cafComma, cafPoint, cafRoundBracketOpen, cafRoundBracketClose, cafEdgedBracketOpen, cafEdgedBracketClose, + cafAssignment, cafWord, cafEnd, cafOtherOperator // = other operator ); @@ -59,6 +60,7 @@ const 'Semicolon', 'Equal', 'Colon', 'Comma', 'Point', 'RoundBracketOpen', 'RoundBracketClose', 'EdgedBracketOpen', 'EdgedBracketClose', + 'Assignment', 'Word', 'End', 'Operator' ); diff --git a/components/codetools/codecompletiontool.pas b/components/codetools/codecompletiontool.pas index c1c72fdb31..fdeb5d530f 100644 --- a/components/codetools/codecompletiontool.pas +++ b/components/codetools/codecompletiontool.pas @@ -9256,14 +9256,13 @@ function TCodeCompletionCodeTool.CompleteCode(CursorPos: TCodeXYPosition; end; function TryFirstLocalIdentOccurence(CursorNode: TCodeTreeNode; - OrigCleanCursorPos, CleanCursorPos: Integer): boolean; + CleanCursorPos: Integer): boolean; var AtomContextNode, StatementNode: TCodeTreeNode; IdentAtom, LastCurPos: TAtomPosition; UpIdentifier: string; LastAtomIsDot: Boolean; Params: TFindDeclarationParams; - OldCodePos: TCodePosition; begin Result := false; @@ -9313,10 +9312,7 @@ function TCodeCompletionCodeTool.CompleteCode(CursorPos: TCodeXYPosition; begin FCompletingCursorNode:=CursorNode; try - if not CleanPosToCodePos(OrigCleanCursorPos,OldCodePos) then - RaiseException('TCodeCompletionCodeTool.TryFirstLocalIdentOccurence CleanPosToCodePos'); CompleteCode:=TryCompleteLocalVar(LastCurPos.StartPos,AtomContextNode); - AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine); exit(true); finally FCompletingCursorNode:=nil; @@ -9331,9 +9327,58 @@ function TCodeCompletionCodeTool.CompleteCode(CursorPos: TCodeXYPosition; end; end; + procedure ClearAndRaise(var E: ECodeToolError; CleanPos: Integer); + var + TempE: ECodeToolError; + begin + TempE := E; + E := nil; + MoveCursorToCleanPos(CleanPos); + RaiseExceptionInstance(TempE); + end; + + function TryAssignment(CleanCursorPos: Integer; CursorNode: TCodeTreeNode; + var LastCodeToolsError: ECodeToolError; LastCodeToolsErrorCleanPos: Integer): Boolean; + begin + // Search only within the current instruction - stop on semicolon or else + // (else isn't prepended by a semicolon in contrast to other keywords). + + Result := False; + MoveCursorToCleanPos(CleanCursorPos); + while CurPos.StartPos > 0 do + begin + ReadPriorAtom; + case CurPos.Flag of + cafAssignment: + begin + // OK FOUND! + ReadPriorAtom; + try + if TryComplete(CursorNode, CurPos.StartPos) then + exit(true); + except + if LastCodeToolsError<>nil then + ClearAndRaise(LastCodeToolsError, LastCodeToolsErrorCleanPos) // in case of error, raise the last one + else + raise; + end; + break; + end; + cafWord: + if UpAtomIs('ELSE') then // stop on else + break; + cafSemicolon: + break; // stop on semicolon + end; + end; + end; + var CleanCursorPos, OrigCleanCursorPos: integer; CursorNode: TCodeTreeNode; + OldCodePos: TCodePosition; + LastCodeToolsErrorCleanPos: Integer; + LastCodeToolsError: ECodeToolError; begin //DebugLn(['TCodeCompletionCodeTool.CompleteCode CursorPos=',Dbgs(CursorPos),' OldTopLine=',OldTopLine]); @@ -9364,18 +9409,46 @@ begin CodeCompleteSrcChgCache:=SourceChangeCache; CursorNode:=FindDeepestNodeAtPos(CleanCursorPos,true); - if TryComplete(CursorNode, CleanCursorPos) then - exit(true); + try + LastCodeToolsError := nil; + try + if TryComplete(CursorNode, CleanCursorPos) then + exit(true); - { Find the first occurence of the (local) identifier at cursor in current - procedure body and try again. } - if TryFirstLocalIdentOccurence(CursorNode,OrigCleanCursorPos,CleanCursorPos) then - exit(true); + { Find the first occurence of the (local) identifier at cursor in current + procedure body and try again. } + if TryFirstLocalIdentOccurence(CursorNode,CleanCursorPos) then + exit(true); - if CompleteMethodByBody(OrigCleanCursorPos,OldTopLine,CursorNode, - NewPos,NewTopLine,SourceChangeCache) - then - exit(true); + if CompleteMethodByBody(OrigCleanCursorPos,OldTopLine,CursorNode, + NewPos,NewTopLine,SourceChangeCache) + then + exit(true); + except + on E: ECodeToolError do + begin + // we have a codetool error, let's try to find the assignment in any case + LastCodeToolsErrorCleanPos := CurPos.StartPos; + LastCodeToolsError := ECodeToolError.Create(E.Sender, E.Message); + end else + raise; + end; + + // find first assignment before current. + if TryAssignment(CleanCursorPos, CursorNode, LastCodeToolsError, LastCodeToolsErrorCleanPos) then + Exit(true); + + if LastCodeToolsError<>nil then // no assignment found, reraise + ClearAndRaise(LastCodeToolsError, LastCodeToolsErrorCleanPos); + finally + LastCodeToolsError.Free; + if Result then + begin + if not CleanPosToCodePos(OrigCleanCursorPos,OldCodePos) then + RaiseException('TCodeCompletionCodeTool.CompleteCode CleanPosToCodePos'); + AdjustCursor(OldCodePos,OldTopLine,NewPos,NewTopLine); + end; + end; {$IFDEF CTDEBUG} DebugLn('TCodeCompletionCodeTool.CompleteCode nothing to complete ... '); diff --git a/components/codetools/customcodetool.pas b/components/codetools/customcodetool.pas index f0adc2051a..9810d49f46 100644 --- a/components/codetools/customcodetool.pas +++ b/components/codetools/customcodetool.pas @@ -1290,6 +1290,7 @@ begin end else begin // := inc(CurPos.EndPos); + CurPos.Flag:=cafAssignment; end; end; '.': @@ -1740,6 +1741,11 @@ begin if CurPos.StartPos>1 then begin c1:=Src[CurPos.StartPos-1]; // test for double char operators :=, +=, -=, /=, *=, <>, <=, >=, **, >< + if ((c2='=') and (c1=':')) then + begin + dec(CurPos.StartPos); + CurPos.Flag:=cafAssignment; + end else if ((c2='=') and (IsEqualOperatorStartChar[c1])) or ((c1='<') and (c2='>')) or ((c1='>') and (c2='<'))