Skip to content

Omni signaturehelp #1011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,154 @@
//
// 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.Concurrent;
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 readonly ConcurrentDictionary<string, bool> commonParameterNames =
new ConcurrentDictionary<string, bool>();

static ParameterSetSignature()
{
commonParameterNames.TryAdd("Verbose", true);
commonParameterNames.TryAdd("Debug", true);
commonParameterNames.TryAdd("ErrorAction", true);
commonParameterNames.TryAdd("WarningAction", true);
commonParameterNames.TryAdd("InformationAction", true);
commonParameterNames.TryAdd("ErrorVariable", true);
commonParameterNames.TryAdd("WarningVariable", true);
commonParameterNames.TryAdd("InformationVariable", true);
commonParameterNames.TryAdd("OutVariable", true);
commonParameterNames.TryAdd("OutBuffer", true);
commonParameterNames.TryAdd("PipelineVariable", true);
}

#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.ContainsKey(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);
}
}
}