Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH and Re: simulation of dabbrev-expand



Sven Wischnowsky (wischnow@xxxxxxxxxxxxxxxxxxxxxxx) wrote:
> 
> First: there was a buglet in `_history_complete_word' which made it
> pretty useless if `history_stop' wasn't set. I hope the patch below
> doesn't interfere with your work...

Nope -- I ended up rewriting the whole damn thing :-)

> Adam Spiers wrote:
> > The current situation is that _history_complete_word crawls through
> > matching history words, oldest first. 
> 
> Now I'm confused. For me it works from the bottom up.

Of course I was talking rubbish again here ... but I'm sure you had
realised that and were just being polite :-)

> *But* words
> from the same line are inserted left-to-right. Since the C-code walks
> up maybe we should add the words from the end of the line to the
> beginning.
> 
> We should then use the hunk for `zle_tricky.c' below.

Nice touch.  Reminds me of another feature I've wanted in
_history_complete_word for ages - would it be possible to modify
compgen -H to include words from the currently edited line?

> > However, I got stuck when handling the history_stop feature.
> > When in verbose mode, history_stop uses _message to indicate that the
> > beginning/end of the history has been reached.  However, unless I've
> > got things really wrong, _message seems to destroy any old list of matches
> > which you might want to keep.  I can't understand why, as it's only
> > essentially a compadd -X ... -n ''. 
> 
> That's right. To be able to display the message we have to add a dummy 
> completion (that empty string) and throw away the old list.

Hmm.  I'm still puzzled why compadd -X ... -n '' throws away the old
list though, since compwidget == lastcompwidget and
compstate[old_list] is set to keep.

> > so that if you hit the oldest match and press M-/ again, it displays
> > this message but keeps the oldest match (should I need a
> > compstate[insert]=1 again, or is it enough to have done that the first
> > time the oldest match was displayed?), and if you switch to M-, it
> > will keep this old_list again and start moving in the opposite
> > direction.
> 
> If I were to implement that, I would use two (global) parameters set
> when the end is reached with `verbose' (or always when the end is
> reached). Any of the two ends, that is. The first parameter just says
> that the end was reached (and could say which end if that is
> needed). The other one would contain the value of `BUFFER' at the time 
> the end was reached. Now if you invoke the widget for the other
> direction when an end was reached (the first parameter sayeth so), you 
> compare the second parameter with the current value of `BUFFER'. If
> they are equal, the thing was just invoked (hm, maybe we shouldn't
> compare `BUFFER', maybe we should check `LASTWIDGET'; or both; and if
> you use the numeric argument thing, the latter may also need to have
> to take that into its calculation). Anyway, the test would say that we 
> just reached one of the ends by the history_complete function for the
> other direction and if we find that out, we just call completion again 
> (that `compgen') and then use `compstate[insert]=-1' (or `...=1' for
> the other direction). Getting words to complete from the history
> should be fast enough so that the user doesn't really notice that a
> new list was created. For this we would also have to keep the original 
> values of `PREFIX' and `SUFFIX' -- and set them anew in the right
> place.

Yep, a similar approach had occurred to me after my last post on the
subject.

> Anyway, I think it can be done...

It can and it has :-)  Here's a full replacement for
Completion/Commands/_history_complete_word.

-------- 8< -------- 8< --------
#compdef -k complete-word \e/ \e,
#
# Complete words from the history
#
# by Adam Spiers, with help gratefully received from
# Sven Wischnowsky and Bart Schaefer
#
# Available configuration keys:
#
#   history_list -- display lists of available matches
#   history_stop -- prevent looping at beginning and end of matches
#                   during menu-completion
#   history_sort -- sort matches lexically (default is to sort by age)
#   history_remove_all_dups --
#                   remove /all/ duplicate matches rather than just
#                   consecutives
#

_history_complete_word () {
  local expl direction

  case "$KEYS" in 
    ',')  direction='newer'
            ;;
    '/')  direction='older'
            ;;
        *)  print <<EOF
The keypress \`$KEYS\' was not understood by _history_complete_word.
You must alter _history_complete_word if you want to bind it to keys
other than the defaults, so that it knows which direction the key
should move in the history.
EOF
            return 1
  esac

  [[ -z "$compconfig[history_list]" ]] && compstate[list]=''

  if [[ -n "$compstate[old_list]" && -n "$compconfig[history_stop]" ]]; then
    # array of matches is newest -> oldest (reverse of history order)
    if [[ "$direction" == 'older' ]]; then
      if [[ compstate[old_insert] -eq $_hist_menu_length ||
            "$_hist_stop" == 'oldest' ]]; then
        _hist_stop='oldest'
        [[ "$compconfig[history_stop]" = verbose ]] &&
          _message 'beginning of history reached'
      elif [[ "$_hist_stop" == 'newest' ]]; then
        zle -Rc
        _history_complete_word_gen_matches
      else
        compstate[old_list]=keep
        (( compstate[insert] = compstate[old_insert] + 1 ))
      fi
    elif [[ "$direction" == 'newer' ]]; then
      if [[ compstate[old_insert] -eq 1 || "$_hist_stop" == 'newest' ]]; then
        _hist_stop='newest'
        [[ "$compconfig[history_stop]" = verbose ]] &&
          _message 'end of history reached'
      elif [[ "$_hist_stop" == 'oldest' ]]; then
        zle -Rc
        _history_complete_word_gen_matches
      else
        compstate[old_list]=keep
        (( compstate[insert] = compstate[old_insert] - 1 ))
      fi
    fi
  else
    _hist_stop=''
    _hist_old_prefix="$PREFIX"
    _history_complete_word_gen_matches
  fi

  [[ -n "$compstate[nmatches]" ]]
}

_history_complete_word_gen_matches () {
  if [[ -n "$compconfig[history_list]" ]]; then
    if [[ -n "$compconfig[history_sort]" ]]; then
      _description expl 'history word'
    else
      _description -V expl 'history word'
    fi
  else
    if [[ -n "$compconfig[history_sort]" ]]; then
      expl=()
    else
      expl=('-V' '')
    fi
  fi

  [[ -n "$_hist_stop" ]] && PREFIX="$_hist_old_prefix"

  local rem_dups
  if [[ -n "$compconfig[history_remove_all_dups]" ]]; then
    rem_dups=''
  else
    rem_dups='-1'
  fi

  compgen "$expl[@]" $rem_dups -Q -H 0 ''
  _hist_menu_length="$compstate[nmatches]"

  case "$direction" in 
    newer)  compstate[insert]=$_hist_menu_length
	    [[ -n "$_hist_stop" ]] && (( compstate[insert]-- ))
            ;;
    older)  compstate[insert]=1
	    [[ -n "$_hist_stop" ]] && (( compstate[insert]++ ))
            ;;
  esac

  [[ -n "$_hist_stop" ]] && _hist_stop=''
}

_history_complete_word "$@"
-------- 8< -------- 8< --------

Please have a play with the different options and see how robust it is
(not very, probably!)  Known problems:

  - Doesn't cope with numeric arguments yet.
  - Whenever duplicates get removed, it breaks.  It looks like
    compstate[nmatches] corresponds with the number of matches
    /including/ duplicates, even if some/all duplicates have been
    removed.
  - The error message given when unknown keys are bound to the
    widget doesn't work.  Should I be using zle -R here?

> I hope it helped.

It did.  Thanks!



Messages sorted by: Reverse Date, Date, Thread, Author