Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 11eeae9

Browse files
authored
Use parameter HelpMessage for tool tip in parameter completion (PowerShell#25108)
Use the help message on the parameter in the completion tool tip to provide better completion help. This can be utilized by other tools to provide more context for a parameter outside of the current type and parameter name which is quite minimal.
1 parent b801027 commit 11eeae9

2 files changed

Lines changed: 243 additions & 15 deletions

File tree

src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -670,14 +670,20 @@ private static List<CompletionResult> GetParameterCompletionResults(string param
670670
{
671671
Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed");
672672
List<CompletionResult> result = new List<CompletionResult>();
673+
Assembly commandAssembly = null;
674+
if (bindingInfo.CommandInfo is CmdletInfo cmdletInfo)
675+
{
676+
commandAssembly = cmdletInfo.CommandMetadata.CommandType.Assembly;
677+
}
673678

674679
if (parameterName == string.Empty)
675680
{
676681
result = GetParameterCompletionResults(
677682
parameterName,
678683
bindingInfo.ValidParameterSetsFlags,
679684
bindingInfo.UnboundParameters,
680-
withColon);
685+
withColon,
686+
commandAssembly);
681687
return result;
682688
}
683689

@@ -699,7 +705,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param
699705
parameterName,
700706
bindingInfo.ValidParameterSetsFlags,
701707
bindingInfo.UnboundParameters,
702-
withColon);
708+
withColon,
709+
commandAssembly);
703710
}
704711

705712
return result;
@@ -714,7 +721,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param
714721
parameterName,
715722
bindingInfo.ValidParameterSetsFlags,
716723
bindingInfo.BoundParameters.Values,
717-
withColon);
724+
withColon,
725+
commandAssembly);
718726
}
719727

720728
return result;
@@ -778,19 +786,34 @@ private static List<CompletionResult> GetParameterCompletionResults(string param
778786
parameterName,
779787
bindingInfo.ValidParameterSetsFlags,
780788
bindingInfo.UnboundParameters,
781-
withColon);
789+
withColon,
790+
commandAssembly);
782791
return result;
783792
}
784793

785794
MergedCompiledCommandParameter param = bindingInfo.BoundParameters[matchedParameterName];
786795

787796
WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase);
788797
string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";
798+
799+
string helpMessage = string.Empty;
800+
if (param.Parameter.CompiledAttributes is not null)
801+
{
802+
foreach (Attribute attr in param.Parameter.CompiledAttributes)
803+
{
804+
if (attr is ParameterAttribute pattr && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))
805+
{
806+
helpMessage = $" - {attrHelpMessage}";
807+
break;
808+
}
809+
}
810+
}
811+
789812
string colonSuffix = withColon ? ":" : string.Empty;
790813
if (pattern.IsMatch(matchedParameterName))
791814
{
792-
string completionText = "-" + matchedParameterName + colonSuffix;
793-
string tooltip = parameterType + matchedParameterName;
815+
string completionText = $"-{matchedParameterName}{colonSuffix}";
816+
string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}";
794817
result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip));
795818
}
796819
else
@@ -804,27 +827,67 @@ private static List<CompletionResult> GetParameterCompletionResults(string param
804827
$"-{alias}{colonSuffix}",
805828
alias,
806829
CompletionResultType.ParameterName,
807-
parameterType + alias));
830+
$"{parameterType}{alias}{helpMessage}"));
808831
}
809832
}
810833
}
811834

812835
return result;
813836
}
814837

