gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

zsh_completions.go (11020B)


      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 )
     23 
     24 // GenZshCompletionFile generates zsh completion file including descriptions.
     25 func (c *Command) GenZshCompletionFile(filename string) error {
     26 	return c.genZshCompletionFile(filename, true)
     27 }
     28 
     29 // GenZshCompletion generates zsh completion file including descriptions
     30 // and writes it to the passed writer.
     31 func (c *Command) GenZshCompletion(w io.Writer) error {
     32 	return c.genZshCompletion(w, true)
     33 }
     34 
     35 // GenZshCompletionFileNoDesc generates zsh completion file without descriptions.
     36 func (c *Command) GenZshCompletionFileNoDesc(filename string) error {
     37 	return c.genZshCompletionFile(filename, false)
     38 }
     39 
     40 // GenZshCompletionNoDesc generates zsh completion file without descriptions
     41 // and writes it to the passed writer.
     42 func (c *Command) GenZshCompletionNoDesc(w io.Writer) error {
     43 	return c.genZshCompletion(w, false)
     44 }
     45 
     46 // MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was
     47 // not consistent with Bash completion. It has therefore been disabled.
     48 // Instead, when no other completion is specified, file completion is done by
     49 // default for every argument. One can disable file completion on a per-argument
     50 // basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp.
     51 // To achieve file extension filtering, one can use ValidArgsFunction and
     52 // ShellCompDirectiveFilterFileExt.
     53 //
     54 // Deprecated
     55 func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error {
     56 	return nil
     57 }
     58 
     59 // MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore
     60 // been disabled.
     61 // To achieve the same behavior across all shells, one can use
     62 // ValidArgs (for the first argument only) or ValidArgsFunction for
     63 // any argument (can include the first one also).
     64 //
     65 // Deprecated
     66 func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error {
     67 	return nil
     68 }
     69 
     70 func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error {
     71 	outFile, err := os.Create(filename)
     72 	if err != nil {
     73 		return err
     74 	}
     75 	defer outFile.Close()
     76 
     77 	return c.genZshCompletion(outFile, includeDesc)
     78 }
     79 
     80 func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error {
     81 	buf := new(bytes.Buffer)
     82 	genZshComp(buf, c.Name(), includeDesc)
     83 	_, err := buf.WriteTo(w)
     84 	return err
     85 }
     86 
     87 func genZshComp(buf io.StringWriter, name string, includeDesc bool) {
     88 	compCmd := ShellCompRequestCmd
     89 	if !includeDesc {
     90 		compCmd = ShellCompNoDescRequestCmd
     91 	}
     92 	WriteStringAndCheck(buf, fmt.Sprintf(`#compdef %[1]s
     93 compdef _%[1]s %[1]s
     94 
     95 # zsh completion for %-36[1]s -*- shell-script -*-
     96 
     97 __%[1]s_debug()
     98 {
     99     local file="$BASH_COMP_DEBUG_FILE"
    100     if [[ -n ${file} ]]; then
    101         echo "$*" >> "${file}"
    102     fi
    103 }
    104 
    105 _%[1]s()
    106 {
    107     local shellCompDirectiveError=%[3]d
    108     local shellCompDirectiveNoSpace=%[4]d
    109     local shellCompDirectiveNoFileComp=%[5]d
    110     local shellCompDirectiveFilterFileExt=%[6]d
    111     local shellCompDirectiveFilterDirs=%[7]d
    112     local shellCompDirectiveKeepOrder=%[8]d
    113 
    114     local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder
    115     local -a completions
    116 
    117     __%[1]s_debug "\n========= starting completion logic =========="
    118     __%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"
    119 
    120     # The user could have moved the cursor backwards on the command-line.
    121     # We need to trigger completion from the $CURRENT location, so we need
    122     # to truncate the command-line ($words) up to the $CURRENT location.
    123     # (We cannot use $CURSOR as its value does not work when a command is an alias.)
    124     words=("${=words[1,CURRENT]}")
    125     __%[1]s_debug "Truncated words[*]: ${words[*]},"
    126 
    127     lastParam=${words[-1]}
    128     lastChar=${lastParam[-1]}
    129     __%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"
    130 
    131     # For zsh, when completing a flag with an = (e.g., %[1]s -n=<TAB>)
    132     # completions must be prefixed with the flag
    133     setopt local_options BASH_REMATCH
    134     if [[ "${lastParam}" =~ '-.*=' ]]; then
    135         # We are dealing with a flag with an =
    136         flagPrefix="-P ${BASH_REMATCH}"
    137     fi
    138 
    139     # Prepare the command to obtain completions
    140     requestComp="${words[1]} %[2]s ${words[2,-1]}"
    141     if [ "${lastChar}" = "" ]; then
    142         # If the last parameter is complete (there is a space following it)
    143         # We add an extra empty parameter so we can indicate this to the go completion code.
    144         __%[1]s_debug "Adding extra empty parameter"
    145         requestComp="${requestComp} \"\""
    146     fi
    147 
    148     __%[1]s_debug "About to call: eval ${requestComp}"
    149 
    150     # Use eval to handle any environment variables and such
    151     out=$(eval ${requestComp} 2>/dev/null)
    152     __%[1]s_debug "completion output: ${out}"
    153 
    154     # Extract the directive integer following a : from the last line
    155     local lastLine
    156     while IFS='\n' read -r line; do
    157         lastLine=${line}
    158     done < <(printf "%%s\n" "${out[@]}")
    159     __%[1]s_debug "last line: ${lastLine}"
    160 
    161     if [ "${lastLine[1]}" = : ]; then
    162         directive=${lastLine[2,-1]}
    163         # Remove the directive including the : and the newline
    164         local suffix
    165         (( suffix=${#lastLine}+2))
    166         out=${out[1,-$suffix]}
    167     else
    168         # There is no directive specified.  Leave $out as is.
    169         __%[1]s_debug "No directive found.  Setting do default"
    170         directive=0
    171     fi
    172 
    173     __%[1]s_debug "directive: ${directive}"
    174     __%[1]s_debug "completions: ${out}"
    175     __%[1]s_debug "flagPrefix: ${flagPrefix}"
    176 
    177     if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
    178         __%[1]s_debug "Completion received error. Ignoring completions."
    179         return
    180     fi
    181 
    182     local activeHelpMarker="%[9]s"
    183     local endIndex=${#activeHelpMarker}
    184     local startIndex=$((${#activeHelpMarker}+1))
    185     local hasActiveHelp=0
    186     while IFS='\n' read -r comp; do
    187         # Check if this is an activeHelp statement (i.e., prefixed with $activeHelpMarker)
    188         if [ "${comp[1,$endIndex]}" = "$activeHelpMarker" ];then
    189             __%[1]s_debug "ActiveHelp found: $comp"
    190             comp="${comp[$startIndex,-1]}"
    191             if [ -n "$comp" ]; then
    192                 compadd -x "${comp}"
    193                 __%[1]s_debug "ActiveHelp will need delimiter"
    194                 hasActiveHelp=1
    195             fi
    196 
    197             continue
    198         fi
    199 
    200         if [ -n "$comp" ]; then
    201             # If requested, completions are returned with a description.
    202             # The description is preceded by a TAB character.
    203             # For zsh's _describe, we need to use a : instead of a TAB.
    204             # We first need to escape any : as part of the completion itself.
    205             comp=${comp//:/\\:}
    206 
    207             local tab="$(printf '\t')"
    208             comp=${comp//$tab/:}
    209 
    210             __%[1]s_debug "Adding completion: ${comp}"
    211             completions+=${comp}
    212             lastComp=$comp
    213         fi
    214     done < <(printf "%%s\n" "${out[@]}")
    215 
    216     # Add a delimiter after the activeHelp statements, but only if:
    217     # - there are completions following the activeHelp statements, or
    218     # - file completion will be performed (so there will be choices after the activeHelp)
    219     if [ $hasActiveHelp -eq 1 ]; then
    220         if [ ${#completions} -ne 0 ] || [ $((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then
    221             __%[1]s_debug "Adding activeHelp delimiter"
    222             compadd -x "--"
    223             hasActiveHelp=0
    224         fi
    225     fi
    226 
    227     if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
    228         __%[1]s_debug "Activating nospace."
    229         noSpace="-S ''"
    230     fi
    231 
    232     if [ $((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then
    233         __%[1]s_debug "Activating keep order."
    234         keepOrder="-V"
    235     fi
    236 
    237     if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
    238         # File extension filtering
    239         local filteringCmd
    240         filteringCmd='_files'
    241         for filter in ${completions[@]}; do
    242             if [ ${filter[1]} != '*' ]; then
    243                 # zsh requires a glob pattern to do file filtering
    244                 filter="\*.$filter"
    245             fi
    246             filteringCmd+=" -g $filter"
    247         done
    248         filteringCmd+=" ${flagPrefix}"
    249 
    250         __%[1]s_debug "File filtering command: $filteringCmd"
    251         _arguments '*:filename:'"$filteringCmd"
    252     elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
    253         # File completion for directories only
    254         local subdir
    255         subdir="${completions[1]}"
    256         if [ -n "$subdir" ]; then
    257             __%[1]s_debug "Listing directories in $subdir"
    258             pushd "${subdir}" >/dev/null 2>&1
    259         else
    260             __%[1]s_debug "Listing directories in ."
    261         fi
    262 
    263         local result
    264         _arguments '*:dirname:_files -/'" ${flagPrefix}"
    265         result=$?
    266         if [ -n "$subdir" ]; then
    267             popd >/dev/null 2>&1
    268         fi
    269         return $result
    270     else
    271         __%[1]s_debug "Calling _describe"
    272         if eval _describe $keepOrder "completions" completions $flagPrefix $noSpace; then
    273             __%[1]s_debug "_describe found some completions"
    274 
    275             # Return the success of having called _describe
    276             return 0
    277         else
    278             __%[1]s_debug "_describe did not find completions."
    279             __%[1]s_debug "Checking if we should do file completion."
    280             if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
    281                 __%[1]s_debug "deactivating file completion"
    282 
    283                 # We must return an error code here to let zsh know that there were no
    284                 # completions found by _describe; this is what will trigger other
    285                 # matching algorithms to attempt to find completions.
    286                 # For example zsh can match letters in the middle of words.
    287                 return 1
    288             else
    289                 # Perform file completion
    290                 __%[1]s_debug "Activating file completion"
    291 
    292                 # We must return the result of this command, so it must be the
    293                 # last command, or else we must store its result to return it.
    294                 _arguments '*:filename:_files'" ${flagPrefix}"
    295             fi
    296         fi
    297     fi
    298 }
    299 
    300 # don't run the completion function when being source-ed or eval-ed
    301 if [ "$funcstack[1]" = "_%[1]s" ]; then
    302     _%[1]s
    303 fi
    304 `, name, compCmd,
    305 		ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
    306 		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder,
    307 		activeHelpMarker))
    308 }