Custom Backward Word Deletion in Zsh
Custom Backward Word Deletion In ZSH
1. Intro
Do you use ctrl+w
keyboard shortcut in Zsh?
It’s a handy function for deleting “words”.
Some of you may be wondering, how does Zsh know that a “word” is?
Can I change what a “word” means?
If neither of those questions sound interesting, this post isn’t for you.
If they do, stay tuned.
I’ll show you how ctrl+w
works along with a basic and an advanced example of customizing it.
2. What is a “word”?
What do you think a “word” means? You may think of the words that you are currently reading. A sequence of symbols containing [a-z] and [A-Z]. Commands typed out in your terminal contain more than letters. They contain brackets, punctuation, and even mathematical symbols.
Zsh needs to know which special characters you want to consider part of a “word”.
It uses an environmental variable called WORDCHARS
.
You can inspect the variable with the echo command:
$ echo $WORDCHARS
*?_-.[]~=/&;!#$%^(){}<>
It’s defined as:
WORDCHARS A list of non-alphanumeric characters considered part of a word by the line editor.
When you press ctrl+w
, Zsh will delete all letters plus any symbols in WORDCHARS
.
If you want ctrl+w
to stop deleting up to certain symbol, we remove it from WORDCHARS
.
3. Basic Example
Let’s walk through a basic example example.
The default WORDCHARS
variable does not include colons (:
).
To test this you can:
- Type a string with colons on you command line (e.g
1234🔢1234
) - Hit
ctrl+w
- Watch as the cursor stops at each colon
Let’s update WORDCHARS to include colons.
You can update your ~/.zshrc
to add the colon to the $WORDCHARS
variable by running this command:
echo "export WORDCHARS='${WORDCHARS}:'" >> ~/.zshrc
To see the change you will need to reload your ~/.zshrc
or open a new terminal window.
If your run the same experiment above you’ll see you only need to hit ctrl+w
once.
Being able to update WORDCHARS
like this may give you more than enough mileage.
Or, if you like to tinker, you may want even want more customization.
Let’s ramp it up.
4. Advanced Example
I like Vim.
I like Vim’s visual mode motions.
Vim defines both a word
and a WORD
, which you can check out with :help word
:
word
A word consists of a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs,
). This can be changed with the ‘iskeyword’ option. An empty line is also considered to be a word. WORD
A WORD consists of a sequence of non-blank characters, separated with white space. An empty line is also considered to be a WORD.
We’re going to build similar functionality into Zsh.
Our default ctrl+w
will be for deleting a word
.
We’ll also define ctrl+alt+w
to delete a WORD
.
Why?
Because we hate holding the backspace key down.
4.1 ZSH Widgets
Widgets are how zsh performs actions in your terminal. Everything from moving the cursor to command completion to executing commands.
First we need to figure out which widget we are trying to extend.
We know that the key command is ctrl+w
.
This is where the bindkey
command comes in.
Browsing the commands output helps us figure out what zsh widget we are trying to extend:
$ bindkey
...
"^W" backward-kill-word
...
4.2 All Together Now
We know which widget we’re extending, backward-kill-word
.
We also know how to customize the widget, with WORDCHARS
.
Combining these, we can make our own custom widgets and then bind them:
# This will be our new default `ctrl+w` command
my-backward-delete-word() {
# Copy the global WORDCHARS variable to a local variable. That way any
# modifications are scoped to this function only
local WORDCHARS=$WORDCHARS
# Use bash string manipulation to remove `:` so our delete will stop at it
WORDCHARS="${WORDCHARS//:}"
# Use bash string manipulation to remove `/` so our delete will stop at it
WORDCHARS="${WORDCHARS//\/}"
# Use bash string manipulation to remove `.` so our delete will stop at it
WORDCHARS="${WORDCHARS//.}"
# zle <widget-name> will run an existing widget.
zle backward-delete-word
}
# `zle -N` will create a new widget that we can use on the command line
zle -N my-backward-delete-word
# bind this new widget to `ctrl+w`
bindkey '^W' my-backward-delete-word
# This will be our `ctrl+alt+w` command
my-backward-delete-whole-word() {
# Copy the global WORDCHARS variable to a local variable. That way any
# modifications are scoped to this function only
local WORDCHARS=$WORDCHARS
# Use bash string manipulation to add `:` to WORDCHARS if it's not present
# already.
[[ ! $WORDCHARS == *":"* ]] && WORDCHARS="$WORDCHARS"":"
# zle <widget-name> will run that widget.
zle backward-delete-word
}
# `zle -N` will create a new widget that we can use on the command line
zle -N my-backward-delete-whole-word
# bind this new widget to `ctrl+alt+w`
bindkey '^[^w' my-backward-delete-whole-word
After you add this to your ~/.zshrc
you can test it against the following strings:
asdf/asdf/asdf/
asdf:asdf:asdf:
asdf.asdf.asdf
5. Conclusion
Zsh has some powerful mechanisms for customizing your command line experience. This post only scratches a small surface. Hope you enjoy your new widgets! Happy hacking!
Additional Reading: