3

I use the zsh shell as default shell on both Ubuntu and Arch.

I configured a shortcut (the up arrow) to autocomplete from history in my zsh shell, using the following line in my .zshrc:

bindkey "^[[A" history-beginning-search-backward

However, when I source my .zshrc and/or reboot in Ubuntu, the shortcut does not work (I only get the previous command, no matter what I started typing), whereas on Arch it works fine (I only get the last command starting with what I typed).

Does anyone know how to solve this?

Ul Tome
  • 117
  • You need to provide some additional information. I'd first check which version of zsh you're running on each system. Then I'd make sure that the key is correct because it's not uncommon that keyboards are mapped different in different distributions. Also try to verify that the .zshrc is really executed in the Ubuntu environment (e.g. echo something to terminal or touch a file). – Mikko Rantalainen Aug 28 '18 at 10:09
  • 1
    What do you see when you type Ctrl+V followed by the Up key? You may want to bind both ^[[A and ^[OA (or $terminfo[kcuu1] or $key[Up]) – Stéphane Chazelas Aug 28 '18 at 11:17
  • A related question is https://unix.stackexchange.com/questions/419068/ . – JdeBP Aug 28 '18 at 11:43
  • Stéphane, thank you for the Ctrl+V tip, it made solving the problem a triviality! – Ul Tome Aug 28 '18 at 13:34

2 Answers2

5

On most xterm-like terminals, the Up (and it's similar for most navigation keys) send either ␛[A or ␛OA depending on whether the terminal has been put in keypad transmit mode or not. The smkx and rmkx terminfo entries can be used to put a terminal in or out of that mode.

The kcuu1 (key cursor up by 1) terminfo entry describes the sequence sent by Up when in keypad transmit mode, that is ␛OA.

Debian and derivatives have a /etc/zsh/zshrc file that does a

function zle-line-init () {
   emulate -L zsh
   printf > /dev/tty '%s' ${terminfo[smkx]}
}

Which puts the terminal in that mode when zle is active, which means you can now rely on the terminfo database to know what character sequences keys transmit.

The file also defines a $key associative array based on the terminfo entries to help you map those to widgets. So on those systems, you can do:

(($+key[Up])) && bindkey $key[Up] history-beginning-search-backward

For something that works on systems where the terminal is in keypad transmit mode and those that don't or don't have the $key hash, you can do:

bindkey $terminfo[kcuu1] history-beginning-search-backward
bindkey ${terminfo[kcuu1]/O/[} history-beginning-search-backward

See also:

  • Thank you, I had no idea such problems existed, and now thanks to you everything is working smoothly! – Ul Tome Aug 28 '18 at 13:32
2

Cursor keys are fun.

Albeit that they are not quite as fun as editing keys, which are really fun.

You have two sets of cursor keys on your keyboard, ones on the cursor keypad and ones on the calculator keypad.

Most terminal emulators try (sometimes quite poorly) to employ the model of DEC VTs, where each set of keys is individually switchable between application mode and normal mode using the private mode settings DECCKM (Cursor Keypad Mode) and DECNKM (Numeric Keypad Mode), respectively. The idea of application mode is essentially that the keys on the relevant keypad turn into extra application function keys.

⇐ This is the cursor keypad.
  • In normal mode, the arrow keys send the ECMA-48 CUB, CUF, CUU, and CUD control sequences, unless the ⎇ Alt modifier is in effect in which case they send DECFNK control sequences.
  • In application mode, the arrow keys send SS3 single-shift 3 sequences.
⇐ This is the calculator keypad.
  • In normal mode, the arrow keys send the ECMA-48 CUB, CUF, CUU, and CUD control sequences, unless the ⎇ Alt modifier is in effect in which case they send DECFNK control sequences, or unless the combination of numeric lock and shift causes them to send digits.
  • In application mode, the arrow keys send a different set of SS3 single-shift 3 sequences (unless, again, the combination of numeric lock and shift causes them to send digits).

The [ A sequence that you have told ZLE to bind to a widget is an ECMA-48 7-bit alias for the CSI A control sequence, which is the CUP ("Cursor UP") control sequence. That control sequence is only generated by DEC VTs and their imitator terminal emulators when a keypad is in normal mode and the ⎇ Alt modifier is not in effect. It won't match the shift sequences sent when the relevant keypads are in application mode.

The terminfo database muddies the waters, and causes extra fun here, because it does not employ this model for terminal I/O. Rather, it employs its own different model that embodies notions of "local" and "remote" keys, which is not what DEC VT application/normal mode switching actually involves at all; and it has a single local/remote switch mechanism, that ends up switching both keypads between application/normal modes indivisibly.

terminfo is the way not to hardwire to one specific terminal type how you are configuring ZLE, just in case you find yourself with a terminal or terminal emulator that does not imitate a DEC VT. And the Z shell provides you with ways of accessing the necessary capability entries from the database record. So you could read from terminfo what control sequences terminfo expects the up/down/left/right cursor keys to produce, and issue appropriate bindkey commands that map those control sequences to widgets.

The problem is that terminfo is inadequate for this job. It only has a way to record one control sequence per key, whereas as you can see keys can send at least three different sequences, dependent from mode and modifiers pressed. (Modifiers can influence sent control sequences quite significantly in the DEC VT model.) So you need to switch the terminal into the mode that generates what terminfo tells you to expect.

But it gets worse: terminfo isn't consistent. The single control sequence is sometimes the DEC VT application mode sequence, as terminfo records for the putty terminal type, sometimes the DEC VT normal mode sequence, as terminfo records for the rxvt terminal type, but never the DECFNK sequence. So you have no way to know whether you should switch to application or to normal mode with any given terminal or terminal emulator. What will work right for one will go wrong for another.

The other approach, therefore, is to ignore terminfo and realize that you are already and quite happily assuming that your terminal is always going to be like a DEC VT with your original bindkey command. You just need two of them, to make sure that whether your terminal is in application or normal mode the control sequence that it sends will be matched:

bindkey "^[OA" history-beginning-search-backward

However, this will not cope with modifier keys being pressed, which adds extra parameters to the CUP control sequence that causes the simplistic string matching that ZLE uses to fail when all that it is looking for is plain old parameterless CUP. You have to manually issue an additional bindkey command for each possible CUP control sequence resulting from each possible combination of modifiers.

seq 1 8 |
while read -r i
do
    bindkey "^[[1;${i}A" history-beginning-search-backward
done

ZLE is not alone, here. Other terminfo-based programs, such as the fish shell, suffer in the same way. (The fish shell people, too, found out that what choice of application/normal mode works right for one terminal emulator will go wrong for another.) Rearchitecting this (compare libtermkey which has an actual ECMA-48 control sequence parser for input) in these programs is long overdue. But no-one has tackled it yet.

Further reading

JdeBP
  • 68,745