ZSHZLE(1)
๐ธ NAME ๐ธ
zshzle โ zsh command line editor. Yep bestie, this is the ZSHZLE(1) page from the General Commands Manual, and we're about to make your command line editing era absolutely iconic. ๐
โจ
โจ DESCRIPTION โจ
Okay so picture this: if the ZLE option is set โ and yes, it's set by default in interactive shells โ and the shell input is attached to the terminal, then the user can edit command lines. Main character energy fr fr. ๐
There are two display modes, and the first one is multiline mode, which is the default. It only works if the TERM parameter is set to a valid terminal type that can move the cursor up. The second one is single line mode, and it's used if TERM is invalid or incapable of moving the cursor up, or if the SINGLE_LINE_ZLE option is set. This mode is similar to ksh, and it uses no termcap sequences. Also: if TERM is "emacs", the ZLE option will be unset by default. Noted. ๐ซถ
The parameters BAUD, COLUMNS, and LINES are also used by the line editor. See Parameters Used By The Shell in zshparam(1). ๐
The parameter zle_highlight is also used by the line editor; see Character Highlighting below. Highlighting of special characters and the region between the cursor and the mark โ as set with set-mark-command in Emacs mode, or by visual-mode in Vi mode โ is enabled by default; consult this reference for more information. And for the irascible conservatives in the chat: yes, all highlighting may be disabled by the following setting:
zle_highlight=(none)
In many places, references are made to the numeric argument. This can by default be entered in emacs mode by holding the alt key and typing a number, or pressing escape before each digit, and in vi command mode by typing the number before entering a command. Generally the numeric argument causes the next command entered to be repeated the specified number of times, unless otherwise noted below; this is implemented by the digit-argument widget. See also the Arguments subsection of the Widgets section for some other ways the numeric argument can be modified. ๐ฅ
๐ KEYMAPS ๐
A keymap in ZLE contains a set of bindings between key sequences and ZLE commands. The empty key sequence cannot be bound. Periodt. ๐
There can be any number of keymaps at any time, and each keymap has one or more names. If all of a keymap's names are deleted, it disappears. bindkey can be used to manipulate keymap names. ๐ซ
Initially, there are eight keymaps:
emacsโ EMACS emulationviinsโ vi emulation - insert modevicmdโ vi emulation - command modevioppโ vi emulation - operator pendingvisualโ vi emulation - selection activeisearchโ incremental search modecommandโ read a command name.safeโ fallback keymap
The .safe keymap is special. It can never be altered, and the name can never be removed. However, it can be linked to other names, which can be removed. In the future other special keymaps may be added; users should avoid using names beginning with . for their own keymaps. ๐ฆ
In addition to these names, either emacs or viins is also linked to the name main. If one of the VISUAL or EDITOR environment variables contain the string vi when the shell starts up then it will be viins, otherwise it will be emacs. bindkey's -e and -v options provide a convenient way to override this default choice. ๐
When the editor starts up, it will select the main keymap. If that keymap doesn't exist, it will use .safe instead. ๐
In the .safe keymap, each single key is bound to self-insert, except for ^J (line feed) and ^M (return) which are bound to accept-line. This is deliberately not pleasant to use; if you are using it, it means you deleted the main keymap, and you should put it back. ๐ญ
๐ Reading Commands ๐
When ZLE is reading a command from the terminal, it may read a sequence that is bound to some command and is also a prefix of a longer bound string. In this case ZLE will wait a certain time to see if more characters are typed, and if not (or they don't match any longer string) it will execute the binding. This timeout is defined by the KEYTIMEOUT parameter; its default is 0.4 sec. There is no timeout if the prefix string is not itself bound to a command. โณ
The key timeout is also applied when ZLE is reading the bytes from a multibyte character string when it is in the appropriate mode. (This requires that the shell was compiled with multibyte mode enabled; typically also the locale has characters with the UTF-8 encoding, although any multibyte encoding known to the operating system is supported.) If the second or a subsequent byte is not read within the timeout period, the shell acts as if ? were typed and resets the input state. ๐ธ
As well as ZLE commands, key sequences can be bound to other strings, by using bindkey -s. When such a sequence is read, the replacement string is pushed back as input, and the command reading process starts again using these fake keystrokes. This input can itself invoke further replacement strings, but in order to detect loops the process will be stopped if there are twenty such replacements without a real command being read. ๐
A key sequence typed by the user can be turned into a command name for use in user-defined widgets with the read-command widget, described in the subsection Miscellaneous of the section Standard Widgets below. ๐
๐ Local Keymaps ๐
While for normal editing a single keymap is used exclusively, in many modes a local keymap allows for some keys to be customised. For example, in an incremental search mode, a binding in the isearch keymap will override a binding in the main keymap but all keys that are not overridden can still be used. โจ
If a key sequence is defined in a local keymap, it will hide a key sequence in the global keymap that is a prefix of that sequence. An example of this occurs with the binding of iw in viopp as this hides the binding of i in vicmd. However, a longer sequence in the global keymap that shares the same prefix can still apply so for example the binding of ^Xa in the global keymap will be unaffected by the binding of ^Xb in the local keymap. ๐ง
๐ ๏ธ ZLE BUILTINS ๐ ๏ธ
The ZLE module contains three related builtin commands. The bindkey command manipulates keymaps and key bindings; the vared command invokes ZLE on the value of a shell parameter; and the zle command manipulates editing widgets and allows command line access to ZLE commands from within shell functions. ๐
bindkey [ options ] -l [ -L ] [ keymap ... ]
bindkey [ options ] -d
bindkey [ options ] -D keymap ...
bindkey [ options ] -A old-keymap new-keymap
bindkey [ options ] -N new-keymap [ old-keymap ]
bindkey [ options ] -m
bindkey [ options ] -r in-string ...
bindkey [ options ] -s in-string out-string ...
bindkey [ options ] in-string command ...
bindkey [ options ] [ in-string ]
bindkey's options can be divided into three categories: keymap selection for the current command, operation selection, and others. The keymap selection options are:
-eโ Selects keymapemacsfor any operations by the current command, and also linksemacstomainso that it is selected by default the next time the editor starts.-vโ Selects keymapviinsfor any operations by the current command, and also linksviinstomainso that it is selected by default the next time the editor starts.-aโ Selects keymapvicmdfor any operations by the current command.-Mkeymap โ The keymap specifies a keymap name that is selected for any operations by the current command.
If a keymap selection is required and none of the options above are used, the main keymap is used. Some operations do not permit a keymap to be selected, namely: ๐ซ
-lโ List all existing keymap names; if any arguments are given, list just those keymaps.
If the -L option is also used, list in the form of bindkey commands to create or link the keymaps. bindkey -lL main shows which keymap is linked to main, if any, and hence if the standard emacs or vi emulation is in effect. This option does not show the .safe keymap because it cannot be created in that fashion; however, neither is bindkey -lL .safe reported as an error, it simply outputs nothing. ๐ซถ
-dโ Delete all existing keymaps and reset to the default state.-Dkeymap ... โ Delete the named keymaps.-Aold-keymap new-keymap โ Make the new-keymap name an alias for old-keymap, so that both names refer to the same keymap. The names have equal standing; if either is deleted, the other remains. If there is already a keymap with the new-keymap name, it is deleted.-Nnew-keymap [ old-keymap ] โ Create a new keymap, named new-keymap. If a keymap already has that name, it is deleted. If an old-keymap name is given, the new keymap is initialized to be a duplicate of it, otherwise the new keymap will be empty.
To use a newly created keymap, it should be linked to main. Hence the sequence of commands to create and use a new keymap mymap initialized from the emacs keymap (which remains unchanged) is:
bindkey -N mymap emacs
bindkey -A mymap main
Note that while bindkey -A newmap main will work when newmap is emacs or viins, it will not work for vicmd, as switching from vi insert to command mode becomes impossible. ๐
The following operations act on the main keymap if no keymap selection option was given:
-mโ Add the built-in set of meta-key bindings to the selected keymap. Only keys that are unbound or bound toself-insertare affected.-rin-string ... โ Unbind the specified in-strings in the selected keymap. This is exactly equivalent to binding the strings toundefined-key.
When -R is also used, interpret the in-strings as ranges.
When -p is also used, the in-strings specify prefixes. Any binding that has the given in-string as a prefix, not including the binding for the in-string itself, if any, will be removed. For example,
bindkey -rpM viins '^['
will remove all bindings in the vi-insert keymap beginning with an escape character (probably cursor keys), but leave the binding for the escape character itself (probably vi-cmd-mode). This is incompatible with the option -R. ๐ฅ
-s in-string out-string ...โ pairs them up, bestie: eachin-stringgets bound to its matchingout-string. Typein-stringand theout-stringgets pushed back and treated as input to the line editor. And if you're also using-R, thein-strings are read as ranges instead. ๐ซ Heads up tho: bothin-stringandout-stringget interpreted the same way, which is the whole escape-sequence tea described down below.in-string command ...โ binds eachin-stringto eachcommand. With-R, thein-strings are ranges again.[ in-string ]โ the listing era โจ. If you give anin-string, you get the binding for that string in the selected keymap. Noin-string? Then every key binding in the selected keymap gets dumped out. (Special case alert ๐: if-eor-vis used alone, the keymap is not displayed โ the implicit linking of keymaps is literally the only thing that happens.)
With -p, the in-string must be present. The listing then shows every binding that has the given key sequence as a prefix, and it does not include bindings for the key sequence itself.
With -L, the list comes out in the form of bindkey commands you could run to create those key bindings. ๐ซถ
When -R is used like we said, a valid range is two characters with an optional - between them. Every character between the two you specified โ inclusive, babes โ gets bound as specified. ๐
For either in-string or out-string, these escape sequences are recognised:
\aโ bell character\bโ backspace\e,\Eโ escape\fโ form feed\nโ linefeed (newline)\rโ carriage return\tโ horizontal tab\vโ vertical tab\NNNโ character code in octal\xNNโ character code in hexadecimal\uNNNNโ unicode character code in hexadecimal\UNNNNNNNNโ unicode character code in hexadecimal\M[-]Xโ character with meta bit set\C[-]Xโ control character^Xโ control character
In all other cases, \ escapes the character right after it. Delete is written as ^?. And clock this: \M^? and ^\M? are not the same thing, and โ unlike emacs โ the bindings \M-X and \eX are entirely distinct, even though bindkey -m initialises them to the same bindings. ๐ญ no cap, they're different.
๐ธ vared
vared [ -Aacghe ] [ -p prompt ] [ -r rprompt ]
[ -M main-keymap ] [ -m vicmd-keymap ]
[ -i init-widget ] [ -f finish-widget ]
[ -t tty ] name
Okay so here's the vibe: the value of the parameter name gets loaded into the edit buffer, and the line editor is invoked. When the editor exits, name is set to the string value the editor returned. ๐
- Give
-cand the parameter gets created if it doesn't already exist. -acan be given with-cto create an array parameter, or-Ato create an associative array.- If the type of an existing parameter doesn't match the type to be created, the parameter is unset and recreated. ๐
-gmay be given to suppress warnings from theWARN_CREATE_GLOBALandWARN_NESTED_VARoptions.
If you're editing an array or an array slice, separator characters as defined in $IFS are shown quoted with a backslash โ and so are backslashes themselves. Conversely, when the edited text gets split into an array, a backslash quotes an immediately following separator character or backslash; no other special handling of backslashes happens, and no handling of quotes happens either. ๐
Individual elements of existing array or associative array parameters can be edited by using subscript syntax on name. New elements are created automatically, even without -c. โจ
-pโ the following string is taken as the prompt to display at the left.-rโ the following string is the prompt to display at the right.-hโ the history can be accessed from ZLE.-eโ typing^D(Control-D) on an empty line causesvaredto exit immediately with a non-zero return value.
The -M option gives a keymap to link to the main keymap during editing, and -m gives a keymap to link to the vicmd keymap during editing. For vi-style editing, this lets a pair of keymaps override viins and vicmd. For emacs-style editing, only -M is normally needed, but -m may still be used. On exit, the previous keymaps get restored. ๐ซถ
vared calls the usual zle-line-init and zle-line-finish hooks before and after it takes control. Using -i and -f, you can replace these with other custom widgets. iconic.
If -t tty is given, tty is the name of a terminal device to be used instead of the default /dev/tty. If tty doesn't refer to a terminal, an error is reported. โ ๏ธ
๐ zle
zle
zle -l [ -L | -a ] [ string ... ]
zle -D widget ...
zle -A old-widget new-widget
zle -N widget [ function ]
zle -f flag [ flag... ]
zle -C widget completion-widget function
zle -R [ -c ] [ display-string ] [ string ... ]
zle -M string
zle -U string
zle -K keymap
zle -F [ -L | -w ] [ fd [ handler ] ]
zle -I
zle -T [ tc function | -r tc | -L ]
zle widget [ -n num ] [ -f flag ] [ -Nw ] [ -K keymap ] args ...
The zle builtin does a whole bunch of different things concerning ZLE, and honestly it's giving main character energy with how many forms it has. ๐
With no options and no arguments, only the return status gets set. It's zero if ZLE is currently active and widgets could be invoked using this builtin command, and non-zero otherwise. Note: even if a non-zero status is returned, zle may still be active as part of the completion system โ but that doesn't allow direct calls to ZLE widgets. ๐ญ
Otherwise, which operation it performs depends on its options:
-l [ -L | -a ] [ string ]โ lists all existing user-defined widgets. With-L, it lists them in the form ofzlecommands that would create the widgets.
Combined with -a, all widget names are listed, including the builtin ones. In that case -L is ignored. ๐
If at least one string is given, and -a is present or -L is not used, nothing gets printed. The return status is zero if all the strings are names of existing widgets, and non-zero if at least one string isn't the name of a defined widget. If -a is also present, all widget names are used for the comparison including builtin widgets; otherwise only user-defined widgets are used.
If at least one string is present and -L is used, user-defined widgets matching any string are listed in the form of zle commands to create the widgets. โจ
-D widget ...โ deletes the named widgets. bye bestie ๐-A old-widget new-widgetโ makes the namenew-widgetan alias forold-widget, so both names refer to the same widget. The names have equal standing; if either is deleted, the other remains. If there's already a widget with thenew-widgetname, it gets deleted. ๐-N widget [ function ]โ creates a user-defined widget. If a widget with that name already exists, it's overwritten. When the new widget is invoked from within the editor, the specified shell function is called. If no function name is given, it defaults to the same name as the widget. For more, see the sectionWidgetsbelow. ๐-f flag [ flag... ]โ sets various flags on the running widget. Possible values forflagare:yankโ says the widget has yanked text into the buffer. If the widget is wrapping an existing internal widget, no further action is necessary, but if it inserted the text manually, then it should also take care to setYANK_STARTandYANK_ENDcorrectly.yankbeforedoes the same but is used when the yanked text appears after the cursor.killโ says text has been killed into the cutbuffer. When you repeatedly invoke a kill widget, text is appended to the cutbuffer instead of replacing it, but when wrapping such widgets it's necessary to callzle -f killto keep that effect. ๐ฅvichangeโ says the widget represents a vi change that can be repeated as a whole withvi-repeat-change. The flag should be set early in the function, before inspecting the value ofNUMERICor invoking other widgets. This has no effect for a widget invoked from insert mode. If insert mode is active when the widget finishes, the change extends until next returning to command mode.
-C widget completion-widget functionโ creates a user-defined completion widget namedwidget. It behaves like the built-in completion widget whose name is given ascompletion-widget. To generate the completions, the shell functionfunctionis called. For more, seezshcompwid(1). ๐ฆ-R [ -c ] [ display-string ] [ string ... ]โ redisplays the command line. If adisplay-stringis given and isn't empty, it's shown in the status line (immediately below the line being edited).
If the optional strings are given, they're listed below the prompt the same way completion lists get printed. If no strings are given but -c is used, such a list is cleared. โจ
Note that immediately after those widgets return, the command line gets redisplayed and all those strings you were showing get erased ๐ญ so this option is genuinely only useful for widgets that don't exit straight after using it. Like, think about it.
This command can also be called safely outside user-defined widgets โ if zle is active the display gets refreshed, and if zle isn't active the command just does nothing at all. In that case there'll usually be no other arguments.
The status is zero if zle was active, else one. ๐
-M stringโ so this is like-R(string shows up below the command line) but not like-Rin one key way: the string does not go into the status line, it just gets printed normally below the prompt instead. Which means it's still there after the widget returns, until subsequent commands overwrite it. Iconic behaviour honestly.-U stringโ pushes the characters in string onto ZLE's input stack ๐ซถ. After the widget that's currently running finishes, ZLE behaves exactly as if the user typed those characters themselves. It's a stack, so if you use this repeatedly the last string pushed gets processed first โ but the characters within each string still get processed in the order they appear in the string. Order matters, bestie.-K keymapโ selects the keymap named keymap. If there's no such keymap you get an error message, sorry ๐ฅฒ. This selection affects how following keystrokes are interpreted within this invocation of ZLE. Any following invocation (like, the next command line) starts fresh with themainkeymap selected. It does not persist. Periodt.-F [ -L | -w ] [ fd [ handler ] ]โ only available if your system supports one of thepollorselectsystem calls; most modern systems do, so you're probably fine โจ.
Installs handler (the name of a shell function) to handle input from file descriptor fd. If you install a handler for an fd that's already handled, the existing handler gets replaced. You can have any number of handlers for any number of readable file descriptors. Heads up though: zle makes no attempt to check whether the fd is actually readable when you install the handler, and the user has to make their own arrangements for handling the fd while zle is not active.
When zle is trying to read data it looks at both the terminal and the list of handled fds. If data becomes available on a handled fd, zle calls handler with the fd that's ready for reading as the first argument. Normally that's the only argument, but if an error got detected, a second argument gives you the details: hup for a disconnect, nval for a closed or otherwise invalid descriptor, or err for any other condition. Systems that support only the select system call always use err.
If you also give the -w option, the handler is a line editor widget instead โ typically a shell function turned into a widget with zle -N. In that case handler gets to use all the facilities of zle to update the current editing line ๐. But note: because handling the fd happens at a low level, changes to the display won't automatically appear โ the widget should call zle -R to force a redisplay. And as of this writing, widget handlers only support a single argument and are therefore never passed a string for error state, so widgets have to be prepared to test the descriptor themselves.
If either type of handler produces output to the terminal, it should call zle -I before doing so (see below ๐). Handlers should not attempt to read from the terminal.
If no handler is given but an fd is present, any handler for that fd gets removed. And if there isn't one, an error message is printed and status 1 is returned.
If no arguments are given at all, or the -L option is supplied, a list of handlers is printed in a form you can store and execute later. An fd (but not a handler) can optionally be given with -L โ in that case the function lists the handler if there is one, and otherwise silently returns status 1.
โ ๏ธ Use this feature with care, fr fr. Activity on one of the fds that isn't properly handled can make the terminal unusable ๐. And removing an fd handler from inside a signal trap may cause unpredictable behaviour. You've been warned.
Here's a simple example of the whole feature in action ๐ฆ. A connection to a remote TCP port is created with the ztcp command; peep the description of the zsh/net/tcp module in zshmodules(1). Then a handler gets installed that simply prints out any data arriving on that connection. Note that select will say the file descriptor needs handling if the remote side has closed the connection โ we deal with that by testing for a failed read.
if ztcp pwspc 2811; then
tcpfd=$REPLY
handler() {
zle -I
local line
if ! read -r line <&$1; then
# select marks this fd if we reach EOF,
# so handle this specially.
print "[Read on fd $1 failed, removing.]" >&2
zle -F $1
return 1
fi
print -r - $line
}
zle -F $tcpfd handler
fi
-Iโ unusually, this one is most useful outside ordinary widget functions, though you can use it inside one too if you need normal output to the terminal. It invalidates the current zle display in preparation for output โ typically from a trap function. It has no effect if zle isn't active. When a trap exits, the shell checks whether the display needs restoring, which is why the following prints output without disturbing the line you're editing ๐ธ:
TRAPUSR1() {
# Invalidate zle display
[[ -o zle ]] && zle -I
# Show output
print Hello
}
In general the trap function may need to test whether zle is active before using this method (like the example does), because the zsh/zle module might not even be loaded โ and if it isn't, the command can just be skipped.
You can call zle -I several times before control goes back to the editor; the display only gets invalidated the first time, to minimise disruption โจ.
Note that there are normally better ways to mess with the display from inside zle widgets โ see, for example, zle -R above.
The returned status is zero if zle was invalidated, even if that happened via a previous zle -I call or a system notification. If you want to test whether a zle widget can be called at this point, just run zle with no arguments and check the return status. iykyk ๐
-Tโ used to add, list or remove internal transformations on the processing the line editor does. It's typically only for debugging or testing, so it's lowkey of little interest to the general user ๐.
zle -T transformation func says the given transformation (see below) is handled by shell function func.
zle -Tr transformation removes the given transformation if it was present โ and it's not an error if none was, so no stress.
zle -TL lists all transformations currently in operation.
Currently the only transformation is tc. It's used instead of outputting termcap codes to the terminal. When the transformation is in operation the shell function gets the termcap code that would have been output as its first argument, and if the operation required a numeric argument, that's passed as a second argument. The function should set the shell variable REPLY to the transformed termcap code. Typically this is used to produce some simply formatted version of the code and optional argument for debugging or testing. Note that this transformation is not applied to other non-printing characters like carriage returns and newlines ๐.
widget [ -n num ] [ -f flag ] [ -Nw ] [ -K keymap ] args ...โ invokes the specified widget. This can only be done when ZLE is active; normally that means you're inside a user-defined widget.
With the -n and -N options, the current numeric argument gets saved and then restored after the call to widget โ -n num sets the numeric argument temporarily to num, while -N sets it to the default, i.e. as if there were none at all.
With the -K option, keymap is used as the current keymap while the widget executes, and the previous keymap gets restored when the widget exits. Slay. ๐
...and lowkey that's the whole point: calling a widget this way doesn't normally set the special parameter WIDGET and its little friends, so the environment looks exactly like the top-level widget the user actually called is still the one running the show. But bestie, if you pass the -w option? Then WIDGET and the related parameters do get set to reflect the widget being executed by the zle call. Slay.
And normally, when a widget returns, the special parameter LASTWIDGET will point at it. Wanna block that? Just pass -f nolast. Iconic veto energy.
Any further arguments get passed straight to the widget โ but heads up, standard argument handling is performed first, so if you've got a general argument list you gotta precede it with --. If it's a shell function, those args are passed down as positional parameters; for builtin widgets, what happens to them is entirely up to the widget in question. RN arguments are only handled by the incremental-search commands, the history-search-forward and -backward and the corresponding functions prefixed by vi-, and by universal-argument. And no error is flagged if the command doesn't use the arguments at all, or only uses some of them. Chill.
The return status reflects the success or failure of the operation the widget carried out, or โ if it's a user-defined widget โ the return status of the shell function. โจ
๐จ A non-zero return status causes the shell to beep when the widget exits, unless theBEEPoption was unset or the widget was called via thezlecommand. So if a user-defined widget needs an immediate beep, it should call thebeepwidget directly. No cap.
๐ฆ ZLE WIDGETS ๐ฆ
Okay so every single action in the editor is performed by widgets. That's it, that's the whole job description โ a widget's job is simply to perform some small action. And plot twist: the ZLE commands that key sequences in keymaps are bound to are in fact widgets. Mind blown. Widgets can be user-defined or built in. ๐ซถ
The standard widgets built into ZLE are listed in the section Standard Widgets below (iykyk). Other built-in widgets can be defined by other modules โ see zshmodules(1).
Here's a cute detail: each built-in widget has two names โ its normal canonical name, and the same name preceded by a .. The . name is special: it can't be rebound to a different widget. So even when the usual name gets redefined, the widget stays available. Main character refuses to be replaced. ๐
User-defined widgets are defined using zle -N, and implemented as shell functions. When the widget is executed, the corresponding shell function is executed, and it can perform editing (or other) actions. Oh, and a recommendation from the manual itself: user-defined widgets should not have names starting with ..
๐ USER-DEFINED WIDGETS ๐
So user-defined widgets are implemented as shell functions, which means they can execute any normal shell command. They can also run other widgets โ built-in or user-defined โ using the zle builtin command. But here's the thing: the standard input of the function is redirected from /dev/null so external commands can't unintentionally block ZLE by reading from the terminal. ๐ซถ However, read -k or read -q can be used to read characters, so you're not totally locked out.
Finally, they can examine and edit the ZLE buffer being edited by reading and setting the special parameters described below. This is where it gets good.
These special parameters are always available in widget functions, but they're not in any way special outside ZLE. If they have some normal value outside ZLE, that value is temporarily inaccessible โ but it'll return when the widget function exits. These special parameters in fact have local scope, like parameters created in a function using local. ๐
Inside completion widgets and traps called while ZLE is active, these parameters are available read-only.
โ ๏ธ Note that the parameters appear as local to any ZLE widget in which they appear. Hence if you wanna override them, this needs to be done within a nested function:
widget-function() {
# $WIDGET here refers to the special variable
# that is local inside widget-function
() {
# This anonymous nested function allows WIDGET
# to be used as a local variable. The -h
# removes the special status of the variable.
local -h WIDGET
}
}
Okay now let's meet the whole squad, one by one. ๐
BUFFER (scalar)
The entire contents of the edit buffer. If it's written to, the cursor stays at the same offset โ unless that would put it outside the buffer, in which case, well, it doesn't. Keep up. ๐
BUFFERLINES (integer)
The number of screen lines needed for the edit buffer currently displayed on screen (i.e. without any changes to the preceding parameters done after the last redisplay). Read-only. โจ
CONTEXT (scalar)
The context in which zle was called to read a line โ read-only. One of these values:
startโ the start of a command line (at promptPS1).contโ a continuation to a command line (at promptPS2).selectโ in aselectloop (at promptPS3).varedโ editing a variable invared.
CURSOR (integer)
The offset of the cursor, within the edit buffer. This is in the range 0 to $#BUFFER, and is by definition equal to $#LBUFFER. Any attempts to move the cursor outside the buffer will just result in the cursor being moved to the appropriate end of the buffer. It said no and put itself where it belongs. ๐
CUTBUFFER (scalar)
The last item cut using one of the kill- commands; the string which the next yank would insert in the line. Later entries in the kill ring are in the array killring. And ngl this is a cute trick: the command zle copy-region-as-kill string can be used to set the text of the cut buffer from a shell function and cycle the kill ring in the same way as interactively killing text. Chef's kiss. ๐ฅ
HISTNO (integer)
The current history number. Setting this has the same effect as moving up or down in the history to the corresponding history line. An attempt to set it is ignored if the line is not stored in the history. And don't get it twisted โ this is not the same as the parameter HISTCMD, which always gives the number of the history line being added to the main shell's history. HISTNO refers to the line being retrieved within zle. Two different girlies, two different jobs. ๐
ISEARCHMATCH_ACTIVE (integer), ISEARCHMATCH_START (integer), ISEARCHMATCH_END (integer)
ISEARCHMATCH_ACTIVE indicates whether a part of the BUFFER is currently matched by an incremental search pattern. ISEARCHMATCH_START and ISEARCHMATCH_END give the location of the matched part and are in the same units as CURSOR. They're only valid for reading when ISEARCHMATCH_ACTIVE is non-zero. โญ
All parameters are read-only.
KEYMAP (scalar)
The name of the currently selected keymap; read-only.
KEYS (scalar)
The keys typed to invoke this widget, as a literal string; read-only.
KEYS_QUEUED_COUNT (integer)
The number of bytes pushed back to the input queue and therefore available for reading immediately before any I/O is done; read-only. See also PENDING; the two values are distinct. Don't mix them up bestie. ๐
killring (array)
The array of previously killed items, with the most recently killed first. This gives the items that would be retrieved by a yank-pop in the same order. But note, however, that the most recently killed item is in $CUTBUFFER; $killring shows the array of previous entries. Order matters, okay?
The default size for the kill ring is eight, however the length may be changed by normal array operations. Any empty string in the kill ring is ignored by the yank-pop command โ hence the size of the array effectively sets the maximum length of the kill ring, while the number of non-zero strings gives the current length, both as seen by the user at the command line. Geometry. ๐
LASTABORTEDSEARCH (scalar)
The last search string used by an interactive search that was aborted by the user (status 3 returned by the search widget). The quitter's last words, preserved forever. ๐ญ
LASTSEARCH (scalar)
The last search string used by an interactive search; read-only. This is set even if the search failed (status 0, 1 or 2 returned by the search widget), but not if it was aborted by the user. So failed searches get remembered, rage-quits don't. Flaky but valid.
LASTWIDGET (scalar)
The name of the last widget that was executed; read-only.
LBUFFER (scalar)
The part of the buffer that lies to the left of the cursor position. If it's assigned to, only that part of the buffer is replaced, and the cursor remains between the new $LBUFFER and the old $RBUFFER. Seamless. โจ
MARK (integer)
Like CURSOR, but for the mark. With vi-mode operators that wait for a movement command to select a region of text, setting MARK allows the selection to extend in both directions from the initial cursor position. Both directions! Obsessed. ๐
NUMERIC (integer)
The numeric argument. If no numeric argument was given, this parameter is unset. When this is set inside a widget function, builtin widgets called with the zle builtin command will use the value assigned. If it's unset inside a widget function, builtin widgets called behave as if no numeric argument was given. Very "you get what you give" of it. ๐
PENDING (integer)
The number of bytes pending for input, i.e. the number of bytes which have already been typed and can immediately be read. On systems where the shell is not able to get this information, this parameter will always have a value of zero. Read-only. See also KEYS_QUEUED_COUNT; the two values are distinct. Again! Distinct! ๐ธ
PREBUFFER (scalar)
In a multi-line input at the secondary prompt, this read-only parameter contains the contents of the lines before the one the cursor is currently in. The lore before the line you're on. ๐
PREDISPLAY (scalar)
Text to be displayed before the start of the editable text buffer. This does not have to be a complete line; to display a complete line, a newline must be appended explicitly. The text is reset on each new invocation (but not recursive invocation) of zle. Important distinction, don't forget it. ๐ฆ
POSTDISPLAY (scalar)
Text to be displayed after the end of the editable text buffer. This does not have to be a complete line; to display a complete line, a newline must be prepended explicitly. The text is reset on each new invocation (but not recursive invocation) of zle. Same energy, other side of the cursor. ๐
RBUFFER(scalar) โ the chunk of the buffer sitting to the right of the cursor โจ. If you assign to it, only that part gets replaced, and the cursor just chills between the old$LBUFFERand the new$RBUFFER. No cap.REGION_ACTIVE(integer) โ tells you if the region is active rn. Assign0or1to deactivate / activate it. A value of2activates the region in line-wise mode, where the highlighted text only ever extends over whole lines โ see Character Highlighting below ๐ .region_highlight(array) โ each element is a string describing highlighting for any arbitrary region of the command line, and it kicks in the next time the command line gets redisplayed ๐ฅ. You can highlight the non-editable parts of the command line inPREDISPLAYandPOSTDISPLAY, but heads up: you need thePflag for character indexing to includePREDISPLAY.
Each string is made of these whitespace-separated parts:
- Optionally, a
Pto signify that the start and end offsets that follow include any string set by thePREDISPLAYspecial parameter โ you need this if the predisplay string itself is gonna be highlighted. Whitespace between thePand the start offset is optional. - A start offset, in the same units as
CURSOR. - An end offset, in the same units as
CURSOR. - A highlight specification in the same format used for contexts in the parameter
zle_highlightโ see the sectionCharacter Highlightingbelow; likestandoutorfg=red,bold, bestie. - Optionally, a string of the form
memo=token. The token is everything between the=and the next whitespace, comma, NUL, or the end of the string. It's preserved verbatim but not parsed in any way.
Plugins may use this to identify array elements they added ๐ โ for example, a plugin might set token to its (the plugin's) name and then use region_highlight=( ${region_highlight:#*memo=token} ) to remove the array elements it added.
(That example uses the ${name:#pattern} array-grepping syntax described in the section Parameter Expansion in zshexpn(1).)
For example:
region_highlight=("P0 20 bold memo=foobar")
says the first twenty characters of the text, including any predisplay string, should be bold. Iconic ๐.
Note that the effect of region_highlight is not saved and disappears as soon as the line is accepted.
Note that zsh 5.8 and older do not support the memo=token field and may misparse the third (highlight specification) field when a memo is given. ๐
The final highlighting on the command line depends on both region_highlight and zle_highlight; see the section CHARACTER HIGHLIGHTING below for the deets.
registers(associative array) โ the contents of each of the vi register buffers ๐ฆ. These are typically set usingvi-set-bufferfollowed by a delete, change or yank command.SUFFIX_ACTIVE(integer)SUFFIX_START(integer)SUFFIX_END(integer) โSUFFIX_ACTIVEsays whether an auto-removable completion suffix is currently active.SUFFIX_STARTandSUFFIX_ENDgive the location of that suffix, in the same units asCURSOR๐ธ. They're only valid for reading whenSUFFIX_ACTIVEis non-zero.
All three parameters are read-only ๐.
UNDO_CHANGE_NO(integer) โ a number representing the state of the undo history. The only use for this is passing it as an argument to theundowidget so you can undo back to the recorded point. Read-only ๐.UNDO_LIMIT_NO(integer) โ a number corresponding to an existing change in the undo history; compareUNDO_CHANGE_NO. If you set this to a value greater than zero, theundocommand won't let the line be undone beyond that change number. You can still usezle undo changein a widget to undo beyond that point โ but in that case, it won't be possible to undo at all untilUNDO_LIMIT_NOis reduced ๐ญ. Set it to0to disable the limit.
A typical use of this variable in a widget function goes like this (note the extra function scope is required, bestie):
() {
local UNDO_LIMIT_NO=$UNDO_CHANGE_NO
# Perform some form of recursive edit.
}
WIDGET(scalar) โ the name of the widget currently being executed; read-only ๐.WIDGETFUNC(scalar) โ the name of the shell function implementing a widget defined with eitherzle -Norzle -C. Forzle -N, this is the second argument to thezle -Ncommand that defined the widget, or the first argument if there was no second argument. Forzle -C, it's the third argument to thezle -Ccommand that defined the widget. Read-only ๐ซถ.WIDGETSTYLE(scalar) โ describes the implementation behind the completion widget currently being executed; it's the second argument that followedzle -Cwhen the widget was defined, i.e. the name of a builtin completion widget. For widgets defined withzle -Nthis is set to the empty string. Read-only โจ.YANK_ACTIVE(integer)YANK_START(integer)YANK_END(integer) โYANK_ACTIVEsays whether text has just been yanked (pasted) into the buffer ๐.YANK_STARTandYANK_ENDgive the location of the pasted text, in the same units asCURSOR. They're only valid for reading whenYANK_ACTIVEis non-zero. They can also be assigned by widgets that insert text in a yank-like fashion โ wrappers ofbracketed-paste, for example. See alsozle -f.
YANK_ACTIVE is read-only ๐.
ZLE_RECURSIVE(integer) โ usually zero, but it gets incremented inside any instance ofrecursive-edit, so it tells you the current recursion level ๐.
ZLE_RECURSIVE is read-only.
ZLE_STATE(scalar) โ holds a set of space-separated words describing the current zle state.
Currently, the states shown are the insert mode as set by the overwrite-mode or vi-replace widgets, and whether history commands will visit imported entries as controlled by the set-local-history widget ๐ซ. The string contains insert if characters inserted on the command line move existing characters to the right, or overwrite if inserted characters overwrite existing ones. It contains localhistory if only local history commands get visited, or globalhistory if imported history commands get visited too.
The substrings are sorted alphabetically, so if you wanna test for two specific substrings in a future-proof way, you can match like this:
if [[ $ZLE_STATE == *globalhistory*insert* ]]; then ...; fi
๐ง Special Widgets ๐ง
There are a few user-defined widgets that are special to the shell โจ. If they don't exist, no special action is taken. The environment they get is identical to that of any other editing widget โ no VIP treatment, just vibes.
zle-isearch-exitโ executed at the end of incremental search, right at the point where the isearch prompt is removed from the display. Seezle-isearch-updatefor an example ๐.zle-isearch-updateโ executed within incremental search when the display is about to be redrawn. You can generate extra output below the incremental search prompt by usingzle -Minside the widget ๐. For example:
zle-isearch-update() { zle -M "Line $HISTNO"; }
zle -N zle-isearch-update
Note that the line output byzle -Mis not deleted on exit from incremental search ๐ญ. You can do that from azle-isearch-exitwidget:
zle-isearch-exit() { zle -M ""; }
zle -N zle-isearch-exit
zle-line-pre-redrawโ executed whenever the input line is about to be redrawn, giving you a lil opportunity to update theregion_highlightarray ๐ .zle-line-initโ executed every time the line editor starts up to read a new line of input. This example puts the line editor into vi command mode on startup:
zle-line-init() { zle -K vicmd; }
zle -N zle-line-init
(The command inside the function sets the keymap directly; it's equivalent to zle vi-cmd-mode.)
zle-line-finishโ similar tozle-line-init, but executed every time the line editor has finished reading a line of input ๐ธ.zle-history-line-setโ executed when the history line changes ๐.zle-keymap-selectโ executed every time the keymap changes, i.e. every time the special parameterKEYMAPgets set to a different value, while the line editor is active ๐ฅ. Initialising the keymap when the line editor starts does not cause the widget to be called.
The value $KEYMAP within the function reflects the new keymap. The old keymap is passed as the sole argument.
You can use this to detect switches between the vi command (vicmd) and insert (usually main) keymaps. iykyk ๐.
๐ STANDARD WIDGETS ๐
Here's the full list of all the standard widgets, plus their default bindings in emacs mode, vi command mode and vi insert mode (the emacs, vicmd and viins keymaps, respectively) ๐.
Note that cursor keys are bound to movement keys in all three keymaps โ the shell assumes the cursor keys send the key sequences reported by the terminal-handling library (termcaporterminfo) ๐ฅ๏ธ. The key sequences shown in the list are those based on the VT100, common on many modern terminals, but fr fr these are not necessarily bound. In the case of theviinskeymap, the initial escape character of the sequences also serves to return to thevicmdkeymap: whether that happens is determined by theKEYTIMEOUTparameter, seezshparam(1)โฑ๏ธ.
๐ซ Movement ๐ซ
vi-backward-blank-word(unbound) (B) (unbound) โ move backward one word, where a word is defined as a series of non-blank characters ๐ฆ.vi-backward-blank-word-end(unbound) (gE) (unbound) โ bounce back to the end of the previous word, where a "word" is literally just a stretch of non-blank characters. no cap, blanks are the word boundaries here ๐backward-char(^B ESC-[D) (unbound) (unbound) โ slide back exactly one character. tiny steps, bestie ๐พvi-backward-char(unbound) (^H h ^?) (ESC-[D) โ one character back, but it will not hop up to the previous line. stays in its lane โจbackward-word(ESC-B ESC-b) (unbound) (unbound) โ zoom to the beginning of the previous word.emacs-backward-wordโ same energy: beginning of the previous word. (no default bindings listed, she's just vibing ๐)vi-backward-word(unbound) (b) (unbound) โ beginning of the previous word, but make it vi-style ๐vi-backward-word-end(unbound) (ge) (unbound) โ end of the previous word, vi-style. periodt.beginning-of-line(^A) (unbound) (unbound) โ hop to the start of the line. and if you're already at the start? it goes to the beginning of the previous line, if there is one. main character energy, always going further ๐ซถvi-beginning-of-lineโ start of the line, but without changing lines. no field trips.down-line(unbound) (unbound) (unbound) โ move down a line in the buffer. that's it, that's the widget ๐end-of-line(^E) (unbound) (unbound) โ to the end of the line. if you're already there, it continues to the end of the next line, if any. slay, keep going โจvi-end-of-line(unbound) ($) (unbound) โ end of the line. and if you hand this command an argument, the cursor gets moved to the end of the line(argument - 1)lines down. yes, the math is the math ๐งฎvi-forward-blank-word(unbound) (W) (unbound) โ forward one word, word = a run of non-blank characters.vi-forward-blank-word-end(unbound) (E) (unbound) โ to the end of the current word, or, if you're already chillin' at the end of the current word, to the end of the next one. again: word = series of non-blank characters ๐forward-char(^F ESC-[C) (unbound) (unbound) โ one character forward. baby steps ๐ธvi-forward-char(unbound) (space l) (ESC-[C) โ one character forward, vi flavor.vi-find-next-char(^X^F) (f) (unbound) โ reads a character from your keyboard, then jumps to the next place that character shows up in the line. iconic little seeker ๐vi-find-next-char-skip(unbound) (t) (unbound) โ reads a char from the keyboard and parks you just before the next occurrence of it in the line. so close, but not on it ๐vi-find-prev-char(unbound) (F) (unbound) โ reads a char from the keyboard, jumps to the previous occurrence of it in the line.vi-find-prev-char-skip(unbound) (T) (unbound) โ reads a char and lands you just after the previous occurrence of it in the line.vi-first-non-blank(unbound) (^) (unbound) โ straight to the first non-blank character in the line. bye spaces ๐vi-forward-word(unbound) (w) (unbound) โ forward one word, vi-style.forward-word(ESC-F ESC-f) (unbound) (unbound) โ beginning of the next word. and the editor's whole idea of what counts as a "word" is decided by theWORDCHARSparameter, so that's the knob to turn ๐๏ธemacs-forward-wordโ to the end of the next word. different destination, same vibe.vi-forward-word-end(unbound) (e) (unbound) โ end of the next word.vi-goto-column(ESC-|) (|) (unbound) โ jump to the column the numeric argument tells you. by the numbers ๐vi-goto-mark(unbound) (`) (unbound) โ teleport to the mark you specify. bookmark behavior fr fr ๐vi-goto-mark-line(unbound) (') (unbound) โ to the beginning of the line that contains the mark you specify.vi-repeat-find(unbound) (;) (unbound) โ reruns your lastvi-findcommand. encore, encore ๐vi-rev-repeat-find(unbound) (,) (unbound) โ reruns your lastvi-findcommand, but in the opposite direction. plot twist ๐up-line(unbound) (unbound) (unbound) โ move up a line in the buffer. ascending, literally ๐ฆ
๐ฐ๏ธ History Control ๐ธ
beginning-of-buffer-or-history(ESC-<) (gg) (unbound) โ to the beginning of the buffer, or, if you're already sitting there, to the first event in the history list. two-for-one ๐beginning-of-line-histโ to the beginning of the line. but if you're already at the beginning of the buffer, it moves to the previous history line. sneaky but slay.beginning-of-historyโ jumps to the first event in the history list. back to the very first lore drop ๐down-line-or-history(^N ESC-[B) (j) (ESC-[B) โ down a line in the buffer, or, if you're already on the bottom line, to the next event in the history list.vi-down-line-or-history(unbound) (+) (unbound) โ down a line in the buffer, or, if you're on the bottom line, to the next history event. then it moves to the first non-blank character on the line. whole little routine โจdown-line-or-searchโ down a line in the buffer, or, if you're already on the bottom line, search forward in the history for a line that begins with the first word in the buffer.
if it's called from a function by the zle command with arguments, then the first argument is used as the string to search for, instead of the first word in the buffer. so you get to choose the quest ๐บ๏ธ
down-history(unbound) (^N) (unbound) โ next event in the history list. onward ๐history-beginning-search-backwardโ searches backward through history for a line beginning with the current line up to the cursor. and it leaves the cursor exactly where it originally was, because she's polite like that ๐end-of-buffer-or-history(ESC->) (unbound) (unbound) โ to the end of the buffer, or, if you're already there, to the last event in the history list.end-of-line-histโ to the end of the line. if you're already at the end of the buffer, it goes to the next history line.end-of-historyโ to the last event in the history list. the finale ๐vi-fetch-history(unbound) (G) (unbound) โ fetch the history line given by the numeric argument. it defaults to the current history line (i.e. the one that isn't history yet). iykyk ๐history-incremental-search-backward(^R ^Xr) (unbound) (unbound) โ incremental backward search for a string you type. it's case-insensitive if the search string has no uppercase letters and no numeric argument was given. the string can start with^to anchor the search to the beginning of the line. ๐
when it's called from a user-defined function it returns these statuses: 0 if the search succeeded; 1 if the search failed; 2 if the search term was a bad pattern; 3 if the search was aborted by the send-break command. receipts, always ๐งพ
a restricted set of editing functions is available in the mini-buffer. keys get looked up in the special isearch keymap, and if they're not found there, in the main keymap (note that by default the isearch keymap is empty). an interrupt signal, as defined by the stty setting, will stop the search and go back to the original line. an undefined key does exactly the same thing. ๐
also important: the following always do the same task inside incremental searches and cannot be replaced by user defined widgets, and the set of functions cannot be extended. immutable. no edits. ๐ซ
the supported functions are:
accept-and-holdaccept-and-infer-next-historyaccept-lineaccept-line-and-down-historyโ do the usual function after you exit incremental search. the command line displayed is executed. ๐backward-delete-charvi-backward-delete-charโ back up one place in the search history. if the search has been repeated, this does not immediately erase a character in the minibuffer.accept-searchโ exit incremental search, keeping the command line but doing nothing else. note this function is not bound by default and has no effect outside incremental search. ๐backward-delete-wordbackward-kill-wordvi-backward-kill-wordโ back up one character in the minibuffer; and if multiple searches have happened since that character was inserted, the search history gets rewound to just before the character was entered. so basically it repeatsbackward-delete-char. cute recursion ๐clear-screenโ clears the screen and stays in incremental search mode.history-incremental-search-backwardโ finds the next occurrence of whatever's in the mini-buffer. if the mini-buffer is empty, the most recent previously used search string comes back. she remembers ๐ญhistory-incremental-search-forwardโ flips the direction of the search.magic-spaceโ inserts a non-magical space. the plain kind โจquoted-insertvi-quoted-insertโ quote the character that's going into the minibuffer.redisplayโ redisplay the command line, staying in incremental search mode.vi-cmd-modeโ picks thevicmdkeymap; themainkeymap (insert mode) gets selected initially.
on top of that, the modifications made while in vi insert mode get merged into a single undo event. one undo, all of it, obsessed ๐ซถ
vi-repeat-searchvi-rev-repeat-searchโ repeat the search. the direction is shown right there in the mini-buffer. arrows, basically โก๏ธโฌ ๏ธ
any character that isn't bound to one of the functions above, or to self-insert or self-insert-unmeta, will make the mode exit. then that character is looked up and executed in whatever keymap is in effect at that point. eviction notice ๐ช
and when it's called from a widget function by the zle command, the incremental search commands can take a string argument. that's treated as a string of keys, exactly like arguments to the bindkey command, and used as the initial input for the command. any characters in the string that incremental search doesn't use just get silently ignored. for example:
zle history-incremental-search-backward forceps
searches backwards for forceps and leaves the minibuffer containing the string forceps. ๐ฉบโจ
history-incremental-search-forward(^S ^Xs) (unbound) (unbound) โ incremental forward search for a string. case-insensitive if the search string has no uppercase letters and no numeric argument was given. the string may begin with^to anchor the search to the beginning of the line. the functions available in the mini-buffer are exactly the same as forhistory-incremental-search-backward. same menu, opposite direction ๐
๐ฆ history-incremental-pattern-search-backward / history-incremental-pattern-search-forward
Okay so these two are basically the -pattern-less versions but with โจpattern matchingโจ turned on. The search string you type is treated as a pattern โ respecting the current settings of the various options affecting pattern matching, no cap. Peep FILENAME GENERATION in zshexpn(1) for the whole pattern saga.
If no numeric argument was given, lowercase letters in the search string may match uppercase letters in the history (yes bestie, case-insensitive vibes). And the string may begin with ^ to anchor the search to the beginning of the line ๐ฏ
The prompt changes to indicate an invalid pattern โ which, lowkey, may simply mean the pattern is not yet complete. Don't panic ๐
Note that only non-overlapping matches are reported, so an expression with wildcards may return fewer matches on a line than are visible by inspection. It's giving "trust the process", not a bug.
๐ history-search-backward โ emacs ESC-P ESC-p ยท vi cmd unbound ยท vi ins unbound
Search backward in the history for a line beginning with the first word in the buffer. Obsessed, honestly.
If called from a function by the zle command with arguments, the first argument is taken as the string for which to search, rather than the first word in the buffer ๐
๐ธ vi-history-search-backward โ emacs unbound ยท vi cmd / ยท vi ins unbound
Search backward in the history for a specified string. The string may begin with ^ to anchor the search to the beginning of the line.
A restricted set of editing functions is available in the mini-buffer. An interrupt signal, as defined by the stty setting, will stop the search. The functions available in the mini-buffer are:
accept-linebackward-delete-charvi-backward-delete-charbackward-kill-wordvi-backward-kill-wordclear-screenredisplayquoted-insertvi-quoted-insert
vi-cmd-mode is treated the same as accept-line, and magic-space is treated as a space. Any other character that is not bound to self-insert or self-insert-unmeta will beep and be ignored ๐ If the function is called from vi command mode, the bindings of the current insert mode will be used.
If called from a function by the zle command with arguments, the first argument is taken as the string for which to search, rather than the first word in the buffer.
๐ history-search-forward โ emacs ESC-N ESC-n ยท vi cmd unbound ยท vi ins unbound
Search forward in the history for a line beginning with the first word in the buffer. Same energy as its backward sibling, just pointing the other way ๐ซถ
If called from a function by the zle command with arguments, the first argument is taken as the string for which to search, rather than the first word in the buffer.
๐ธ vi-history-search-forward โ emacs unbound ยท vi cmd ? ยท vi ins unbound
Search forward in the history for a specified string. The string may begin with ^ to anchor the search to the beginning of the line.
The functions available in the mini-buffer are the same as for vi-history-search-backward. Argument handling is also the same as for that command. Reuse is the moment, periodt ๐
๐ง infer-next-history โ emacs ^X^N ยท vi cmd unbound ยท vi ins unbound
Search in the history list for a line matching the current one and fetch the event following it. It's giving main character energy: find your twin, then take the next step ๐ฅ
๐ insert-last-word โ emacs ESC-_ ESC-. ยท vi cmd unbound ยท vi ins unbound
Insert the last word from the previous history event at the cursor position.
- If a positive numeric argument is given, insert that word from the end of the previous history event.
- If the argument is zero or negative, insert that word from the left (zero inserts the previous command word).
Repeating this command replaces the word just inserted with the last word from the history event prior to the one just used; numeric arguments can be used in the same way to pick a word from that event ๐ญ (the way this thing just keeps walking backwards through history is iconic).
When called from a shell function invoked from a user-defined widget, the command can take one to three arguments:
- The first argument specifies a history offset which applies to successive calls to this widget: if it is
-1, the default behaviour is used; if it is1, successive calls will move forwards through the history. The value0can be used to indicate that the history line examined by the previous execution of the command will be reexamined. - If two arguments are given, the second specifies the word on the command line in normal array index notation (as a more natural alternative to the numeric argument). Hence
1is the first word, and-1(the default) is the last word. - If a third argument is given, its value is ignored, but it is used to signify that the history offset is relative to the current history line, rather than the one remembered after the previous invocations of
insert-last-word.
โ ๏ธ Note that negative numbers should be preceded by a -- argument to avoid confusing them with options. The classic little trap ๐
For example, the default behaviour of the command corresponds to
zle insert-last-word -- -1 -1
while the command
zle insert-last-word -- -1 1 -
always copies the first word of the line in the history immediately before the line being edited. This has the side effect that later invocations of the widget will be relative to that line. Sneaky but slay โจ
๐ vi-repeat-search โ emacs unbound ยท vi cmd n ยท vi ins unbound
Repeat the last vi history search. Again, again, again ๐
๐ vi-rev-repeat-search โ emacs unbound ยท vi cmd N ยท vi ins unbound
Repeat the last vi history search, but in reverse. Same search, opposite direction, literally ๐
โฌ๏ธ up-line-or-history โ emacs ^P ESC-[A ยท vi cmd k ยท vi ins ESC-[A
Move up a line in the buffer, or โ if you're already at the top line โ move to the previous event in the history list. Two-for-one behaviour, we love a multitasker ๐
๐ธ vi-up-line-or-history โ emacs unbound ยท vi cmd - ยท vi ins unbound
Move up a line in the buffer, or if already at the top line, move to the previous event in the history list. Then move to the first non-blank character on the line. Extra step, extra precision ๐ฏ
โฌ๏ธ up-line-or-search
Move up a line in the buffer, or if already at the top line, search backward in the history for a line beginning with the first word in the buffer.
If called from a function by the zle command with arguments, the first argument is taken as the string for which to search, rather than the first word in the buffer ๐ซถ
โฌ๏ธ up-history โ emacs unbound ยท vi cmd ^P ยท vi ins unbound
Move to the previous event in the history list. Simple. Clean. No notes ๐
๐ history-beginning-search-forward
Search forward in the history for a line beginning with the current line up to the cursor. This leaves the cursor in its original position โ so your cursor stays exactly where it was, bestie, no jumpscares ๐
๐ set-local-history
By default, history movement commands visit the imported lines as well as the local lines. This widget lets you toggle this on and off, or set it with the numeric argument. 0 for both local and imported lines, and nonzero for only local lines. Your little history filter switch ๐
๐ Modifying Text ๐
Okay this section is where we actually change things. Destructive girlies, gather round ๐ฅ
๐ธ vi-add-eol โ emacs unbound ยท vi cmd A ยท vi ins unbound
Move to the end of the line and enter insert mode. Simple slay.
๐ธ vi-add-next โ emacs unbound ยท vi cmd a ยท vi ins unbound
Enter insert mode after the current cursor position, without changing lines. Right where you're at, just one to the right ๐
โซ backward-delete-char โ emacs ^H ^? ยท vi cmd unbound ยท vi ins unbound
Delete the character behind the cursor. Bye bestie ๐
๐ธ vi-backward-delete-char โ emacs unbound ยท vi cmd X ยท vi ins ^H
Delete the character behind the cursor, without changing lines. If in insert mode, this won't delete past the point where insert mode was last entered โ there's a boundary, and it respects it ๐ง
โซ backward-delete-word
Delete the word behind the cursor. Whole word, gone, no crumbs.
โ๏ธ backward-kill-line
Kill from the beginning of the line to the cursor position. Very "cut to the chase", literally.
โ๏ธ backward-kill-word โ emacs ^W ESC-^H ESC-^? ยท vi cmd unbound ยท vi ins unbound
Kill the word behind the cursor. Say it with your chest ๐ฃ๏ธ
๐ธ vi-backward-kill-word โ emacs unbound ยท vi cmd unbound ยท vi ins ^W
Kill the word behind the cursor, without going past the point where insert mode was last entered. Respect the boundary, always ๐ซถ
๐
ฐ๏ธ capitalize-word โ emacs ESC-C ESC-c ยท vi cmd unbound ยท vi ins unbound
Capitalize the current word and move past it. Glow-up arc for your word โจ
๐ธ vi-change โ emacs unbound ยท vi cmd c ยท vi ins unbound
Read a movement command from the keyboard, and kill from the cursor position to the endpoint of the movement. Then enter insert mode. If the command is vi-change, change the current line.
For compatibility with vi, if the command is vi-forward-word or vi-forward-blank-word, the whitespace after the word is not included. If you prefer the more consistent behaviour with the whitespace included, use the following key binding:
bindkey -a -s cw dwi
iykyk ๐
๐ธ vi-change-eol โ emacs unbound ยท vi cmd C ยท vi ins unbound
Kill to the end of the line and enter insert mode. Clean sweep to the edge ๐งน
๐ธ vi-change-whole-line โ emacs unbound ยท vi cmd S ยท vi ins unbound
Kill the current line and enter insert mode. Total reset, fresh start, main character reboot ๐ฅ
๐ copy-region-as-kill โ emacs ESC-W ESC-w ยท vi cmd unbound ยท vi ins unbound
Copy the area from the cursor to the mark to the kill buffer.
If called from a ZLE widget function in the form zle copy-region-as-kill string then string will be taken as the text to copy to the kill buffer. The cursor, the mark and the text on the command line are not used in this case. So you can just hand it text directly, no cap ๐
๐๏ธ copy-prev-word โ emacs ESC-^_ ยท vi cmd unbound ยท vi ins unbound
Duplicate the word to the left of the cursor. Copy-paste but make it effortless ๐ซถ
๐๏ธ copy-prev-shell-word
Like copy-prev-word, but the word is found by using shell parsing, whereas copy-prev-word looks for blanks. This makes a difference when the word is quoted and contains spaces. So if your word is being extra about it, this one gets it ๐
๐ธ vi-delete โ emacs unbound ยท vi cmd d ยท vi ins unbound
Read a movement command from the keyboard, and kill from the cursor position to the endpoint of the movement. If the command is vi-delete, kill the current line. Combo move, very iconic ๐
โซ delete-char
Delete the character under the cursor. Right there. Gone. ๐
๐ธ vi-delete-char โ emacs unbound ยท vi cmd x ยท vi ins unbound
Delete the character under the cursor, without going past the end of the line. Knows when to stop, unlike some of us ๐ญ
โซ delete-word
Delete the current word. Just the one. Precise.
๐ก down-case-word โ emacs ESC-L ESC-l ยท vi cmd unbound ยท vi ins unbound
Convert the current word to all lowercase and move past it. Quiet-luxury typography moment ๐ธ
๐ธ vi-down-case โ emacs unbound ยท vi cmd gu ยท vi ins unbound
Read a movement command from the keyboard, and convert all characters from the cursor position to the endpoint of the movement to lowercase. If the movement command is vi-down-case, swap the case of all characters on the current line. Surprise mechanic, ilysm ๐
โ๏ธ kill-word โ emacs ESC-D ESC-d ยท vi cmd unbound ยท vi ins unbound
Kill the current word. That's it, that's the widget ๐ฅ
๐ gosmacs-transpose-chars
Exchange the two characters behind the cursor. Little swap, retro name, love that journey for it โจ
๐ธ vi-indent โ emacs unbound ยท vi cmd > ยท vi ins unbound
Indent a number of lines. Nested aesthetics, ltr ๐
vi-insertโ keys(unbound)(i)(unbound)โ bestie we're going into insert mode โจvi-insert-bolโ keys(unbound)(I)(unbound)โ jump to the first non-blank character on the line and slide into insert mode, two birds one stone ๐vi-joinโ keys(^X^J)(J)(unbound)โ smoosh the current line together with the next one ๐ซถkill-lineโ keys(^K)(unbound)(unbound)โ kill everything from the cursor to the end of the line, no cap. And if you're already chillin' at the end of the line? It kills the newline character instead ๐vi-kill-lineโ keys(unbound)(unbound)(^U)โ kill from the cursor all the way back to wherever insert mode was last entered, iconic rewind energy โชvi-kill-eolโ keys(unbound)(D)(unbound)โ kill from the cursor to the end of the line, periodt ๐kill-regionโ kill from the cursor to the mark ๐ฏkill-bufferโ keys(^X^K)(unbound)(unbound)โ kill the entire buffer. like, all of it. main character destruction ๐ฅkill-whole-lineโ keys(^U)(unbound)(unbound)โ kill the current line, the whole line, nothing but the line โจvi-match-bracketโ keys(^X^B)(%)(unbound)โ bounce to the bracket character (one of{},()or[]) that matches the one under the cursor. and if the cursor isn't sitting on a bracket? it moves forward โ without going past the end of the line โ until it finds one, then goes to its matching bracket. lowkey detective work ๐vi-open-line-aboveโ keys(unbound)(O)(unbound)โ open a line above the cursor and enter insert mode ๐ฆvi-open-line-belowโ keys(unbound)(o)(unbound)โ open a line below the cursor and enter insert mode ๐ธvi-oper-swap-caseโ keys(unbound)(g~)(unbound)โ reads a movement command straight from the keyboard, then swaps the case of every character from the cursor position to wherever that movement ends up. and if the movement command isvi-oper-swap-case? it swaps the case of all characters on the current line, we love a self-referential moment ๐overwrite-modeโ keys(^X^O)(unbound)(unbound)โ toggle between overwrite mode and insert mode, flip flopping like your fave ๐vi-put-beforeโ keys(unbound)(P)(unbound)โ insert the contents of the kill buffer before the cursor. if the kill buffer holds a sequence of lines (as opposed to characters), it pastes it above the current line ๐ซถvi-put-afterโ keys(unbound)(p)(unbound)โ insert the contents of the kill buffer after the cursor. if the kill buffer holds a sequence of lines (as opposed to characters), it pastes it below the current line ๐ซถput-replace-selectionโ keys(unbound)(unbound)(unbound)โ replace the contents of the current region or selection with the contents of the kill buffer. and if the kill buffer holds a sequence of lines (as opposed to characters), the current line gets split by the pasted lines. no thoughts, just vibes โจquoted-insertโ keys(^V)(unbound)(unbound)โ insert the next character you type into the buffer literally. warning bestie: an interrupt character will not be inserted ๐จvi-quoted-insertโ keys(unbound)(unbound)(^Q ^V)โ displays a^at the cursor position, then inserts the next character you type into the buffer literally. same deal: an interrupt character will not be inserted ๐quote-lineโ keys(ESC-')(unbound)(unbound)โ quote the current line, meaning it puts a'character at the beginning and the end, and converts all'characters to'\''๐quote-regionโ keys(ESC-")(unbound)(unbound)โ quote the region from the cursor to the mark ๐vi-replaceโ keys(unbound)(R)(unbound)โ enter overwrite mode ๐vi-repeat-changeโ keys(unbound)(.)(unbound)โ repeat the last vi mode text modification. if a count was used with that modification, it's remembered. if you give a count to this command, it overrides the remembered count and gets remembered for future uses of this command. the cut buffer specification is remembered in the same way. the way this remembers everything?? memory of an elephant fr fr ๐vi-replace-charsโ keys(unbound)(r)(unbound)โ replace the character under the cursor with a character read from the keyboard โจ๏ธself-insertโ keys(printable characters)(unbound)(printable characters and some control characters)โ insert a character into the buffer at the cursor position. the moment itself ๐ซself-insert-unmetaโ keys(ESC-^I ESC-^J ESC-^M)(unbound)(unbound)โ insert a character into the buffer after stripping the meta bit and converting^Mto^J๐งvi-substituteโ keys(unbound)(s)(unbound)โ substitute the next character(s) ๐vi-swap-caseโ keys(unbound)(~)(unbound)โ swap the case of the character under the cursor and move past it โจtranspose-charsโ keys(^T)(unbound)(unbound)โ exchange the two characters to the left of the cursor if you're at the end of the line, otherwise exchange the character under the cursor with the character to the left ๐คtranspose-wordsโ keys(ESC-T ESC-t)(unbound)(unbound)โ exchange the current word with the one before it. with a positive numeric argument N, the word around the cursor (or following it, if the cursor is between words) is transposed with the preceding N words, and the cursor ends up at the end of the resulting group of words. with a negative numeric argument -N, it's the same as using a positive argument N โ except the original cursor position is kept, no matter how the words get rearranged. chaotic but principled ๐ช๏ธvi-unindentโ keys(unbound)(<)(unbound)โ unindent a number of lines ๐vi-up-caseโ keys(unbound)(gU)(unbound)โ read a movement command from the keyboard, and convert all characters from the cursor position to the endpoint of the movement to lowercase. and if the movement command isvi-up-case, swap the case of all characters on the current line ๐ (yes, that's exactly what it says, keep reading, it's giving "the docs said what they said" ๐)up-case-wordโ keys(ESC-U ESC-u)(unbound)(unbound)โ convert the current word to all caps and move past it ๐ขyankโ keys(^Y)(unbound)(unbound)โ insert the contents of the kill buffer at the cursor position ๐yank-popโ keys(ESC-y)(unbound)(unbound)โ remove the text just yanked, rotate the kill-ring (that's the history of previously killed text, iykyk) and yank the new top. only works followingyank,vi-put-before,vi-put-afteroryank-popโ ๏ธvi-yankโ keys(unbound)(y)(unbound)โ read a movement command from the keyboard, and copy the region from the cursor position to the endpoint of the movement into the kill buffer. if the command isvi-yank, copy the current line ๐vi-yank-whole-lineโ keys(unbound)(Y)(unbound)โ copy the current line into the kill buffer ๐vi-yank-eolโ copy the region from the cursor position to the end of the line into the kill buffer. arguably, this is whatYshould do in vi, but it isn't what it actually does ๐ (the audacity, honestly)
โจ Arguments โจ
okay so this is the whole how many times / which direction department, and it's giving math class but make it cute ๐งฎ
digit-argumentโ keys(ESC-0..ESC-9)(1-9)(unbound)โ start a new numeric argument, or add to the current one. see alsovi-digit-or-beginning-of-line. this only works if it's bound to a key sequence ending in a decimal digit. pro tip: inside a widget function, a call to this function treats the last key of the key sequence that called the widget as the digit ๐neg-argumentโ keys(ESC--)(unbound)(unbound)โ changes the sign of the following argument. flip it ๐universal-argumentโ multiply the argument of the next command by4. alternatively, if this command is followed by an integer (positive or negative), use that as the argument for the next command. so yeah, digits can't be repeated using this command, noted ๐. for example: if this command occurs twice, followed immediately byforward-char, you move forward sixteen spaces. if instead it's followed by-2, thenforward-char, you move backward two spaces. and inside a widget function, if you pass it an argument, i.e.zle universal-argument num, the numeric argument gets set to num โ which is equivalent toNUMERIC=numโจargument-baseโ use the existing numeric argument as a numeric base, which must be in the range2to36inclusive. after that, usingdigit-argumentanduniversal-argumentinputs a new numeric argument in that base. the usual hexadecimal convention applies: the letteraorAcorresponds to10, and so on. arguments in bases that need digits from10upwards are way more conveniently input withuniversal-argument, sinceESC-aetc. aren't usually bound todigit-argument. this function can be used with a command argument inside a user-defined widget โ the following code sets the base to16and lets the user input a hexadecimal argument until a key out of the digit range is typed:
zle argument-base 16
zle universal-argument
Completion โจ
accept-and-menu-completeโ in a menu completion, insert the current completion into the buffer, and advance to the next possible completion ๐complete-wordโ attempt completion on the current word ๐ฏdelete-char-or-listโ keys(^D)(unbound)(unbound)โ delete the character under the cursor. but if the cursor is at the end of the line, list possible completions for the current word instead. dual purpose icon ๐expand-cmd-pathโ expand the current command to its full pathname ๐ฃ๏ธexpand-or-completeโ keys(TAB)(unbound)(TAB)โ attempt shell expansion on the current word, and if that fails, attempt completion. the try-again energy ๐ซถexpand-or-complete-prefixโ attempt shell expansion on the current word up to the cursor โ๏ธexpand-historyโ keys(ESC-space ESC-!)(unbound)(unbound)โ perform history expansion on the edit buffer ๐expand-wordโ keys(^X*)(unbound)(unbound)โ attempt shell expansion on the current word ๐ซlist-choicesโ keys(ESC-^D)(^D =)(^D)โ list possible completions for the current word ๐list-expandโ keys(^Xg ^XG)(^G)(^G)โ list the expansion of the current word ๐magic-spaceโ perform history expansion and insert a space into the buffer. this is intended to be bound to space โจ (yes, the space bar. magic fr)menu-completeโ likecomplete-word, except that menu completion is used. see theMENU_COMPLETEoption ๐menu-expand-or-completeโ likeexpand-or-complete, except that menu completion is used ๐reverse-menu-completeโ perform menu completion, likemenu-complete, except that if a menu completion is already in progress, move to the previous completion rather than the next one โชend-of-listโ when a previous completion displayed a list below the prompt, this widget can be used to move the prompt below the list ๐ฆ
Miscellaneous ๐ธ
accept-and-holdโ keys(ESC-A ESC-a)(unbound)(unbound)โ push the contents of the buffer on the buffer stack and execute it ๐ซถaccept-and-infer-next-historyโ execute the contents of the buffer. then search the history list for a line matching the current one and push the event following onto the buffer stack ๐ฎ
accept-line โ (^J ^M) (^J ^M) (^J ^M)
Finish editing the buffer, bestie. Normally this is the moment your line gets executed as a shell command. Yes, that's just the Return key being iconic ๐
accept-line-and-down-history โ (^O) (unbound) (unbound)
Execute the current line, and push the next history event on the buffer stack. Run it, then your next command is already waiting for you like a true main character โจ
auto-suffix-remove
If the previous action added a suffix (space, slash, etc.) to the word on the command line, remove it. Otherwise do nothing โ no drama. Removing the suffix ends any active menu completion or menu selection.
This widget is intended to be called from user-defined widgets to enforce a desired suffix-removal behavior ๐
auto-suffix-retain
Same vibe check as above, opposite ending: if the previous action added a suffix (space, slash, etc.) to the word on the command line, force it to be preserved. Otherwise do nothing. Retaining the suffix also ends any active menu completion or menu selection.
This widget is intended to be called from user-defined widgets to enforce a desired suffix-preservation behavior ๐ซถ
beep
Beep, unless the BEEP option is unset. That's it. That's the widget ๐
bracketed-paste โ (^[[200~) (^[[200~) (^[[200~)
This widget is invoked when text is pasted to the terminal emulator. It is not intended to be bound to actual keys โ instead it's for the special sequence generated by the terminal emulator when text is pasted, iykyk.
When invoked interactively, the pasted text is inserted to the buffer and placed in the cutbuffer. If a numeric argument is given, shell quoting will be applied to the pasted text before it is inserted.
When a named buffer is specified with vi-set-buffer ("x), the pasted text is stored in that named buffer but not inserted.
When called from a widget function as bracketed-paste name, the pasted text is assigned to the variable name and no other processing is done.
See also the zle_bracketed_paste parameter ๐ธ
vi-cmd-mode โ (^X^V) (unbound) (^[)
Enter command mode โ that is, select the vicmd keymap. And yes, this is bound by default in emacs mode. Not me pointing that out ๐
vi-caps-lock-panic
Hang until any lowercase key is pressed. This is for vi users without the mental capacity to keep track of their caps lock key (like the author). Respect for the self-callout, honestly ๐
clear-screen โ (^L ESC-^L) (^L) (^L)
Clear the screen and redraw the prompt. Fresh canvas energy โจ
deactivate-region
Make the current region inactive. This disables vim-style visual selection mode if it is active ๐ฆ
describe-key-briefly
Reads a key sequence, then prints the function bound to that sequence. Like a little fortune teller for your keyboard ๐ฎ
exchange-point-and-mark โ (^X^X) (unbound) (unbound)
Exchange the cursor position (point) with the position of the mark. Unless a negative numeric argument is given, the region between point and mark is activated so that it can be highlighted. If a zero numeric argument is given, the region is activated but point and mark are not swapped. Sneaky little detail, don't sleep on it ๐
execute-named-cmd โ (ESC-x) (:) (unbound)
Read the name of an editor command and execute it. Heads up: aliasing this widget with zle -A or replacing it with zle -N has no effect when interpreting key bindings โ but zle execute-named-cmd will invoke such an alias or replacement. Pick your lane, bestie ๐
A restricted set of editing functions is available in the mini-buffer. Keys are looked up in the special command keymap, and if not found there, in the main keymap. An interrupt signal, as defined by the stty setting, will abort the function. Note that the following always perform the same task within the executed-named-cmd environment and cannot be replaced by user defined widgets, nor can the set of functions be extended. The allowed functions are:
backward-delete-charvi-backward-delete-charclear-screenredisplayquoted-insertvi-quoted-insertbackward-kill-wordvi-backward-kill-wordkill-whole-linevi-kill-linebackward-kill-linelist-choicesdelete-char-or-listcomplete-wordaccept-lineexpand-or-completeexpand-or-complete-prefix
kill-region kills the last word, and vi-cmd-mode is treated the same as accept-line. The space and tab characters, if not bound to one of these functions, will complete the name and then list the possibilities if the AUTO_LIST option is set. Any other character that is not bound to self-insert or self-insert-unmeta will beep and be ignored. The bindings of the current insert mode will be used.
Currently this command may not be redefined or called by name ๐ญ
execute-last-named-cmd โ (ESC-z) (unbound) (unbound)
Redo the last function executed with execute-named-cmd. Like execute-named-cmd, this command may not be redefined โ but it may be called by name. Character development fr fr ๐
get-line โ (ESC-G ESC-g) (unbound) (unbound)
Pop the top line off the buffer stack and insert it at the cursor position. Little snack, delivered fresh ๐
pound-insert โ (unbound) (#) (unbound)
If there is no # character at the beginning of the buffer, add one to the beginning of each line. If there is one, remove a # from each line that has one. In either case, accept the current line. The INTERACTIVE_COMMENTS option must be set for this to have any usefulness. Comment out the whole vibe, periodt โจ
vi-pound-insert
If there is no # character at the beginning of the current line, add one. If there is one, remove it. The INTERACTIVE_COMMENTS option must be set for this to have any usefulness. One line only, no over-committing ๐
push-input
Push the entire current multiline construct onto the buffer stack and return to the top-level (PS1) prompt. If the current parser construct is only a single line, this is exactly like push-line. Next time the editor starts up or is popped with get-line, the construct will be popped off the top of the buffer stack and loaded into the editing buffer ๐
push-line โ (^Q ESC-Q ESC-q) (unbound) (unbound)
Push the current buffer onto the buffer stack and clear the buffer. Next time the editor starts up, the buffer will be popped off the top of the buffer stack and loaded into the editing buffer. Stash it, don't trash it ๐ฆ
push-line-or-edit
At the top-level (PS1) prompt, equivalent to push-line. At a secondary (PS2) prompt, move the entire current multiline construct into the editor buffer. That latter one is equivalent to push-input followed by get-line. Two modes, one widget, main character energy ๐
read-command
Only useful from a user-defined widget. A keystroke is read just as in normal operation, but instead of the command being executed, the name of the command that would be executed is stored in the shell parameter REPLY. This can be used as the argument of a future zle command. If the key sequence is not bound, status 1 is returned; typically, however, REPLY is set to undefined-key to indicate a useless key sequence ๐
recursive-edit
Only useful from a user-defined widget. At this point in the function, the editor regains control until one of the standard widgets which would normally cause zle to exit (typically an accept-line caused by hitting the return key) is executed. Instead, control returns to the user-defined widget. The status returned is non-zero if the return was caused by an error, but the function still continues executing and hence may tidy up. This makes it safe for the user-defined widget to alter the command line or key bindings temporarily ๐ซถ
The following widget, caps-lock, serves as an example.
self-insert-ucase() {
LBUFFER+=${(U)KEYS[-1]}
}
integer stat
zle -N self-insert self-insert-ucase
zle -A caps-lock save-caps-lock
zle -A accept-line caps-lock
zle recursive-edit
stat=$?
zle -A .self-insert self-insert
zle -A save-caps-lock caps-lock
zle -D save-caps-lock
(( stat )) && zle send-break
return $stat
This causes typed letters to be inserted capitalised until either accept-line (i.e. typically the return key) is typed or the caps-lock widget is invoked again; the latter is handled by saving the old definition of caps-lock as save-caps-lock and then rebinding it to invoke accept-line. Note that an error from the recursive edit is detected as a non-zero return status and propagated by using the send-break widget ๐ฅ
redisplay โ (unbound) (^R) (^R)
Redisplays the edit buffer. A little refresh for the timeline ๐
reset-prompt โ (unbound) (unbound) (unbound)
Force the prompts on both the left and right of the screen to be re-expanded, then redisplay the edit buffer. This reflects changes both to the prompt variables themselves and changes in the expansion of the values (for example, changes in time or directory, or changes to the value of variables referred to by the prompt).
Otherwise, the prompt is only expanded each time zle starts, and when the display has been interrupted by output from another part of the shell (such as a job notification) which causes the command line to be reprinted.
reset-prompt doesn't alter the special parameter LASTWIDGET. Leave that one alone, she's booked โจ
send-break โ (^G ESC-^G) (unbound) (unbound)
Abort the current editor function, e.g. execute-named-command, or the editor itself, e.g. if you are in vared. Otherwise abort the parsing of the current line; in this case the aborted line is available in the shell variable ZLE_LINE_ABORTED. If the editor is aborted from within vared, the variable ZLE_VARED_ABORTED is set. Emergency exit, but make it chic ๐ญ
run-help โ (ESC-H ESC-h) (unbound) (unbound)
Push the buffer onto the buffer stack, and execute the command run-help cmd, where cmd is the current command. run-help is normally aliased to man. Asking for help is self-care, bestie ๐
vi-set-buffer โ (unbound) (") (unbound)
Specify a buffer to be used in the following command. There are 37 buffers that can be specified: the 26 named buffers "a to "z, the yank buffer "0, the nine queued buffers "1 to "9 and the black hole buffer "_. The named buffers can also be specified as "A to "Z. Thirty-seven options and every single one of them serves ๐
When you name a buffer for a cut, change or yank, the text you're dealing with replaces whatever was chillin' in that buffer before โ no mercy. But if you name it with a capital letter, plot twist: the newly cut text gets appended to the buffer instead of overwriting it ๐ซถ. And when you're on that "_ buffer? Literally nothing happens. Which is lowkey iconic for deleting text without touching any of your buffers โจ.
No buffer specified on a cut or change command? Then "1 is the default, and the contents of "1 through "8 each get shifted along one buffer โ so the contents of "9 is gone ๐ญ. For a yank command with no buffer specified, it's "0. And finally, a paste command with no specified buffer will paste the text from the most recent command, regardless of any buffer that might have been used with that command. iykyk.
When you call it from a widget function using the zle command, you can optionally pass the buffer as an argument. Like so:
zle vi-set-buffer A
๐ vi-set-mark โ (unbound) (m) (unbound)
Set the specified mark right at the cursor position. Drop a pin, basically ๐.
๐ set-mark-command โ (^@) (unbound) (unbound)
Set the mark at the cursor position. But if you call it with a negative numeric argument, it does not set the mark โ instead it deactivates the region so it's no longer highlighted (tho it's still usable for other purposes, bestie). Otherwise, the region is marked as active ๐.
๐ spell-word โ (ESC-$ ESC-S ESC-s) (unbound) (unbound)
Attempt spelling correction on the current word. Your personal autocorrect era ๐ .
๐ split-undo
Breaks the undo sequence at the current change. Super useful in vi mode, because changes made in insert mode are coalesced on entering command mode. Same vibe: undo will normally revert as one all the changes made by a user-defined widget. So like, if you want them separate โ split it ๐ช.
๐ undefined-key
This one runs when a key sequence that isn't bound to any command gets typed. By default, it beeps. Yeah. Just a little beep. That's the whole personality ๐.
๐ undo โ (^_ ^Xu ^X^U) (u) (unbound)
Incrementally undo the last text modification. When it's called from a user-defined widget, it takes an optional argument indicating a previous state of the undo history as returned by the UNDO_CHANGE_NO variable โ and modifications are undone until that state is reached, subject to any limit imposed by the UNDO_LIMIT_NO variable โช.
Heads up: when invoked from vi command mode, the full prior change made in insert mode is reverted โ because those changes were merged when command mode was selected. Sneaky.
๐ redo โ (unbound) (^R) (unbound)
Incrementally redo undone text modifications. The comeback tour โฉ.
๐ vi-undo-change โ (unbound) (unbound) (unbound)
Undo the last text modification. If repeated, it redoes the modification. It flips back and forth, it's giving indecisive ๐ญ.
๐ visual-mode โ (unbound) (v) (unbound)
Toggle vim-style visual selection mode. If line-wise visual mode is currently enabled, then it's changed to being character-wise. And if you use it following an operator, it forces the subsequent movement command to be treated as a character-wise movement ๐.
๐ visual-line-mode โ (unbound) (V) (unbound)
Toggle vim-style line-wise visual selection mode. If character-wise visual mode is currently enabled, then it's changed to being line-wise. And if used following an operator, it forces the subsequent movement command to be treated as a line-wise movement ๐ฅ.
๐ what-cursor-position โ (^X=) (ga) (unbound)
Print the character under the cursor, its code as an octal, decimal and hexadecimal number, the current cursor position within the buffer, and the column of the cursor in the current line. Full intel, no cap ๐.
๐ where-is
Read the name of an editor command and print the listing of key sequences that invoke that specified command. There's a restricted set of editing functions available in the mini-buffer. Keys are looked up in the special command keymap, and if they're not found there, in the main keymap โจ.
๐ which-command โ (ESC-?) (unbound) (unbound)
Push the buffer onto the buffer stack, and execute the command which-command cmd, where cmd is the current command. Fun fact: which-command is normally aliased to whence ๐ต๏ธโโ๏ธ.
๐ vi-digit-or-beginning-of-line โ (unbound) (0) (unbound)
If the last command executed was a digit as part of an argument, it continues the argument. Otherwise, it executes vi-beginning-of-line. Two moods, one binding ๐.
๐ธ Text Objects ๐ธ
Text objects are commands that can be used to select a block of text according to some criteria. They're a feature of the vim text editor, so they're primarily intended for use with vi operators or from visual selection mode. However โ plot twist โ they can also be used from vi-insert or emacs mode ๐ฆ.
Key bindings listed below apply to the viopp and visual keymaps. Slay.
๐
select-a-blank-word โ (aW)
Select a word including adjacent blanks, where a word is defined as a series of non-blank characters. With a numeric argument, multiple words will be selected ๐.
๐
select-a-shell-word โ (aa)
Select the current command argument, applying the normal rules for quoting ๐.
๐
select-a-word โ (aw)
Select a word including adjacent blanks, using the normal vi-style word definition. With a numeric argument, multiple words will be selected โญ.
๐
select-in-blank-word โ (iW)
Select a word, where a word is defined as a series of non-blank characters. With a numeric argument, multiple words will be selected ๐.
๐
select-in-shell-word โ (ia)
Select the current command argument, applying the normal rules for quoting. And if the argument begins and ends with matching quote characters, those are not included in the selection โ tidy ๐ .
๐
select-in-word โ (iw)
Select a word, using the normal vi-style word definition. With a numeric argument, multiple words will be selected ๐ง.
โจ CHARACTER HIGHLIGHTING โจ
okay so your line editor can literally highlight characters or whole regions of the line that have some kind of particular significance โ and it's all controlled by the array parameter zle_highlight, but only if you set it, bestie. it's opt-in.
if the parameter has the single entry none, every bit of highlighting gets turned off. and like โ note that the parameter is still expected to be an array, okay? don't come at it with a scalar. ๐
otherwise, every entry of the array should be a word that names a context for highlighting, then a colon, then a comma-separated list of the types of highlighting to apply in that context.
the contexts you can use are these ๐
defaultโ any text in the command line that isn't affected by any other highlighting. text outside the editable area of the command line is not affected. periodt.isearchโ when one of the incremental history search widgets is active, this is the area of the command line matched by the search string or pattern.regionโ the currently selected text. in emacs terminology this is called the region and it's bounded by the cursor (point) and the mark. it only gets highlighted if it's active, which is the case after the mark is modified withset-mark-commandorexchange-point-and-mark. note that whether the region is active or not has no effect on how it's used inside emacs style widgets โ it purely determines whether it's highlighted. in vi mode, the region corresponds to selected text in visual mode. slay. ๐specialโ individual characters that have no direct printable representation but get shown in a special manner by the line editor. these are described below, keep scrolling ๐ซถsuffixโ this context is used in completion for characters marked as suffixes that will be removed if the completion ends at that point. the most obvious example is a slash (/) after a directory name. note that suffix removal is configurable โ the circumstances under which the suffix gets removed may differ for different completions.pasteโ following a command to paste text, this is the characters that were inserted. ๐
when region_highlight is set, the contexts that describe a region โ isearch, region, suffix, and paste โ get applied first, then region_highlight is applied, then the remaining zle_highlight contexts get applied. and if a particular character gets hit by multiple specifications? the last specification wins. no cap.
๐ฅ๏ธ terminal sequence fields (the lowkey nerdy part)
zle_highlight may also contain extra fields for controlling how the terminal sequences that change colours get output. each of these is followed by a colon and a string in the same form as for key bindings. for the vast majority of terminals you will not need this, because the defaults (shown in parentheses) are widely used. โจ
fg_start_code(\e[3) โ the start of the escape sequence for the foreground colour. it's followed by one to three ASCII digits representing the colour. only used for palette colors, i.e. not 24-bit colors specified via a color triplet.fg_default_code(9) โ the number to use instead of the colour to reset the default foreground colour.fg_end_code(m) โ the end of the escape sequence for the foreground colour.bg_start_code(\e[4) โ the start of the escape sequence for the background colour. seefg_start_codeabove, it's giving same energy.bg_default_code(9) โ the number to use instead of the colour to reset the default background colour.bg_end_code(m) โ the end of the escape sequence for the background colour.
๐ the types of highlighting
here's what you can actually apply. heads up though: not all types of highlighting are available on all terminals. check your vibe (and your terminal). ๐ฅ
noneโ no highlighting is applied to the given context. it's not useful for this to show up alongside other types of highlighting; it's there to override a default.fg=colourโ the foreground colour should be set to colour, which can be a decimal integer, the name of one of the eight most widely-supported colours, or a#followed by an RGB triplet in hexadecimal format.
so like โ not all terminals support this, and of the ones that do, not all of them provide a way to test the support. hence you gotta decide based on your terminal type. most terminals support the colours black, red, green, yellow, blue, magenta, cyan and white, and you can set those by name. in addition, default may be used to set the terminal's default foreground colour. abbreviations are allowed โ b or bl selects black. some terminals may generate additional colours if the bold attribute is also present.
on recent terminals, and on systems with an up-to-date terminal database, you can test how many colours are supported with the command echotc Co. if that succeeds, it indicates a limit on the number of colours that'll be enforced by the line editor. the number of colours is in any case limited to 256 (i.e. the range 0 to 255).
some modern terminal emulators support 24-bit true colour (16 million colours ๐ญ). in that case you can use the hex triplet format โ a # followed by either a three or six digit hexadecimal number describing the red, green and blue components of the colour. hex triplets can also be used with 88 and 256 colour terminals via the zsh/nearcolor module (see zshmodules(1)). ๐
also: colour is also known as color. iykyk.
bg=colourโ the background colour should be set to colour. works similarly to the foreground colour, except the background isn't usually affected by the bold attribute.boldโ the characters in the given context are shown in a bold font. not all terminals distinguish bold fonts, unfortunately.standoutโ the characters in the given context are shown in the terminal's standout mode. the actual effect is specific to the terminal; on many terminals it's inverse video. and on some of those terminals, where the cursor doesn't blink, it appears with standout mode negated โ making it less than clear where the cursor actually is ๐ญ. on such terminals, one of the other effects may be preferable for highlighting the region and the matched search string.underlineโ the characters in the given context are shown underlined. some terminals show the foreground in a different colour instead; in this case whitespace will not be highlighted.
๐ป the special characters, explained
the characters we called special up there are these ones. and note: the formatting described here is used irrespective of whether the characters are highlighted. it always happens. ๐
- ASCII control characters โ control characters in the ASCII range are shown as
^followed by the base character. - Unprintable multibyte characters โ this one applies to control characters not in the ASCII range, plus other characters as follows. if the
MULTIBYTEoption is in effect, multibyte characters that aren't in the ASCII character set and are reported as having zero width get treated as combining characters when the optionCOMBINING_CHARSis on. if that option is off, or if a character appears somewhere a combining character isn't valid, the character is treated as unprintable.
unprintable multibyte characters are shown as a hexadecimal number between angle brackets. the number is the code point of the character in the wide character set โ and this may or may not be Unicode, depending on your operating system. ๐ฆ
- Invalid multibyte characters โ if the
MULTIBYTEoption is in effect, any sequence of one or more bytes that doesn't form a valid character in the current character set is treated as a series of bytes, each one shown as a special character. you can tell this case apart from other unprintable characters because the bytes are represented as two hexadecimal digits between angle brackets โ as distinct from the four or eight digits used for unprintable characters that are nonetheless valid in the current character set.
not all systems support this: for it to work, the system's representation of wide characters must be code values from the Universal Character Set, as defined by IS0 10646 (also known as Unicode). ๐ง
- Wrapped double-width characters โ when a double-width character lands in the final column of a line, it gets shown on the next line instead. the empty space left behind in the original position is highlighted as a special character. lowkey iconic behaviour.
๐ the defaults
if zle_highlight isn't set, or no value applies to a particular context, the defaults applied are equivalent to:
zle_highlight=(region:standout special:standout
suffix:bold isearch:underline paste:standout)
i.e. both the region and the special characters are shown in standout mode. that's the whole vibe check. ๐
๐ซถ one last thing
within widgets, arbitrary regions may be highlighted by setting the special array parameter region_highlight โ see above, bestie, i didn't forget. โจ
and that's the whole page!! ๐ this was zsh version 5.9.2, dated July 12, 2026, and you've been reading ZSHZLE(1). go forth and highlight literally everything, you absolute main character. ๐๐
๐ธ
๐ฆ where this page comes from
- arch rolling: zsh 5.9.2-1 ยท custom
- nixpkgs unstable: zsh 5.9.2
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms