Skip to content

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

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
3 changes: 3 additions & 0 deletions scripts/azurePipelinesBuild.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ if ($IsWindows -or $PSVersionTable.PSVersion.Major -lt 6) {
Import-Module -Name PackageManagement -MinimumVersion 1.1.7.0 -Force
}

# Update help needed for SignatureHelp LSP request.
Update-Help -Force -ErrorAction SilentlyContinue

# Needed for build and docs gen.
Install-Module InvokeBuild -MaximumVersion 5.1.0 -Scope CurrentUser
Install-Module PlatyPS -RequiredVersion 0.9.0 -Scope CurrentUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public async Task StartAsync()
{
_languageServer = await OS.LanguageServer.From(options => {

options.AddDefaultLoggingProvider();
options.LoggerFactory = _configuration.LoggerFactory;
ILogger logger = options.LoggerFactory.CreateLogger("OptionsStartup");

if (_configuration.Stdio)
Expand Down Expand Up @@ -89,10 +91,8 @@ public async Task StartAsync()
options.Output = outNamedPipe ?? namedPipe;
}

options.LoggerFactory = _configuration.LoggerFactory;
options.MinimumLogLevel = _configuration.MinimumLogLevel;
options.Services = _configuration.Services;

logger.LogInformation("Adding handlers");

options
Expand Down
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
Expand Up @@ -55,7 +55,7 @@ internal static class AstOperations
/// A CommandCompletion instance that contains completions for the
/// symbol at the given offset.
/// </returns>
static public async Task<CommandCompletion> GetCompletionsAsync(
public static async Task<CommandCompletion> GetCompletionsAsync(
Ast scriptAst,
Token[] currentTokens,
int fileOffset,
Expand Down Expand Up @@ -146,7 +146,7 @@ await powerShellContext.InvokeOnPipelineThreadAsync(
/// <param name="columnNumber">The coulumn number of the cursor for the given script</param>
/// <param name="includeFunctionDefinitions">Includes full function definition ranges in the search.</param>
/// <returns>SymbolReference of found symbol</returns>
static public SymbolReference FindSymbolAtPosition(
public static SymbolReference FindSymbolAtPosition(
Ast scriptAst,
int lineNumber,
int columnNumber,
Expand All @@ -170,7 +170,7 @@ static public SymbolReference FindSymbolAtPosition(
/// <param name="lineNumber">The line number of the cursor for the given script</param>
/// <param name="columnNumber">The column number of the cursor for the given script</param>
/// <returns>SymbolReference of found command</returns>
static public SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumber, int columnNumber)
public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumber, int columnNumber)
{
FindCommandVisitor commandVisitor = new FindCommandVisitor(lineNumber, columnNumber);
scriptAst.Visit(commandVisitor);
Expand All @@ -186,7 +186,7 @@ static public SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe
/// <param name="CmdletToAliasDictionary">Dictionary maping cmdlets to aliases for finding alias references</param>
/// <param name="AliasToCmdletDictionary">Dictionary maping aliases to cmdlets for finding alias references</param>
/// <returns></returns>
static public IEnumerable<SymbolReference> FindReferencesOfSymbol(
public static IEnumerable<SymbolReference> FindReferencesOfSymbol(
Ast scriptAst,
SymbolReference symbolReference,
Dictionary<String, List<String>> CmdletToAliasDictionary,
Expand All @@ -212,7 +212,7 @@ static public IEnumerable<SymbolReference> FindReferencesOfSymbol(
/// This should always be false and used for occurence requests</param>
/// <returns>A collection of SymbolReference objects that are refrences to the symbolRefrence
/// not including aliases</returns>
static public IEnumerable<SymbolReference> FindReferencesOfSymbol(
public static IEnumerable<SymbolReference> FindReferencesOfSymbol(
ScriptBlockAst scriptAst,
SymbolReference foundSymbol,
bool needsAliases)
Expand All @@ -230,7 +230,7 @@ static public IEnumerable<SymbolReference> FindReferencesOfSymbol(
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
/// <param name="symbolReference">The symbol that we are looking for the definition of</param>
/// <returns>A SymbolReference of the definition of the symbolReference</returns>
static public SymbolReference FindDefinitionOfSymbol(
public static SymbolReference FindDefinitionOfSymbol(
Ast scriptAst,
SymbolReference symbolReference)
{
Expand All @@ -248,7 +248,7 @@ static public SymbolReference FindDefinitionOfSymbol(
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
/// <param name="powerShellVersion">The PowerShell version the Ast was generated from</param>
/// <returns>A collection of SymbolReference objects</returns>
static public IEnumerable<SymbolReference> FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion)
public static IEnumerable<SymbolReference> FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion)
{
IEnumerable<SymbolReference> symbolReferences = null;

Expand All @@ -275,7 +275,7 @@ static public IEnumerable<SymbolReference> FindSymbolsInDocument(Ast scriptAst,
/// </summary>
/// <param name="ast">The abstract syntax tree of the given script</param>
/// <returns>true if the AST represts a *.psd1 file, otherwise false</returns>
static public bool IsPowerShellDataFileAst(Ast ast)
public static bool IsPowerShellDataFileAst(Ast ast)
{
// sometimes we don't have reliable access to the filename
// so we employ heuristics to check if the contents are
Expand Down Expand Up @@ -330,7 +330,7 @@ static private bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap
/// <param name="scriptAst">The abstract syntax tree of the given script</param>
/// <param name="psScriptRoot">Pre-calculated value of $PSScriptRoot</param>
/// <returns></returns>
static public string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot)
public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot)
{
FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor(psScriptRoot);
scriptAst.Visit(dotSourcedVisitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,7 @@ internal static CompletionDetails Create(
/// <returns>True if the CompletionResults instances have the same details.</returns>
public override bool Equals(object obj)
{
CompletionDetails otherDetails = obj as CompletionDetails;
if (otherDetails == null)
if (!(obj is CompletionDetails otherDetails))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public CodeActionHandler(ILoggerFactory factory, AnalysisService analysisService
_analysisService = analysisService;
_registrationOptions = new CodeActionRegistrationOptions()
{
DocumentSelector = new DocumentSelector(new DocumentFilter() { Pattern = "**/*.ps*1" }),
DocumentSelector = new DocumentSelector(new DocumentFilter() { Language = "powershell" }),
CodeActionKinds = s_supportedCodeActions
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class CodeLensHandlers : ICodeLensHandler, ICodeLensResolveHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
Language = "powershell"
Copy link
Member Author

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.

}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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[] { ".", "-", ":", "\\" }
};
Expand Down Expand Up @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this called? Just on select? Or for every completion item?

Copy link
Member Author

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -291,5 +316,16 @@ private static CompletionItemKind MapCompletionKind(CompletionType completionTyp
return CompletionItemKind.Text;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 == '\'';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public DocumentHighlightHandler(
_symbolsService = symbolService;
_registrationOptions = new TextDocumentRegistrationOptions()
{
DocumentSelector = new DocumentSelector(new DocumentFilter() { Pattern = "**/*.ps*1" } )
DocumentSelector = new DocumentSelector(new DocumentFilter() { Language = "powershell" } )
};
_logger.LogInformation("highlight handler loaded");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class DocumentSymbolHandler : IDocumentSymbolHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
Language = "powershell"
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class FoldingRangeHandler : IFoldingRangeHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
Language = "powershell"
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class DocumentFormattingHandler : IDocumentFormattingHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
Language = "powershell"
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ReferencesHandler : IReferencesHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
Language = "powershell"
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class TextDocumentHandler : ITextDocumentSyncHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
Pattern = "**/*.ps*1"
Language = "powershell"
}
);

Expand Down
Loading