1package cobra 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8) 9 10func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) { 11 compCmd := ShellCompRequestCmd 12 if !includeDesc { 13 compCmd = ShellCompNoDescRequestCmd 14 } 15 buf.WriteString(fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name)) 16 buf.WriteString(fmt.Sprintf(` 17function __%[1]s_debug 18 set file "$BASH_COMP_DEBUG_FILE" 19 if test -n "$file" 20 echo "$argv" >> $file 21 end 22end 23 24function __%[1]s_perform_completion 25 __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv" 26 27 set args (string split -- " " "$argv") 28 set lastArg "$args[-1]" 29 30 __%[1]s_debug "args: $args" 31 __%[1]s_debug "last arg: $lastArg" 32 33 set emptyArg "" 34 if test -z "$lastArg" 35 __%[1]s_debug "Setting emptyArg" 36 set emptyArg \"\" 37 end 38 __%[1]s_debug "emptyArg: $emptyArg" 39 40 set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg" 41 __%[1]s_debug "Calling $requestComp" 42 43 set results (eval $requestComp 2> /dev/null) 44 set comps $results[1..-2] 45 set directiveLine $results[-1] 46 47 # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>) 48 # completions must be prefixed with the flag 49 set flagPrefix (string match -r -- '-.*=' "$lastArg") 50 51 __%[1]s_debug "Comps: $comps" 52 __%[1]s_debug "DirectiveLine: $directiveLine" 53 __%[1]s_debug "flagPrefix: $flagPrefix" 54 55 for comp in $comps 56 printf "%%s%%s\n" "$flagPrefix" "$comp" 57 end 58 59 printf "%%s\n" "$directiveLine" 60end 61 62# This function does three things: 63# 1- Obtain the completions and store them in the global __%[1]s_comp_results 64# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed 65# and unset it otherwise 66# 3- Return true if the completion results are not empty 67function __%[1]s_prepare_completions 68 # Start fresh 69 set --erase __%[1]s_comp_do_file_comp 70 set --erase __%[1]s_comp_results 71 72 # Check if the command-line is already provided. This is useful for testing. 73 if not set --query __%[1]s_comp_commandLine 74 set __%[1]s_comp_commandLine (commandline) 75 end 76 __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine" 77 78 set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine") 79 set --erase __%[1]s_comp_commandLine 80 __%[1]s_debug "Completion results: $results" 81 82 if test -z "$results" 83 __%[1]s_debug "No completion, probably due to a failure" 84 # Might as well do file completion, in case it helps 85 set --global __%[1]s_comp_do_file_comp 1 86 return 0 87 end 88 89 set directive (string sub --start 2 $results[-1]) 90 set --global __%[1]s_comp_results $results[1..-2] 91 92 __%[1]s_debug "Completions are: $__%[1]s_comp_results" 93 __%[1]s_debug "Directive is: $directive" 94 95 if test -z "$directive" 96 set directive 0 97 end 98 99 set compErr (math (math --scale 0 $directive / %[3]d) %% 2) 100 if test $compErr -eq 1 101 __%[1]s_debug "Received error directive: aborting." 102 # Might as well do file completion, in case it helps 103 set --global __%[1]s_comp_do_file_comp 1 104 return 0 105 end 106 107 set nospace (math (math --scale 0 $directive / %[4]d) %% 2) 108 set nofiles (math (math --scale 0 $directive / %[5]d) %% 2) 109 110 __%[1]s_debug "nospace: $nospace, nofiles: $nofiles" 111 112 # Important not to quote the variable for count to work 113 set numComps (count $__%[1]s_comp_results) 114 __%[1]s_debug "numComps: $numComps" 115 116 if test $numComps -eq 1; and test $nospace -ne 0 117 # To support the "nospace" directive we trick the shell 118 # by outputting an extra, longer completion. 119 __%[1]s_debug "Adding second completion to perform nospace directive" 120 set --append __%[1]s_comp_results $__%[1]s_comp_results[1]. 121 end 122 123 if test $numComps -eq 0; and test $nofiles -eq 0 124 __%[1]s_debug "Requesting file completion" 125 set --global __%[1]s_comp_do_file_comp 1 126 end 127 128 # If we don't want file completion, we must return true even if there 129 # are no completions found. This is because fish will perform the last 130 # completion command, even if its condition is false, if no other 131 # completion command was triggered 132 return (not set --query __%[1]s_comp_do_file_comp) 133end 134 135# Remove any pre-existing completions for the program since we will be handling all of them 136# TODO this cleanup is not sufficient. Fish completions are only loaded once the user triggers 137# them, so the below deletion will not work as it is run too early. What else can we do? 138complete -c %[1]s -e 139 140# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions 141# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable. 142# 143# This completion will be run second as complete commands are added FILO. 144# It triggers file completion choices when __%[1]s_comp_do_file_comp is set. 145complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp' 146 147# This completion will be run first as complete commands are added FILO. 148# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp. 149# It provides the program's completion choices. 150complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' 151 152`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) 153} 154 155// GenFishCompletion generates fish completion file and writes to the passed writer. 156func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error { 157 buf := new(bytes.Buffer) 158 genFishComp(buf, c.Name(), includeDesc) 159 _, err := buf.WriteTo(w) 160 return err 161} 162 163// GenFishCompletionFile generates fish completion file. 164func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error { 165 outFile, err := os.Create(filename) 166 if err != nil { 167 return err 168 } 169 defer outFile.Close() 170 171 return c.GenFishCompletion(outFile, includeDesc) 172} 173