EdgeAIG's picture
download
raw
7.72 kB
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const escapeFishString = s => s.replace(/'/g, "\\'");
/**
* Build a Fish condition that checks the current subcommand context.
*
* For root-level completions with subcommands: `__fish_use_subcommand`
* For nested commands: verify the parent subcommand is active AND none of its
* child subcommands have been entered yet.
*/
const subcommandCondition = (parentPath, childSubcommandNames) => {
if (parentPath.length === 0) {
if (childSubcommandNames.length > 0) {
return `__fish_use_subcommand`;
}
return ``;
}
const parent = parentPath[parentPath.length - 1];
if (childSubcommandNames.length > 0) {
// Show only when parent is active but no child subcommand has been entered
return `__fish_seen_subcommand_from ${parent}; and not __fish_seen_subcommand_from ${childSubcommandNames.join(" ")}`;
}
return `__fish_seen_subcommand_from ${parent}`;
};
/**
* Build a __fish_contains_opt condition that checks whether any form of this
* flag has already been typed. Returns the condition string without wrapping
* quotes (the caller adds those).
*/
const flagContainsOptCondition = flag => {
const optArgs = [];
for (const alias of flag.aliases) {
if (alias.length === 1) {
optArgs.push(`-s ${alias}`);
}
}
// Long names (fish __fish_contains_opt uses bare words for long opts)
optArgs.push(flag.name);
for (const alias of flag.aliases) {
if (alias.length > 1) {
optArgs.push(alias);
}
}
if (flag.type._tag === "Boolean") {
optArgs.push(`no-${flag.name}`);
}
return `not __fish_contains_opt ${optArgs.join(" ")}`;
};
const flagCompletionArgs = flag => {
const args = [`-l ${flag.name}`];
for (const alias of flag.aliases) {
if (alias.length === 1) {
args.push(`-s ${alias}`);
} else {
args.push(`-l ${alias}`);
}
}
if (flag.description) {
args.push(`-d '${escapeFishString(flag.description)}'`);
}
const valueArgs = flagValueArgs(flag.type);
if (valueArgs) {
args.push(valueArgs);
}
return args;
};
const flagValueArgs = type => {
switch (type._tag) {
case "Boolean":
return undefined;
case "Choice":
return `-r -f -a '${type.values.join(" ")}'`;
case "Path":
if (type.pathType === "directory") return `-r -F`;
return `-r -F`;
default:
// -r: requires a value, -f: don't fall back to file completion
return `-r -f`;
}
};
const argValueArgs = type => {
switch (type._tag) {
case "Choice":
return `-r -f -a '${type.values.join(" ")}'`;
case "Path":
return `-r -F`;
default:
return undefined;
}
};
// ---------------------------------------------------------------------------
// Generator
// ---------------------------------------------------------------------------
const generateCompletions = (executableName, descriptor, parentPath, lines) => {
const allSubNames = descriptor.subcommands.map(s => s.name);
const condition = subcommandCondition(parentPath, allSubNames);
const conditionArg = condition ? `-n '${condition}'` : ``;
// Suppress default file completion unless the command has path-type
// positional arguments. Without this, fish falls back to listing files
// even when only flags are valid.
const hasPathArgs = descriptor.arguments.some(a => a.type._tag === "Path");
if (!hasPathArgs) {
const parts = [`complete -c ${executableName}`];
if (conditionArg) parts.push(conditionArg);
parts.push(`-f`);
lines.push(parts.join(" "));
}
// Subcommand completions
for (const sub of descriptor.subcommands) {
const parts = [`complete -c ${executableName}`];
if (conditionArg) parts.push(conditionArg);
parts.push(`-f -a '${escapeFishString(sub.name)}'`);
if (sub.description) {
parts.push(`-d '${escapeFishString(sub.description)}'`);
}
lines.push(parts.join(" "));
}
// Flag completions
for (const flag of descriptor.flags) {
// Only apply __fish_contains_opt dedup for boolean flags. For value-taking
// flags, the dedup condition would suppress the entry while fish is waiting
// for a value (e.g. typing `--env <TAB>` wouldn't show choices).
const isBoolean = flag.type._tag === "Boolean";
const flagCondition = isBoolean ? condition ? `${condition}; and ${flagContainsOptCondition(flag)}` : flagContainsOptCondition(flag) : condition;
const flagCondArg = flagCondition ? `-n '${flagCondition}'` : ``;
const parts = [`complete -c ${executableName}`];
if (flagCondArg) parts.push(flagCondArg);
parts.push(...flagCompletionArgs(flag));
lines.push(parts.join(" "));
// Boolean negation
if (isBoolean) {
const negParts = [`complete -c ${executableName}`];
if (flagCondArg) negParts.push(flagCondArg);
negParts.push(`-l no-${flag.name}`);
if (flag.description) {
negParts.push(`-d '${escapeFishString(`Disable ${flag.name}`)}'`);
}
lines.push(negParts.join(" "));
}
}
// Flags as -a entries for bare <TAB> visibility.
// Fish only shows -l/-s entries when the current word starts with -, so
// without this, flags are invisible on bare <TAB>. Guarded to only show
// when NOT already typing an option (avoids duplication with -l/-s above)
// and hidden once a flag has been used (__fish_contains_opt).
if (descriptor.flags.length > 0) {
const notDash = `not string match -q -- "-*" (commandline -ct)`;
const bareBase = condition ? `${condition}; and ${notDash}` : notDash;
for (const flag of descriptor.flags) {
const bareCondition = `${bareBase}; and ${flagContainsOptCondition(flag)}`;
const isBoolean = flag.type._tag === "Boolean";
const parts = [`complete -c ${executableName}`];
parts.push(`-n '${bareCondition}'`);
parts.push(`-f -a '--${flag.name}'`);
if (flag.description) {
parts.push(`-d '${escapeFishString(flag.description)}'`);
}
lines.push(parts.join(" "));
if (isBoolean) {
const negParts = [`complete -c ${executableName}`];
negParts.push(`-n '${bareCondition}'`);
negParts.push(`-f -a '--no-${flag.name}'`);
if (flag.description) {
negParts.push(`-d '${escapeFishString(`Disable ${flag.name}`)}'`);
}
lines.push(negParts.join(" "));
}
}
}
// Argument completions (type hints only)
for (const arg of descriptor.arguments) {
const valueArg = argValueArgs(arg.type);
if (valueArg) {
const parts = [`complete -c ${executableName}`];
if (conditionArg) parts.push(conditionArg);
parts.push(valueArg);
if (arg.description) {
parts.push(`-d '${escapeFishString(arg.description)}'`);
}
lines.push(parts.join(" "));
}
}
// Recurse into subcommands
for (const sub of descriptor.subcommands) {
generateCompletions(executableName, sub, [...parentPath, sub.name], lines);
}
};
/** @internal */
export const generate = (executableName, descriptor) => {
const lines = [];
lines.push(`###-begin-${executableName}-completions-###`);
lines.push(`#`);
lines.push(`# Static completion script for Fish`);
lines.push(`#`);
lines.push(`# Installation:`);
lines.push(`# ${executableName} --completions fish > ~/.config/fish/completions/${executableName}.fish`);
lines.push(`#`);
lines.push(``);
generateCompletions(executableName, descriptor, [], lines);
lines.push(``);
lines.push(`###-end-${executableName}-completions-###`);
return lines.join("\n");
};
//# sourceMappingURL=fish.js.map

Xet Storage Details

Size:
7.72 kB
·
Xet hash:
f570b9ab02f31da72d3abba3c5d238fbe83c7b92462be8fc7e85a6b7ace2e55d

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.