Useful zsh
Init File Things
/Errata: in the section on adding a bottom bar to the ZSH prompt,
the previously given $PROMPT
variable contained bugs.
Use the current one instead./
/This post's intended audience is those with Unix command line experience. More in-depth terminal and shell experience is required to understand how the given snippets work, but they're designed such that anyone could copy and paste them into their init file without understanding how they work./
I use zsh
quite a lot, and in the process of using it I've created several
pieces of code one can put in their shell init files (~/.zshenv
, ~/.zshrc
,
~/.zlogin
, etc.).
These chiefly include a system to store the current directory of each shell
session, allowing one to move between them;
and an extension of the shell prompt at the bottom of the screen.
One may ask: why aren't these published on Github or something? These aren't fully-fledged programs in and of themselves, and they warrant some explanation. My dotfiles are in a Github repository, but that repository isn't particularly suitable for public release.
Examples
Click the below examples to enlarge them.
Current directory indexing
1# ~/.zshrc
2local curr_dir_file=~/.curr_dirs
3upd_curr_dirs()
4{
5 rm_self_curr_dirs
6 tty >/dev/null && printf '%-12s %s\n' $(tty) "$(print -P '%~')" \
7 >>$curr_dir_file
8}
9add-zsh-hook chpwd upd_curr_dirs
10
11rm_self_curr_dirs()
12{
13 if tty >/dev/null
14 then
15 local newdirs=$(cat $curr_dir_file | grep -v "^$(tty)")
16 echo $newdirs >$curr_dir_file
17 fi
18}
19add-zsh-hook zshexit rm_self_curr_dirs
20
21select_saved_directory()
22{
23 cat $curr_dir_file | fzf | sed -E 's!^(.*) (/|~)!\2!g'
24}
25
26d()
27{
28 dir=$(select_saved_directory)
29 [[ -n $dir ]] && eval cd "$dir"
30}
I often find myself in the situation of having to open a new shell session
(in a new terminal window or tmux
pane) with the same current working
directory as an existing session.
This script makes jumping to the current working directory of an existing shell
session easy.
One need only type d
,
and a menu of other shell sessions and their working directories will be
displayed.
Selecting a directory and pressing Enter will change to that directory.
When exiting a shell, the entry associated with that shell session will be
cleared.
Typically, the ~/.curr_dirs
file looks like this:
1/dev/pts/5 ~/docs/website
2/dev/pts/1 ~/code/projects/trt-dotfiles/desktops
3/dev/pts/9 ~/docs/website
4/dev/pts/3 ~/docs/website
Each line represents a shell session.
The first component of each line represents the pseudoterminal associated with
that session, while the second represents the current working directory of the
session.
It's theoretically possible to use the ~/.curr_dirs
file to store directory
bookmarks by making the first component a value that doesn't start with the name
of a terminal and doesn't contain words starting with ~
or /
.
However, the system was not designed for this, nor have I used it this way.
A quick note on design:
the use of an intermediate variable in rm_self_curr_dirs
is necessary to
prevent clearing the current directories file before reading it,
as would happen if a pipeline read the file, processed it, and wrote back to it
all at once.
Ensuring that directories with spaces are handled properly is perilous when the
file format used to store them separates records with spaces.
Nonetheless, the code as written handles it correctly unless the terminals
associated with each shell session (or whatever other "keys" there are) contain
a space followed by a slash or a space followed by a tilde.
This won't happen in normal operation.
Bottom bar
1clearprompt()
2{
3 # notes: aaaaabbbbccccccccccdddddeeee
4 print -n "\e[2K\e[s\e[1000;1H\e[2K\e[u" # Bottom bar
5 # Alerts certain terminals that a command has been typed and is about to be
6 # run.
7 print -n "\e]133;C\e\\" # Shell int
8}
9
10if [[ ! (($TERM == dumb && $INSIDE_EMACS) || -n $VIM) &&
11 $TERM =~ "screen.*" || $TERM =~ "xterm.*" ]]
12then
13 # Assumes that terminal is no greater than 999 lines high
14 # see notes: fffffffffgggggggghhiiiiiiiiiiijjkk
15 local bbcontents="%F{white}%K{blue}%Ehello world%k%f"
16 # see notes: ll bbbbmmccccccccccnnoooooooooooooeeee ll
17 PROMPT+=$(print "%{\e[B\e[s\n\e[1000;1H%E${bbcontents}\e[u\e[A%}")
18fi
19add-zsh-hook preexec clearprompt
What all of that means
This involves a fair amount of terminal wizardry.
A useful reference on the escape codes supported by all modern terminals is
the XTerm project's
ctlseqs(ms)
manpage.
A useful reference for zsh
prompt expansion escape codes (the %x
codes) is
the relevant
chapter in the zsh
manual.
I've put together a table below explaining what each part of the character
strings on lines 4, 15, and 17 do;
match each entry with the relevant part of the string using the letters in the
# Notes: …
line above the part in question of the string.
Note | Who interprets it | What it does | Why |
---|---|---|---|
a |
Terminal | Clears the current line. | When clearprompt is run, the Enter key has just been pressed and the cursor is at the beginning of a new line. This ensures that that line, which might be at the bottom of the screen, is cleared. This is necessary to fix certain bugs. |
b |
Terminal | Saves the current cursor position | This saves the current cursor position, because we're about to explicitly move to the bottom of the screen and we need to be able to get back to where we were. |
c |
Terminal | Goes to character cell (1,1000); (1,1) is top left of screen | Since the relevant standards define no "go to bottom of screen" sequence, we just assume that the terminal has fewer than 1000 lines. If that assumption holds, this escape sequence will put the cursor at the bottom left corner of the screen. |
d |
Terminal | Clears the current line. | Erases the bottom bar. |
e |
Terminal | Returns the cursor to the previously saved position. | Returns the cursor to where it was. |
f |
zsh |
Sets the foreground color to white. | |
g |
zsh |
Sets the background color to blue. | |
h |
zsh |
Clears to the end of the line. | This "paints" the entire bottom bar with the selected background color, no matter how many characters of space are actually used on the bottom. This is useful for setting the bottom bar apart from everything else. |
i |
A human | This is the actual text displayed on the bottom bar. | |
j |
zsh |
Resets the foreground color. | |
k |
zsh |
Resets the background color. | |
l |
zsh |
Begins/ends an "escape sequence" block. | zsh needs to properly calculate the length of the prompt so it knows where the cursor should be. This tells zsh that everything we're sending will not be displayed with the prompt. |
m |
Terminal | Creates a new line, moving the cursor down and scrolling if necessary. | If the cursor was at the bottom of the screen, this ensures that space is made for the bottom bar. |
n |
zsh |
Clears to the end of the line. | This erases whatever might be at the bottom. |
o |
zsh |
Substitutes in the contents of the bottom bar. |
How I have it configured
1if [[ $SSH_CONNECTION ]]
2then
3 ssh="%f[SSH ]%K{red}"
4else
5 ssh="[LOCAL]"
6fi
7
8clearprompt()
9{
10 print -n "\e[2K\e[s\e[1000;1H\e[2K\e[u" # Bottom bar
11 print -n "\e]133;C\e\\" # Shell int
12}
13
14local fancy_prompt=1
15if [[ ($TERM == dumb && $INSIDE_EMACS) || -n $VIM ]]
16then
17 promptinit
18 prompt adam2
19elif [[ $TERM =~ "screen.*" || $TERM =~ "xterm.*" ]]
20then
21 PROMPT=''
22 if [[ $fancy_prompt == 1 ]]
23 then
24 # Assumes that terminal is no greater than 999 lines high
25 local bbcontents=$(print "%F{white}%K{blue}%E\
26%B$ssh%n%k%K{blue}@%m %~ $jobctl%k%f")
27 PROMPT+=$(print "%{\e[s\n\e[1000;1H%E${bbcontents}\e[u%}")
28 fi
29 # Set xterm title.
30 PROMPT+=$'%{\e]0;%~\e\\%}'
31
32 PROMPT+=$'%{\e]133;D;%?\e\\\e]133;P\e\\%}'
33 PROMPT+=$'%B[\$(printf "%03u" \$?)]%F{blue}%2~%f%b%# '
34 PROMPT+=$'%{\e]133;B\e\\%}'
35fi
36add-zsh-hook preexec clearprompt
37
38export PROMPT RPROMPT
This implements a few useful things.
First, the bottom bar has an explicit marker of whether this shell is being
accessed over an SSH connection or locally (and, in the latter case, marks the
hostname in red);
this is to prevent, say, shutting down or disconnecting the network on a server
located 30 miles away.
(I have done this.)
Lines 15-18 display a simple prompt for the remaining terminals in common use
that don't support modern escape sequences: the emacs
and nvim
integrated
terminals.
(As of right now, the nvim
terminal detection does not always work.)
Lines 25-26 define the contents of the bottom bar, which consists of the
previously-mentioned local/SSH indicator, the username, the hostname, and the
current directory.
The actual prompt is defined on line 33,
and contains, in order, the exit status of the previous command, the last two
components of the current directory, and the %
or #
prompt.
Because I frequently use zsh
job control, I'm looking into putting a count of
suspended processes somewhere.