diff --git a/scripts/azurePipelinesBuild.ps1 b/scripts/azurePipelinesBuild.ps1
index ab31ed358..774f57815 100644
--- a/scripts/azurePipelinesBuild.ps1
+++ b/scripts/azurePipelinesBuild.ps1
@@ -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
diff --git a/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs b/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs
index 9f1152270..807b5bcaa 100644
--- a/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs
+++ b/src/PowerShellEditorServices.Engine/LanguageServer/OmnisharpLanguageServer.cs
@@ -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)
@@ -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
diff --git a/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Utilities/CommandHelpers.cs b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Utilities/CommandHelpers.cs
new file mode 100644
index 000000000..a73689dd4
--- /dev/null
+++ b/src/PowerShellEditorServices.Engine/Services/PowerShellContext/Utilities/CommandHelpers.cs
@@ -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
+{
+ ///
+ /// Provides utility methods for working with PowerShell commands.
+ ///
+ public static class CommandHelpers
+ {
+ private static readonly ConcurrentDictionary NounExclusionList =
+ new ConcurrentDictionary();
+
+ 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);
+ }
+
+ ///
+ /// Gets the CommandInfo instance for a command with a particular name.
+ ///
+ /// The name of the command.
+ /// The PowerShellContext to use for running Get-Command.
+ /// A CommandInfo object with details about the specified command.
+ public static async Task 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(command, false, false))
+ .Select(o => o.BaseObject)
+ .OfType()
+ .FirstOrDefault();
+ }
+
+ ///
+ /// Gets the command's "Synopsis" documentation section.
+ ///
+ /// The CommandInfo instance for the command.
+ /// The PowerShellContext to use for getting command documentation.
+ ///
+ public static async Task 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(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;
+ }
+ }
+}
diff --git a/src/PowerShellEditorServices.Engine/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices.Engine/Services/Symbols/Vistors/AstOperations.cs
index ede463fb3..fd54cc7d1 100644
--- a/src/PowerShellEditorServices.Engine/Services/Symbols/Vistors/AstOperations.cs
+++ b/src/PowerShellEditorServices.Engine/Services/Symbols/Vistors/AstOperations.cs
@@ -55,7 +55,7 @@ internal static class AstOperations
/// A CommandCompletion instance that contains completions for the
/// symbol at the given offset.
///
- static public async Task GetCompletionsAsync(
+ public static async Task GetCompletionsAsync(
Ast scriptAst,
Token[] currentTokens,
int fileOffset,
@@ -146,7 +146,7 @@ await powerShellContext.InvokeOnPipelineThreadAsync(
/// The coulumn number of the cursor for the given script
/// Includes full function definition ranges in the search.
/// SymbolReference of found symbol
- static public SymbolReference FindSymbolAtPosition(
+ public static SymbolReference FindSymbolAtPosition(
Ast scriptAst,
int lineNumber,
int columnNumber,
@@ -170,7 +170,7 @@ static public SymbolReference FindSymbolAtPosition(
/// The line number of the cursor for the given script
/// The column number of the cursor for the given script
/// SymbolReference of found command
- 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);
@@ -186,7 +186,7 @@ static public SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumbe
/// Dictionary maping cmdlets to aliases for finding alias references
/// Dictionary maping aliases to cmdlets for finding alias references
///
- static public IEnumerable FindReferencesOfSymbol(
+ public static IEnumerable FindReferencesOfSymbol(
Ast scriptAst,
SymbolReference symbolReference,
Dictionary> CmdletToAliasDictionary,
@@ -212,7 +212,7 @@ static public IEnumerable FindReferencesOfSymbol(
/// This should always be false and used for occurence requests
/// A collection of SymbolReference objects that are refrences to the symbolRefrence
/// not including aliases
- static public IEnumerable FindReferencesOfSymbol(
+ public static IEnumerable FindReferencesOfSymbol(
ScriptBlockAst scriptAst,
SymbolReference foundSymbol,
bool needsAliases)
@@ -230,7 +230,7 @@ static public IEnumerable FindReferencesOfSymbol(
/// The abstract syntax tree of the given script
/// The symbol that we are looking for the definition of
/// A SymbolReference of the definition of the symbolReference
- static public SymbolReference FindDefinitionOfSymbol(
+ public static SymbolReference FindDefinitionOfSymbol(
Ast scriptAst,
SymbolReference symbolReference)
{
@@ -248,7 +248,7 @@ static public SymbolReference FindDefinitionOfSymbol(
/// The abstract syntax tree of the given script
/// The PowerShell version the Ast was generated from
/// A collection of SymbolReference objects
- static public IEnumerable FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion)
+ public static IEnumerable FindSymbolsInDocument(Ast scriptAst, Version powerShellVersion)
{
IEnumerable symbolReferences = null;
@@ -275,7 +275,7 @@ static public IEnumerable FindSymbolsInDocument(Ast scriptAst,
///
/// The abstract syntax tree of the given script
/// true if the AST represts a *.psd1 file, otherwise false
- 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
@@ -330,7 +330,7 @@ static private bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap
/// The abstract syntax tree of the given script
/// Pre-calculated value of $PSScriptRoot
///
- static public string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot)
+ public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot)
{
FindDotSourcedVisitor dotSourcedVisitor = new FindDotSourcedVisitor(psScriptRoot);
scriptAst.Visit(dotSourcedVisitor);
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/CompletionResults.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/CompletionResults.cs
index 3f59eccfe..433d4e5c3 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/CompletionResults.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/CompletionResults.cs
@@ -243,8 +243,7 @@ internal static CompletionDetails Create(
/// True if the CompletionResults instances have the same details.
public override bool Equals(object obj)
{
- CompletionDetails otherDetails = obj as CompletionDetails;
- if (otherDetails == null)
+ if (!(obj is CompletionDetails otherDetails))
{
return false;
}
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeActionHandler.cs
index 64f994423..7afd6f37e 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeActionHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeActionHandler.cs
@@ -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
};
}
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeLensHandlers.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeLensHandlers.cs
index 463fbf767..f83e35d18 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeLensHandlers.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CodeLensHandlers.cs
@@ -19,7 +19,7 @@ public class CodeLensHandlers : ICodeLensHandler, ICodeLensResolveHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
- Pattern = "**/*.ps*1"
+ Language = "powershell"
}
);
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CompletionHandler.cs
index e66a5d709..0f4d81406 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CompletionHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/CompletionHandler.cs
@@ -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 Handle(CompletionItem request, CancellationToken cancellationToken)
+ {
+ // 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;
}
}
+
+ private static bool EndsWithQuote(string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ {
+ return false;
+ }
+
+ char lastChar = text[text.Length - 1];
+ return lastChar == '"' || lastChar == '\'';
+ }
}
}
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentHighlightHandler.cs
index aafc1c79f..838d40331 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentHighlightHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentHighlightHandler.cs
@@ -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");
}
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentSymbolHandler.cs
index e5289746c..6cbe8ace7 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentSymbolHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/DocumentSymbolHandler.cs
@@ -20,7 +20,7 @@ public class DocumentSymbolHandler : IDocumentSymbolHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
- Pattern = "**/*.ps*1"
+ Language = "powershell"
}
);
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FoldingRangeHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FoldingRangeHandler.cs
index 9aee91b0e..928eb5d03 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FoldingRangeHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FoldingRangeHandler.cs
@@ -14,7 +14,7 @@ public class FoldingRangeHandler : IFoldingRangeHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
- Pattern = "**/*.ps*1"
+ Language = "powershell"
}
);
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FormattingHandlers.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FormattingHandlers.cs
index 57c0744a1..4565d7ff8 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FormattingHandlers.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/FormattingHandlers.cs
@@ -15,7 +15,7 @@ internal class DocumentFormattingHandler : IDocumentFormattingHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
- Pattern = "**/*.ps*1"
+ Language = "powershell"
}
);
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/ReferencesHandler.cs
index 6e10d5fe8..d49f7f8d2 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/ReferencesHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/ReferencesHandler.cs
@@ -16,7 +16,7 @@ class ReferencesHandler : IReferencesHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
- Pattern = "**/*.ps*1"
+ Language = "powershell"
}
);
diff --git a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/TextDocumentHandler.cs b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/TextDocumentHandler.cs
index 34f5358b9..d7ec17827 100644
--- a/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/TextDocumentHandler.cs
+++ b/src/PowerShellEditorServices.Engine/Services/TextDocument/Handlers/TextDocumentHandler.cs
@@ -24,7 +24,7 @@ class TextDocumentHandler : ITextDocumentSyncHandler
private readonly DocumentSelector _documentSelector = new DocumentSelector(
new DocumentFilter()
{
- Pattern = "**/*.ps*1"
+ Language = "powershell"
}
);
diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs
index 7c5bd52b9..a29563f70 100644
--- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs
+++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs
@@ -628,18 +628,21 @@ await LanguageClient.SendRequest(
}
[Fact]
- public async Task CanSendCompletionRequest()
+ public async Task CanSendCompletionAndCompletionResolveRequest()
{
string filePath = NewTestFile("Write-H");
CompletionList completionItems = await LanguageClient.TextDocument.Completions(
filePath, line: 0, column: 7);
- Assert.Collection(completionItems,
- completionItem1 => {
- Assert.Equal("Write-Host", completionItem1.Label);
- }
- );
+ CompletionItem completionItem = Assert.Single(completionItems,
+ completionItem1 => completionItem1.Label == "Write-Host");
+
+ CompletionItem updatedCompletionItem = await LanguageClient.SendRequest(
+ "completionItem/resolve",
+ completionItem);
+
+ Assert.Contains("Writes customized output to a host", updatedCompletionItem.Documentation.String);
}
}
}