KEMBAR78
Add single/double quote support for `Join-String` Argument Completer by ArmaanMcleod · Pull Request #25283 · PowerShell/PowerShell · GitHub
Skip to content

Conversation

@ArmaanMcleod
Copy link
Contributor

@ArmaanMcleod ArmaanMcleod commented Apr 5, 2025

PR Summary

This PR makes sure JoinItemCompleter uses CompletionHelpers.GetMatchingResults so completions can work with single/double quotes.

There is multiple improvements with this completer which is also included in tests, e.g. completing newlines and supporting more variations/flexibility with single/double quotes. I also split JoinItemCompleter into SeparatorArgumentCompleter and FormatStringArgumentCompleter`.

I've also made the following changes in preparation to extend IArgumentCompleter further:

  • MatchStrategy delegates which can be implemented and overriden by user potentially. This will allow GetMatchingResults to be more flexible with how values are matched.

  • added new listItemTextMapping parameter to GetMatchingResults so we can customize list item text which is needed for JoinItemCompleter.

PR Context

Fixes #24873

This was the last completer I intended to move to GetMatchingResults since it requires behaviour which will help influence the interface extension in #25033.

PR Checklist

@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Apr 8, 2025
{
string completionText = QuoteCompletionText(value, quote);
string toolTip = toolTipMapping?.Invoke(value) ?? value;
string listItemText = listItemTextMapping?.Invoke(value) ?? value;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should do the both mappings in one method. If this requires many changes in other completers it is better to implement this in follow PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree. I am actually thinking of just changing the type from Func<string, string> to Dictionary<string, (string Tooltip, string ListItemText)>. I am not sure Func is the best type to use here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It could be Func<string, (string Tooltip, string ListItemText)>. It's not critical right now. We can postpone the decision when we choose how best to extend the interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep sounds good. Will postpone this. I'd like to just get IsMatch and ListItemTextMapping in interface in next PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should do the both mappings in one method.

+ for flexibility we could consider cascading as for possible values method => static list.

@ArmaanMcleod
Copy link
Contributor Author

ArmaanMcleod commented Apr 9, 2025

Thanks for reviews @iSazonov. Let me know what you think with latest changes 🙂

@ArmaanMcleod ArmaanMcleod requested a review from iSazonov April 9, 2025 12:02
@iSazonov
Copy link
Collaborator

@ArmaanMcleod Please look test failures.

@ArmaanMcleod
Copy link
Contributor Author

@ArmaanMcleod Please look test failures.

Thanks @iSazonov.

I think test failures are from wildcard pattern like [* not being escaped before passing to WildcardPattern.Get.

I fixed this in 09818bb

@iSazonov iSazonov self-assigned this Apr 13, 2025
@iSazonov
Copy link
Collaborator

Thanks for in-depth anaysis @iSazonov.

So we should move the normalization from MatchStrategy to main method, do the normalization only if quote is single quote.

I don't think this can just apply to single quotes given newlines need to be normalized and current code forces double quotes.

Sorry, it is my typo. Right wording is "do the normalization only if quote is NOT single quote."

So ideally we should handle all, not only line endings.

We can but not sure the value in supporting all in this PR. It seems fine to do normalization with just line endings for this PR.

I mean we can add this in follow PR. I don't force you to implement this. Now I'd only move the normalization (two replace() methods) in separate method and put it in right place. This allows us easily to enhance the method in future. Obviously, it is edge case but since we want to get very user-friendly capabilities for simple completer creation the feature is "nice to have".

But it seems we haven't method to convert raw string (WordToComplete) to pwsh expandable string.
On the other hand, we already passing possible strings through the tokenizer every time in CompletionRequiresQuotes().

I think it is easier using Parser.ParseInput to get tokens convert possible values with backticks to \r\n so it can compare with word to complete. But even then this is pointless because it will still leave back ticks in which after expansion and you'll need to normalize word to complete anyways.

I considered the case but there can be some tokens and it is not easy combine them back in source string. So I concluded it is better to convert WordToComplete.

I don't think this effort is worth it if Replace() will work. I have added a NormalizeLineEndings method which runs outside of MatchStrategy delegates which works well.

We should probably keep this code as simple as possible since it can complex very fast 😄.

As I said I agree with current Replace() normalization for the PR but I suggest having it in new normalization method under right condition and in right place.

@ArmaanMcleod
Copy link
Contributor Author

@iSazonov I have moved this to a method called NormalizeToExpandableString and only running this when string is double quote for normalizing to expandable string.

…letionHelpers.cs

Co-authored-by: Ilya <darpa@yandex.ru>
@ArmaanMcleod ArmaanMcleod requested a review from iSazonov April 20, 2025 11:08
@iSazonov
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

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.

I merge this as-is since the PR is good and too large to keep improving.

@ArmaanMcleod Thanks for your efforts!

The comments are below, which I have compiled for further work. Some of them are optional, some are important.

IEnumerable<string> possibleCompletionValues,
Func<string, string> toolTipMapping = null,
CompletionResultType resultType = CompletionResultType.Text)
Func<string, string> listItemTextMapping = null,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps convert these Func<string, string> to delegate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep agree it should all be consolidated to delegates.

{
string completionText = QuoteCompletionText(value, quote);
string toolTip = toolTipMapping?.Invoke(value) ?? value;
string listItemText = listItemTextMapping?.Invoke(value) ?? value;
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should do the both mappings in one method.

+ for flexibility we could consider cascading as for possible values method => static list.

Comment on lines +79 to +83
internal static string NormalizeToExpandableString(string value)
=> value
.Replace("\r", "`r")
.Replace("\n", "`n")
.Replace("\t", "`t")
Copy link
Collaborator

