Skip to content

Commit 87cd721

Browse files
Fix symbol highlight when hovering function name (#1890)
* move helper to VistiorUtils class * find function name if indented or after newline * extend tests to cover function definition name * extend test to include end position * Fix `postion` to `position` typo Co-authored-by: Andy Jordan <[email protected]>
1 parent b0bfce7 commit 87cd721

File tree

4 files changed

+90
-51
lines changed

4 files changed

+90
-51
lines changed

Diff for: src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs

+2-35
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Management.Automation.Language;
7+
using Microsoft.PowerShell.EditorServices.Utility;
78

89
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
910
{
@@ -116,7 +117,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
116117
/// <returns>A visit action that continues the search for references</returns>
117118
public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
118119
{
119-
(int startColumnNumber, int startLineNumber) = GetStartColumnAndLineNumbersFromAst(functionDefinitionAst);
120+
(int startColumnNumber, int startLineNumber) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst);
120121

121122
IScriptExtent nameExtent = new ScriptExtent()
122123
{
@@ -167,39 +168,5 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var
167168
}
168169
return AstVisitAction.Continue;
169170
}
170-
171-
// Computes where the start of the actual function name is.
172-
private static (int, int) GetStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast)
173-
{
174-
int startColumnNumber = ast.Extent.StartColumnNumber;
175-
int startLineNumber = ast.Extent.StartLineNumber;
176-
int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length;
177-
string astText = ast.Extent.Text;
178-
// The line offset represents the offset on the line that we're on where as
179-
// astOffset is the offset on the entire text of the AST.
180-
int lineOffset = astOffset;
181-
for (; astOffset < astText.Length; astOffset++, lineOffset++)
182-
{
183-
if (astText[astOffset] == '\n')
184-
{
185-
// reset numbers since we are operating on a different line and increment the line number.
186-
startColumnNumber = 0;
187-
startLineNumber++;
188-
lineOffset = 0;
189-
}
190-
else if (astText[astOffset] == '\r')
191-
{
192-
// Do nothing with carriage returns... we only look for line feeds since those
193-
// are used on every platform.
194-
}
195-
else if (!char.IsWhiteSpace(astText[astOffset]))
196-
{
197-
// This is the start of the function name so we've found our start column and line number.
198-
break;
199-
}
200-
}
201-
202-
return (startColumnNumber + lineOffset, startLineNumber);
203-
}
204171
}
205172
}

Diff for: src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs

+14-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.Management.Automation.Language;
5+
using Microsoft.PowerShell.EditorServices.Utility;
56

67
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
78
{
@@ -57,22 +58,28 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
5758
/// or a decision to continue if it wasn't found</returns>
5859
public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
5960
{
60-
int startColumnNumber = 1;
61+
int startLineNumber = functionDefinitionAst.Extent.StartLineNumber;
62+
int startColumnNumber = functionDefinitionAst.Extent.StartColumnNumber;
63+
int endLineNumber = functionDefinitionAst.Extent.EndLineNumber;
64+
int endColumnNumber = functionDefinitionAst.Extent.EndColumnNumber;
6165

6266
if (!includeFunctionDefinitions)
6367
{
64-
startColumnNumber =
65-
functionDefinitionAst.Extent.Text.IndexOf(
66-
functionDefinitionAst.Name) + 1;
68+
// We only want the function name
69+
(int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst);
70+
startLineNumber = startLine;
71+
startColumnNumber = startColumn;
72+
endLineNumber = startLine;
73+
endColumnNumber = startColumn + functionDefinitionAst.Name.Length;
6774
}
6875

6976
IScriptExtent nameExtent = new ScriptExtent()
7077
{
7178
Text = functionDefinitionAst.Name,
72-
StartLineNumber = functionDefinitionAst.Extent.StartLineNumber,
73-
EndLineNumber = functionDefinitionAst.Extent.EndLineNumber,
79+
StartLineNumber = startLineNumber,
80+
EndLineNumber = endLineNumber,
7481
StartColumnNumber = startColumnNumber,
75-
EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length,
82+
EndColumnNumber = endColumnNumber,
7683
File = functionDefinitionAst.Extent.File
7784
};
7885

Diff for: src/PowerShellEditorServices/Utility/VisitorUtils.cs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Management.Automation.Language;
5+
6+
namespace Microsoft.PowerShell.EditorServices.Utility
7+
{
8+
/// <summary>
9+
/// General common utilities for AST visitors to prevent reimplementation.
10+
/// </summary>
11+
internal static class VisitorUtils
12+
{
13+
/// <summary>
14+
/// Calculates the start line and column of the actual function name in a function definition AST.
15+
/// </summary>
16+
/// <param name="ast">A FunctionDefinitionAst object in the script's AST</param>
17+
/// <returns>A tuple with start column and line for the function name</returns>
18+
internal static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast)
19+
{
20+
int startColumnNumber = ast.Extent.StartColumnNumber;
21+
int startLineNumber = ast.Extent.StartLineNumber;
22+
int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length;
23+
string astText = ast.Extent.Text;
24+
// The line offset represents the offset on the line that we're on where as
25+
// astOffset is the offset on the entire text of the AST.
26+
int lineOffset = astOffset;
27+
for (; astOffset < astText.Length; astOffset++, lineOffset++)
28+
{
29+
if (astText[astOffset] == '\n')
30+
{
31+
// reset numbers since we are operating on a different line and increment the line number.
32+
startColumnNumber = 0;
33+
startLineNumber++;
34+
lineOffset = 0;
35+
}
36+
else if (astText[astOffset] == '\r')
37+
{
38+
// Do nothing with carriage returns... we only look for line feeds since those
39+
// are used on every platform.
40+
}
41+
else if (!char.IsWhiteSpace(astText[astOffset]))
42+
{
43+
// This is the start of the function name so we've found our start column and line number.
44+
break;
45+
}
46+
}
47+
48+
return (startColumnNumber + lineOffset, startLineNumber);
49+
}
50+
}
51+
}

Diff for: test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs

+23-9
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,30 @@ function FunctionWithExtraSpace
3535
3636
3737
FunctionNameOnDifferentLine
38+
39+
function IndentedFunction { } IndentedFunction
3840
";
3941
private static readonly ScriptBlockAst s_ast = (ScriptBlockAst)ScriptBlock.Create(s_scriptString).Ast;
4042

4143
[Theory]
44+
[InlineData(1, 15, "BasicFunction")]
4245
[InlineData(2, 3, "BasicFunction")]
46+
[InlineData(4, 31, "FunctionWithExtraSpace")]
4347
[InlineData(7, 18, "FunctionWithExtraSpace")]
48+
[InlineData(12, 22, "FunctionNameOnDifferentLine")]
4449
[InlineData(22, 13, "FunctionNameOnDifferentLine")]
45-
public void CanFindSymbolAtPostion(int lineNumber, int columnNumber, string expectedName)
50+
[InlineData(24, 30, "IndentedFunction")]
51+
[InlineData(24, 52, "IndentedFunction")]
52+
public void CanFindSymbolAtPosition(int lineNumber, int columnNumber, string expectedName)
4653
{
4754
SymbolReference reference = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber);
4855
Assert.NotNull(reference);
4956
Assert.Equal(expectedName, reference.SymbolName);
5057
}
5158

