Skip to content

Commit 2b723b0

Browse files
add hover handler
1 parent b0aec47 commit 2b723b0

File tree

5 files changed

+257
-0
lines changed

5 files changed

+257
-0
lines changed

src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public async Task StartAsync()
111111
.WithHandler<CodeActionHandler>()
112112
.WithHandler<InvokeExtensionCommandHandler>()
113113
.WithHandler<CompletionHandler>()
114+
.WithHandler<HoverHandler>()
114115
.OnInitialize(
115116
async (languageServer, request) =>
116117
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System.Diagnostics;
7+
using System.Management.Automation;
8+
using System.Threading.Tasks;
9+
using Microsoft.PowerShell.EditorServices.Symbols;
10+
11+
namespace Microsoft.PowerShell.EditorServices
12+
{
13+
/// <summary>
14+
/// Provides detailed information for a given symbol.
15+
/// </summary>
16+
[DebuggerDisplay("SymbolReference = {SymbolReference.SymbolType}/{SymbolReference.SymbolName}, DisplayString = {DisplayString}")]
17+
public class SymbolDetails
18+
{
19+
#region Properties
20+
21+
/// <summary>
22+
/// Gets the original symbol reference which was used to gather details.
23+
/// </summary>
24+
public SymbolReference SymbolReference { get; private set; }
25+
26+
/// <summary>
27+
/// Gets the display string for this symbol.
28+
/// </summary>
29+
public string DisplayString { get; private set; }
30+
31+
/// <summary>
32+
/// Gets the documentation string for this symbol. Returns an
33+
/// empty string if the symbol has no documentation.
34+
/// </summary>
35+
public string Documentation { get; private set; }
36+
37+
#endregion
38+
39+
#region Constructors
40+
41+
static internal async Task<SymbolDetails> CreateAsync(
42+
SymbolReference symbolReference,
43+
PowerShellContextService powerShellContext)
44+
{
45+
SymbolDetails symbolDetails = new SymbolDetails
46+
{
47+
SymbolReference = symbolReference
48+
};
49+
50+
switch (symbolReference.SymbolType)
51+
{
52+
case SymbolType.Function:
53+
CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync(
54+
symbolReference.SymbolName,
55+
powerShellContext);
56+
57+
if (commandInfo != null)
58+
{
59+
symbolDetails.Documentation =
60+
await CommandHelpers.GetCommandSynopsisAsync(
61+
commandInfo,
62+
powerShellContext);
63+
64+
if (commandInfo.CommandType == CommandTypes.Application)
65+
{
66+
symbolDetails.DisplayString = "(application) " + symbolReference.SymbolName;
67+
}
68+
else
69+
{
70+
symbolDetails.DisplayString = "function " + symbolReference.SymbolName;
71+
}
72+
}
73+
else
74+
{
75+
// Command information can't be loaded. This is likely due to
76+
// the symbol being a function that is defined in a file that
77+
// hasn't been loaded in the runspace yet.
78+
symbolDetails.DisplayString = "function " + symbolReference.SymbolName;
79+
}
80+
81+
break;
82+
case SymbolType.Parameter:
83+
// TODO: Get parameter help
84+
symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName;
85+
break;
86+
case SymbolType.Variable:
87+
symbolDetails.DisplayString = symbolReference.SymbolName;
88+
break;
89+
}
90+
91+
return symbolDetails;
92+
}
93+
94+
#endregion
95+
}
96+
}

src/PowerShellEditorServices.Engine/Services/Symbols/SymbolsService.cs

+35
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Collections.Specialized;
99
using System.Linq;
1010
using System.Runtime.InteropServices;
11+
using System.Threading.Tasks;
1112
using Microsoft.Extensions.Logging;
1213
using Microsoft.PowerShell.EditorServices.Symbols;
1314

@@ -200,6 +201,7 @@ public IReadOnlyList<SymbolReference> FindOccurrencesInFile(
200201
needsAliases: false).ToArray();
201202
}
202203

