Skip to content

Commit

Permalink
Support SignatureHelp
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerLeonhardt committed Aug 21, 2019
1 parent 2b723b0 commit 5580e80
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public async Task StartAsync()
.WithHandler<InvokeExtensionCommandHandler>()
.WithHandler<CompletionHandler>()
.WithHandler<HoverHandler>()
.WithHandler<SignatureHelpHandler>()
.OnInitialize(
async (languageServer, request) =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections.Generic;
using System.Management.Automation;
using Microsoft.PowerShell.EditorServices.Symbols;

namespace Microsoft.PowerShell.EditorServices
{
/// <summary>
/// A class for containing the commandName, the command's
/// possible signatures, and the script extent of the command
/// </summary>
public class ParameterSetSignatures
{
#region Properties
/// <summary>
/// Gets the name of the command
/// </summary>
public string CommandName { get; internal set; }

/// <summary>
/// Gets the collection of signatures for the command
/// </summary>
public ParameterSetSignature[] Signatures { get; internal set; }

/// <summary>
/// Gets the script extent of the command
/// </summary>
public ScriptRegion ScriptRegion { get; internal set; }
#endregion

/// <summary>
/// Constructs an instance of a ParameterSetSignatures object
/// </summary>
/// <param name="commandInfoSet">Collection of parameter set info</param>
/// <param name="foundSymbol"> The SymbolReference of the command</param>
public ParameterSetSignatures(IEnumerable<CommandParameterSetInfo> commandInfoSet, SymbolReference foundSymbol)
{
List<ParameterSetSignature> paramSetSignatures = new List<ParameterSetSignature>();
foreach (CommandParameterSetInfo setInfo in commandInfoSet)
{
paramSetSignatures.Add(new ParameterSetSignature(setInfo));
}
Signatures = paramSetSignatures.ToArray();
CommandName = foundSymbol.ScriptRegion.Text;
ScriptRegion = foundSymbol.ScriptRegion;
}
}

/// <summary>
/// A class for containing the signature text and the collection of parameters for a signature
/// </summary>
public class ParameterSetSignature
{
private static HashSet<string> commonParameterNames =
new HashSet<string>
{
"Verbose",
"Debug",
"ErrorAction",
"WarningAction",
"InformationAction",
"ErrorVariable",
"WarningVariable",
"InformationVariable",
"OutVariable",
"OutBuffer",
"PipelineVariable",
};

#region Properties
/// <summary>
/// Gets the signature text
/// </summary>
public string SignatureText { get; internal set; }

/// <summary>
/// Gets the collection of parameters for the signature
/// </summary>
public IEnumerable<ParameterInfo> Parameters { get; internal set; }
#endregion

/// <summary>
/// Constructs an instance of a ParameterSetSignature
/// </summary>
/// <param name="commandParamInfoSet">Collection of parameter info</param>
public ParameterSetSignature(CommandParameterSetInfo commandParamInfoSet)
{
List<ParameterInfo> parameterInfo = new List<ParameterInfo>();
foreach (CommandParameterInfo commandParameterInfo in commandParamInfoSet.Parameters)
{
if (!commonParameterNames.Contains(commandParameterInfo.Name))
{
parameterInfo.Add(new ParameterInfo(commandParameterInfo));
}
}

SignatureText = commandParamInfoSet.ToString();
Parameters = parameterInfo.ToArray();
}
}

/// <summary>
/// A class for containing the parameter info of a parameter
/// </summary>
public class ParameterInfo
{
#region Properties
/// <summary>
/// Gets the name of the parameter
/// </summary>
public string Name { get; internal set; }

/// <summary>
/// Gets the type of the parameter
/// </summary>
public string ParameterType { get; internal set; }

/// <summary>
/// Gets the position of the parameter
/// </summary>
public int Position { get; internal set; }

/// <summary>
/// Gets a boolean for whetheer or not the parameter is required
/// </summary>
public bool IsMandatory { get; internal set; }

/// <summary>
/// Gets the help message of the parameter
/// </summary>
public string HelpMessage { get; internal set; }
#endregion

/// <summary>
/// Constructs an instance of a ParameterInfo object
/// </summary>
/// <param name="parameterInfo">Parameter info of the parameter</param>
public ParameterInfo(CommandParameterInfo parameterInfo)
{
this.Name = "-" + parameterInfo.Name;
this.ParameterType = parameterInfo.ParameterType.FullName;
this.Position = parameterInfo.Position;
this.IsMandatory = parameterInfo.IsMandatory;
this.HelpMessage = parameterInfo.HelpMessage;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -262,5 +263,62 @@ public async Task<SymbolDetails> FindSymbolDetailsAtLocationAsync(

return symbolDetails;
}

/// <summary>
/// Finds the parameter set hints of a specific command (determined by a given file location)
/// </summary>
/// <param name="file">The details and contents of a open script file</param>
/// <param name="lineNumber">The line number of the cursor for the given script</param>
/// <param name="columnNumber">The coulumn number of the cursor for the given script</param>
/// <returns>ParameterSetSignatures</returns>
public async Task<ParameterSetSignatures> FindParameterSetsInFileAsync(
ScriptFile file,
int lineNumber,
int columnNumber,
PowerShellContextService powerShellContext)
{
SymbolReference foundSymbol =
AstOperations.FindCommandAtPosition(
file.ScriptAst,
lineNumber,
columnNumber);

if (foundSymbol == null)
{
return null;
}

CommandInfo commandInfo =
await CommandHelpers.GetCommandInfoAsync(
foundSymbol.SymbolName,
powerShellContext);

if (commandInfo == null)
{
return null;
}

try
{
IEnumerable<CommandParameterSetInfo> commandParamSets = commandInfo.ParameterSets;
return new ParameterSetSignatures(commandParamSets, foundSymbol);
}
catch (RuntimeException e)
{
// A RuntimeException will be thrown when an invalid attribute is
// on a parameter binding block and then that command/script has
// its signatures resolved by typing it into a script.
_logger.LogException("RuntimeException encountered while accessing command parameter sets", e);

return null;
}
catch (InvalidOperationException)
{
// For some commands there are no paramsets (like applications). Until
// the valid command types are better understood, catch this exception
// which gets raised when there are no ParameterSets for the command type.
return null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;

namespace PowerShellEditorServices.Engine.Services.Handlers
{
public class SignatureHelpHandler : ISignatureHelpHandler
{
private static readonly SignatureInformation[] s_emptySignatureResult = new SignatureInformation[0];

private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
}
);

private readonly ILogger _logger;
private readonly SymbolsService _symbolsService;
private readonly WorkspaceService _workspaceService;
private readonly PowerShellContextService _powerShellContextService;

private SignatureHelpCapability _capability;

public SignatureHelpHandler(
ILoggerFactory factory,
SymbolsService symbolsService,
WorkspaceService workspaceService,
PowerShellContextService powerShellContextService)
{
_logger = factory.CreateLogger<HoverHandler>();
_symbolsService = symbolsService;
_workspaceService = workspaceService;
_powerShellContextService = powerShellContextService;
}

public SignatureHelpRegistrationOptions GetRegistrationOptions()
{
return new SignatureHelpRegistrationOptions
{
DocumentSelector = _documentSelector,
// A sane default of " ". We may be able to include others like "-".
TriggerCharacters = new Container<string>(" ")
};
}

public async Task<SignatureHelp> Handle(SignatureHelpParams request, CancellationToken cancellationToken)
{
ScriptFile scriptFile =
_workspaceService.GetFile(
request.TextDocument.Uri.ToString());

ParameterSetSignatures parameterSets =
await _symbolsService.FindParameterSetsInFileAsync(
scriptFile,
(int) request.Position.Line + 1,
(int) request.Position.Character + 1,
_powerShellContextService);

SignatureInformation[] signatures = s_emptySignatureResult;

if (parameterSets != null)
{
signatures = new SignatureInformation[parameterSets.Signatures.Length];
for (int i = 0; i < signatures.Length; i++)
{
var parameters = new ParameterInformation[parameterSets.Signatures[i].Parameters.Count()];
int j = 0;
foreach (ParameterInfo param in parameterSets.Signatures[i].Parameters)
{
parameters[j] = CreateParameterInfo(param);
j++;
}

signatures[i] = new SignatureInformation
{
Label = parameterSets.CommandName + " " + parameterSets.Signatures[i].SignatureText,
Documentation = null,
Parameters = parameters,
};
}
}

return new SignatureHelp
{
Signatures = signatures,
ActiveParameter = null,
ActiveSignature = 0
};
}

public void SetCapability(SignatureHelpCapability capability)
{
_capability = capability;
}

private static ParameterInformation CreateParameterInfo(ParameterInfo parameterInfo)
{
return new ParameterInformation
{
Label = parameterInfo.Name,
Documentation = string.Empty
};
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using System;
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand Down Expand Up @@ -664,5 +669,28 @@ public async Task CanSendHoverRequest()
Assert.Equal("Writes customized output to a host.", str2.Value);
});
}

[Fact]
public async Task CanSendSignatureHelpRequest()
{
string filePath = NewTestFile("Get-Date ");

SignatureHelp signatureHelp = await LanguageClient.SendRequest<SignatureHelp>(
"textDocument/signatureHelp",
new SignatureHelpParams
{
TextDocument = new TextDocumentIdentifier
{
Uri = new Uri(filePath)
},
Position = new Position
{
Line = 0,
Character = 9
}
});

Assert.Contains("Get-Date", signatureHelp.Signatures.First().Label);
}
}
}

0 comments on commit 5580e80

Please sign in to comment.