git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/cobra/fish_completions.go (about)

     1  // Copyright 2013-2022 The Cobra Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cobra
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"strings"
    23  )
    24  
    25  func genFishComp(buf io.StringWriter, name string, includeDesc bool) {
    26  	// Variables should not contain a '-' or ':' character
    27  	nameForVar := name
    28  	nameForVar = strings.ReplaceAll(nameForVar, "-", "_")
    29  	nameForVar = strings.ReplaceAll(nameForVar, ":", "_")
    30  
    31  	compCmd := ShellCompRequestCmd
    32  	if !includeDesc {
    33  		compCmd = ShellCompNoDescRequestCmd
    34  	}
    35  	WriteStringAndCheck(buf, fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name))
    36  	WriteStringAndCheck(buf, fmt.Sprintf(`
    37  function __%[1]s_debug
    38      set -l file "$BASH_COMP_DEBUG_FILE"
    39      if test -n "$file"
    40          echo "$argv" >> $file
    41      end
    42  end
    43  
    44  function __%[1]s_perform_completion
    45      __%[1]s_debug "Starting __%[1]s_perform_completion"
    46  
    47      # Extract all args except the last one
    48      set -l args (commandline -opc)
    49      # Extract the last arg and escape it in case it is a space
    50      set -l lastArg (string escape -- (commandline -ct))
    51  
    52      __%[1]s_debug "args: $args"
    53      __%[1]s_debug "last arg: $lastArg"
    54  
    55      # Disable ActiveHelp which is not supported for fish shell
    56      set -l requestComp "%[9]s=0 $args[1] %[3]s $args[2..-1] $lastArg"
    57  
    58      __%[1]s_debug "Calling $requestComp"
    59      set -l results (eval $requestComp 2> /dev/null)
    60  
    61      # Some programs may output extra empty lines after the directive.
    62      # Let's ignore them or else it will break completion.
    63      # Ref: https://github.com/spf13/cobra/issues/1279
    64      for line in $results[-1..1]
    65          if test (string trim -- $line) = ""
    66              # Found an empty line, remove it
    67              set results $results[1..-2]
    68          else
    69              # Found non-empty line, we have our proper output
    70              break
    71          end
    72      end
    73  
    74      set -l comps $results[1..-2]
    75      set -l directiveLine $results[-1]
    76  
    77      # For Fish, when completing a flag with an = (e.g., <program> -n=<TAB>)
    78      # completions must be prefixed with the flag
    79      set -l flagPrefix (string match -r -- '-.*=' "$lastArg")
    80  
    81      __%[1]s_debug "Comps: $comps"
    82      __%[1]s_debug "DirectiveLine: $directiveLine"
    83      __%[1]s_debug "flagPrefix: $flagPrefix"
    84  
    85      for comp in $comps
    86          printf "%%s%%s\n" "$flagPrefix" "$comp"
    87      end
    88  
    89      printf "%%s\n" "$directiveLine"
    90  end
    91  
    92  # This function does two things:
    93  # - Obtain the completions and store them in the global __%[1]s_comp_results
    94  # - Return false if file completion should be performed
    95  function __%[1]s_prepare_completions
    96      __%[1]s_debug ""
    97      __%[1]s_debug "========= starting completion logic =========="
    98  
    99      # Start fresh
   100      set --erase __%[1]s_comp_results
   101  
   102      set -l results (__%[1]s_perform_completion)
   103      __%[1]s_debug "Completion results: $results"
   104  
   105      if test -z "$results"
   106          __%[1]s_debug "No completion, probably due to a failure"
   107          # Might as well do file completion, in case it helps
   108          return 1
   109      end
   110  
   111      set -l directive (string sub --start 2 $results[-1])
   112      set --global __%[1]s_comp_results $results[1..-2]
   113  
   114      __%[1]s_debug "Completions are: $__%[1]s_comp_results"
   115      __%[1]s_debug "Directive is: $directive"
   116  
   117      set -l shellCompDirectiveError %[4]d
   118      set -l shellCompDirectiveNoSpace %[5]d
   119      set -l shellCompDirectiveNoFileComp %[6]d
   120      set -l shellCompDirectiveFilterFileExt %[7]d
   121      set -l shellCompDirectiveFilterDirs %[8]d
   122  
   123      if test -z "$directive"
   124          set directive 0
   125      end
   126  
   127      set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
   128      if test $compErr -eq 1
   129          __%[1]s_debug "Received error directive: aborting."
   130          # Might as well do file completion, in case it helps
   131          return 1
   132      end
   133  
   134      set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
   135      set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
   136      if test $filefilter -eq 1; or test $dirfilter -eq 1
   137          __%[1]s_debug "File extension filtering or directory filtering not supported"
   138          # Do full file completion instead
   139          return 1
   140      end
   141  
   142      set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
   143      set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
   144  
   145      __%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
   146  
   147      # If we want to prevent a space, or if file completion is NOT disabled,
   148      # we need to count the number of valid completions.
   149      # To do so, we will filter on prefix as the completions we have received
   150      # may not already be filtered so as to allow fish to match on different
   151      # criteria than the prefix.
   152      if test $nospace -ne 0; or test $nofiles -eq 0
   153          set -l prefix (commandline -t | string escape --style=regex)
   154          __%[1]s_debug "prefix: $prefix"
   155  
   156          set -l completions (string match -r -- "^$prefix.*" $__%[1]s_comp_results)
   157          set --global __%[1]s_comp_results $completions
   158          __%[1]s_debug "Filtered completions are: $__%[1]s_comp_results"
   159  
   160          # Important not to quote the variable for count to work
   161          set -l numComps (count $__%[1]s_comp_results)
   162          __%[1]s_debug "numComps: $numComps"
   163  
   164          if test $numComps -eq 1; and test $nospace -ne 0
   165              # We must first split on \t to get rid of the descriptions to be
   166              # able to check what the actual completion will be.
   167              # We don't need descriptions anyway since there is only a single
   168              # real completion which the shell will expand immediately.
   169              set -l split (string split --max 1 \t $__%[1]s_comp_results[1])
   170  
   171              # Fish won't add a space if the completion ends with any
   172              # of the following characters: @=/:.,
   173              set -l lastChar (string sub -s -1 -- $split)
   174              if not string match -r -q "[@=/:.,]" -- "$lastChar"
   175                  # In other cases, to support the "nospace" directive we trick the shell
   176                  # by outputting an extra, longer completion.
   177                  __%[1]s_debug "Adding second completion to perform nospace directive"
   178                  set --global __%[1]s_comp_results $split[1] $split[1].
   179                  __%[1]s_debug "Completions are now: $__%[1]s_comp_results"
   180              end
   181          end
   182  
   183          if test $numComps -eq 0; and test $nofiles -eq 0
   184              # To be consistent with bash and zsh, we only trigger file
   185              # completion when there are no other completions
   186              __%[1]s_debug "Requesting file completion"
   187              return 1
   188          end
   189      end
   190  
   191      return 0
   192  end
   193  
   194  # Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
   195  # so we can properly delete any completions provided by another script.
   196  # Only do this if the program can be found, or else fish may print some errors; besides,
   197  # the existing completions will only be loaded if the program can be found.
   198  if type -q "%[2]s"
   199      # The space after the program name is essential to trigger completion for the program
   200      # and not completion of the program name itself.
   201      # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
   202      complete --do-complete "%[2]s " > /dev/null 2>&1
   203  end
   204  
   205  # Remove any pre-existing completions for the program since we will be handling all of them.
   206  complete -c %[2]s -e
   207  
   208  # The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
   209  # which provides the program's completion choices.
   210  complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
   211  
   212  `, nameForVar, name, compCmd,
   213  		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
   214  		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, activeHelpEnvVar(name)))
   215  }
   216  
   217  // GenFishCompletion generates fish completion file and writes to the passed writer.
   218  func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error {
   219  	buf := new(bytes.Buffer)
   220  	genFishComp(buf, c.Name(), includeDesc)
   221  	_, err := buf.WriteTo(w)
   222  	return err
   223  }
   224  
   225  // GenFishCompletionFile generates fish completion file.
   226  func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error {
   227  	outFile, err := os.Create(filename)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	defer outFile.Close()
   232  
   233  	return c.GenFishCompletion(outFile, includeDesc)
   234  }