Skip to content

Commit c4d8f7f

Browse files
Support SignatureHelp
1 parent b93bce7 commit c4d8f7f

File tree

5 files changed

+355
-1
lines changed

5 files changed

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

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)