mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
fix: restore PowerShell completion before later arguments (#147737)
Use the supplied cursor position when deriving completion context, so later words do not hide command and option-value suggestions. Preserve existing end-of-line behavior through the same parser.
This commit is contained in:
parent
cde0cdaf59
commit
f6d984fcbe
4 changed files with 41 additions and 5 deletions
|
|
@ -65,6 +65,7 @@ profile selected in the table above. It has no profile-file destination option.
|
|||
- Completion generation eagerly loads the full command tree, including plugin CLI commands, so nested subcommands are included.
|
||||
- If invalid configuration prevents plugin discovery, generation warns and still includes core commands. Repair the configuration and regenerate to include plugin commands.
|
||||
- Bash completion supports both `--flag value` and `--flag=value`, including named profiles before nested commands and single-quoted, double-quoted, or backslash-escaped value prefixes.
|
||||
- PowerShell completes commands and option values at the cursor, including when later arguments are already present.
|
||||
- `openclaw update` refreshes the completion cache automatically after a successful update. `openclaw doctor` can repair missing or stale completion setups.
|
||||
|
||||
## Related
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ while (($encodedRequest = [Console]::In.ReadLine()) -ne $null) {
|
|||
$completions = @(
|
||||
[System.Management.Automation.CommandCompletion]::CompleteInput(
|
||||
$commandLine,
|
||||
$commandLine.Length,
|
||||
[int]$request.cursorPosition,
|
||||
$null
|
||||
).CompletionMatches | ForEach-Object { [string]$_.CompletionText }
|
||||
)
|
||||
|
|
@ -249,15 +249,23 @@ export class PowerShellCompletionRunner {
|
|||
private readyPromise: Promise<void> | undefined;
|
||||
private stdoutLines: ReadlineInterface | undefined;
|
||||
|
||||
complete(program: Command, commandLine: string): Promise<string[]> {
|
||||
complete(
|
||||
program: Command,
|
||||
commandLine: string,
|
||||
cursorPosition = commandLine.length,
|
||||
): Promise<string[]> {
|
||||
const script = getCompletionScript("powershell", program);
|
||||
const caseId = createHash("sha256")
|
||||
.update(script)
|
||||
.update("\0")
|
||||
.update(commandLine)
|
||||
.update("\0")
|
||||
.update(String(cursorPosition))
|
||||
.digest("hex")
|
||||
.slice(0, 20);
|
||||
const result = this.queue.then(() => this.completeCase(caseId, script, commandLine));
|
||||
const result = this.queue.then(() =>
|
||||
this.completeCase(caseId, script, commandLine, cursorPosition),
|
||||
);
|
||||
this.queue = result.then(
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
|
|
@ -307,6 +315,7 @@ export class PowerShellCompletionRunner {
|
|||
caseId: string,
|
||||
script: string,
|
||||
commandLine: string,
|
||||
cursorPosition: number,
|
||||
): Promise<string[]> {
|
||||
await this.start();
|
||||
if (this.failure) {
|
||||
|
|
@ -322,6 +331,7 @@ export class PowerShellCompletionRunner {
|
|||
id: caseId,
|
||||
script: Buffer.from(script, "utf8").toString("base64"),
|
||||
commandLine: Buffer.from(commandLine, "utf8").toString("base64"),
|
||||
cursorPosition,
|
||||
}),
|
||||
"utf8",
|
||||
).toString("base64");
|
||||
|
|
|
|||
|
|
@ -287,6 +287,31 @@ _openclaw_root_completion
|
|||
).toEqual(["fish"]);
|
||||
});
|
||||
|
||||
itWithPowerShell.each([
|
||||
{
|
||||
name: "an option after a shell value",
|
||||
prefix: "openclaw completion --shell f",
|
||||
suffix: " --yes",
|
||||
expected: ["fish"],
|
||||
},
|
||||
{
|
||||
name: "a shared command name after the root command",
|
||||
prefix: "openclaw g",
|
||||
suffix: " status --json",
|
||||
expected: ["gateway"],
|
||||
},
|
||||
])(
|
||||
"ignores real PowerShell words after the cursor: $name",
|
||||
async ({ prefix, suffix, expected }) => {
|
||||
const program = createDocumentedCompletionProgram();
|
||||
program.command("status").description("Root status").option("--json", "JSON output");
|
||||
|
||||
expect(
|
||||
await powerShellCompletion.complete(program, `${prefix}${suffix}`, prefix.length),
|
||||
).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
itWithPowerShell.each([
|
||||
{
|
||||
name: "an omitted optional value",
|
||||
|
|
|
|||
|
|
@ -494,7 +494,8 @@ ${optionChoiceCases}
|
|||
Register-ArgumentCompleter -Native -CommandName ${rootCmd} -ScriptBlock {
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$commandElements = $commandAst.CommandElements
|
||||
# Limit context to the cursor; command-path parsing below skips option operands.
|
||||
$commandElements = @($commandAst.CommandElements.Where({ $_.Extent.StartOffset -lt $cursorPosition }))
|
||||
$commandPath = ""
|
||||
$valueOptions = ${formatPowerShellArray(rootValueOptions)}
|
||||
$previousElementIndex = if ($wordToComplete -eq '') { $commandElements.Count - 1 } else { $commandElements.Count - 2 }
|
||||
|
|
@ -508,7 +509,6 @@ Register-ArgumentCompleter -Native -CommandName ${rootCmd} -ScriptBlock {
|
|||
$choiceCompletionPrefix = "$choiceFlag="
|
||||
}
|
||||
|
||||
# Skip option values so global and nested flags cannot hide the command path.
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {
|
||||
$element = $commandElements[$i].Extent.Text
|
||||
if ($i -eq $commandElements.Count - 1 -and $wordToComplete -ne "") { break }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue