From ba3e30949af0cc34d4c74853f0cb2cad8df2652f Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 14 May 2024 11:56:21 -0700 Subject: [PATCH] Properly handle posix style options for CLI commands --- PSReadLine/KillYank.cs | 24 +++++++++++++++++++- test/KillYankTest.cs | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/PSReadLine/KillYank.cs b/PSReadLine/KillYank.cs index f05fc8ba0..77fe33f1b 100644 --- a/PSReadLine/KillYank.cs +++ b/PSReadLine/KillYank.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation.Language; +using System.Text.RegularExpressions; using Microsoft.PowerShell.Internal; namespace Microsoft.PowerShell @@ -29,6 +30,10 @@ class YankLastArgState private YankLastArgState _yankLastArgState; private int _visualSelectionCommandCount; + // Pattern to check for CLI parameters like '--json'. + // Valid characters are 'a-z', 'A-Z', '0-9', '_' (all covered by '\w'), and '-'. + private static readonly Regex s_cliOptionPattern = new(@"^--[\w-]+$", RegexOptions.Compiled); + /// /// Mark the current location of the cursor for use in a subsequent editing command. /// @@ -480,7 +485,7 @@ public static void SelectCommandArgument(ConsoleKeyInfo? key = null, object arg var argument = cmdAst.CommandElements[j] switch { CommandParameterAst paramAst => paramAst.Argument, - ExpressionAst expAst => expAst, + ExpressionAst exprAst => ProcessExpressionAst(exprAst), _ => null, }; @@ -614,6 +619,7 @@ public static void SelectCommandArgument(ConsoleKeyInfo? key = null, object arg _singleton.VisualSelectionCommon(() => SetCursorPosition(newEndCursor), forceSetMark: true); + // ===== Local Functions ===== // Get the script block AST's whose extent contains the cursor. bool GetScriptBlockAst(Ast ast) { @@ -639,6 +645,22 @@ bool GetScriptBlockAst(Ast ast) ? ast.Extent.EndOffset - 1 > cursor : ast.Extent.EndOffset >= cursor; } + + // Process an expression AST to check if it's a CLI posix style option. + static ExpressionAst ProcessExpressionAst(ExpressionAst exprAst) + { + if (exprAst is StringConstantExpressionAst strAst + && strAst.StringConstantType is StringConstantType.BareWord + && strAst.Value.StartsWith("--") + && s_cliOptionPattern.IsMatch(strAst.Value)) + { + // It's a CLI posix style option, like '--json' or '--machine-type', + // so we treat it as a parameter. + return null; + } + + return exprAst; + } } /// diff --git a/test/KillYankTest.cs b/test/KillYankTest.cs index 3433f7dab..8705faab6 100644 --- a/test/KillYankTest.cs +++ b/test/KillYankTest.cs @@ -619,6 +619,56 @@ public void SelectCommandArgument_VariousArgs() _.Escape)); } + [SkippableFact] + public void SelectCommandArgument_CLIArgs() + { + TestSetup(KeyMode.Cmd); + + Test("", Keys( + "az webapp --name MyWebApp --resource-group MyResourceGroup", + _.Alt_a, CheckThat(() => AssertScreenIs(1, + TokenClassification.Command, "az", + TokenClassification.None, ' ', + TokenClassification.Selection, "webapp", + TokenClassification.None, ' ', + TokenClassification.Parameter, "--name", + TokenClassification.None, " MyWebApp ", + TokenClassification.Parameter, "--resource-group", + TokenClassification.None, " MyResourceGroup ")), + + _.Alt_a, CheckThat(() => AssertScreenIs(1, + TokenClassification.Command, "az", + TokenClassification.None, " webapp ", + TokenClassification.Parameter, "--name", + TokenClassification.None, ' ', + TokenClassification.Selection, "MyWebApp", + TokenClassification.None, ' ', + TokenClassification.Parameter, "--resource-group", + TokenClassification.None, " MyResourceGroup ")), + + _.Alt_a, CheckThat(() => AssertScreenIs(1, + TokenClassification.Command, "az", + TokenClassification.None, " webapp ", + TokenClassification.Parameter, "--name", + TokenClassification.None, " MyWebApp ", + TokenClassification.Parameter, "--resource-group", + TokenClassification.None, ' ', + TokenClassification.Selection, "MyResourceGroup")), + + // Verify that we can loop through the arguments. + _.Alt_a, CheckThat(() => AssertScreenIs(1, + TokenClassification.Command, "az", + TokenClassification.None, ' ', + TokenClassification.Selection, "webapp", + TokenClassification.None, ' ', + TokenClassification.Parameter, "--name", + TokenClassification.None, " MyWebApp ", + TokenClassification.Parameter, "--resource-group", + TokenClassification.None, " MyResourceGroup ")), + + _.Escape)); + } + [SkippableFact] public void SelectCommandArgument_HereStringArgs() {