204+
/// <summary>
203205
/// Finds a function definition in the script given a file location
204206
/// </summary>
205207
/// <param name="scriptFile">The details and contents of a open script file</param>
@@ -227,5 +229,38 @@ public SymbolReference FindFunctionDefinitionAtLocation(
227229

228230
return symbolReference;
229231
}
232+
233+
/// <summary>
234+
/// Finds the details of the symbol at the given script file location.
235+
/// </summary>
236+
/// <param name="scriptFile">The ScriptFile in which the symbol can be located.</param>
237+
/// <param name="lineNumber">The line number at which the symbol can be located.</param>
238+
/// <param name="columnNumber">The column number at which the symbol can be located.</param>
239+
/// <returns></returns>
240+
public async Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(
241+
ScriptFile scriptFile,
242+
int lineNumber,
243+
int columnNumber,
244+
PowerShellContextService powerShellContext)
245+
{
246+
SymbolReference symbolReference =
247+
AstOperations.FindSymbolAtPosition(
248+
scriptFile.ScriptAst,
249+
lineNumber,
250+
columnNumber);
251+
252+
if (symbolReference == null)
253+
{
254+
// TODO #21: Return Result<T>
255+
return null;
256+
}
257+
258+
symbolReference.FilePath = scriptFile.FilePath;
259+
SymbolDetails symbolDetails = await SymbolDetails.CreateAsync(
260+
symbolReference,
261+
powerShellContext);
262+
263+
return symbolDetails;
264+
}
230265
}
231266
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Collections.Generic;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Microsoft.Extensions.Logging;
5+
using Microsoft.PowerShell.EditorServices;
6+
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
7+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
8+
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
9+
10+
namespace PowerShellEditorServices.Engine.Services.Handlers
11+
{
12+
public class HoverHandler : IHoverHandler
13+
{
14+
private readonly DocumentSelector _documentSelector = new DocumentSelector(
15+
new DocumentFilter()
16+
{
17+
Pattern = "**/*.ps*1"
18+
}
19+
);
20+
21+
private readonly ILogger _logger;
22+
private readonly SymbolsService _symbolsService;
23+
private readonly WorkspaceService _workspaceService;
24+
private readonly PowerShellContextService _powerShellContextService;
25+
26+
private HoverCapability _capability;
27+
28+
public HoverHandler(
29+
ILoggerFactory factory,
30+
SymbolsService symbolsService,
31+
WorkspaceService workspaceService,
32+
PowerShellContextService powerShellContextService)
33+
{
34+
_logger = factory.CreateLogger<HoverHandler>();
35+
_symbolsService = symbolsService;
36+
_workspaceService = workspaceService;
37+
_powerShellContextService = powerShellContextService;
38+
}
39+
40+
public TextDocumentRegistrationOptions GetRegistrationOptions()
41+
{
42+
return new TextDocumentRegistrationOptions()
43+
{
44+
DocumentSelector = _documentSelector,
45+
};
46+
}
47+
48+
public async Task<Hover> Handle(HoverParams request, CancellationToken cancellationToken)
49+
{
50+
ScriptFile scriptFile =
51+
_workspaceService.GetFile(
52+
request.TextDocument.Uri.ToString());
53+
54+
SymbolDetails symbolDetails =
55+
await _symbolsService.FindSymbolDetailsAtLocationAsync(
56+
scriptFile,
57+
(int) request.Position.Line + 1,
58+
(int) request.Position.Character + 1,
59+
_powerShellContextService);
60+
61+
List<MarkedString> symbolInfo = new List<MarkedString>();
62+
Range symbolRange = null;
63+
64+
if (symbolDetails != null)
65+
{
66+
symbolInfo.Add(new MarkedString("PowerShell", symbolDetails.DisplayString));
67+
68+
if (!string.IsNullOrEmpty(symbolDetails.Documentation))
69+
{
70+
symbolInfo.Add(new MarkedString("markdown", symbolDetails.Documentation));
71+
}
72+
73+
symbolRange = GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion);
74+
}
75+
76+
return new Hover
77+
{
78+
Contents = new MarkedStringsOrMarkupContent(symbolInfo),
79+
Range = symbolRange
80+
};
81+
}
82+
83+
public void SetCapability(HoverCapability capability)
84+
{
85+
_capability = capability;
86+
}
87+
88+
private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
89+
{
90+
return new Range
91+
{
92+
Start = new Position
93+
{
94+
Line = scriptRegion.StartLineNumber - 1,
95+
Character = scriptRegion.StartColumnNumber - 1
96+
},
97+
End = new Position
98+
{
99+
Line = scriptRegion.EndLineNumber - 1,
100+
Character = scriptRegion.EndColumnNumber - 1
101+
}
102+
};
103+
}
104+
}
105+
}

test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs

+20
Original file line numberDiff line numberDiff line change
@@ -644,5 +644,25 @@ public async Task CanSendCompletionAndCompletionResolveRequest()
644644

645645
Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String);
646646
}
647+
648+
[Fact]
649+
public async Task CanSendHoverRequest()
650+
{
651+
string filePath = NewTestFile("Write-Host");
652+
653+
Hover hover = await LanguageClient.TextDocument.Hover(filePath, line: 0, column: 1);
654+
655+
Assert.True(hover.Contents.HasMarkedStrings);
656+
Assert.Collection(hover.Contents.MarkedStrings,
657+
str1 =>
658+
{
659+
Assert.Equal("function Write-Host", str1.Value);
660+
},
661+
str2 =>
662+
{
663+
Assert.Equal("markdown", str2.Language);
664+
Assert.Equal("Writes customized output to a host.", str2.Value);
665+
});
666+
}
647667
}
648668
}

0 commit comments

Comments
 (0)