Skip to content

Commit ef13cef

Browse files
hover support (#1010)
* handle log messages * switch to using xUnit output helper * add hover handler * move to language=powershell * refactoring for feedback * codacy
1 parent ba997b7 commit ef13cef

File tree

5 files changed

+252
-2
lines changed

5 files changed

+252
-2
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,90 @@
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+
return symbolDetails;
68+
}
69+
}
70+
71+
symbolDetails.DisplayString = "function " + symbolReference.SymbolName;
72+
return symbolDetails;
73+
74+
case SymbolType.Parameter:
75+
// TODO: Get parameter help
76+
symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName;
77+
return symbolDetails;
78+
79+
case SymbolType.Variable:
80+
symbolDetails.DisplayString = symbolReference.SymbolName;
81+
return symbolDetails;
82+
83+
default:
84+
return symbolDetails;
85+
}
86+
}
87+
88+
#endregion
89+
}
90+
}

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

+37-2
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

@@ -22,7 +23,7 @@ public class SymbolsService
2223
#region Private Fields
2324

2425
private readonly ILogger _logger;
25-
26+
private readonly PowerShellContextService _powerShellContextService;
2627
private readonly IDocumentSymbolProvider[] _documentSymbolProviders;
2728

2829
#endregion
@@ -35,9 +36,11 @@ public class SymbolsService
3536
/// </summary>
3637
/// <param name="factory">An ILoggerFactory implementation used for writing log messages.</param>
3738
public SymbolsService(
38-
ILoggerFactory factory)
39+
ILoggerFactory factory,
40+
PowerShellContextService powerShellContextService)
3941
{
4042
_logger = factory.CreateLogger<SymbolsService>();
43+
_powerShellContextService = powerShellContextService;
4144
_documentSymbolProviders = new IDocumentSymbolProvider[]
4245
{
4346
new ScriptDocumentSymbolProvider(VersionUtils.PSVersion),
@@ -200,6 +203,7 @@ public IReadOnlyList<SymbolReference> FindOccurrencesInFile(
200203
needsAliases: false).ToArray();
201204
}
202205

206+
/// <summary>
203207
/// Finds a function definition in the script given a file location
204208
/// </summary>
205209
/// <param name="scriptFile">The details and contents of a open script file</param>
@@ -227,5 +231,36 @@ public SymbolReference FindFunctionDefinitionAtLocation(
227231

228232
return symbolReference;
229233
}
234+
235+
/// <summary>
236+
/// Finds the details of the symbol at the given script file location.
237+
/// </summary>
238+
/// <param name="scriptFile">The ScriptFile in which the symbol can be located.</param>
239+
/// <param name="lineNumber">The line number at which the symbol can be located.</param>
240+
/// <param name="columnNumber">The column number at which the symbol can be located.</param>
241+
/// <returns></returns>
242+
public async Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(
243+
ScriptFile scriptFile,
244+
int lineNumber,
245+
int columnNumber)
246+
{
247+
SymbolReference symbolReference =
248+
AstOperations.FindSymbolAtPosition(
249+
scriptFile.ScriptAst,
250+
lineNumber,
251+
columnNumber);
252+
253+
if (symbolReference == null)
254+
{
255+
return null;
256+
}
257+
258+
symbolReference.FilePath = scriptFile.FilePath;
259+
SymbolDetails symbolDetails = await SymbolDetails.CreateAsync(
260+
symbolReference,
261+
_powerShellContextService);
262+
263+
return symbolDetails;
264+
}
230265
}
231266
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
Language = "powershell"
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+
60+
List<MarkedString> symbolInfo = new List<MarkedString>();
61+
Range symbolRange = null;
62+
63+
if (symbolDetails != null)
64+
{
65+
symbolInfo.Add(new MarkedString("PowerShell", symbolDetails.DisplayString));
66+
67+
if (!string.IsNullOrEmpty(symbolDetails.Documentation))
68+
{
69+
symbolInfo.Add(new MarkedString("markdown", symbolDetails.Documentation));
70+
}
71+
72+
symbolRange = GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion);
73+
}
74+
75+
return new Hover
76+
{
77+
Contents = new MarkedStringsOrMarkupContent(symbolInfo),
78+
Range = symbolRange
79+
};
80+
}
81+
82+
public void SetCapability(HoverCapability capability)
83+
{
84+
_capability = capability;
85+
}
86+
87+
private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
88+
{
89+
return new Range
90+
{
91+
Start = new Position
92+
{
93+
Line = scriptRegion.StartLineNumber - 1,
94+
Character = scriptRegion.StartColumnNumber - 1
95+
},
96+
End = new Position
97+
{
98+
Line = scriptRegion.EndLineNumber - 1,
99+
Character = scriptRegion.EndColumnNumber - 1
100+
}
101+
};
102+
}
103+
}
104+
}

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)