1

我正在尝试使用 cobra 在 zsh 中进行动态自动完成,它基本上可以工作,但不是我想要的。

参数仅在空格后传递给函数以进行自动完成,但需要在没有空格的情况下启用自动完成。

通缉:

core list hg pg[TAB][TAB][TAB]
*project-dev-pgsql project2-dev-pgsql project3-dev-pgsql
project4-dev-pgsql project5-dev-pgsql project6-dev-pgsql
[ENTER]
core list hg project-dev-pgsql

现实:

core list hg pg [TAB][TAB][TAB]
*project-dev-pgsql project2-dev-pgsql project3-dev-pgsql
project4-dev-pgsql project5-dev-pgsql project6-dev-pgsql
[ENTER]
core list hg pg project-dev-pgsql

代码命令:

func ListCmdHG() *cobra.Command {
    cmd := &cobra.Command{
        Use:   "hg <hostgroup name> [(-o|--output=)json|yaml|text(default)]",
        Short: "Shows list HOSTs or VMs of specified hostgroup",
        Long:  "Shows list HOSTs or VMs of specified hostgroup",
        Args: cobra.ExactArgs(1),
        ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
            result := make([]string, 0)

            if len(args) == 1 {
                result = suggester.NewClient().GetHostGroupList(args[0])
            }
            return result, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp

        },
        Run: func(cmd *cobra.Command, args []string) {
            hg := args[0]
            vms, err := S.ListHG(hg)
            if err != nil {
                fmt.Println(err)
                os.Exit(0)
            }
            output := S.Formatte.ListVMPrint(hg, cmd.Flag("output").Value.String(), vms)
            fmt.Println(output)
        },
    }
    
    cmd.PersistentFlags().StringP("output", "o", "text", "Format output")

    return cmd
}
_core.zsh
#compdef _core core

# zsh completion for core                                 -*- shell-script -*-

__core_debug()
{
  local file="$BASH_COMP_DEBUG_FILE"
  if [[ -n ${file} ]]; then
      echo "$*" >> "${file}"
  fi
}

_core()
{
  local shellCompDirectiveError=1
  local shellCompDirectiveNoSpace=2
  local shellCompDirectiveNoFileComp=4
  local shellCompDirectiveFilterFileExt=8
  local shellCompDirectiveFilterDirs=16

  local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace
  local -a completions

  __core_debug "\n========= starting completion logic =========="
  __core_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}"

  # The user could have moved the cursor backwards on the command-line.
  # We need to trigger completion from the $CURRENT location, so we need
  # to truncate the command-line ($words) up to the $CURRENT location.
  # (We cannot use $CURSOR as its value does not work when a command is an alias.)
  words=("${=words[1,CURRENT]}")
  
  __core_debug "Truncated words[*]: ${words[*]},"

  lastParam=${words[-1]}
  lastChar=${lastParam[-1]}
  __core_debug "lastParam: ${lastParam}, lastChar: ${lastChar}"

  # For zsh, when completing a flag with an = (e.g., core -n=<TAB>)
  # completions must be prefixed with the flag
  setopt local_options BASH_REMATCH
  if [[ "${lastParam}" =~ '-.*=' ]]; then
      # We are dealing with a flag with an =
      flagPrefix="-P ${BASH_REMATCH}"
  fi

  # Prepare the command to obtain completions
  requestComp="${words[1]} __complete ${words[2,-1]}"
  
  if [ "${lastChar}" = "" ]; then
      # If the last parameter is complete (there is a space following it)
      # We add an extra empty parameter so we can indicate this to the go completion code.
      __core_debug "Adding extra empty parameter"
      requestComp="${requestComp} \"\""
  fi
  
  __core_debug "About to call: eval ${requestComp}"

  # Use eval to handle any environment variables and such
  out=$(eval ${requestComp} 2>/dev/null)
  __core_debug "completion output: ${out}"
  

  # Extract the directive integer following a : from the last line
  local lastLine
  while IFS='\n' read -r line; do
      lastLine=${line}
  done < <(printf "%s\n" "${out[@]}")
  __core_debug "last line: ${lastLine}"

  if [ "${lastLine[1]}" = : ]; then
      directive=${lastLine[2,-1]}
      # Remove the directive including the : and the newline
      local suffix
      (( suffix=${#lastLine}+2))
      out=${out[1,-$suffix]}
  else
      # There is no directive specified.  Leave $out as is.
      __core_debug "No directive found.  Setting do default"
      directive=0
  fi

  __core_debug "directive: ${directive}"
  __core_debug "completions: ${out}"
  __core_debug "flagPrefix: ${flagPrefix}"

  if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then
      __core_debug "Completion received error. Ignoring completions."
      return
  fi

  while IFS='\n' read -r comp; do 
      if [ -n "$comp" ]; then
          # If requested, completions are returned with a description.
          # The description is preceded by a TAB character.
          # For zsh's _describe, we need to use a : instead of a TAB.
          # We first need to escape any : as part of the completion itself.
          comp=${comp//:/\\:}

          local tab=$(printf '\t')
          comp=${comp//$tab/:}

          __core_debug "Adding completion: ${comp}"
          completions+=${comp}
          lastComp=$comp
      fi
  done < <(printf "%s\n" "${out[@]}")

  if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
      __core_debug "Activating nospace."
      noSpace="-S ''"
  fi

  if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
      # File extension filtering
      local filteringCmd
      filteringCmd='_files'
      for filter in ${completions[@]}; do
          if [ ${filter[1]} != '*' ]; then
              # zsh requires a glob pattern to do file filtering
              filter="\*.$filter"
          fi
          filteringCmd+=" -g $filter"
      done
      filteringCmd+=" ${flagPrefix}"

      __core_debug "File filtering command: $filteringCmd"
      _arguments '*:filename:'"$filteringCmd"
  elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
      # File completion for directories only
      local subDir
      subdir="${completions[1]}"
      if [ -n "$subdir" ]; then
          __core_debug "Listing directories in $subdir"
          pushd "${subdir}" >/dev/null 2>&1
      else
          __core_debug "Listing directories in ."
      fi

      local result
      _arguments '*:dirname:_files -/'" ${flagPrefix}"
      result=$?
      if [ -n "$subdir" ]; then
          popd >/dev/null 2>&1
      fi
      return $result
  else
      __core_debug "Calling _describe"
      if eval _describe "completions" completions $flagPrefix $noSpace; then
          __core_debug "_describe found some completions"

          # Return the success of having called _describe
          return 0
      else
          __core_debug "_describe did not find completions."
          __core_debug "Checking if we should do file completion."
          if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
              __core_debug "deactivating file completion"

              # We must return an error code here to let zsh know that there were no
              # completions found by _describe; this is what will trigger other
              # matching algorithms to attempt to find completions.
              # For example zsh can match letters in the middle of words.
              return 1
          else
              # Perform file completion
              __core_debug "Activating file completion"

              # We must return the result of this command, so it must be the
              # last command, or else we must store its result to return it.
              _arguments '*:filename:_files'" ${flagPrefix}"
          fi
      fi
  fi
}

# don't run the completion function when being source-ed or eval-ed
if [ "$funcstack[1]" = "_core" ]; then
  _core
fi

我怎样才能解决这个问题?

4

1 回答 1

0

这可能不受支持,考虑到完成需要空间才能知道它需要完成文件,而不是pg命令本身。

如cobra PR 1249所示,该ShellCompDirectiveNoSpace 指令用于防止完成添加空格(完成)。

但是要完成文件而不是命令本身,可能需要一个空格。

于 2021-10-07T06:07:33.667 回答