diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs
index 29e36ce25..c3d7a39c5 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CompletionHandler.cs
@@ -327,15 +327,15 @@ CompletionResultType.ProviderItem or CompletionResultType.ProviderContainer
Data = item.Detail,
Detail = SupportsMarkdown ? null : item.Detail,
},
- CompletionResultType.ParameterName => TryExtractType(detail, out string type)
- ? item with { Kind = CompletionItemKind.Variable, Detail = type }
+ CompletionResultType.ParameterName => TryExtractType(detail, item.Label, out string type, out string documentation)
+ ? item with { Kind = CompletionItemKind.Variable, Detail = type, Documentation = documentation }
// The comparison operators (-eq, -not, -gt, etc) unfortunately come across as
// ParameterName types but they don't have a type associated to them, so we can
// deduce it is an operator.
: item with { Kind = CompletionItemKind.Operator },
CompletionResultType.ParameterValue => item with { Kind = CompletionItemKind.Value },
- CompletionResultType.Variable => TryExtractType(detail, out string type)
- ? item with { Kind = CompletionItemKind.Variable, Detail = type }
+ CompletionResultType.Variable => TryExtractType(detail, "$" + item.Label, out string type, out string documentation)
+ ? item with { Kind = CompletionItemKind.Variable, Detail = type, Documentation = documentation }
: item with { Kind = CompletionItemKind.Variable },
CompletionResultType.Namespace => item with { Kind = CompletionItemKind.Module },
CompletionResultType.Type => detail.StartsWith("Class ", StringComparison.CurrentCulture)
@@ -450,16 +450,45 @@ private static string GetTypeFilterText(string textToBeReplaced, string completi
/// type names in [] to be consistent with PowerShell syntax and how the debugger displays
/// type names.
///
- ///
- ///
+ /// The tooltip text to parse
+ /// The extracted type string, if found
+ /// The remaining text after the type, if any
+ /// The label to check for in the documentation prefix
/// Whether or not the type was found.
- private static bool TryExtractType(string toolTipText, out string type)
+ internal static bool TryExtractType(string toolTipText, string label, out string type, out string documentation)
{
MatchCollection matches = s_typeRegex.Matches(toolTipText);
type = string.Empty;
+ documentation = null; //We use null instead of String.Empty to indicate no documentation was found.
+
if ((matches.Count > 0) && (matches[0].Groups.Count > 1))
{
type = matches[0].Groups[1].Value;
+
+ // Extract the description as everything after the type
+ if (matches[0].Length < toolTipText.Length)
+ {
+ documentation = toolTipText.Substring(matches[0].Length).Trim();
+
+ if (documentation is not null)
+ {
+ // If the substring is the same as the label, documentation should remain blank
+ if (documentation.Equals(label, StringComparison.OrdinalIgnoreCase))
+ {
+ documentation = null;
+ }
+ // If the documentation starts with "label - ", remove this prefix
+ else if (documentation.StartsWith(label + " - ", StringComparison.OrdinalIgnoreCase))
+ {
+ documentation = documentation.Substring((label + " - ").Length).Trim();
+ }
+ }
+ if (string.IsNullOrWhiteSpace(documentation))
+ {
+ documentation = null;
+ }
+ }
+
return true;
}
return false;
diff --git a/test/PowerShellEditorServices.Test.Shared/Completion/CompletionExamples.psm1 b/test/PowerShellEditorServices.Test.Shared/Completion/CompletionExamples.psm1
index b8427615e..840ec9fdf 100644
--- a/test/PowerShellEditorServices.Test.Shared/Completion/CompletionExamples.psm1
+++ b/test/PowerShellEditorServices.Test.Shared/Completion/CompletionExamples.psm1
@@ -13,7 +13,7 @@ Import-Module PowerShellGet
Get-Rand
function Test-Completion {
- param([Parameter(Mandatory, Value)])
+ param([Parameter(Mandatory, Value)]$test)
}
Get-ChildItem /
diff --git a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs
index 35546cefe..fb8076476 100644
--- a/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs
+++ b/test/PowerShellEditorServices.Test/Language/CompletionHandlerTests.cs
@@ -105,7 +105,6 @@ public async Task CompletesVariableInFile()
[SkippableFact]
public async Task CompletesAttributeValue()
{
- Skip.If(VersionUtils.IsPS74, "PowerShell 7.4 isn't returning these!");
(_, IEnumerable results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails);
// NOTE: Since the completions come through un-ordered from PowerShell, their SortText
// (which has an index prepended from the original order) will mis-match our assumed
@@ -126,5 +125,28 @@ public async Task CompletesFilePath()
Assert.Equal(actual.TextEdit.TextEdit with { NewText = "" }, CompleteFilePath.ExpectedEdit);
Assert.All(results, r => Assert.True(r.Kind is CompletionItemKind.File or CompletionItemKind.Folder));
}
+
+ // TODO: These should be an integration tests at a higher level if/when https://github.com/PowerShell/PowerShell/pull/25108 is merged. As of today, we can't actually test this in the PS engine currently.
+ [Fact]
+ public void CanExtractTypeAndDescriptionFromTooltip()
+ {
+ string expectedType = "[string]";
+ string expectedDescription = "Test String";
+ string paramName = "TestParam";
+ string testHelp = $"{expectedType} {paramName} - {expectedDescription}";
+ Assert.True(PsesCompletionHandler.TryExtractType(testHelp, paramName, out string type, out string description));
+ Assert.Equal(expectedType, type);
+ Assert.Equal(expectedDescription, description);
+ }
+
+ [Fact]
+ public void CanExtractTypeFromTooltip()
+ {
+ string expectedType = "[string]";
+ string testHelp = $"{expectedType}";
+ Assert.True(PsesCompletionHandler.TryExtractType(testHelp, string.Empty, out string type, out string description));
+ Assert.Null(description);
+ Assert.Equal(expectedType, type);
+ }
}
}
diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
index 359a424f8..28a51ae3a 100644
--- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
+++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
@@ -796,37 +796,37 @@ public void FindsSymbolsInFile()
Assert.False(symbol.IsDeclaration);
AssertIsRegion(symbol.NameRegion, 16, 29, 16, 39);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Workflow));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Workflow);
Assert.Equal("fn AWorkflow", symbol.Id);
Assert.Equal("workflow AWorkflow ()", symbol.Name);
Assert.True(symbol.IsDeclaration);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Class);
Assert.Equal("type AClass", symbol.Id);
Assert.Equal("class AClass { }", symbol.Name);
Assert.True(symbol.IsDeclaration);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Property);
Assert.Equal("prop AProperty", symbol.Id);
Assert.Equal("[string] $AProperty", symbol.Name);
Assert.True(symbol.IsDeclaration);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Constructor);
Assert.Equal("mtd AClass", symbol.Id);
Assert.Equal("AClass([string]$AParameter)", symbol.Name);
Assert.True(symbol.IsDeclaration);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Method);
Assert.Equal("mtd AMethod", symbol.Id);
Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.Name);
Assert.True(symbol.IsDeclaration);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Enum);
Assert.Equal("type AEnum", symbol.Id);
Assert.Equal("enum AEnum { }", symbol.Name);
Assert.True(symbol.IsDeclaration);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.EnumMember);
Assert.Equal("prop AValue", symbol.Id);
Assert.Equal("AValue", symbol.Name);
Assert.True(symbol.IsDeclaration);
@@ -866,38 +866,38 @@ public void FindsSymbolsWithNewLineInFile()
{
IEnumerable symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails);
- SymbolReference symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Function));
+ SymbolReference symbol = Assert.Single(symbols, i => i.Type == SymbolType.Function);
Assert.Equal("fn returnTrue", symbol.Id);
AssertIsRegion(symbol.NameRegion, 2, 1, 2, 11);
AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Class);
Assert.Equal("type NewLineClass", symbol.Id);
AssertIsRegion(symbol.NameRegion, 7, 1, 7, 13);
AssertIsRegion(symbol.ScriptRegion, 6, 1, 23, 2);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Constructor);
Assert.Equal("mtd NewLineClass", symbol.Id);
AssertIsRegion(symbol.NameRegion, 8, 5, 8, 17);
AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Property);
Assert.Equal("prop SomePropWithDefault", symbol.Id);
AssertIsRegion(symbol.NameRegion, 15, 5, 15, 25);
AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Method);
Assert.Equal("mtd MyClassMethod", symbol.Id);
Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.Name);
AssertIsRegion(symbol.NameRegion, 20, 5, 20, 18);
AssertIsRegion(symbol.ScriptRegion, 17, 5, 22, 6);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.Enum);
Assert.Equal("type MyNewLineEnum", symbol.Id);
AssertIsRegion(symbol.NameRegion, 26, 1, 26, 14);
AssertIsRegion(symbol.ScriptRegion, 25, 1, 28, 2);
- symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember));
+ symbol = Assert.Single(symbols, i => i.Type == SymbolType.EnumMember);
Assert.Equal("prop First", symbol.Id);
AssertIsRegion(symbol.NameRegion, 27, 5, 27, 10);
AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10);