Unable to do two word search in Zsh TAB for Man

Problem: have tab completion that takes two words and calculates the best match against them for Man and then returns the best matches

Example: The following pseudocode should give me at least a Zsh reverse-menu-complete -command. Right now, I cannot look for guides in guides without zgrep.

man zsh:reverse <TAB>

      

where ":" is the delimiter I want.

Initial Problem: What files do TAB completion when I hit TAB for one word while looking for Zsh manuals?

0


a source to share


1 answer


I will try to give an idea of ​​how the zsh seeding system works and an incomplete problem in solving this problem.

The file that starts when using TAB completion for man

zsh is in the directory /usr/share/zsh/${ZSH_VERSION}/functions

. The tree varies from distribution to distribution, but the file is named _man

and provides completion for man

, apropos

and whatis

.

After calling _man, it works like this (approximate description):

  • if completion for man

    and --local-file

    was specified as the first flag, call standard file completion ( _files

    )
  • build a variable manpath

    from a set of default values ​​/ $MANPATH

    . Searches will be found here
  • determine if we call man

    with the section number parameter, if yes - only those sections will be searched
  • if used zstyle ':completion:*:manuals' separate-sections true

    , separate sections in output (do not mix between them)
  • invoke _man_pages

    to provide a list of man pages to match
  • _man_pages

    now a little magic with compfiles -p pages '' '' "$matcher" '' dummy '*'

    . pages

    is a variable with all directories containing manpages for the requested partitions. The actual globe pattern is built from the implicit parameter $PREFIX

    and the last parameter in compfiles

    - *

    in this case. This causes it to /usr/share/man/man1

    convert to/usr/share/man/man1/foo*

  • New list of glob patterns globbed, getting all files matching pattern
  • _man_pages

    then removes any suffixes from the files and adds them to the completion widget list using compadd

Now, as you can see, the manpages list is directly defined by a variable $PREFIX

. To make it zsh:foo

only list man pages zsh*

that contain a word foo

, it needs to be split into a character :

(if any).

The following addition in _man_pages

part solves the problem (zsh 4.3.4):



Original:

_man_pages() {
  local matcher pages dummy sopt

  zparseopts -E M+:=matcher

  if (( $#matcher )); then
    matcher=( ${matcher:#-M} )
    matcher="$matcher"
  else
    matcher=
  fi

  pages=( ${(M)dirs:#*$sect/} )

  compfiles -p pages '' '' "$matcher" '' dummy '*'
  pages=( ${^~pages}(N:t) )

  (($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))

  # Remove any compression suffix, then remove the minimum possible string
  # beginning with .<->: that handles problem cases like files called
  # `POSIX.1.5'.

  [[ $OSTYPE = solaris* ]] && sopt='-s '
  if ((CURRENT > 2)) ||
      ! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
  then
    compadd "$@" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
  else
    compadd "$@" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
  fi
}

      

Changed (see ## mod comments):

_man_pages() {
  local matcher pages dummy sopt

  zparseopts -E M+:=matcher

  if (( $#matcher )); then
    matcher=( ${matcher:#-M} )
    matcher="$matcher"
  else
    matcher=
  fi

  pages=( ${(M)dirs:#*$sect/} )

  ##mod
  # split components by the ":" character
  local pref_words manpage_grep orig_prefix
  # save original prefix (just in case)
  orig_prefix=${PREFIX}
  # split $PREFIX by ':' and make it an array
  pref_words=${PREFIX//:/ }
  set -A pref_words ${=pref_words}
  # if there are both manpage name and grep string, use both
  if (( $#pref_words == 2 )); then
      manpage_grep=$pref_words[2]
      # PREFIX is used internally by compfiles
      PREFIX=$pref_words[1]
  elif (( $#pref_words == 1 )) && [[ ${PREFIX[1,1]} == ":" ]]; then
      # otherwise, prefix is empty and only grep string exists
      PREFIX=
      manpage_grep=$pref_words[1]
  fi


  compfiles -p pages '' '' "$matcher" '' dummy '*'
  ##mod: complete, but don't strip path names
  pages=( ${^~pages} )

  (($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))

  ##mod: grep pages
  # Build a list of matching pages that pass the grep
  local matching_pages
  typeset -a matching_pages

  # manpage_grep exists and not empty 
  if (( ${#manpage_grep} > 0 )); then
      for p in ${pages}; do
          zgrep "${manpage_grep}" $p > /dev/null
          if (( $? == 0 )); then
              #echo "$p matched $manpage_grep"
              matching_pages+=($p)
          fi
      done
  else
  # there no manpage_grep, so all pages match
      matching_pages=( ${pages} )
  fi

  #echo "\nmatching manpages: "${matching_pages}
  pages=( ${matching_pages}(N:t) )
  # keep the stripped prefix for now
  #PREFIX=${orig_prefix}


  # Remove any compression suffix, then remove the minimum possible string
  # beginning with .<->: that handles problem cases like files called
  # `POSIX.1.5'.


  [[ $OSTYPE = solaris* ]] && sopt='-s '
  if ((CURRENT > 2)) ||
      ! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
  then
    compadd "$@" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
  else
    compadd "$@" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
  fi
}

      

However, it still doesn't work completely (if you uncomment the line #echo "$p matched $manpage_grep"

, you can see that it creates a list) - I suspect that somewhere inside, the completion system sees that, for example, "zshcompctl" does not match the "zsh" prefix: foo "and does not display the resulting matches. I tried to save the $PREFIX

same as after deleting the grep line but it still doesn't want to work.

If anything, that should at least get you started.

+2


a source







All Articles