[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

new refactor for demo

Mohammad Bagher Abiyat (Oct 8, 2024, 4:24 PM +0330) 4e466253 123f7fdb

+395
+2
bash.ts
··· 1 + export function generate(name: string, includeDescription: boolean) { 2 + }
+94
demo.ts
··· 1 + import fs from "fs/promises"; 2 + import cac, { CAC } from "cac"; 3 + import { Callback, flagMap, Positional, positionalMap } from "./shared"; 4 + import path from "path"; 5 + 6 + const cli = cac("cac"); 7 + 8 + cli.option("--type [type]", "Choose a project type", { 9 + default: "node", 10 + }); 11 + cli.option("--name <name>", "Provide your name"); 12 + cli.option("--name23 <name>", "Provide your name23"); 13 + 14 + cli.command("start").option("--port <port>", "your port"); 15 + 16 + cli.command("help"); 17 + 18 + cli.command("help config").option("--foo", "foo option"); 19 + 20 + cli.command("test dev").option("--foo", "foo option"); 21 + 22 + cli 23 + .command("deploy <environment> [version] [...files]") 24 + 25 + cli.version("0.0.0"); 26 + cli.help(); 27 + 28 + cli.parse(); 29 + 30 + for (const c of [cli.globalCommand, ...cli.commands]) { 31 + for (const o of [...cli.globalCommand.options, ...c.options]) { 32 + if (o.rawName === "--type [type]") { 33 + flagMap.set(`${c.name} ${o.name}`, () => { 34 + return [ 35 + { 36 + action: "standalone", 37 + description: "Standalone type", 38 + }, 39 + { 40 + action: "complex", 41 + description: "Complex type", 42 + }, 43 + ]; 44 + }); 45 + } 46 + } 47 + 48 + // console.log(c.args); 49 + if (c.name === "deploy") { 50 + const positionals: Positional[] = []; 51 + positionalMap.set(c.name, positionals); 52 + for (const arg of c.args) { 53 + const completion: Callback = async () => { 54 + if (arg.value === "environment") { 55 + return [ 56 + { 57 + action: "node", 58 + }, 59 + { 60 + action: "deno", 61 + }, 62 + ]; 63 + } 64 + if (arg.value === "version") { 65 + return [ 66 + { 67 + action: "0.0.0", 68 + }, 69 + { 70 + action: "0.0.1", 71 + }, 72 + ]; 73 + } 74 + if (arg.value === "files") { 75 + const currentDir = process.cwd(); 76 + const files = await fs.readdir(currentDir); 77 + const jsonFiles = files.filter( 78 + (file) => path.extname(file).toLowerCase() === ".json", 79 + ); 80 + return jsonFiles.map((file) => ({ action: file })); 81 + } 82 + return []; 83 + }; 84 + 85 + positionals.push({ 86 + required: arg.required, 87 + variadic: arg.variadic, 88 + completion, 89 + }); 90 + } 91 + } 92 + } 93 + 94 +
+2
fish.ts
··· 1 + export function generate(name: string, includeDescription: boolean) { 2 + }
+2
powershell.ts
··· 1 + export function generate(name: string, includeDescription: boolean) { 2 + }
+75
shared.ts
··· 1 + 2 + // ShellCompRequestCmd is the name of the hidden command that is used to request 3 + // completion results from the program. It is used by the shell completion scripts. 4 + export const ShellCompRequestCmd: string = "__complete"; 5 + 6 + // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request 7 + // completion results without their description. It is used by the shell completion scripts. 8 + export const ShellCompNoDescRequestCmd: string = "__completeNoDesc"; 9 + 10 + // ShellCompDirective is a bit map representing the different behaviors the shell 11 + // can be instructed to have once completions have been provided. 12 + export const ShellCompDirective = { 13 + // ShellCompDirectiveError indicates an error occurred and completions should be ignored. 14 + ShellCompDirectiveError: 1 << 0, 15 + 16 + // ShellCompDirectiveNoSpace indicates that the shell should not add a space 17 + // after the completion even if there is a single completion provided. 18 + ShellCompDirectiveNoSpace: 1 << 1, 19 + 20 + // ShellCompDirectiveNoFileComp indicates that the shell should not provide 21 + // file completion even when no completion is provided. 22 + ShellCompDirectiveNoFileComp: 1 << 2, 23 + 24 + // ShellCompDirectiveFilterFileExt indicates that the provided completions 25 + // should be used as file extension filters. 26 + // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename() 27 + // is a shortcut to using this directive explicitly. The BashCompFilenameExt 28 + // annotation can also be used to obtain the same behavior for flags. 29 + ShellCompDirectiveFilterFileExt: 1 << 3, 30 + 31 + // ShellCompDirectiveFilterDirs indicates that only directory names should 32 + // be provided in file completion. To request directory names within another 33 + // directory, the returned completions should specify the directory within 34 + // which to search. The BashCompSubdirsInDir annotation can be used to 35 + // obtain the same behavior but only for flags. 36 + ShellCompDirectiveFilterDirs: 1 << 4, 37 + 38 + // ShellCompDirectiveKeepOrder indicates that the shell should preserve the order 39 + // in which the completions are provided. 40 + ShellCompDirectiveKeepOrder: 1 << 5, 41 + 42 + // =========================================================================== 43 + 44 + // All directives using iota (or equivalent in Go) should be above this one. 45 + // For internal use. 46 + shellCompDirectiveMaxValue: 1 << 6, 47 + 48 + // ShellCompDirectiveDefault indicates to let the shell perform its default 49 + // behavior after completions have been provided. 50 + // This one must be last to avoid messing up the iota count. 51 + ShellCompDirectiveDefault: 0, 52 + }; 53 + 54 + 55 + 56 + type Completion = { 57 + action: string; 58 + description?: string; 59 + }; 60 + 61 + export type Callback = ( 62 + previousArgs: string[], 63 + toComplete: string, 64 + ) => Completion[] | Promise<Completion[]>; 65 + 66 + export type Positional = { 67 + required: boolean; 68 + variadic: boolean; 69 + completion: Callback; 70 + }; 71 + 72 + 73 + export const positionalMap = new Map<string, Positional[]>(); 74 + 75 + export const flagMap = new Map<string, Callback>();
+220
zsh.ts
··· 1 + import { ShellCompDirective, ShellCompNoDescRequestCmd, ShellCompRequestCmd } from "./shared"; 2 + 3 + export function generate(name: string, exec: string, includeDescription: boolean) { 4 + let compCmd = ShellCompRequestCmd; 5 + if (!includeDescription) { 6 + compCmd = ShellCompNoDescRequestCmd; 7 + } 8 + 9 + return `#compdef ${name} 10 + compdef _${name} ${name} 11 + 12 + # zsh completion for ${name} -*- shell-script -*- 13 + 14 + __${name}_debug() { 15 + local file="$BASH_COMP_DEBUG_FILE" 16 + if [[ -n \${file} ]]; then 17 + echo "$*" >> "\${file}" 18 + fi 19 + } 20 + 21 + _${name}() { 22 + local shellCompDirectiveError=${ShellCompDirective.ShellCompDirectiveError} 23 + local shellCompDirectiveNoSpace=${ShellCompDirective.ShellCompDirectiveNoSpace} 24 + local shellCompDirectiveNoFileComp=${ShellCompDirective.ShellCompDirectiveNoFileComp} 25 + local shellCompDirectiveFilterFileExt=${ShellCompDirective.ShellCompDirectiveFilterFileExt} 26 + local shellCompDirectiveFilterDirs=${ShellCompDirective.ShellCompDirectiveFilterDirs} 27 + local shellCompDirectiveKeepOrder=${ShellCompDirective.ShellCompDirectiveKeepOrder} 28 + 29 + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder 30 + local -a completions 31 + 32 + __${name}_debug "\\n========= starting completion logic ==========" 33 + __${name}_debug "CURRENT: \${CURRENT}, words[*]: \${words[*]}" 34 + 35 + # The user could have moved the cursor backwards on the command-line. 36 + # We need to trigger completion from the \$CURRENT location, so we need 37 + # to truncate the command-line (\$words) up to the \$CURRENT location. 38 + # (We cannot use \$CURSOR as its value does not work when a command is an alias.) 39 + words=( "\${=words[1,CURRENT]}" ) 40 + __${name}_debug "Truncated words[*]: \${words[*]}," 41 + 42 + lastParam=\${words[-1]} 43 + lastChar=\${lastParam[-1]} 44 + __${name}_debug "lastParam: \${lastParam}, lastChar: \${lastChar}" 45 + 46 + # For zsh, when completing a flag with an = (e.g., ${name} -n=<TAB>) 47 + # completions must be prefixed with the flag 48 + setopt local_options BASH_REMATCH 49 + if [[ "\${lastParam}" =~ '-.*=' ]]; then 50 + # We are dealing with a flag with an = 51 + flagPrefix="-P \${BASH_REMATCH}" 52 + fi 53 + 54 + # Prepare the command to obtain completions 55 + requestComp="${exec} ${compCmd} -- \${words[2,-1]}" 56 + if [ "\${lastChar}" = "" ]; then 57 + # If the last parameter is complete (there is a space following it) 58 + # We add an extra empty parameter so we can indicate this to the go completion code. 59 + __${name}_debug "Adding extra empty parameter" 60 + requestComp="\${requestComp} ''" 61 + fi 62 + 63 + __${name}_debug "About to call: eval \${requestComp}" 64 + 65 + # Use eval to handle any environment variables and such 66 + out=\$(eval \${requestComp} 2>/dev/null) 67 + __${name}_debug "completion output: \${out}" 68 + 69 + # Extract the directive integer following a : from the last line 70 + local lastLine 71 + while IFS='\n' read -r line; do 72 + lastLine=\${line} 73 + done < <(printf "%s\n" "\${out[@]}") 74 + __${name}_debug "last line: \${lastLine}" 75 + 76 + if [ "\${lastLine[1]}" = : ]; then 77 + directive=\${lastLine[2,-1]} 78 + # Remove the directive including the : and the newline 79 + local suffix 80 + (( suffix=\${#lastLine}+2)) 81 + out=\${out[1,-\$suffix]} 82 + else 83 + # There is no directive specified. Leave \$out as is. 84 + __${name}_debug "No directive found. Setting to default" 85 + directive=0 86 + fi 87 + 88 + __${name}_debug "directive: \${directive}" 89 + __${name}_debug "completions: \${out}" 90 + __${name}_debug "flagPrefix: \${flagPrefix}" 91 + 92 + if [ \$((directive & shellCompDirectiveError)) -ne 0 ]; then 93 + __${name}_debug "Completion received error. Ignoring completions." 94 + return 95 + fi 96 + 97 + local activeHelpMarker="%" 98 + local endIndex=\${#activeHelpMarker} 99 + local startIndex=\$((\${#activeHelpMarker}+1)) 100 + local hasActiveHelp=0 101 + while IFS='\n' read -r comp; do 102 + # Check if this is an activeHelp statement (i.e., prefixed with \$activeHelpMarker) 103 + if [ "\${comp[1,\$endIndex]}" = "\$activeHelpMarker" ];then 104 + __${name}_debug "ActiveHelp found: \$comp" 105 + comp="\${comp[\$startIndex,-1]}" 106 + if [ -n "\$comp" ]; then 107 + compadd -x "\${comp}" 108 + __${name}_debug "ActiveHelp will need delimiter" 109 + hasActiveHelp=1 110 + fi 111 + continue 112 + fi 113 + 114 + if [ -n "\$comp" ]; then 115 + # If requested, completions are returned with a description. 116 + # The description is preceded by a TAB character. 117 + # For zsh's _describe, we need to use a : instead of a TAB. 118 + # We first need to escape any : as part of the completion itself. 119 + comp=\${comp//:/\\:} 120 + 121 + local tab="\$(printf '\\t')" 122 + comp=\${comp//\$tab/:} 123 + 124 + __${name}_debug "Adding completion: \${comp}" 125 + completions+=\${comp} 126 + lastComp=\$comp 127 + fi 128 + done < <(printf "%s\n" "\${out[@]}") 129 + 130 + # Add a delimiter after the activeHelp statements, but only if: 131 + # - there are completions following the activeHelp statements, or 132 + # - file completion will be performed (so there will be choices after the activeHelp) 133 + if [ \$hasActiveHelp -eq 1 ]; then 134 + if [ \${#completions} -ne 0 ] || [ \$((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then 135 + __${name}_debug "Adding activeHelp delimiter" 136 + compadd -x "--" 137 + hasActiveHelp=0 138 + fi 139 + fi 140 + 141 + if [ \$((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then 142 + __${name}_debug "Activating nospace." 143 + noSpace="-S ''" 144 + fi 145 + 146 + if [ \$((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then 147 + __${name}_debug "Activating keep order." 148 + keepOrder="-V" 149 + fi 150 + 151 + if [ \$((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then 152 + # File extension filtering 153 + local filteringCmd 154 + filteringCmd='_files' 155 + for filter in \${completions[@]}; do 156 + if [ \${filter[1]} != '*' ]; then 157 + # zsh requires a glob pattern to do file filtering 158 + filter="\\*.\$filter" 159 + fi 160 + filteringCmd+=" -g \$filter" 161 + done 162 + filteringCmd+=" \${flagPrefix}" 163 + 164 + __${name}_debug "File filtering command: \$filteringCmd" 165 + _arguments '*:filename:'"\$filteringCmd" 166 + elif [ \$((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then 167 + # File completion for directories only 168 + local subdir 169 + subdir="\${completions[1]}" 170 + if [ -n "\$subdir" ]; then 171 + __${name}_debug "Listing directories in \$subdir" 172 + pushd "\${subdir}" >/dev/null 2>&1 173 + else 174 + __${name}_debug "Listing directories in ." 175 + fi 176 + 177 + local result 178 + _arguments '*:dirname:_files -/'" \${flagPrefix}" 179 + result=\$? 180 + if [ -n "\$subdir" ]; then 181 + popd >/dev/null 2>&1 182 + fi 183 + return \$result 184 + else 185 + __${name}_debug "Calling _describe" 186 + if eval _describe \$keepOrder "completions" completions -Q \${flagPrefix} \${noSpace}; then 187 + __${name}_debug "_describe found some completions" 188 + 189 + # Return the success of having called _describe 190 + return 0 191 + else 192 + __${name}_debug "_describe did not find completions." 193 + __${name}_debug "Checking if we should do file completion." 194 + if [ \$((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then 195 + __${name}_debug "deactivating file completion" 196 + 197 + # We must return an error code here to let zsh know that there were no 198 + # completions found by _describe; this is what will trigger other 199 + # matching algorithms to attempt to find completions. 200 + # For example zsh can match letters in the middle of words. 201 + return 1 202 + else 203 + # Perform file completion 204 + __${name}_debug "Activating file completion" 205 + 206 + # We must return the result of this command, so it must be the 207 + # last command, or else we must store its result to return it. 208 + _arguments '*:filename:_files'" \${flagPrefix}" 209 + fi 210 + fi 211 + fi 212 + } 213 + 214 + # don't run the completion function when being sourced or eval-ed 215 + if [ "\${funcstack[1]}" = "_${name}" ]; then 216 + _${name} 217 + fi 218 + `; 219 + } 220 +