fix: avoid OOM during completion script generation

This commit is contained in:
Xiaoyi 2026-03-14 07:43:58 +08:00
parent 47a15d7a9a
commit b9c57bbb97

View File

@ -442,17 +442,17 @@ function generateZshSubcmdList(cmd: Command): string {
}
function generateZshSubcommands(program: Command, prefix: string): string {
let script = "";
const segments: string[] = [];
for (const cmd of program.commands) {
const cmdName = cmd.name();
const funcName = `_${prefix}_${cmdName.replace(/-/g, "_")}`;
// Recurse first
script += generateZshSubcommands(cmd, `${prefix}_${cmdName.replace(/-/g, "_")}`);
segments.push(generateZshSubcommands(cmd, `${prefix}_${cmdName.replace(/-/g, "_")}`));
const subCommands = cmd.commands;
if (subCommands.length > 0) {
script += `
segments.push(`
${funcName}() {
local -a commands
local -a options
@ -470,17 +470,17 @@ ${funcName}() {
;;
esac
}
`;
`);
} else {
script += `
segments.push(`
${funcName}() {
_arguments -C \\
${generateZshArgs(cmd)}
}
`;
`);
}
}
return script;
return segments.join("");
}
function generateBashCompletion(program: Command): string {
@ -529,11 +529,11 @@ function generateBashSubcommand(cmd: Command): string {
function generatePowerShellCompletion(program: Command): string {
const rootCmd = program.name();
const visit = (cmd: Command, parents: string[]): string => {
const visit = (cmd: Command, parents: string[]): string[] => {
const cmdName = cmd.name();
const fullPath = [...parents, cmdName].join(" ");
let script = "";
const segments: string[] = [];
// Command completion for this level
const subCommands = cmd.commands.map((c) => c.name());
@ -541,25 +541,25 @@ function generatePowerShellCompletion(program: Command): string {
const allCompletions = [...subCommands, ...options].map((s) => `'${s}'`).join(",");
if (allCompletions.length > 0) {
script += `
segments.push(`
if ($commandPath -eq '${fullPath}') {
$completions = @(${allCompletions})
$completions | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_)
}
}
`;
`);
}
// Recurse
for (const sub of cmd.commands) {
script += visit(sub, [...parents, cmdName]);
segments.push(...visit(sub, [...parents, cmdName]));
}
return script;
return segments;
};
const rootBody = visit(program, []);
const rootBody = visit(program, []).join("");
return `
Register-ArgumentCompleter -Native -CommandName ${rootCmd} -ScriptBlock {
@ -593,65 +593,57 @@ Register-ArgumentCompleter -Native -CommandName ${rootCmd} -ScriptBlock {
function generateFishCompletion(program: Command): string {
const rootCmd = program.name();
let script = "";
const segments: string[] = [];
const visit = (cmd: Command, parents: string[]) => {
const cmdName = cmd.name();
const fullPath = [...parents];
if (parents.length > 0) {
fullPath.push(cmdName);
} // Only push if not root, or consistent root handling
// Fish uses 'seen_subcommand_from' to determine context.
// For root: complete -c openclaw -n "__fish_use_subcommand" -a "subcmd" -d "desc"
// Root logic
if (parents.length === 0) {
// Subcommands of root
for (const sub of cmd.commands) {
script += buildFishSubcommandCompletionLine({
rootCmd,
condition: "__fish_use_subcommand",
name: sub.name(),
description: sub.description(),
});
segments.push(
buildFishSubcommandCompletionLine({
rootCmd,
condition: "__fish_use_subcommand",
name: sub.name(),
description: sub.description(),
}),
);
}
// Options of root
for (const opt of cmd.options) {
script += buildFishOptionCompletionLine({
rootCmd,
condition: "__fish_use_subcommand",
flags: opt.flags,
description: opt.description,
});
segments.push(
buildFishOptionCompletionLine({
rootCmd,
condition: "__fish_use_subcommand",
flags: opt.flags,
description: opt.description,
}),
);
}
} else {
// Nested commands
// Logic: if seen subcommand matches parents...
// But fish completion logic is simpler if we just say "if we haven't seen THIS command yet but seen parent"
// Actually, a robust fish completion often requires defining a function to check current line.
// For simplicity, we'll assume standard fish helper __fish_seen_subcommand_from.
// To properly scope to 'openclaw gateway' and not 'openclaw other gateway', we need to check the sequence.
// A simplified approach:
// Subcommands
for (const sub of cmd.commands) {
script += buildFishSubcommandCompletionLine({
rootCmd,
condition: `__fish_seen_subcommand_from ${cmdName}`,
name: sub.name(),
description: sub.description(),
});
segments.push(
buildFishSubcommandCompletionLine({
rootCmd,
condition: `__fish_seen_subcommand_from ${cmdName}`,
name: sub.name(),
description: sub.description(),
}),
);
}
// Options
for (const opt of cmd.options) {
script += buildFishOptionCompletionLine({
rootCmd,
condition: `__fish_seen_subcommand_from ${cmdName}`,
flags: opt.flags,
description: opt.description,
});
segments.push(
buildFishOptionCompletionLine({
rootCmd,
condition: `__fish_seen_subcommand_from ${cmdName}`,
flags: opt.flags,
description: opt.description,
}),
);
}
}
@ -661,5 +653,5 @@ function generateFishCompletion(program: Command): string {
};
visit(program, []);
return script;
return segments.join("");
}