EdgeAIG/opencode / .opencode /node_modules /effect /dist /unstable /cli /internal /completions /fish.js
| // --------------------------------------------------------------------------- | |
| // 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.