-
Notifications
You must be signed in to change notification settings - Fork 235
completionresolve support #1009
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
Changes from all commits
87a4aca
97b9d90
0ad5d6b
f419a80
db6ad9a
8e3b4a8
e0c3865
f6430ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// | ||
// 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.Linq; | ||
using System.Management.Automation; | ||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.PowerShell.EditorServices | ||
{ | ||
/// <summary> | ||
/// Provides utility methods for working with PowerShell commands. | ||
/// </summary> | ||
public static class CommandHelpers | ||
{ | ||
private static readonly ConcurrentDictionary<string, bool> NounExclusionList = | ||
new ConcurrentDictionary<string, bool>(); | ||
|
||
static CommandHelpers() | ||
{ | ||
NounExclusionList.TryAdd("Module", true); | ||
NounExclusionList.TryAdd("Script", true); | ||
NounExclusionList.TryAdd("Package", true); | ||
NounExclusionList.TryAdd("PackageProvider", true); | ||
NounExclusionList.TryAdd("PackageSource", true); | ||
NounExclusionList.TryAdd("InstalledModule", true); | ||
NounExclusionList.TryAdd("InstalledScript", true); | ||
NounExclusionList.TryAdd("ScriptFileInfo", true); | ||
NounExclusionList.TryAdd("PSRepository", true); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the CommandInfo instance for a command with a particular name. | ||
/// </summary> | ||
/// <param name="commandName">The name of the command.</param> | ||
/// <param name="powerShellContext">The PowerShellContext to use for running Get-Command.</param> | ||
/// <returns>A CommandInfo object with details about the specified command.</returns> | ||
public static async Task<CommandInfo> GetCommandInfoAsync( | ||
string commandName, | ||
PowerShellContextService powerShellContext) | ||
{ | ||
Validate.IsNotNull(nameof(commandName), commandName); | ||
|
||
// Make sure the command's noun isn't blacklisted. This is | ||
// currently necessary to make sure that Get-Command doesn't | ||
// load PackageManagement or PowerShellGet because they cause | ||
// a major slowdown in IntelliSense. | ||
var commandParts = commandName.Split('-'); | ||
if (commandParts.Length == 2 && NounExclusionList.ContainsKey(commandParts[1])) | ||
{ | ||
return null; | ||
} | ||
|
||
PSCommand command = new PSCommand(); | ||
command.AddCommand(@"Microsoft.PowerShell.Core\Get-Command"); | ||
command.AddArgument(commandName); | ||
command.AddParameter("ErrorAction", "Ignore"); | ||
|
||
return | ||
(await powerShellContext | ||
.ExecuteCommandAsync<PSObject>(command, false, false)) | ||
.Select(o => o.BaseObject) | ||
.OfType<CommandInfo>() | ||
.FirstOrDefault(); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the command's "Synopsis" documentation section. | ||
/// </summary> | ||
/// <param name="commandInfo">The CommandInfo instance for the command.</param> | ||
/// <param name="powerShellContext">The PowerShellContext to use for getting command documentation.</param> | ||
/// <returns></returns> | ||
public static async Task<string> GetCommandSynopsisAsync( | ||
CommandInfo commandInfo, | ||
PowerShellContextService powerShellContext) | ||
{ | ||
string synopsisString = string.Empty; | ||
|
||
if (commandInfo != null && | ||
(commandInfo.CommandType == CommandTypes.Cmdlet || | ||
commandInfo.CommandType == CommandTypes.Function || | ||
commandInfo.CommandType == CommandTypes.Filter)) | ||
{ | ||
PSCommand command = new PSCommand(); | ||
command.AddCommand(@"Microsoft.PowerShell.Core\Get-Help"); | ||
command.AddArgument(commandInfo); | ||
command.AddParameter("ErrorAction", "Ignore"); | ||
|
||
var results = await powerShellContext.ExecuteCommandAsync<PSObject>(command, false, false); | ||
PSObject helpObject = results.FirstOrDefault(); | ||
|
||
if (helpObject != null) | ||
{ | ||
// Extract the synopsis string from the object | ||
synopsisString = | ||
(string)helpObject.Properties["synopsis"].Value ?? | ||
string.Empty; | ||
|
||
// Ignore the placeholder value for this field | ||
if (string.Equals(synopsisString, "SHORT DESCRIPTION", System.StringComparison.CurrentCultureIgnoreCase)) | ||
{ | ||
synopsisString = string.Empty; | ||
} | ||
} | ||
} | ||
|
||
return synopsisString; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ | |
|
||
namespace Microsoft.PowerShell.EditorServices.TextDocument | ||
{ | ||
internal class CompletionHandler : ICompletionHandler | ||
internal class CompletionHandler : ICompletionHandler, ICompletionResolveHandler | ||
{ | ||
const int DefaultWaitTimeoutMilliseconds = 5000; | ||
private readonly CompletionItem[] s_emptyCompletionResult = new CompletionItem[0]; | ||
|
@@ -49,7 +49,7 @@ public CompletionRegistrationOptions GetRegistrationOptions() | |
{ | ||
return new CompletionRegistrationOptions | ||
{ | ||
DocumentSelector = new DocumentSelector(new DocumentFilter { Pattern = "**/*.ps*1" }), | ||
DocumentSelector = new DocumentSelector(new DocumentFilter { Language = "powershell" }), | ||
ResolveProvider = true, | ||
TriggerCharacters = new[] { ".", "-", ":", "\\" } | ||
}; | ||
|
@@ -84,6 +84,32 @@ await GetCompletionsInFileAsync( | |
return new CompletionList(completionItems); | ||
} | ||
|
||
public bool CanResolve(CompletionItem value) | ||
{ | ||
return value.Kind == CompletionItemKind.Function; | ||
} | ||
|
||
// Handler for "completionItem/resolve". In VSCode this is fired when a completion item is highlighted in the completion list. | ||
public async Task<CompletionItem> Handle(CompletionItem request, CancellationToken cancellationToken) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When is this called? Just on select? Or for every completion item? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's called when a completion item is highlighted in the completion list |
||
{ | ||
// Get the documentation for the function | ||
CommandInfo commandInfo = | ||
await CommandHelpers.GetCommandInfoAsync( | ||
request.Label, | ||
_powerShellContextService); | ||
|
||
if (commandInfo != null) | ||
{ | ||
request.Documentation = | ||
await CommandHelpers.GetCommandSynopsisAsync( | ||
commandInfo, | ||
_powerShellContextService); | ||
} | ||
|
||
// Send back the updated CompletionItem | ||
return request; | ||
} | ||
|
||
public void SetCapability(CompletionCapability capability) | ||
{ | ||
_capability = capability; | ||
|
@@ -213,8 +239,7 @@ private static CompletionItem CreateCompletionItem( | |
} | ||
} | ||
} | ||
else if ((completionDetails.CompletionType == CompletionType.Folder) && | ||
(completionText.EndsWith("\"") || completionText.EndsWith("'"))) | ||
else if (completionDetails.CompletionType == CompletionType.Folder && EndsWithQuote(completionText)) | ||
{ | ||
// Insert a final "tab stop" as identified by $0 in the snippet provided for completion. | ||
// For folder paths, we take the path returned by PowerShell e.g. 'C:\Program Files' and insert | ||
|
@@ -291,5 +316,16 @@ private static CompletionItemKind MapCompletionKind(CompletionType completionTyp | |
return CompletionItemKind.Text; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re: expression switch/array map |
||
} | ||
} | ||
|
||
private static bool EndsWithQuote(string text) | ||
{ | ||
if (string.IsNullOrEmpty(text)) | ||
{ | ||
return false; | ||
} | ||
|
||
char lastChar = text[text.Length - 1]; | ||
return lastChar == '"' || lastChar == '\''; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm planning on factoring this out into an abstract class or interface... just haven't gotten around to it.