Skip to content

Use parameter HelpMessage for tool tip completion #25108

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

jborean93
Copy link
Collaborator

PR Summary

Use the help message on the parameter in the completion tool tip to provide better completion help. This can be utilised by other tools to provide more context for a parameter outside of the current type and parameter name which is quite minimal.

PR Context

The current tool tip provided by MenuComplete for parameter is not too useful as it has just the parameter name and type. With this change it will also include the HelpMessage (or HelpMessageBaseName and HelpMessageResourceId on compiled cmdlets).

For example here is the output of Write-Progress today

image

Now this is the output with the changes in this PR.

image

I went with using HelpMessage for a few reasons:

  • It's available in the completion code so no expensive lookups
  • It can be used for dynamic parameters as they build the ParameterAttribute at runtime
  • The constraints around attribute field strings would mean it would most likely be a shortened description making it nice for the console to display

The final point I think is pretty important as CBH or MAML help could have quite long descriptions for a parameter which can be messy to display in the console. By having the HelpMessage set we can reserve the more detailed help for things like Get-Help to show but use this attribute field for things that display the tool tip (tip being short here).

PR Checklist

Use the help message on the parameter in the completion tool tip to
provide better completion help. This can be utilised by other tools to
provide more context for a parameter outside of the current type and
parameter name which is quite minimal.
@iSazonov
Copy link
Collaborator

/cc @MartinGC94 Please review.

@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Feb 28, 2025
Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

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

Looks great!

@MartinGC94
Copy link
Contributor

Looks good to me. My only concern was the tooltip showing the exception "Failed to find the help message: {e.Message}" but it seems like you already plan to change this.

/// <returns></returns>
private static List<CompletionResult> GetParameterCompletionResults(
string parameterName,
uint validParameterSetFlags,
IEnumerable<MergedCompiledCommandParameter> parameters,
bool withColon)
bool withColon,
Assembly commandAssembly = null)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The same.

Copy link
Collaborator Author

@jborean93 jborean93 Mar 1, 2025

Choose a reason for hiding this comment

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

We don't but it saved me from specifying null for the argument where it is called. I can certainly make it without a default if you prefer that.

I do think the default is a nice way to see that the argument is nullable without having to turn on the nullability checks and annotating it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see your point. This is not critical, it just makes me think that there are a number of call sites that use this default.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In this case there still are, this is why I added the default in the first place because there are some calls where there is no assembly object to provide.

@jborean93
Copy link
Collaborator Author

Thanks for the review @iSazonov, I've pushed a change that is a tiny bit different from your suggestion (still very similar). Please let me know if I've missed anything or if you want me to make the commandAssembly on GetParameterCompletionResults a non-default.

Comment on lines +915 to +918
if (helpMessage is not null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage))
{
helpMessage = $" - {attrHelpMessage}";
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Last attribute win? In code above first win. I'd expect the same here.

Copy link
Collaborator Author

@jborean93 jborean93 Mar 9, 2025

Choose a reason for hiding this comment

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

It's somewhat confusing but the "first" attribute is actually the closes one to the definition rather than it being top -> bottom.

So in the below example the first attribute is actually Bottom, then Middle, then finally Top.

[Parameter(HelpMessage='Top')]
[Parameter(HelpMessage='Middle')]
[Parameter(HelpMessage='Bottom')]
[string]
$Param

This could change in the future but the code already goes bottom -> top and I don't think this PR should be the one to revisit that choice.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I mean line 791. There is a break but not here. Why is it different?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The code from 783-794 only check for the help message and does not check for DontShow (what it is doing today) so once we get a message we can cancel the loop entirely.

The code from 904-921 currently checks if DontShow is present hence why it breaks early there in that code branch. With this PR it also now checks for the help message if present but it doesn't break because it still needs to check for DontShow if present in subsequent attributes for the parameter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I ask about follow difference:

  • In code from 783-794 we get help message from "Bottom" (first attribute in the loop)
  • In code from 904-921 we get help message from "Top" (last attribute in the loop)
    Yes?

Copy link
Collaborator Author

@jborean93 jborean93 Mar 10, 2025

Choose a reason for hiding this comment

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

No differences they are both the same, both will loop through the attributes in the "compiled" order from PowerShell (bottom up). It will stop at the first attribute where there is a valid help message found.

The only reason why there is a difference in code is because one handles DontShow while the other does not. This is a behaviour not touched by this PR at all.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This could change in the future but the code already goes bottom -> top and I don't think this PR should be the one to revisit that choice.

Before this change, there was nothing in the result to distinguish one result from another for the same parameter. But now we have such property - Tooltip. That's why I can't agree with your statement. The results should be generated correct now.

In [string][int]$var first attribute win - [string]. The common expectation is that first parameter attribute win too and we see help message from the attribute.
Also if parameter has two parameter attributes for different parameter sets we should get help message from correct parameter set if possible or all cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm just going to have to leave this as is. This is beyond what I find needed for such a PR. Will just have to wait until someone from the pwsh team gets to this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

MSFT team has already clearly outlined the work that they will do in this milestone and they will not consider others as this exceeds their capabilities.

@microsoft-github-policy-service microsoft-github-policy-service bot added the Review - Needed The PR is being reviewed label Mar 8, 2025
Copy link
Contributor

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

Comment on lines +955 to +966
if ($SingleMatch) {
$Script = 'Test-Function -ParamWithHelp'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
}
else {
$Script = 'Test-Function -'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp'
}

$res.Count | Should -Be 1
$res.CompletionText | Should -BeExactly '-ParamWithHelp'
$res.ToolTip | Should -BeExactly $expected
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is better to check all the cases explicitly to avoid regression in any form (in tests below too):

Suggested change
if ($SingleMatch) {
$Script = 'Test-Function -ParamWithHelp'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
}
else {
$Script = 'Test-Function -'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp'
}
$res.Count | Should -Be 1
$res.CompletionText | Should -BeExactly '-ParamWithHelp'
$res.ToolTip | Should -BeExactly $expected
if ($SingleMatch) {
$Script = 'Test-Function -ParamWithHelp'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches
$res.Count | Should -Be 1
$res.CompletionText | Should -BeExactly '-ParamWithHelp'
$res.ToolTip | Should -BeExactly $expected
}
else {
$Script = 'Test-Function -'
$res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp'
$res.Count | Should -Be 2
$res[0].CompletionText | Should -BeExactly '-ParamWithHelp'
$res[0].ToolTip | Should -BeExactly $expected
$res[1].CompletionText | Should -BeExactly '-ParamWithoutHelp'
$res[1].ToolTip | Should -BeExactly '[Object] ParamWithoutHelp'
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In this case we are only testing the ToolTip message for each returned option but even if you wanted to have multiple tests there are a few problems with this approach:

Copy link
Collaborator

Choose a reason for hiding this comment

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

This may not apply to this change directly, but if it is possible to make an additional check, why not add it and exclude regression in case someone reworks this code?
My suggestion is to expand this test a bit and check that the text from the first parameter is not randomly displayed for the second one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because if attributes for another parameter are being applied to another we have a major problem that would/should be picked up by other tests. By testing it here as well we complicate an already somewhat complicated test that isn’t designed to check for this particular problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log Review - Needed The PR is being reviewed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants