| name | shell-prompt |
| description | Modern shell prompt configuration with Powerlevel10k and Zsh Vi Mode. Use when configuring shell prompts, setting up vi/vim keybindings in zsh, customizing cursor styles per mode, adding mode indicators, optimizing prompt performance, or troubleshooting slow prompts. Covers P10k instant prompt, vi mode plugins, and cursor customization. |
Shell Prompt Skill
Configure high-performance shell prompts with Powerlevel10k and Zsh Vi Mode.
Overview
Modern shell prompts provide:
- Git status with branch, dirty state, and remote tracking
- Environment indicators (Python venv, Node version, K8s context)
- Execution time for long-running commands
- Exit code visualization
- Async updates for responsive experience
- Vi mode indicators and cursor changes
Zsh Vi Mode
Zsh supports vi-style line editing with visual feedback through cursor changes and mode indicators.
Quick Setup (Built-in)
# ~/.zshrc
bindkey -v # Enable vi mode
# Reduce key timeout for faster mode switching (default 400ms)
export KEYTIMEOUT=10 # 100ms - don't go below 10
Cursor Style by Mode
Change cursor shape based on current mode:
# Add to ~/.zshrc
cursor_mode() {
# Beam cursor for insert mode
cursor_beam='\e[6 q'
# Block cursor for normal mode
cursor_block='\e[2 q'
function zle-keymap-select {
if [[ ${KEYMAP} == vicmd ]] ||
[[ $1 = 'block' ]]; then
echo -ne $cursor_block
elif [[ ${KEYMAP} == main ]] ||
[[ ${KEYMAP} == viins ]] ||
[[ ${KEYMAP} = '' ]] ||
[[ $1 = 'beam' ]]; then
echo -ne $cursor_beam
fi
}
zle-line-init() {
echo -ne $cursor_beam
}
zle -N zle-keymap-select
zle -N zle-line-init
}
cursor_mode
Cursor Escape Codes
| Code | Style |
|---|---|
\e[1 q |
Blinking block |
\e[2 q |
Steady block |
\e[3 q |
Blinking underline |
\e[4 q |
Steady underline |
\e[5 q |
Blinking bar/beam |
\e[6 q |
Steady bar/beam |
Vi Mode Plugins
Oh My Zsh vi-mode Plugin
# ~/.zshrc
plugins=(... vi-mode)
# Configuration (before sourcing oh-my-zsh.sh)
VI_MODE_SET_CURSOR=true
VI_MODE_RESET_PROMPT_ON_MODE_CHANGE=true
# Cursor styles (0-6)
VI_MODE_CURSOR_NORMAL=2 # Solid block
VI_MODE_CURSOR_INSERT=6 # Solid beam
VI_MODE_CURSOR_VISUAL=6 # Solid beam
VI_MODE_CURSOR_OPPEND=0 # Blinking block
# Mode indicators
MODE_INDICATOR="%F{red}<<<NORMAL%f"
INSERT_MODE_INDICATOR="%F{green}<<<INSERT%f"
softmoth/zsh-vim-mode
Full-featured vi mode with text objects and surround bindings.
Installation:
# Clone
git clone https://github.com/softmoth/zsh-vim-mode.git ~/.zsh/zsh-vim-mode
# Source in .zshrc (after other plugins)
source ~/.zsh/zsh-vim-mode/zsh-vim-mode.plugin.zsh
Load order matters: zsh-autosuggestions -> zsh-syntax-highlighting -> zsh-vim-mode
Configuration:
# Cursor styles (supports colors!)
MODE_CURSOR_VIINS="#00ff00 blinking bar"
MODE_CURSOR_VICMD="green block"
MODE_CURSOR_REPLACE="red block"
MODE_CURSOR_SEARCH="#ff00ff steady underline"
MODE_CURSOR_VISUAL="$MODE_CURSOR_VICMD steady bar"
MODE_CURSOR_VLINE="$MODE_CURSOR_VISUAL #00ffff"
# Mode indicators (auto-added to RPS1 if unset)
MODE_INDICATOR_VIINS='%F{15}<%F{8}INSERT>%f'
MODE_INDICATOR_VICMD='%F{10}<%F{2}NORMAL>%f'
MODE_INDICATOR_REPLACE='%F{9}<%F{1}REPLACE>%f'
MODE_INDICATOR_SEARCH='%F{13}<%F{5}SEARCH>%f'
MODE_INDICATOR_VISUAL='%F{12}<%F{4}VISUAL>%f'
MODE_INDICATOR_VLINE='%F{12}<%F{4}V-LINE>%f'
# Other options
VIM_MODE_VICMD_KEY='^[' # Default escape key
VIM_MODE_TRACK_KEYMAP=true # Enable mode tracking
VIM_MODE_INITIAL_KEYMAP=viins # Start in insert mode
Features:
- Text objects:
ci",da(,vi[ - Surround:
cs"'(change surrounding " to ') - Visual mode selection
- Emacs bindings in insert mode (Ctrl-A, Ctrl-E)
jeffreytse/zsh-vi-mode
Modern vi mode with operator-pending mode support.
Installation:
# With zinit
zinit ice depth=1
zinit light jeffreytse/zsh-vi-mode
# Manual
git clone https://github.com/jeffreytse/zsh-vi-mode.git ~/.zsh/zsh-vi-mode
source ~/.zsh/zsh-vi-mode/zsh-vi-mode.plugin.zsh
Configuration:
# Cursor styles
ZVM_NORMAL_MODE_CURSOR=$ZVM_CURSOR_BLOCK
ZVM_INSERT_MODE_CURSOR=$ZVM_CURSOR_BEAM
ZVM_VISUAL_MODE_CURSOR=$ZVM_CURSOR_BLOCK
ZVM_VISUAL_LINE_MODE_CURSOR=$ZVM_CURSOR_BLOCK
ZVM_OPPEND_MODE_CURSOR=$ZVM_CURSOR_UNDERLINE
# Mode indicator in prompt
function zvm_after_select_vi_mode() {
case $ZVM_MODE in
$ZVM_MODE_NORMAL)
# Update prompt for normal mode
;;
$ZVM_MODE_INSERT)
# Update prompt for insert mode
;;
$ZVM_MODE_VISUAL)
# Update prompt for visual mode
;;
esac
}
# Disable cursor style changes (if using another method)
ZVM_CURSOR_STYLE_ENABLED=false
Key Bindings Reference
Mode Switching
| Key | Action |
|---|---|
ESC or Ctrl-[ |
Enter Normal mode |
i |
Insert before cursor |
a |
Append after cursor |
I |
Insert at line start |
A |
Append at line end |
v |
Enter Visual mode |
V |
Enter Visual Line mode |
Navigation (Normal Mode)
| Key | Action |
|---|---|
h/l |
Left/right |
j/k |
Down/up in history |
w/W |
Forward word |
b/B |
Backward word |
e/E |
End of word |
0 |
Start of line |
^ |
First non-blank |
$ |
End of line |
f{char} |
Find char forward |
F{char} |
Find char backward |
t{char} |
Till char forward |
T{char} |
Till char backward |
Editing (Normal Mode)
| Key | Action |
|---|---|
x |
Delete char |
dd |
Delete line |
D |
Delete to end |
cc |
Change line |
C |
Change to end |
yy |
Yank line |
p/P |
Paste after/before |
u |
Undo |
Ctrl-r |
Redo |
Text Objects
| Key | Action |
|---|---|
ciw |
Change inner word |
daw |
Delete a word (with space) |
ci" |
Change inside quotes |
da( |
Delete around parens |
vi[ |
Select inside brackets |
KEYTIMEOUT Considerations
The KEYTIMEOUT variable affects multi-key sequences:
# Default is 40 (400ms)
export KEYTIMEOUT=10 # 100ms - good balance
# Too low (<10) breaks multi-key bindings
# Too high (>40) feels sluggish on ESC
Workarounds for escape delay:
# Option 1: Use Ctrl-[ instead of Escape
# (Ctrl-[ sends ESC immediately)
# Option 2: Bind jk or jj to escape
bindkey -M viins 'jk' vi-cmd-mode
bindkey -M viins 'jj' vi-cmd-mode
Powerlevel10k
Installation
# With Oh My Zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
# Set in .zshrc
ZSH_THEME="powerlevel10k/powerlevel10k"
# Run configuration wizard
p10k configure
Instant Prompt Setup
Add at the very top of ~/.zshrc (before anything else):
# Enable Powerlevel10k instant prompt
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
Add at the end of ~/.zshrc:
# Source Powerlevel10k config
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
Configuration Options
Key settings in ~/.p10k.zsh:
# Left prompt segments
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
os_icon
dir
vcs
newline
prompt_char
)
# Right prompt segments
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
status
command_execution_time
background_jobs
virtualenv
kubecontext
azure
aws
vi_mode # Show vi mode indicator!
context
time
)
# Transient prompt (clean up previous prompts)
typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=always
# Directory truncation
typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=3
# Vi mode indicator styling
typeset -g POWERLEVEL9K_VI_INSERT_MODE_STRING=''
typeset -g POWERLEVEL9K_VI_COMMAND_MODE_STRING='NORMAL'
typeset -g POWERLEVEL9K_VI_MODE_NORMAL_FOREGROUND=0
typeset -g POWERLEVEL9K_VI_MODE_NORMAL_BACKGROUND=2
Performance Tuning
# Disable slow segments
typeset -g POWERLEVEL9K_DISABLE_GITSTATUS=false # Keep enabled!
# Large repo optimization
typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=1000
# Async git status (default, don't change)
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
# Reduce segment count for speed
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status command_execution_time vi_mode)
Performance Summary
Benchmark Results (zsh-bench)
| Metric | Target | Powerlevel10k |
|---|---|---|
| First prompt lag | <50ms | 24ms |
| Command lag | <10ms | 15ms |
| Git status (small) | <30ms | <10ms |
| Git status (large) | <100ms | Async/instant |
Architecture
Powerlevel10k (gitstatus daemon):
┌─────────────┐ pipes ┌─────────────┐
│ Zsh │ <============> │ gitstatusd │
│ (prompt) │ │ (C++ daemon)
└─────────────┘ └─────────────┘
│ │
│ async │ keeps state
│ never blocks │ in memory
▼ ▼
Instant prompt Fast git queries
Benchmarking Your Setup
Using zsh-bench
# Install
git clone https://github.com/romkatv/zsh-bench ~/zsh-bench
# Run benchmark
~/zsh-bench/zsh-bench
# Key metrics to watch:
# - first_prompt_lag_ms: <50ms ideal
# - command_lag_ms: <10ms ideal
Manual Timing
# Zsh startup time
time zsh -i -c exit
# Per-command timing
TIMEFMT='%*E seconds'
time (for i in {1..10}; do zsh -i -c 'print -P "$PROMPT"' >/dev/null; done)
Troubleshooting
Slow Prompt
# Check segment timing
zsh -xv # Verbose trace
# Common culprits:
# - git_status in large repos
# - python/node version detection
# - cloud context (aws/azure/gcloud)
P10k: gitstatus Failed
# Reinstall gitstatusd
rm -rf ~/.cache/gitstatus
# Restart zsh
exec zsh
Vi Mode Not Working
# Verify vi mode is enabled
bindkey -l | grep vi
# Check current keymap
echo $KEYMAP
# Reset bindings
bindkey -v
Cursor Not Changing
- Verify terminal supports cursor escape codes
- Check
zle-keymap-selectis defined:whence -f zle-keymap-select - Some terminals (like Apple Terminal) have limited cursor support
- Try iTerm2 or Alacritty for full support
References
- references/powerlevel10k-config.md - Complete P10k configuration
- references/zsh-vim-mode.md - softmoth/zsh-vim-mode details
- references/performance-tuning.md - Advanced optimization
- references/troubleshooting.md - Common issues and fixes
External Links
- Powerlevel10k: https://github.com/romkatv/powerlevel10k
- softmoth/zsh-vim-mode: https://github.com/softmoth/zsh-vim-mode
- jeffreytse/zsh-vi-mode: https://github.com/jeffreytse/zsh-vi-mode
- Oh My Zsh vi-mode: https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/vi-mode
- zsh-bench: https://github.com/romkatv/zsh-bench
- gitstatus: https://github.com/romkatv/gitstatus