I am using ZSH
1 as my default shell. It is POSIX compliant
and it is easy to customize. See Zsh Prompt for my Powerlevel10k
prompt configuration.
{ pkgs, ... }:
{
programs.zsh = {
enable = true;
enableGlobalCompInit = false; # We'll do it ourselves, making startup faster
};
users.defaultUserShell = pkgs.zsh;
}
{
nixosConfig,
config,
pkgs,
...
}:
{
programs.zsh = {
enable = true;
enableVteIntegration = true;
autosuggestion.enable = true;
autocd = true;
dotDir = ".config/zsh";
plugins = [
{
name = "fast-syntax-highlighting";
src = "${pkgs.zsh-fast-syntax-highlighting}/share/zsh/site-functions";
}
];
};
home.packages = with pkgs; [
meslo-lgs-nf
];
}
History handling needs some customization as well2:
{ nixosConfig, config, ... }:
{
programs.zsh.history = {
append = true;
expireDuplicatesFirst = true;
extended = true;
save = 100000;
size = 100000;
# we write the history immediately to our persistence directory
path = "${nixosConfig._.persist.root}${config.home.homeDirectory}/${config.programs.zsh.dotDir}/.zsh_history";
};
}
The following configuration are from GRML’s ZSH config3:
{
programs.zsh.initContent = ''
# in order to use #, ~ and ^ for filename generation grep word
# *~(*.gz|*.bz|*.bz2|*.zip|*.Z) -> searches for word not in compressed files
# don't forget to quote '^', '~' and '#'!
setopt extendedglob
# display PID when suspending processes as well
setopt longlistjobs
# report the status of backgrounds jobs immediately
setopt notify
# whenever a command completion is attempted, make sure the entire command path
# is hashed first.
setopt hash_list_all
# not just at the end
setopt completeinword
# Don't send SIGHUP to background processes when the shell exits.
setopt nohup
# make cd push the old directory onto the directory stack.
setopt auto_pushd
# avoid "beep"ing
setopt nobeep
# don't push the same dir twice.
setopt pushd_ignore_dups
# * shouldn't match dotfiles. ever.
setopt noglobdots
# use zsh style word splitting
setopt noshwordsplit
# don't error out when unset parameters are used
setopt unset
# allow one error for every three characters typed in approximate completer
zstyle ':completion:*:approximate:' max-errors 'reply=( $((($#PREFIX+$#SUFFIX)/3 )) numeric )'
# don't complete backup files as executables
zstyle ':completion:*:complete:-command-::commands' ignored-patterns '(aptitude-*|*\~)'
# start menu completion only if it could find no unambiguous initial string
zstyle ':completion:*:correct:*' insert-unambiguous true
zstyle ':completion:*:corrections' format $'%{\e[0;31m%}%d (errors: %e)%{\e[0m%}'
zstyle ':completion:*:correct:*' original true
# activate color-completion
zstyle ':completion:*:default' list-colors ''${(s.:.)LS_COLORS}
# format on completion
zstyle ':completion:*:descriptions' format $'%{\e[0;31m%}completing %B%d%b%{\e[0m%}'
# automatically complete 'cd -<tab>' and 'cd -<ctrl-d>' with menu
# zstyle ':completion:*:*:cd:*:directory-stack' menu yes select
# insert all expansions for expand completer
zstyle ':completion:*:expand:*' tag-order all-expansions
zstyle ':completion:*:history-words' list false
# activate menu
zstyle ':completion:*:history-words' menu yes
# ignore duplicate entries
zstyle ':completion:*:history-words' remove-all-dups yes
zstyle ':completion:*:history-words' stop yes
# match uppercase from lowercase
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
# separate matches into groups
zstyle ':completion:*:matches' group 'yes'
zstyle ':completion:*' group-name ""
# if there are more than 5 options allow selecting from a menu
zstyle ':completion:*' menu select=5
zstyle ':completion:*:messages' format '%d'
zstyle ':completion:*:options' auto-description '%d'
# describe options in full
zstyle ':completion:*:options' description 'yes'
# on processes completion complete all user processes
zstyle ':completion:*:processes' command 'ps -au$USER'
# offer indexes before parameters in subscripts
zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters
# provide verbose completion information
zstyle ':completion:*' verbose true
# recent (as of Dec 2007) zsh versions are able to provide descriptions
# for commands (read: 1st word in the line) that it will list for the user
# to choose from. The following disables that, because it's not exactly fast.
zstyle ':completion:*:-command-:*:' verbose false
# set format for warnings
zstyle ':completion:*:warnings' format $'%{\e[0;31m%}No matches for:%{\e[0m%} %d'
# define files to ignore for zcompile
zstyle ':completion:*:*:zcompile:*' ignored-patterns '(*~|*.zwc)'
zstyle ':completion:correct:' prompt 'correct to: %e'
# Ignore completion functions for commands you don't have:
zstyle ':completion::(^approximate*):*:functions' ignored-patterns '_*'
# Provide more processes in completion of programs like killall:
zstyle ':completion:*:processes-names' command 'ps c -u ''${USER} -o command | sort -u'
# complete manual by their section
zstyle ':completion:*:manuals' separate-sections true
zstyle ':completion:*:manuals.*' insert-sections true
zstyle ':completion:*:man:*' menu yes select
function bind2maps () {
local i sequence widget
local -a maps
while [[ "$1" != "--" ]]; do
maps+=( "$1" )
shift
done
shift
if [[ "$1" == "-s" ]]; then
shift
sequence="$1"
else
sequence="''${key[$1]}"
fi
widget="$2"
[[ -z "$sequence" ]] && return 1
for i in "''${maps[@]}"; do
bindkey -M "$i" "$sequence" "$widget"
done
}
typeset -A key
key=(
Home "''${terminfo[khome]}"
End "''${terminfo[kend]}"
Insert "''${terminfo[kich1]}"
Delete "''${terminfo[kdch1]}"
Up "''${terminfo[kcuu1]}"
Down "''${terminfo[kcud1]}"
Left "''${terminfo[kcub1]}"
Right "''${terminfo[kcuf1]}"
PageUp "''${terminfo[kpp]}"
PageDown "''${terminfo[knp]}"
BackTab "''${terminfo[kcbt]}"
)
# Guidelines for adding key bindings:
#
# - Do not add hardcoded escape sequences, to enable non standard key
# combinations such as Ctrl-Meta-Left-Cursor. They are not easily portable.
#
# - Adding Ctrl characters, such as '^b' is okay; note that '^b' and '^B' are
# the same key.
#
# - All keys from the $key[] mapping are obviously okay.
#
# - Most terminals send "ESC x" when Meta-x is pressed. Thus, sequences like
# '\ex' are allowed in here as well.
bind2maps emacs -- Home beginning-of-somewhere
bind2maps viins vicmd -- Home vi-beginning-of-line
bind2maps emacs -- End end-of-somewhere
bind2maps viins vicmd -- End vi-end-of-line
bind2maps emacs viins -- Insert overwrite-mode
bind2maps vicmd -- Insert vi-insert
bind2maps emacs -- Delete delete-char
bind2maps viins vicmd -- Delete vi-delete-char
bind2maps emacs viins vicmd -- Up up-line-or-search
bind2maps emacs viins vicmd -- Down down-line-or-search
bind2maps emacs -- Left backward-char
bind2maps viins vicmd -- Left vi-backward-char
bind2maps emacs -- Right forward-char
bind2maps viins vicmd -- Right vi-forward-char
# Do history expansion on space:
bind2maps emacs viins -- -s ' ' magic-space
#k# Trigger menu-complete
bind2maps emacs viins -- -s '\ei' menu-complete # menu completion via esc-i
#k# Insert a timestamp on the command line (yyyy-mm-dd)
zmodload -i zsh/complist
#m# k Shift-tab Perform backwards menu completion
bind2maps menuselect -- BackTab reverse-menu-complete
#k# menu selection: pick item but stay in the menu
bind2maps menuselect -- -s '\e^M' accept-and-menu-complete
# also use + and INSERT since it's easier to press repeatedly
bind2maps menuselect -- -s '+' accept-and-menu-complete
bind2maps menuselect -- Insert accept-and-menu-complete
# accept a completion and try to complete again by using menu
# completion; very useful with completing directories
# by using 'undo' one's got a simple file browser
bind2maps menuselect -- -s '^o' accept-and-infer-next-history
bind2maps emacs viins vicmd -- -s '\e[1;5C' forward-word
bind2maps emacs viins vicmd -- -s '\e[1;5D' backward-word
'';
}
{
programs.zsh.initContent = ''
go-up() {
cd ..
_p9k_on_widget_send-break
}; zle -N go-up
bindkey '^[u' go-up
cd() {
if (( ''${#argv} == 1 )) && [[ -f ''${1} ]]; then
[[ ! -e ''${1:h} ]] && return 1
print "Correcting ''${1} to ''${1:h}"
builtin cd ''${1:h}
else
builtin cd "$@"
fi
}
cdt() {
builtin cd "$(mktemp -d)"
builtin pwd
}
mkcd() {
if (( ARGC != 1 )); then
printf 'usage: mkcd <new-directory>\n'
return 1;
fi
if [[ ! -d "$1" ]]; then
command mkdir -p "$1"
else
printf '`%s'\''' already exists: cd-ing.\n' "$1"
fi
builtin cd "$1"
}
# run command line as user root via doas:
function doas-command-line () {
[[ -z $BUFFER ]] && zle up-history
local cmd="doas "
if [[ $BUFFER == $cmd* ]]; then
CURSOR=$(( CURSOR-''${#cmd} ))
BUFFER="''${BUFFER#$cmd}"
else
BUFFER="''${cmd}''${BUFFER}"
CURSOR=$(( CURSOR+''${#cmd} ))
fi
zle reset-prompt
}
zle -N doas-command-line
bindkey "^od" doas-command-line
'';
}
Footnotes
-
I like Z-Shell, because it is mostly Bash compatible while providing many quality of life improvements. Having to learn a new language just for interactive usage only was the one off-putting thing for Fish for me. (For now at least.) ↩
-
Look at Atuin for a more comfortable way to search in your shell history. ↩
-
GRML is an interactive live-CD with nifty ZSH configuration. I originally found about it in Arch Linux. ↩