Add arguments from previous command to zsh completion

autocompletezsh

In zsh (as well as bash) you can use some history word expansions to denote arguments from previous commands.

This example shows getting the 2nd parameter from the previous command in history with !:# expansion:

% echo foo bar baz
foo bar baz
% echo !:2
echo bar
bar

I often forget exactly what # parameter a particular argument is and typing !:# isn't always that quick when I do remember which arg it is. I know about meta-. to replace the last argument, but sometimes it isn't the last arg that I want.

I'd like to add the arguments from the previous command as suggestions to complete any command that I'm typing in zsh.

I was able to figure out how to create a shell function that can create an array of arguments (0..N) from the last command and bind it to a particular command.

_last_command_args() {
    last_command=$history[$[HISTCMD-1]]
    last_command_array=("${(s/ /)last_command}") 
    _sep_parts last_command_array
}

# trying to get last_command_args to be suggested for any command, this just works for foo
compdef _last_command_args foo

This is what it looks like for completing just foo where I hit the tab key at <TAB>:

% echo bar baz qux
bar baz qux
% foo <TAB>
bar   baz   echo  qux 

This works great for completing the command "foo", but I'd like these to be options on any zsh expansion that I do. I think it's got something to do with the zstyle completer stuff, but after a few hours of hacking around I realized I'm out of my depth.

How can I get the arguments from my previous command as suggested completions for any command in zsh?

I've got my full zshrc compinstall file shared out on bitbucket if that helps. Lots of it is cribbed from a variety of sources and some of it I've hacked together myself.

UPDATE:

@Julien Nicoulaud's answer got me close, I'm marking it as accepted as it got me where I needed to get.

With my particular config, using the suggested:

zstyle ':completion:*' completer _last_command_args _complete

Didn't quite work for me as it was causing tab completion to only display the list of arguments from the last command (though it'd actually complete with filenames as well, just not display them). Changing the order to _complete _last_command_args did the reverse. It'd display the normal filenames, but not last_command_args

I'm guessing this has something to do with the way completer works. I think it only displays the output from the first method that returns successfully, but I'm having trouble parsing the zsh source to understand fully what's going on. I was able to tweak my method to include a call to _complete so that it showed both last argument commands as well as the regular autocomplete stuff. Not quite as separated, but works well enough for me.

Here's the full function I used along with the other zstyle stuff I have:

# adds the arguments from the last commadn to the autocomplete list
# I wasn't able to get this to work standalone and still print out both regular
# completion plus the last args, but this works well enough.
_complete_plus_last_command_args() {
    last_command=$history[$[HISTCMD-1]]
    last_command_array=("${(s/ /)last_command}") 
    _sep_parts last_command_array
    _complete 
}


_force_rehash() {
  (( CURRENT == 1 )) && rehash
  return 1  # Because we didn't really complete anything
}

zstyle ':completion:::::' completer _force_rehash _complete_plus_last_command_args _approximate 

Other zstyle lines I have, not necessary for this to work, but could affect why this works for me:

zstyle -e ':completion:*:approximate:*' max-errors 'reply=( $(( ($#PREFIX + $#SUFFIX) / 3 )) )'
zstyle ':completion:*:descriptions' format "- %d -"
zstyle ':completion:*:corrections' format "- %d - (errors %e})"
zstyle ':completion:*:default' list-prompt '%S%M matches%s'
zstyle ':completion:*' group-name ''
zstyle ':completion:*:manuals' separate-sections true
zstyle ':completion:*' menu select
zstyle ':completion:*' verbose yes

Now, if I'm in a directory with file1.txt and file2.txt, and my last command was echo foo bar baz, I get this for autocomplete which is just what I wanted:

% ls
bar   baz   echo  foo 
- files -
file1.txt   file2.txt 

Best Answer

You can add your completer to the list of completers used by default:

zstyle ':completion:*' completer _last_command_args _complete