diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 2dd6807fa6b..b73bcd5a036 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -6114,12 +6114,13 @@ private static List CompleteRequires(CompletionContext context // Produce completions for all parameters that begin with the prefix we've found, // but which haven't already been specified in the line we need to complete - foreach (KeyValuePair parameter in s_requiresParameters) + foreach (string parameter in s_requiresParameters) { - if (parameter.Key.StartsWith(currentParameterPrefix, StringComparison.OrdinalIgnoreCase) - && !context.CursorPosition.Line.Contains($" -{parameter.Key}", StringComparison.OrdinalIgnoreCase)) + if (parameter.StartsWith(currentParameterPrefix, StringComparison.OrdinalIgnoreCase) + && !context.CursorPosition.Line.Contains($" -{parameter}", StringComparison.OrdinalIgnoreCase)) { - results.Add(new CompletionResult(parameter.Key, parameter.Key, CompletionResultType.ParameterName, parameter.Value)); + string toolTip = GetRequiresParametersToolTip(parameter); + results.Add(new CompletionResult(parameter, parameter, CompletionResultType.ParameterName, toolTip)); } } @@ -6144,11 +6145,12 @@ private static List CompleteRequires(CompletionContext context // Complete PSEdition parameter values if (currentParameterMatch.Value.Equals("PSEdition", StringComparison.OrdinalIgnoreCase)) { - foreach (KeyValuePair psEditionEntry in s_requiresPSEditions) + foreach (string psEditionEntry in s_requiresPSEditions) { - if (psEditionEntry.Key.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) + if (psEditionEntry.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) { - results.Add(new CompletionResult(psEditionEntry.Key, psEditionEntry.Key, CompletionResultType.ParameterValue, psEditionEntry.Value)); + string toolTip = GetRequiresPsEditionsToolTip(psEditionEntry); + results.Add(new CompletionResult(psEditionEntry, psEditionEntry, CompletionResultType.ParameterValue, toolTip)); } } @@ -6176,7 +6178,7 @@ private static List CompleteRequires(CompletionContext context hashtableKeyMatches = Regex.Matches(hashtableString, @"(@{|;)\s*(?:'|\""|\w*)\w*"); // Build the list of keys we might want to complete, based on what's already been provided - var moduleSpecKeysToComplete = new HashSet(s_requiresModuleSpecKeys.Keys); + var moduleSpecKeysToComplete = new HashSet(s_requiresModuleSpecKeys); bool sawModuleNameLast = false; foreach (Match existingHashtableKeyMatch in hashtableKeyMatches) { @@ -6232,7 +6234,8 @@ private static List CompleteRequires(CompletionContext context { if (moduleSpecKey.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) { - results.Add(new CompletionResult(moduleSpecKey, moduleSpecKey, CompletionResultType.ParameterValue, s_requiresModuleSpecKeys[moduleSpecKey])); + string toolTip = GetRequiresModuleSpecKeysToolTip(moduleSpecKey); + results.Add(new CompletionResult(moduleSpecKey, moduleSpecKey, CompletionResultType.ParameterValue, toolTip)); } } } @@ -6240,27 +6243,53 @@ private static List CompleteRequires(CompletionContext context return results; } - private static readonly IReadOnlyDictionary s_requiresParameters = new SortedList(StringComparer.OrdinalIgnoreCase) + private static readonly string[] s_requiresParameters = new string[] { - { "Modules", "Specifies PowerShell modules that the script requires." }, - { "PSEdition", "Specifies a PowerShell edition that the script requires." }, - { "RunAsAdministrator", "Specifies that PowerShell must be running as administrator on Windows." }, - { "Version", "Specifies the minimum version of PowerShell that the script requires." }, + "Modules", + "PSEdition", + "RunAsAdministrator", + "Version" }; - private static readonly IReadOnlyDictionary s_requiresPSEditions = new SortedList(StringComparer.OrdinalIgnoreCase) + private static string GetRequiresParametersToolTip(string name) => name switch { - { "Core", "Specifies that the script requires PowerShell Core to run." }, - { "Desktop", "Specifies that the script requires Windows PowerShell to run." }, + "Modules" => TabCompletionStrings.RequiresModulesParameterDescription, + "PSEdition" => TabCompletionStrings.RequiresPSEditionParameterDescription, + "RunAsAdministrator" => TabCompletionStrings.RequiresRunAsAdministratorParameterDescription, + "Version" => TabCompletionStrings.RequiresVersionParameterDescription, + _ => string.Empty }; - private static readonly IReadOnlyDictionary s_requiresModuleSpecKeys = new SortedList(StringComparer.OrdinalIgnoreCase) + private static readonly string[] s_requiresPSEditions = new string[] { - { "ModuleName", "Required. Specifies the module name." }, - { "GUID", "Optional. Specifies the GUID of the module." }, - { "ModuleVersion", "Specifies a minimum acceptable version of the module." }, - { "RequiredVersion", "Specifies an exact, required version of the module." }, - { "MaximumVersion", "Specifies the maximum acceptable version of the module." }, + "Core", + "Desktop" + }; + + private static string GetRequiresPsEditionsToolTip(string name) => name switch + { + "Core" => TabCompletionStrings.RequiresPsEditionCoreDescription, + "Desktop" => TabCompletionStrings.RequiresPsEditionDesktopDescription, + _ => string.Empty + }; + + private static readonly string[] s_requiresModuleSpecKeys = new string[] + { + "GUID", + "MaximumVersion", + "ModuleName", + "ModuleVersion", + "RequiredVersion" + }; + + private static string GetRequiresModuleSpecKeysToolTip(string name) => name switch + { + "GUID" => TabCompletionStrings.RequiresModuleSpecGUIDDescription, + "MaximumVersion" => TabCompletionStrings.RequiresModuleSpecMaximumVersionDescription, + "ModuleName" => TabCompletionStrings.RequiresModuleSpecModuleNameDescription, + "ModuleVersion" => TabCompletionStrings.RequiresModuleSpecModuleVersionDescription, + "RequiredVersion" => TabCompletionStrings.RequiresModuleSpecRequiredVersionDescription, + _ => string.Empty }; private static readonly char[] s_hashtableKeyPrefixes = new[] @@ -6305,7 +6334,7 @@ private static List CompleteCommentHelp(CompletionContext cont replacementIndex = context.TokenAtCursor.Extent.StartOffset + lineKeyword.Index; replacementLength = lineKeyword.Value.Length; - var validKeywords = new HashSet(s_commentHelpKeywords.Keys, StringComparer.OrdinalIgnoreCase); + var validKeywords = new HashSet(s_commentHelpKeywords, StringComparer.OrdinalIgnoreCase); foreach (Match keyword in usedKeywords) { if (keyword == lineKeyword || s_commentHelpAllowedDuplicateKeywords.Contains(keyword.Value)) @@ -6321,7 +6350,8 @@ private static List CompleteCommentHelp(CompletionContext cont { if (keyword.StartsWith(lineKeyword.Value, StringComparison.OrdinalIgnoreCase)) { - result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, s_commentHelpKeywords[keyword])); + string toolTip = GetCommentHelpKeywordsToolTip(keyword); + result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, toolTip)); } } @@ -6381,46 +6411,66 @@ private static List CompleteCommentHelp(CompletionContext cont return null; } - private static readonly IReadOnlyDictionary s_commentHelpKeywords = new SortedList(StringComparer.OrdinalIgnoreCase) - { - { "SYNOPSIS", "A brief description of the function or script. This keyword can be used only once in each topic." }, - { "DESCRIPTION", "A detailed description of the function or script. This keyword can be used only once in each topic." }, - { "PARAMETER", ".PARAMETER \nThe description of a parameter. Add a .PARAMETER keyword for each parameter in the function or script syntax." }, - { "EXAMPLE", "A sample command that uses the function or script, optionally followed by sample output and a description. Repeat this keyword for each example." }, - { "INPUTS", "The .NET types of objects that can be piped to the function or script. You can also include a description of the input objects." }, - { "OUTPUTS", "The .NET type of the objects that the cmdlet returns. You can also include a description of the returned objects." }, - { "NOTES", "Additional information about the function or script." }, - { "LINK", "The name of a related topic. Repeat the .LINK keyword for each related topic. The .Link keyword content can also include a URI to an online version of the same help topic." }, - { "COMPONENT", "The name of the technology or feature that the function or script uses, or to which it is related." }, - { "ROLE", "The name of the user role for the help topic." }, - { "FUNCTIONALITY", "The keywords that describe the intended use of the function." }, - { "FORWARDHELPTARGETNAME", ".FORWARDHELPTARGETNAME \nRedirects to the help topic for the specified command." }, - { "FORWARDHELPCATEGORY", ".FORWARDHELPCATEGORY \nSpecifies the help category of the item in .ForwardHelpTargetName" }, - { "REMOTEHELPRUNSPACE", ".REMOTEHELPRUNSPACE \nSpecifies a session that contains the help topic. Enter a variable that contains a PSSession object." }, - { "EXTERNALHELP", ".EXTERNALHELP \nThe .ExternalHelp keyword is required when a function or script is documented in XML files." } + private static readonly string[] s_commentHelpKeywords = new string[] + { + "COMPONENT", + "DESCRIPTION", + "EXAMPLE", + "EXTERNALHELP", + "FORWARDHELPCATEGORY", + "FORWARDHELPTARGETNAME", + "FUNCTIONALITY", + "INPUTS", + "LINK", + "NOTES", + "OUTPUTS", + "PARAMETER", + "REMOTEHELPRUNSPACE", + "ROLE", + "SYNOPSIS" + }; + + private static string GetCommentHelpKeywordsToolTip(string name) => name switch + { + "COMPONENT" => TabCompletionStrings.CommentHelpCOMPONENTKeywordDescription, + "DESCRIPTION" => TabCompletionStrings.CommentHelpDESCRIPTIONKeywordDescription, + "EXAMPLE" => TabCompletionStrings.CommentHelpEXAMPLEKeywordDescription, + "EXTERNALHELP" => TabCompletionStrings.CommentHelpEXTERNALHELPKeywordDescription, + "FORWARDHELPCATEGORY" => TabCompletionStrings.CommentHelpFORWARDHELPCATEGORYKeywordDescription, + "FORWARDHELPTARGETNAME" => TabCompletionStrings.CommentHelpFORWARDHELPTARGETNAMEKeywordDescription, + "FUNCTIONALITY" => TabCompletionStrings.CommentHelpFUNCTIONALITYKeywordDescription, + "INPUTS" => TabCompletionStrings.CommentHelpINPUTSKeywordDescription, + "LINK" => TabCompletionStrings.CommentHelpLINKKeywordDescription, + "NOTES" => TabCompletionStrings.CommentHelpNOTESKeywordDescription, + "OUTPUTS" => TabCompletionStrings.CommentHelpOUTPUTSKeywordDescription, + "PARAMETER" => TabCompletionStrings.CommentHelpPARAMETERKeywordDescription, + "REMOTEHELPRUNSPACE" => TabCompletionStrings.CommentHelpREMOTEHELPRUNSPACEKeywordDescription, + "ROLE" => TabCompletionStrings.CommentHelpROLEKeywordDescription, + "SYNOPSIS" => TabCompletionStrings.CommentHelpSYNOPSISKeywordDescription, + _ => string.Empty }; private static readonly HashSet s_commentHelpAllowedDuplicateKeywords = new(StringComparer.OrdinalIgnoreCase) { - "PARAMETER", "EXAMPLE", - "LINK" + "LINK", + "PARAMETER" }; private static readonly string[] s_commentHelpForwardCategories = new string[] { "Alias", + "All", "Cmdlet", - "HelpFile", + "ExternalScript", + "FAQ", + "Filter", "Function", - "Provider", "General", - "FAQ", "Glossary", - "ScriptCommand", - "ExternalScript", - "Filter", - "All" + "HelpFile", + "Provider", + "ScriptCommand" }; private static FunctionDefinitionAst GetCommentHelpFunctionTarget(CompletionContext context) @@ -8191,7 +8241,7 @@ internal static List CompleteHashtableKey(CompletionContext co } var result = new List(); - foreach (var key in s_requiresModuleSpecKeys.Keys) + foreach (string key in s_requiresModuleSpecKeys) { if (excludedKeys.Contains(key) || (wordToComplete is not null && !key.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) @@ -8200,7 +8250,10 @@ internal static List CompleteHashtableKey(CompletionContext co { continue; } - result.Add(new CompletionResult(key, key, CompletionResultType.Property, s_requiresModuleSpecKeys[key])); + + string toolTip = GetRequiresModuleSpecKeysToolTip(key); + + result.Add(new CompletionResult(key, key, CompletionResultType.Property, toolTip)); } return result; @@ -8452,7 +8505,9 @@ private static List GetSpecialHashTableKeyMembers(HashSet GetSpecialHashTableKeyMembers(HashSet name switch + { + "Alignment" => TabCompletionStrings.AlignmentHashtableKeyDescription, + "Ascending" => TabCompletionStrings.AscendingHashtableKeyDescription, + "Data" => TabCompletionStrings.DataHashtableKeyDescription, + "Depth" => TabCompletionStrings.DepthHashtableKeyDescription, + "Descending" => TabCompletionStrings.DescendingHashtableKeyDescription, + "EndTime" => TabCompletionStrings.EndTimeHashtableKeyDescription, + "Expression" => TabCompletionStrings.ExpressionHashtableKeyDescription, + "FormatString" => TabCompletionStrings.FormatStringHashtableKeyDescription, + "ID" => TabCompletionStrings.IDHashtableKeyDescription, + "Keywords" => TabCompletionStrings.KeywordsHashtableKeyDescription, + "Label" => TabCompletionStrings.LabelHashtableKeyDescription, + "Level" => TabCompletionStrings.LevelHashtableKeyDescription, + "LogName" => TabCompletionStrings.LogNameHashtableKeyDescription, + "Name" => TabCompletionStrings.NameHashtableKeyDescription, + "Path" => TabCompletionStrings.PathHashtableKeyDescription, + "ProviderName" => TabCompletionStrings.ProviderNameHashtableKeyDescription, + "StartTime" => TabCompletionStrings.StartTimeHashtableKeyDescription, + "SuppressHashFilter" => TabCompletionStrings.SuppressHashFilterHashtableKeyDescription, + "UserID" => TabCompletionStrings.UserIDHashtableKeyDescription, + "Width" => TabCompletionStrings.WidthHashtableKeyDescription, + _ => string.Empty + }; + #endregion Hashtable Keys #region Helpers diff --git a/src/System.Management.Automation/resources/TabCompletionStrings.resx b/src/System.Management.Automation/resources/TabCompletionStrings.resx index 73399a6d6e8..c201facc3e2 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -329,6 +329,218 @@ Shift Right bit operator. Inserts zero in the left-most bit position. For signed values, sign bit is preserved. + + [string] +Specifies the name of the property being created. + + + [string] +Specifies the name of the property being created. + + + [scriptblock] +A script block used to calculate the value of the new property. + + + [string] +Define how the values are displayed in a column. +Valid values are 'left', 'center', or 'right'. + + + [string] +Specifies a format string that defines how the value is formatted for output. + + + [int] +Specifies the maximum column width in a table when the value is displayed. +The value must be greater than 0. + + + [int] +The depth key specifies the depth of expansion per property. + + + [bool] +Specifies the order of sorting for one or more properties. + + + [bool] +Specifies the order of sorting for one or more properties. + + + [String[]] +Specifies the log names to get events from. +Supports wildcards. + + + [String[]] +Specifies the event log providers to get events from. +Supports wildcards. + + + [String[]] +Specifies file paths to log files to get events from. +Valid file formats are: .etl, .evt, and .evtx + + + [Long[]] +Selects events with the specified keyword bitmasks. +The following are standard keywords: +4503599627370496: AuditFailure +9007199254740992: AuditSuccess +4503599627370496: CorrelationHint +18014398509481984: CorrelationHint2 +36028797018963968: EventLogClassic +281474976710656: ResponseTime +2251799813685248: Sqm +562949953421312: WdiContext +1125899906842624: WdiDiagnostic + + + [int[]] +Selects events with the specified event IDs. + + + [int[]] +Selects events with the specified log levels. +The following log levels are valid: +1: Critical +2: Error +3: Warning +4: Informational +5: Verbose + + + [datetime] +Selects events created after the specified date and time. + + + [datetime] +Selects events created before the specified date and time. + + + [string] +Selects events generated by the specified user. +This can either be a string representation of a SID or a domain and username in the format DOMAIN\USERNAME or USERNAME@DOMAIN + + + [string[]] +Selects events with any of the specified values in the EventData section. + + + [hashtable] +Excludes events that match the values specified in the hashtable. + + + [string] or [hashtable] +Specifies an array of PowerShell modules that the script requires. +Each element can either be a string with the module name as value or a hashtable with the following keys: +Name: Name of the module +GUID: GUID of the module +One of the following: +ModuleVersion: Specifies a minimum acceptable version of the module. +RequiredVersion: Specifies an exact, required version of the module. +MaximumVersion: Specifies the maximum acceptable version of the module. + + + [string] +Specifies a PowerShell edition that the script requires. +Valid values are "Core" and "Desktop" + + + [switch] +Specifies that PowerShell must be running as administrator on Windows. +This must be the last parameter on the #requires statement line. + + + [version] +Specifies the minimum version of PowerShell that the script requires. + + + Specifies that the script requires PowerShell Core to run. + + + Specifies that the script requires Windows PowerShell to run. + + + [string] +Required. Specifies the module name. + + + [string] +Optional. Specifies the GUID of the module. + + + [string] +Specifies a minimum acceptable version of the module. + + + [string] +Specifies an exact, required version of the module. + + + [string] +Specifies the maximum acceptable version of the module. + + + A brief description of the function or script. +This keyword can be used only once in each topic. + + + A detailed description of the function or script. +This keyword can be used only once in each topic. + + + .PARAMETER <Parameter-Name> +The description of a parameter. +Add a .PARAMETER keyword for each parameter in the function or script syntax. + + + A sample command that uses the function or script, optionally followed by sample output and a description. +Repeat this keyword for each example. + + + The .NET types of objects that can be piped to the function or script. +You can also include a description of the input objects. + + + The .NET type of the objects that the cmdlet returns. +You can also include a description of the returned objects. + + + Additional information about the function or script. + + + The name of a related topic. +Repeat the .LINK keyword for each related topic. +The .Link keyword content can also include a URI to an online version of the same help topic. + + + The name of the technology or feature that the function or script uses, or to which it is related. + + + The name of the user role for the help topic. + + + The keywords that describe the intended use of the function. + + + .FORWARDHELPTARGETNAME <Command-Name> +Redirects to the help topic for the specified command. + + + .FORWARDHELPCATEGORY <Category> +Specifies the help category of the item in .ForwardHelpTargetName + + + .REMOTEHELPRUNSPACE <PSSession-variable> +Specifies a session that contains the help topic. +Enter a variable that contains a PSSession object. + + + .EXTERNALHELP <XML Help File> +The .ExternalHelp keyword is required when a function or script is documented in XML files. + Specifies the path to a .NET assembly to load. diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index f07de8b14a7..c7d6ee744e0 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -2557,7 +2557,7 @@ param ($Param1) } It "Test hashtable key completion in #requires statement for modules" { - $res = TabExpansion2 -inputScript "#requires -Modules @{" -cursorColumn 21 + $res = TabExpansion2 -inputScript "#requires -Modules @{" $res.CompletionMatches.Count | Should -BeGreaterThan 0 $res.CompletionMatches[0].CompletionText | Should -BeExactly "GUID" } @@ -3226,23 +3226,23 @@ dir -Recurse ` } It '' -TestCases @( @{ - Intent = 'Complete help keywords with minimum input' + Intent = 'Complete help keywords with minimal input' Expected = @( - 'COMPONENT' - 'DESCRIPTION' - 'EXAMPLE' - 'EXTERNALHELP' - 'FORWARDHELPCATEGORY' - 'FORWARDHELPTARGETNAME' - 'FUNCTIONALITY' - 'INPUTS' - 'LINK' - 'NOTES' - 'OUTPUTS' - 'PARAMETER' - 'REMOTEHELPRUNSPACE' - 'ROLE' - 'SYNOPSIS' + "COMPONENT", + "DESCRIPTION", + "EXAMPLE", + "EXTERNALHELP", + "FORWARDHELPCATEGORY", + "FORWARDHELPTARGETNAME", + "FUNCTIONALITY", + "INPUTS", + "LINK", + "NOTES", + "OUTPUTS", + "PARAMETER", + "REMOTEHELPRUNSPACE", + "ROLE", + "SYNOPSIS" ) TestString = @' <# @@ -3432,6 +3432,7 @@ function MyFunction ($param1, $param2) It 'Should complete module specification keys in using module statement' { $res = TabExpansion2 -inputScript 'using module @{' $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly "GUID MaximumVersion ModuleName ModuleVersion RequiredVersion" + $res.CompletionMatches[0].ToolTip | Should -Not -Be $res.CompletionMatches[0].CompletionText } It 'Should not fallback to file completion when completing typenames' {