...

Source file src/github.com/spf13/cobra/fish_completions.go

Documentation: github.com/spf13/cobra

     1  // Copyright 2013-2023 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 "%[10]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 limits calls to __%[1]s_perform_completion, by caching the result behind $__%[1]s_perform_completion_once_result
    93  function __%[1]s_perform_completion_once
    94      __%[1]s_debug "Starting __%[1]s_perform_completion_once"
    95  
    96      if test -n "$__%[1]s_perform_completion_once_result"
    97          __%[1]s_debug "Seems like a valid result already exists, skipping __%[1]s_perform_completion"
    98          return 0
    99      end
   100  
   101      set --global __%[1]s_perform_completion_once_result (__%[1]s_perform_completion)
   102      if test -z "$__%[1]s_perform_completion_once_result"
   103          __%[1]s_debug "No completions, probably due to a failure"
   104          return 1
   105      end
   106  
   107      __%[1]s_debug "Performed completions and set __%[1]s_perform_completion_once_result"
   108      return 0
   109  end
   110  
   111  # this function is used to clear the $__%[1]s_perform_completion_once_result variable after completions are run
   112  function __%[1]s_clear_perform_completion_once_result
   113      __%[1]s_debug ""
   114      __%[1]s_debug "========= clearing previously set __%[1]s_perform_completion_once_result variable =========="
   115      set --erase __%[1]s_perform_completion_once_result
   116      __%[1]s_debug "Successfully erased the variable __%[1]s_perform_completion_once_result"
   117  end
   118  
   119  function __%[1]s_requires_order_preservation
   120      __%[1]s_debug ""
   121      __%[1]s_debug "========= checking if order preservation is required =========="
   122  
   123      __%[1]s_perform_completion_once
   124      if test -z "$__%[1]s_perform_completion_once_result"
   125          __%[1]s_debug "Error determining if order preservation is required"
   126          return 1
   127      end
   128  
   129      set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
   130      __%[1]s_debug "Directive is: $directive"
   131  
   132      set -l shellCompDirectiveKeepOrder %[9]d
   133      set -l keeporder (math (math --scale 0 $directive / $shellCompDirectiveKeepOrder) %% 2)
   134      __%[1]s_debug "Keeporder is: $keeporder"
   135  
   136      if test $keeporder -ne 0
   137          __%[1]s_debug "This does require order preservation"
   138          return 0
   139      end
   140  
   141      __%[1]s_debug "This doesn't require order preservation"
   142      return 1
   143  end
   144  
   145  
   146  # This function does two things:
   147  # - Obtain the completions and store them in the global __%[1]s_comp_results
   148  # - Return false if file completion should be performed
   149  function __%[1]s_prepare_completions
   150      __%[1]s_debug ""
   151      __%[1]s_debug "========= starting completion logic =========="
   152  
   153      # Start fresh
   154      set --erase __%[1]s_comp_results
   155  
   156      __%[1]s_perform_completion_once
   157      __%[1]s_debug "Completion results: $__%[1]s_perform_completion_once_result"
   158  
   159      if test -z "$__%[1]s_perform_completion_once_result"
   160          __%[1]s_debug "No completion, probably due to a failure"
   161          # Might as well do file completion, in case it helps
   162          return 1
   163      end
   164  
   165      set -l directive (string sub --start 2 $__%[1]s_perform_completion_once_result[-1])
   166      set --global __%[1]s_comp_results $__%[1]s_perform_completion_once_result[1..-2]
   167  
   168      __%[1]s_debug "Completions are: $__%[1]s_comp_results"
   169      __%[1]s_debug "Directive is: $directive"
   170  
   171      set -l shellCompDirectiveError %[4]d
   172      set -l shellCompDirectiveNoSpace %[5]d
   173      set -l shellCompDirectiveNoFileComp %[6]d
   174      set -l shellCompDirectiveFilterFileExt %[7]d
   175      set -l shellCompDirectiveFilterDirs %[8]d
   176  
   177      if test -z "$directive"
   178          set directive 0
   179      end
   180  
   181      set -l compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2)
   182      if test $compErr -eq 1
   183          __%[1]s_debug "Received error directive: aborting."
   184          # Might as well do file completion, in case it helps
   185          return 1
   186      end
   187  
   188      set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2)
   189      set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
   190      if test $filefilter -eq 1; or test $dirfilter -eq 1
   191          __%[1]s_debug "File extension filtering or directory filtering not supported"
   192          # Do full file completion instead
   193          return 1
   194      end
   195  
   196      set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2)
   197      set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
   198  
   199      __%[1]s_debug "nospace: $nospace, nofiles: $nofiles"
   200  
   201      # If we want to prevent a space, or if file completion is NOT disabled,
   202      # we need to count the number of valid completions.
   203      # To do so, we will filter on prefix as the completions we have received
   204      # may not already be filtered so as to allow fish to match on different
   205      # criteria than the prefix.
   206      if test $nospace -ne 0; or test $nofiles -eq 0
   207          set -l prefix (commandline -t | string escape --style=regex)
   208          __%[1]s_debug "prefix: $prefix"
   209  
   210          set -l completions (string match -r -- "^$prefix.*" $__%[1]s_comp_results)
   211          set --global __%[1]s_comp_results $completions
   212          __%[1]s_debug "Filtered completions are: $__%[1]s_comp_results"
   213  
   214          # Important not to quote the variable for count to work
   215          set -l numComps (count $__%[1]s_comp_results)
   216          __%[1]s_debug "numComps: $numComps"
   217  
   218          if test $numComps -eq 1; and test $nospace -ne 0
   219              # We must first split on \t to get rid of the descriptions to be
   220              # able to check what the actual completion will be.
   221              # We don't need descriptions anyway since there is only a single
   222              # real completion which the shell will expand immediately.
   223              set -l split (string split --max 1 \t $__%[1]s_comp_results[1])
   224  
   225              # Fish won't add a space if the completion ends with any
   226              # of the following characters: @=/:.,
   227              set -l lastChar (string sub -s -1 -- $split)
   228              if not string match -r -q "[@=/:.,]" -- "$lastChar"
   229                  # In other cases, to support the "nospace" directive we trick the shell
   230                  # by outputting an extra, longer completion.
   231                  __%[1]s_debug "Adding second completion to perform nospace directive"
   232                  set --global __%[1]s_comp_results $split[1] $split[1].
   233                  __%[1]s_debug "Completions are now: $__%[1]s_comp_results"
   234              end
   235          end
   236  
   237          if test $numComps -eq 0; and test $nofiles -eq 0
   238              # To be consistent with bash and zsh, we only trigger file
   239              # completion when there are no other completions
   240              __%[1]s_debug "Requesting file completion"
   241              return 1
   242          end
   243      end
   244  
   245      return 0
   246  end
   247  
   248  # Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
   249  # so we can properly delete any completions provided by another script.
   250  # Only do this if the program can be found, or else fish may print some errors; besides,
   251  # the existing completions will only be loaded if the program can be found.
   252  if type -q "%[2]s"
   253      # The space after the program name is essential to trigger completion for the program
   254      # and not completion of the program name itself.
   255      # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
   256      complete --do-complete "%[2]s " > /dev/null 2>&1
   257  end
   258  
   259  # Remove any pre-existing completions for the program since we will be handling all of them.
   260  complete -c %[2]s -e
   261  
   262  # this will get called after the two calls below and clear the $__%[1]s_perform_completion_once_result global
   263  complete -c %[2]s -n '__%[1]s_clear_perform_completion_once_result'
   264  # The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results
   265  # which provides the program's completion choices.
   266  # If this doesn't require order preservation, we don't use the -k flag
   267  complete -c %[2]s -n 'not __%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
   268  # otherwise we use the -k flag
   269  complete -k -c %[2]s -n '__%[1]s_requires_order_preservation && __%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results'
   270  `, nameForVar, name, compCmd,
   271  		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
   272  		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder, activeHelpEnvVar(name)))
   273  }
   274  
   275  // GenFishCompletion generates fish completion file and writes to the passed writer.
   276  func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error {
   277  	buf := new(bytes.Buffer)
   278  	genFishComp(buf, c.Name(), includeDesc)
   279  	_, err := buf.WriteTo(w)
   280  	return err
   281  }
   282  
   283  // GenFishCompletionFile generates fish completion file.
   284  func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error {
   285  	outFile, err := os.Create(filename)
   286  	if err != nil {
   287  		return err
   288  	}
   289  	defer outFile.Close()
   290  
   291  	return c.GenFishCompletion(outFile, includeDesc)
   292  }
   293  

View as plain text