838+
#nullable enable
839+
/// <summary>
840+
/// Try and get the help message text for the parameter attribute.
841+
/// </summary>
842+
/// <param name="attr">The attribute to check for the help message.</param>
843+
/// <param name="assembly">The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in.</param>
844+
/// <param name="message">The help message if it was found otherwise null.</param>
845+
/// <returns>True if the help message was set or false if not.></returns>
846+
private static bool TryGetParameterHelpMessage(
847+
ParameterAttribute attr,
848+
Assembly? assembly,
849+
[NotNullWhen(true)] out string? message)
850+
{
851+
message = null;
852+
853+
if (attr.HelpMessage is not null)
854+
{
855+
message = attr.HelpMessage;
856+
return true;
857+
}
858+
859+
if (assembly is null || attr.HelpMessageBaseName is null || attr.HelpMessageResourceId is null)
860+
{
861+
return false;
862+
}
863+
864+
try
865+
{
866+
message = ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId);
867+
return message is not null;
868+
}
869+
catch (Exception)
870+
{
871+
return false;
872+
}
873+
}
874+
#nullable disable
875+
815876
/// <summary>
816877
/// Get the parameter completion results by using the given valid parameter sets and available parameters.
817878
/// </summary>
818879
/// <param name="parameterName"></param>
819880
/// <param name="validParameterSetFlags"></param>
820881
/// <param name="parameters"></param>
821882
/// <param name="withColon"></param>
883+
/// <param name="commandAssembly">Optional assembly used to lookup parameter help messages.</param>
822884
/// <returns></returns>
823885
private static List<CompletionResult> GetParameterCompletionResults(
824886
string parameterName,
825887
uint validParameterSetFlags,
826888
IEnumerable<MergedCompiledCommandParameter> parameters,
827-
bool withColon)
889+
bool withColon,
890+
Assembly commandAssembly = null)
828891
{
829892
var result = new List<CompletionResult>();
830893
var commonParamResult = new List<CompletionResult>();
@@ -840,6 +903,7 @@ private static List<CompletionResult> GetParameterCompletionResults(
840903

841904
string name = param.Parameter.Name;
842905
string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";
906+
string helpMessage = null;
843907
bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase);
844908
List<CompletionResult> listInUse = isCommonParameter ? commonParamResult : result;
845909

@@ -855,20 +919,27 @@ private static List<CompletionResult> GetParameterCompletionResults(
855919
{
856920
foreach (var attr in compiledAttributes)
857921
{
858-
var pattr = attr as ParameterAttribute;
859-
if (pattr != null && pattr.DontShow)
922+
if (attr is ParameterAttribute pattr)
860923
{
861-
showToUser = false;
862-
addCommonParameters = false;
863-
break;
924+
if (pattr.DontShow)
925+
{
926+
showToUser = false;
927+
addCommonParameters = false;
928+
break;
929+
}
930+
931+
if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))
932+
{
933+
helpMessage = $" - {attrHelpMessage}";
934+
}
864935
}
865936
}
866937
}
867938

868939
if (showToUser)
869940
{
870-
string completionText = "-" + name + colonSuffix;
871-
string tooltip = type + name;
941+
string completionText = $"-{name}{colonSuffix}";
942+
string tooltip = $"{type}{name}{helpMessage}";
872943
listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName,
873944
tooltip));
874945
}

