Skip to content

Commit 93ce344

Browse files
Omni signaturehelp (#1011)
* handle log messages * switch to using xUnit output helper * Support SignatureHelp * concurrentdict
1 parent ef13cef commit 93ce344

File tree

5 files changed

+358
-1
lines changed

5 files changed

+358
-1
lines changed

src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public async Task StartAsync()
112112
.WithHandler<InvokeExtensionCommandHandler>()
113113
.WithHandler<CompletionHandler>()
114114
.WithHandler<HoverHandler>()
115+
.WithHandler<SignatureHelpHandler>()
115116
.OnInitialize(
116117
async (languageServer, request) =>
117118
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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.Collections.Concurrent;
7+
using System.Collections.Generic;
8+
using System.Management.Automation;
9+
using Microsoft.PowerShell.EditorServices.Symbols;
10+
11+
namespace Microsoft.PowerShell.EditorServices
12+
{
13+
/// <summary>
14+
/// A class for containing the commandName, the command's
15+
/// possible signatures, and the script extent of the command
16+
/// </summary>
17+
public class ParameterSetSignatures
18+
{
19+
#region Properties
20+
/// <summary>
21+
/// Gets the name of the command
22+
/// </summary>
23+
public string CommandName { get; internal set; }
24+
25+
/// <summary>
26+
/// Gets the collection of signatures for the command
27+
/// </summary>
28+
public ParameterSetSignature[] Signatures { get; internal set; }
29+
30+
/// <summary>
31+
/// Gets the script extent of the command
32+
/// </summary>
33+
public ScriptRegion ScriptRegion { get; internal set; }
34+
#endregion
35+
36+
/// <summary>
37+
/// Constructs an instance of a ParameterSetSignatures object
38+
/// </summary>
39+
/// <param name="commandInfoSet">Collection of parameter set info</param>
40+
/// <param name="foundSymbol"> The SymbolReference of the command</param>
41+
public ParameterSetSignatures(IEnumerable<CommandParameterSetInfo> commandInfoSet, SymbolReference foundSymbol)
42+
{
43+
List<ParameterSetSignature> paramSetSignatures = new List<ParameterSetSignature>();
44+
foreach (CommandParameterSetInfo setInfo in commandInfoSet)
45+
{
46+
paramSetSignatures.Add(new ParameterSetSignature(setInfo));
47+
}
48+
Signatures = paramSetSignatures.ToArray();
49+
CommandName = foundSymbol.ScriptRegion.Text;
50+
ScriptRegion = foundSymbol.ScriptRegion;
51+
}
52+
}
53+
54+
/// <summary>
55+
/// A class for containing the signature text and the collection of parameters for a signature
56+
/// </summary>
57+
public class ParameterSetSignature
58+
{
59+
private static readonly ConcurrentDictionary<string, bool> commonParameterNames =
60+
new ConcurrentDictionary<string, bool>();
61+
62+
static ParameterSetSignature()
63+
{
64+
commonParameterNames.TryAdd("Verbose", true);
65+
commonParameterNames.TryAdd("Debug", true);
66+
commonParameterNames.TryAdd("ErrorAction", true);
67+
commonParameterNames.TryAdd("WarningAction", true);
68+
commonParameterNames.TryAdd("InformationAction", true);
69+
commonParameterNames.TryAdd("ErrorVariable", true);
70+
commonParameterNames.TryAdd("WarningVariable", true);
71+
commonParameterNames.TryAdd("InformationVariable", true);
72+
commonParameterNames.TryAdd("OutVariable", true);
73+
commonParameterNames.TryAdd("OutBuffer", true);
74+
commonParameterNames.TryAdd("PipelineVariable", true);
75+
}
76+
77+
#region Properties
78+
/// <summary>
79+
/// Gets the signature text
80+
/// </summary>
81+
public string SignatureText { get; internal set; }
82+
83+
/// <summary>
84+
/// Gets the collection of parameters for the signature
85+
/// </summary>
86+
public IEnumerable<ParameterInfo> Parameters { get; internal set; }
87+
#endregion
88+
89+
/// <summary>
90+
/// Constructs an instance of a ParameterSetSignature
91+
/// </summary>
92+
/// <param name="commandParamInfoSet">Collection of parameter info</param>
93+
public ParameterSetSignature(CommandParameterSetInfo commandParamInfoSet)
94+
{
95+
List<ParameterInfo> parameterInfo = new List<ParameterInfo>();
96+
foreach (CommandParameterInfo commandParameterInfo in commandParamInfoSet.Parameters)
97+
{
98+
if (!commonParameterNames.ContainsKey(commandParameterInfo.Name))
99+
{
100+
parameterInfo.Add(new ParameterInfo(commandParameterInfo));
101+
}
102+
}
103+
104+
SignatureText = commandParamInfoSet.ToString();
105+
Parameters = parameterInfo.ToArray();
106+
}
107+
}
108+
109+
/// <summary>
110+
/// A class for containing the parameter info of a parameter
111+
/// </summary>
112+
public class ParameterInfo
113+
{
114+
#region Properties
115+
/// <summary>
116+
/// Gets the name of the parameter
117+
/// </summary>
118+
public string Name { get; internal set; }
119+
120+
/// <summary>
121+
/// Gets the type of the parameter
122+
/// </summary>
123+
public string ParameterType { get; internal set; }
124+
125+
/// <summary>
126+
/// Gets the position of the parameter
127+
/// </summary>
128+
public int Position { get; internal set; }
129+
130+
/// <summary>
131+
/// Gets a boolean for whetheer or not the parameter is required
132+
/// </summary>
133+
public bool IsMandatory { get; internal set; }
134+
135+
/// <summary>
136+
/// Gets the help message of the parameter
137+
/// </summary>
138+
public string HelpMessage { get; internal set; }
139+
#endregion
140+
141+
/// <summary>
142+
/// Constructs an instance of a ParameterInfo object
143+
/// </summary>
144+
/// <param name="parameterInfo">Parameter info of the parameter</param>
145+
public ParameterInfo(CommandParameterInfo parameterInfo)
146+
{
147+
this.Name = "-" + parameterInfo.Name;
148+
this.ParameterType = parameterInfo.ParameterType.FullName;
149+
this.Position = parameterInfo.Position;
150+
this.IsMandatory = parameterInfo.IsMandatory;
151+
this.HelpMessage = parameterInfo.HelpMessage;
152+
}
153+
}
154+
}

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

+58
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Generic;
88
using System.Collections.Specialized;
99
using System.Linq;
10+
using System.Management.Automation;
1011
using System.Runtime.InteropServices;
1112
using System.Threading.Tasks;
1213
using Microsoft.Extensions.Logging;
@@ -262,5 +263,62 @@ public async Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(
262263

263264
return symbolDetails;
264265
}
266+
267+
/// <summary>
268+
/// Finds the parameter set hints of a specific command (determined by a given file location)
269+
/// </summary>
270+
/// <param name="file">The details and contents of a open script file</param>
271+
/// <param name="lineNumber">The line number of the cursor for the given script</param>
272+
/// <param name="columnNumber">The coulumn number of the cursor for the given script</param>
273+
/// <returns>ParameterSetSignatures</returns>
274+
public async Task<ParameterSetSignatures> FindParameterSetsInFileAsync(
275+
ScriptFile file,
276+
int lineNumber,
277+
int columnNumber,
278+
PowerShellContextService powerShellContext)
279+
{
280+
SymbolReference foundSymbol =
281+
AstOperations.FindCommandAtPosition(
282+
file.ScriptAst,
283+
lineNumber,
284+
columnNumber);
285+
286+
if (foundSymbol == null)
287+
{
288+
return null;
289+
}
290+
291+
CommandInfo commandInfo =
292+
await CommandHelpers.GetCommandInfoAsync(
293+
foundSymbol.SymbolName,
294+
powerShellContext);
295+
296+
if (commandInfo == null)
297+
{
298+
return null;
299+
}
300+
301+
try
302+
{
303+
IEnumerable<CommandParameterSetInfo> commandParamSets = commandInfo.ParameterSets;
304+
return new ParameterSetSignatures(commandParamSets, foundSymbol);
305+
}
306+
catch (RuntimeException e)
307+
{
308+
// A RuntimeException will be thrown when an invalid attribute is
309+
// on a parameter binding block and then that command/script has
310+
// its signatures resolved by typing it into a script.
311+
_logger.LogException("RuntimeException encountered while accessing command parameter sets", e);
312+
313+
return null;
314+
}
315+
catch (InvalidOperationException)
316+
{
317+
// For some commands there are no paramsets (like applications). Until
318+
// the valid command types are better understood, catch this exception
319+
// which gets raised when there are no ParameterSets for the command type.
320+
return null;
321+
}
322+
}
265323
}
266324
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.Extensions.Logging;
10+
using Microsoft.PowerShell.EditorServices;
11+
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
12+
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
13+
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
14+
15+
namespace PowerShellEditorServices.Engine.Services.Handlers
16+
{
17+
public class SignatureHelpHandler : ISignatureHelpHandler
18+
{
19+
private static readonly SignatureInformation[] s_emptySignatureResult = new SignatureInformation[0];
20+
21+
private readonly DocumentSelector _documentSelector = new DocumentSelector(
22+
new DocumentFilter()
23+
{
24+
Pattern = "**/*.ps*1"
25+
}
26+
);
27+
28+
private readonly ILogger _logger;
29+
private readonly SymbolsService _symbolsService;
30+
private readonly WorkspaceService _workspaceService;
31+
private readonly PowerShellContextService _powerShellContextService;
32+
33+
private SignatureHelpCapability _capability;
34+
35+
public SignatureHelpHandler(
36+
ILoggerFactory factory,
37+
SymbolsService symbolsService,
38+
WorkspaceService workspaceService,
39+
PowerShellContextService powerShellContextService)
40+
{
41+
_logger = factory.CreateLogger<HoverHandler>();
42+
_symbolsService = symbolsService;
43+
_workspaceService = workspaceService;
44+
_powerShellContextService = powerShellContextService;
45+
}
46+
47+
public SignatureHelpRegistrationOptions GetRegistrationOptions()
48+
{
49+
return new SignatureHelpRegistrationOptions
50+
{
51+
DocumentSelector = _documentSelector,
52+
// A sane default of " ". We may be able to include others like "-".
53+
TriggerCharacters = new Container<string>(" ")
54+
};
55+
}
56+
57+
public async Task<SignatureHelp> Handle(SignatureHelpParams request, CancellationToken cancellationToken)
58+
{
59+
ScriptFile scriptFile =
60+
_workspaceService.GetFile(
61+
request.TextDocument.Uri.ToString());
62+
63+
ParameterSetSignatures parameterSets =
64+
await _symbolsService.FindParameterSetsInFileAsync(
65+
scriptFile,
66+
(int) request.Position.Line + 1,
67+
(int) request.Position.Character + 1,
68+
_powerShellContextService);
69+
70+
SignatureInformation[] signatures = s_emptySignatureResult;
71+
72+
if (parameterSets != null)
73+
{
74+
signatures = new SignatureInformation[parameterSets.Signatures.Length];
75+
for (int i = 0; i < signatures.Length; i++)
76+
{
77+
var parameters = new ParameterInformation[parameterSets.Signatures[i].Parameters.Count()];
78+
int j = 0;
79+
foreach (ParameterInfo param in parameterSets.Signatures[i].Parameters)
80+
{
81+
parameters[j] = CreateParameterInfo(param);
82+
j++;
83+
}
84+
85+
signatures[i] = new SignatureInformation
86+
{
87+
Label = parameterSets.CommandName + " " + parameterSets.Signatures[i].SignatureText,
88+
Documentation = null,
89+
Parameters = parameters,
90+
};
91+
}
92+
}
93+
94+
return new SignatureHelp
95+
{
96+
Signatures = signatures,
97+
ActiveParameter = null,
98+
ActiveSignature = 0
99+
};
100+
}
101+
102+
public void SetCapability(SignatureHelpCapability capability)
103+
{
104+
_capability = capability;
105+
}
106+
107+
private static ParameterInformation CreateParameterInfo(ParameterInfo parameterInfo)
108+
{
109+
return new ParameterInformation
110+
{
111+
Label = parameterInfo.Name,
112+
Documentation = string.Empty
113+
};
114+
}
115+
}
116+
}

test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
using System;
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;
27
using System.Collections.Generic;
38
using System.Diagnostics;
49
using System.IO;
@@ -664,5 +669,28 @@ public async Task CanSendHoverRequest()
664669
Assert.Equal("Writes customized output to a host.", str2.Value);
665670
});
666671
}
672+
673+
[Fact]
674+
public async Task CanSendSignatureHelpRequest()
675+
{
676+
string filePath = NewTestFile("Get-Date ");
677+
678+
SignatureHelp signatureHelp = await LanguageClient.SendRequest<SignatureHelp>(
679+
"textDocument/signatureHelp",
680+
new SignatureHelpParams
681+
{
682+
TextDocument = new TextDocumentIdentifier
683+
{
684+
Uri = new Uri(filePath)
685+
},
686+
Position = new Position
687+
{
688+
Line = 0,
689+
Character = 9
690+
}
691+
});
692+
693+
Assert.Contains("Get-Date", signatureHelp.Signatures.First().Label);
694+
}
667695
}
668696
}

0 commit comments

Comments
 (0)