5259
[Theory]
53-
[MemberData(nameof(FindReferencesOfSymbolAtPostionData))]
54-
public void CanFindReferencesOfSymbolAtPostion(int lineNumber, int columnNumber, Position[] positions)
60+
[MemberData(nameof(FindReferencesOfSymbolAtPositionData))]
61+
public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber, Range[] symbolRange)
5562
{
5663
SymbolReference symbol = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber);
5764

@@ -60,18 +67,25 @@ public void CanFindReferencesOfSymbolAtPostion(int lineNumber, int columnNumber,
6067
int positionsIndex = 0;
6168
foreach (SymbolReference reference in references)
6269
{
63-
Assert.Equal(positions[positionsIndex].Line, reference.ScriptRegion.StartLineNumber);
64-
Assert.Equal(positions[positionsIndex].Character, reference.ScriptRegion.StartColumnNumber);
70+
Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.ScriptRegion.StartLineNumber);
71+
Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.ScriptRegion.StartColumnNumber);
72+
Assert.Equal(symbolRange[positionsIndex].End.Line, reference.ScriptRegion.EndLineNumber);
73+
Assert.Equal(symbolRange[positionsIndex].End.Character, reference.ScriptRegion.EndColumnNumber);
6574

6675
positionsIndex++;
6776
}
6877
}
6978

70-
public static object[][] FindReferencesOfSymbolAtPostionData { get; } = new object[][]
79+
public static object[][] FindReferencesOfSymbolAtPositionData { get; } = new object[][]
7180
{
72-
new object[] { 2, 3, new[] { new Position(1, 10), new Position(2, 1) } },
73-
new object[] { 7, 18, new[] { new Position(4, 19), new Position(7, 3) } },
74-
new object[] { 22, 13, new[] { new Position(12, 8), new Position(22, 5) } },
81+
new object[] { 1, 15, new[] { new Range(1, 10, 1, 23), new Range(2, 1, 2, 14) } },
82+
new object[] { 2, 3, new[] { new Range(1, 10, 1, 23), new Range(2, 1, 2, 14) } },
83+
new object[] { 4, 31, new[] { new Range(4, 19, 4, 41), new Range(7, 3, 7, 25) } },
84+
new object[] { 7, 18, new[] { new Range(4, 19, 4, 41), new Range(7, 3, 7, 25) } },
85+
new object[] { 22, 13, new[] { new Range(12, 8, 12, 35), new Range(22, 5, 22, 32) } },
86+
new object[] { 12, 22, new[] { new Range(12, 8, 12, 35), new Range(22, 5, 22, 32) } },
87+
new object[] { 24, 30, new[] { new Range(24, 22, 24, 38), new Range(24, 44, 24, 60) } },
88+
new object[] { 24, 52, new[] { new Range(24, 22, 24, 38), new Range(24, 44, 24, 60) } },
7589
};
7690
}
7791
}

0 commit comments

Comments
 (0)