test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,163 @@ param([ValidatePattern(
11251125
$res.CompletionMatches[1].CompletionText | Should -BeExactly '$DemoVar2'
11261126
}
11271127

1128+
It 'Should include parameter help message in tool tip - SingleMatch <SingleMatch>' -TestCases @(
1129+
@{ SingleMatch = $true }
1130+
@{ SingleMatch = $false }
1131+
) {
1132+
param ($SingleMatch)
1133+
1134+
Function Test-Function {
1135+
param (
1136+
[Parameter(HelpMessage = 'Some help message')]
1137+
$ParamWithHelp,
1138+
1139+
$ParamWithoutHelp
1140+
)
1141+
}
1142+
1143+
$expected = '[Object] ParamWithHelp - Some help message'
1144+
1145+
if ($SingleMatch) {
1146+
$Script = 'Test-Function -ParamWithHelp'
1147+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
1148+
}
1149+
else {
1150+
$Script = 'Test-Function -'
1151+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp'
1152+
}
1153+
1154+
$res.Count | Should -Be 1
1155+
$res.CompletionText | Should -BeExactly '-ParamWithHelp'
1156+
$res.ToolTip | Should -BeExactly $expected
1157+
}
1158+
1159+
It 'Should include parameter help resource message in tool tip - SingleMatch <SingleMatch>' -TestCases @(
1160+
@{ SingleMatch = $true }
1161+
@{ SingleMatch = $false }
1162+
) {
1163+
param ($SingleMatch)
1164+
1165+
$expected = '`[string`] Activity - *'
1166+
1167+
if ($SingleMatch) {
1168+
$Script = 'Write-Progress -Activity'
1169+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
1170+
}
1171+
else {
1172+
$Script = 'Write-Progress -'
1173+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity'
1174+
}
1175+
1176+
$res.Count | Should -Be 1
1177+
$res.CompletionText | Should -BeExactly '-Activity'
1178+
$res.ToolTip | Should -BeLikeExactly $expected
1179+
}
1180+
1181+
It 'Should skip empty parameter HelpMessage with multiple parameters - SingleMatch <SingleMatch>' -TestCases @(
1182+
@{ SingleMatch = $true }
1183+
@{ SingleMatch = $false }
1184+
) {
1185+
param ($SingleMatch)
1186+
1187+
Function Test-Function {
1188+
[CmdletBinding(DefaultParameterSetName = 'SetWithoutHelp')]
1189+
param (
1190+
[Parameter(ParameterSetName = 'SetWithHelp', HelpMessage = 'Help Message')]
1191+
[Parameter(ParameterSetName = 'SetWithoutHelp')]
1192+
[string]
1193+
$ParamWithHelp,
1194+
1195+
[Parameter(ParameterSetName = 'SetWithHelp')]
1196+
[switch]
1197+
$ParamWithoutHelp
1198+
)
1199+
}
1200+
1201+
$expected = '[string] ParamWithHelp - Help Message'
1202+
1203+
if ($SingleMatch) {
1204+
$Script = 'Test-Function -ParamWithHelp'
1205+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
1206+
}
1207+
else {
1208+
$Script = 'Test-Function -'
1209+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp'
1210+
}
1211+
1212+
$res.Count | Should -Be 1
1213+
$res.CompletionText | Should -BeExactly '-ParamWithHelp'
1214+
$res.ToolTip | Should -BeExactly $expected
1215+
}
1216+
1217+
It 'Should retrieve help message from dynamic parameter' {
1218+
Function Test-Function {
1219+
[CmdletBinding()]
1220+
param ()
1221+
dynamicparam {
1222+
$attr = [System.Management.Automation.ParameterAttribute]@{
1223+
HelpMessage = "Howdy partner"
1224+
}
1225+
$attrCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
1226+
$attrCollection.Add($attr)
1227+
1228+
$dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new('DynamicParam', [string], $attrCollection)
1229+
1230+
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
1231+
$paramDictionary.Add('DynamicParam', $dynParam)
1232+
$paramDictionary
1233+
}
1234+
1235+
end {}
1236+
}
1237+
1238+
$expected = '[string] DynamicParam - Howdy partner'
1239+
$Script = 'Test-Function -'
1240+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-DynamicParam'
1241+
$res.Count | Should -Be 1
1242+
$res.CompletionText | Should -BeExactly '-DynamicParam'
1243+
$res.ToolTip | Should -BeExactly $expected
1244+
}
1245+
1246+
It 'Should have type and name for parameter without help message' {
1247+
Function Test-Function {
1248+
param (
1249+
[Parameter()]
1250+
$WithParamAttribute,
1251+
1252+
$WithoutParamAttribute
1253+
)
1254+
}
1255+
1256+
$Script = 'Test-Function -'
1257+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches |
1258+
Where-Object CompletionText -in '-WithParamAttribute', '-WithoutParamAttribute' |
1259+
Sort-Object CompletionText
1260+
$res.Count | Should -Be 2
1261+
1262+
$res.CompletionText[0] | Should -BeExactly '-WithoutParamAttribute'
1263+
$res.ToolTip[0] | Should -BeExactly '[Object] WithoutParamAttribute'
1264+
1265+
$res.CompletionText[1] | Should -BeExactly '-WithParamAttribute'
1266+
$res.ToolTip[1] | Should -BeExactly '[Object] WithParamAttribute'
1267+
}
1268+
1269+
It 'Should ignore errors when faling to get HelpMessage resource' {
1270+
Function Test-Function {
1271+
param (
1272+
[Parameter(HelpMessageBaseName="invalid", HelpMessageResourceId="SomeId")]
1273+
$InvalidHelpParam
1274+
)
1275+
}
1276+
1277+
$expected = '[Object] InvalidHelpParam'
1278+
$Script = 'Test-Function -InvalidHelpParam'
1279+
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
1280+
$res.Count | Should -Be 1
1281+
$res.CompletionText | Should -BeExactly '-InvalidHelpParam'
1282+
$res.ToolTip | Should -BeExactly $expected
1283+
}
1284+
11281285
Context 'Start-Process -Verb parameter completion' {
11291286
BeforeAll {
11301287
function GetProcessInfoVerbs([string]$path, [switch]$singleQuote, [switch]$doubleQuote) {

0 commit comments

Comments
 (0)