Choose a reason for hiding this comment

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

The method could be optimized for performance as separate work (low priority).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one is actually quite difficult to optimize, basic Replace approach outperforms other methods (string builder, regex etc.).

/// </remarks>
internal static readonly MatchStrategy WildcardPatternMatch = (value, wordToComplete)
=> WildcardPattern
.Get(wordToComplete + "*", WildcardOptions.IgnoreCase)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I always wonder why we don't have GetStar(wordToComplete, WildcardOptions.IgnoreCase). At least internally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would be good but the name seems a bit weird. For now its fine to just append * ourselves which is everywhere in this codebase 😄.

@iSazonov
Copy link
Collaborator

@ArmaanMcleod Please remove my two last commits or add new commit to unlock the PR merging.

ArmaanMcleod and others added 5 commits April 21, 2025 09:48
…letionHelpers.cs

Co-authored-by: Ilya <darpa@yandex.ru>
…letionHelpers.cs

Co-authored-by: Ilya <darpa@yandex.ru>
…letionHelpers.cs

Co-authored-by: Ilya <darpa@yandex.ru>
…letionHelpers.cs

Co-authored-by: Ilya <darpa@yandex.ru>
@ArmaanMcleod
Copy link
Contributor Author

Thanks @iSazonov.

I have added some commits from your suggestions. I think for the rest better to do that in another PR for improvements.

@iSazonov

This comment was marked as outdated.

@azure-pipelines

This comment was marked as outdated.

@iSazonov iSazonov merged commit 92c6f7a into PowerShell:master Apr 21, 2025
37 of 40 checks passed
@microsoft-github-policy-service
Copy link
Contributor

microsoft-github-policy-service bot commented Apr 21, 2025

📣 Hey @@ArmaanMcleod, how did we do? We would love to hear your feedback with the link below! 🗣️

🔗 https://aka.ms/PSRepoFeedback

Sysoiev-Yurii pushed a commit to Sysoiev-Yurii/PowerShell that referenced this pull request May 12, 2025
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExperimentalFeature, PSEdition, Noun and JoinItem Argument completers don't handle single/double quotes for parameter text completion

2 participants