MacOS terminal: quick way to edit command options after bash/zsh autocomplete

bashitermkeyboardterminalzsh

The issue

I use autocomplete from history a lot whether that's with up-arrow or typing the first few letters and completing the whole command including all arguments (options and others) with right-arrow.

Quite often I have to run the same command multiple times on the same file but with different options. However, by pressing up-arrow or right-arrow, I complete to the end of the command and it's quicker to type the whole thing manually than bring the cursor back to the options, edit and then re-enter.

Basically, I'd like to know if there's any way to use autocompletion to re-enter a command from history but bring the cursor back to the first option/argument. Alternatively, a keyboard shortcut to fill in the remaining arguments as previously after typing a different one.

e.g.

Option 1

I type and enter:

$ command -opt1 reallyverylongindeedarg1

Pressing up-arrow produces $ command -opt1 reallyverylongindeedarg1 with my cursor at the end of the line.

I'd like to have a different keyboard shortcut that results in me having the cursor somewhere around -opt1 so I can change that to -differntopt2 and re-run the command which would now read:

$ command -differntopt2 really.verylong.indeed.arg1

Or, having previously typed and run:

$ command -opt1 really.verylong.indeed.arg1

Typing comman and then hitting right-arrow completes the full command $ command -opt1 reallyverylongindeedarg1 (again with my cursor at the end of the line). I've love to know whether there's a keyboard shortcut to jump to the first argument/option when autocompleting.

Option 2

Another method would be for my to type in $ command -differntopt2 then press right arrow to fill in the argument really.verylong.indeed.arg1 as previously when I last ran this command. Resulting in:

$ command -differntopt2 really.verylong.indeed.arg1

What about just navigating with the keyboard and jump back by word?

Well, firstly, that's far from ideal as the arguments have lots of punctuation in so each one takes several (and different numbers of) keypresses and it's slow to go back the right number of words each time to reach the option I want to edit.

Secondly, I can't find a way to set up normal keyboard shortcuts for navigating forwards and backwards by word.

In iTerm2 I have tried:

Escape sequences such as [1;5D, [1;5C, \033b, \033f, etc.

Manually typing Esc, F and Esc, B does work but is very slow. Ctrl + A for go to start of line selects all. Ctrl + <- and Ctrl + -> just produce [D and [C.

tl;dr

  1. How can I autocomplete and end up with my cursor at the first
    argument instead of the end of the line?, or,
  2. How do I set up
    keyboard shortcuts for jumping forwards and backwards by argument in
    iTerm2?

Best Answer

For ZSH another option is to put the cursor at the beginning and not the end of the line (this makes it more like mksh or others). The default:

% PS1='%% ' zsh -f
% bindkey | grep up-line-or
"^[OA" up-line-or-history
"^[[A" up-line-or-history

I use set -o vi so you may need to translate the bindkey keys into whatever suits your needs:

bindkey -M vicmd "j" vi-down-line-or-history
bindkey -M vicmd "k" vi-up-line-or-history

no really I want to jump to the first argument

This requires a custom widget, which might run something like

function up-line-first-arg {
    local first offset
    zle vi-up-line-or-history
    first=${BUFFER%% *}
    CURSOR+=$(( 1 + ${#first} ))
}
zle -N up-line-first-arg
autoload -U up-line-first-arg
autoload -U compinit
compinit
set -o vi
bindkey -M vicmd "k" up-line-first-arg

This version will put the cursor on the first argument or the end of the command name (unless there are multiple spaces between the first argument and subsequent, which is not common). Docs in zshexpn(1) and zshzle(1) may help explain the particulars, but the gist is to move up like ZSH does, get the first word (if any), and move the cursor over that length, plus one to get past the space. ZSH is clever will not move the cursor past the end of the BUFFER for the case where there is no space in the command.

but wait there's more

The following version handles (perhaps uncommon) cases such as ls -al and on failure will put the cursor at the beginning of the line.

function up-line-first-arg {
    zle vi-up-line-or-history
    [[ $BUFFER =~ "^[^ ]+[ ]+" ]] && CURSOR+=${#MATCH}
}

Basically a regular expression "from the beginning of the BUFFER match one or more things that are not space followed by one or more things that are spaces" and then moving the CURSOR by that much on match.