Skip to content

Commit 61f598d

Browse files
authored
Update SelectCommandArgument to properly handle POSIX style options for CLI commands (#4016)
1 parent 4023f06 commit 61f598d

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

PSReadLine/KillYank.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Linq;
88
using System.Management.Automation.Language;
9+
using System.Text.RegularExpressions;
910
using Microsoft.PowerShell.Internal;
1011

1112
namespace Microsoft.PowerShell
@@ -29,6 +30,10 @@ class YankLastArgState
2930
private YankLastArgState _yankLastArgState;
3031
private int _visualSelectionCommandCount;
3132

33+
// Pattern to check for CLI parameters like '--json'.
34+
// Valid characters are 'a-z', 'A-Z', '0-9', '_' (all covered by '\w'), and '-'.
35+
private static readonly Regex s_cliOptionPattern = new(@"^--[\w-]+$", RegexOptions.Compiled);
36+
3237
/// <summary>
3338
/// Mark the current location of the cursor for use in a subsequent editing command.
3439
/// </summary>
@@ -480,7 +485,7 @@ public static void SelectCommandArgument(ConsoleKeyInfo? key = null, object arg
480485
var argument = cmdAst.CommandElements[j] switch
481486
{
482487
CommandParameterAst paramAst => paramAst.Argument,
483-
ExpressionAst expAst => expAst,
488+
ExpressionAst exprAst => ProcessExpressionAst(exprAst),
484489
_ => null,
485490
};
486491

@@ -614,6 +619,7 @@ public static void SelectCommandArgument(ConsoleKeyInfo? key = null, object arg
614619
_singleton.VisualSelectionCommon(() => SetCursorPosition(newEndCursor), forceSetMark: true);
615620

616621

622+
// ===== Local Functions =====
617623
// Get the script block AST's whose extent contains the cursor.
618624
bool GetScriptBlockAst(Ast ast)
619625
{
@@ -639,6 +645,22 @@ bool GetScriptBlockAst(Ast ast)
639645
? ast.Extent.EndOffset - 1 > cursor
640646
: ast.Extent.EndOffset >= cursor;
641647
}
648+
649+
// Process an expression AST to check if it's a CLI posix style option.
650+
static ExpressionAst ProcessExpressionAst(ExpressionAst exprAst)
651+
{
652+
if (exprAst is StringConstantExpressionAst strAst
653+
&& strAst.StringConstantType is StringConstantType.BareWord
654+
&& strAst.Value.StartsWith("--")
655+
&& s_cliOptionPattern.IsMatch(strAst.Value))
656+
{
657+
// It's a CLI posix style option, like '--json' or '--machine-type',
658+
// so we treat it as a parameter.
659+
return null;
660+
}
661+
662+
return exprAst;
663+
}
642664
}
643665

644666
/// <summary>

test/KillYankTest.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,56 @@ public void SelectCommandArgument_VariousArgs()
619619
_.Escape));
620620
}
621621

622+
[SkippableFact]
623+
public void SelectCommandArgument_CLIArgs()
624+
{
625+
TestSetup(KeyMode.Cmd);
626+
627+
Test("", Keys(
628+
"az webapp --name MyWebApp --resource-group MyResourceGroup",
629+
_.Alt_a, CheckThat(() => AssertScreenIs(1,
630+
TokenClassification.Command, "az",
631+
TokenClassification.None, ' ',
632+
TokenClassification.Selection, "webapp",
633+
TokenClassification.None, ' ',
634+
TokenClassification.Parameter, "--name",
635+
TokenClassification.None, " MyWebApp ",
636+
TokenClassification.Parameter, "--resource-group",
637+
TokenClassification.None, " MyResourceGroup ")),
638+
639+
_.Alt_a, CheckThat(() => AssertScreenIs(1,
640+
TokenClassification.Command, "az",
641+
TokenClassification.None, " webapp ",
642+
TokenClassification.Parameter, "--name",
643+
TokenClassification.None, ' ',
644+
TokenClassification.Selection, "MyWebApp",
645+
TokenClassification.None, ' ',
646+
TokenClassification.Parameter, "--resource-group",
647+
TokenClassification.None, " MyResourceGroup ")),
648+
649+
_.Alt_a, CheckThat(() => AssertScreenIs(1,
650+
TokenClassification.Command, "az",
651+
TokenClassification.None, " webapp ",
652+
TokenClassification.Parameter, "--name",
653+
TokenClassification.None, " MyWebApp ",
654+
TokenClassification.Parameter, "--resource-group",
655+
TokenClassification.None, ' ',
656+
TokenClassification.Selection, "MyResourceGroup")),
657+
658+
// Verify that we can loop through the arguments.
659+
_.Alt_a, CheckThat(() => AssertScreenIs(1,
660+
TokenClassification.Command, "az",
661+
TokenClassification.None, ' ',
662+
TokenClassification.Selection, "webapp",
663+
TokenClassification.None, ' ',
664+
TokenClassification.Parameter, "--name",
665+
TokenClassification.None, " MyWebApp ",
666+
TokenClassification.Parameter, "--resource-group",
667+
TokenClassification.None, " MyResourceGroup ")),
668+
669+
_.Escape));
670+
}
671+
622672
[SkippableFact]
623673
public void SelectCommandArgument_HereStringArgs()
624674
{

0 commit comments

Comments
 (0)