← Back to articles

Shell Line Editing with Zsh

Path: Computer Tech/Terminal/Shell Integrations/Shell Line Editing with Zsh.mdUpdated: 2/3/2026

Shell Line Editing with Zsh

Zsh provides powerful line editing capabilities through the ZLE (Zsh Line Editor) system. Understanding how to configure and use it will transform your command-line efficiency.

What is Shell Line Editing?

When you type in your terminal, you're not directly sending characters to programs. Instead, the shell (zsh, bash, etc.) captures your keystrokes and lets you edit the line before executing it.

This is Layer 3 in the Dev Environment Stack.

Two Editing Modes: Emacs vs Vi

Zsh supports two editing modes:

Emacs Mode (Default)

Uses Ctrl-based shortcuts:

  • Ctrl+A/E for line start/end
  • Ctrl+W to delete words
  • Ctrl+R for reverse search

This is what most people use and what Terminal Keyboard Shortcuts covers.

Vi Mode

Mimics Vim's modal editing in the shell:

  • Normal mode: Navigate with h/j/k/l
  • Insert mode: Type normally
  • Press Esc to switch to normal mode

Enable vi mode:

bash
# Add to ~/.zshrc
set -o vi

Customizing Line Editing

Check Current Mode

bash
# See current keybindings
bindkey

# Check if in vi or emacs mode
bindkey -l  # List keymaps

Emacs Mode Customizations

Add these to ~/.zshrc:

bash
# Ensure emacs mode
bindkey -e

# Custom keybindings
bindkey '^X^E' edit-command-line  # Ctrl+X Ctrl+E: Edit in $EDITOR
bindkey '^P' up-line-or-search    # Ctrl+P: Previous with search
bindkey '^N' down-line-or-search  # Ctrl+N: Next with search

# Make Option+arrows work on Mac
bindkey "^[^[[D" backward-word    # Option+Left
bindkey "^[^[[C" forward-word     # Option+Right

Vi Mode Power User Setup

bash
# Add to ~/.zshrc
set -o vi

# Show mode in prompt (insert vs normal)
function zle-line-init zle-keymap-select {
    VIM_PROMPT="%{$fg_bold[yellow]%} [% NORMAL]%  %{$reset_color%}"
    RPS1="${${KEYMAP/vicmd/$VIM_PROMPT}/(main|viins)/} $EPS1"
    zle reset-prompt
}

zle -N zle-line-init
zle -N zle-keymap-select

# Better vi mode bindings
bindkey -M vicmd 'k' history-beginning-search-backward
bindkey -M vicmd 'j' history-beginning-search-forward

Advanced ZLE Features

Edit Command in Your Editor

Sometimes a command is too complex to edit on one line. Open it in your full editor:

bash
# Add to ~/.zshrc
autoload -U edit-command-line
zle -N edit-command-line
bindkey '^X^E' edit-command-line  # Ctrl+X Ctrl+E

# Now when you press Ctrl+X Ctrl+E:
# 1. Opens your $EDITOR (nvim, vim, etc.)
# 2. You edit the command with full editor features
# 3. Save and quit β†’ command executes

History Substring Search

Search history based on what you've already typed:

bash
# Install the plugin
brew install zsh-history-substring-search

# Add to ~/.zshrc
source /opt/homebrew/share/zsh-history-substring-search/zsh-history-substring-search.zsh

# Bind to arrow keys
bindkey '^[[A' history-substring-search-up      # Up arrow
bindkey '^[[B' history-substring-search-down    # Down arrow

Usage:

bash
# Type partial command
docker run

# Press ↑ β†’ Cycles through all 'docker run' commands in history
# Much better than Ctrl+R for recent commands!

Syntax Highlighting

See your command highlighted as you type (like a modern IDE):

bash
# Install
brew install zsh-syntax-highlighting

# Add to ~/.zshrc (must be at END of file)
source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

What you get:

  • βœ… Valid commands show green
  • ❌ Invalid commands show red
  • πŸ“ Paths show underlined when they exist
  • πŸ”§ Options and flags show in different colors

Autosuggestions (Fish-like)

Get suggestions based on history as you type:

bash
# Install
brew install zsh-autosuggestions

# Add to ~/.zshrc
source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh

# Accept suggestion: Right arrow or Ctrl+E

Usage:

bash
# You start typing:
docker r

# Immediately shows gray suggestion:
docker run -it mycontainer bash
       ↑ (from your history)

# Press β†’ to accept

Complete Recommended Setup

Here's a production-ready ~/.zshrc line editing section:

bash
# ============================================
# ZSH Line Editing Configuration
# ============================================

# Use emacs mode (Ctrl-based shortcuts)
bindkey -e

# Custom keybindings
bindkey '^X^E' edit-command-line      # Ctrl+X Ctrl+E: Edit in editor
bindkey '^P' up-line-or-search        # Ctrl+P: Search up
bindkey '^N' down-line-or-search      # Ctrl+N: Search down

# Fix Option+arrows on Mac (if needed)
bindkey "^[^[[D" backward-word
bindkey "^[^[[C" forward-word

# Load edit-command-line widget
autoload -U edit-command-line
zle -N edit-command-line

# ============================================
# Enhanced Features (requires brew install)
# ============================================

# Syntax highlighting (must be near end of .zshrc)
source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

# Autosuggestions (Fish-like)
source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh

# History substring search
source /opt/homebrew/share/zsh-history-substring-search/zsh-history-substring-search.zsh
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down

# ============================================
# History Configuration
# ============================================

HISTFILE=~/.zsh_history
HISTSIZE=50000                 # Lines in memory
SAVEHIST=50000                 # Lines saved to file
setopt EXTENDED_HISTORY        # Save timestamp
setopt SHARE_HISTORY           # Share between sessions
setopt HIST_IGNORE_DUPS        # Don't save duplicates
setopt HIST_IGNORE_SPACE       # Ignore commands starting with space
setopt HIST_VERIFY             # Show before executing !

Troubleshooting

Option+Arrows Insert Weird Characters

Problem: Option+← inserts ^[b instead of moving back one word

Solution: Configure your terminal to send Option as Meta/Esc+:

  • Ghostty: Check preferences for "Option as Meta"
  • iTerm2: Preferences β†’ Profiles β†’ Keys β†’ Use Option as Meta key
  • Alacritty: Add to config:
    yaml
    key_bindings:
      - { key: Left, mods: Alt, chars: "\x1bb" }
      - { key: Right, mods: Alt, chars: "\x1bf" }
    

Ctrl+W Deletes Entire Path

Problem: Ctrl+W on /path/to/file deletes the whole thing, not just file

Solution: Customize word boundaries:

bash
# Add to ~/.zshrc
# Define word boundaries to treat / as a separator
WORDCHARS='*?_-.[]~=&;!#$%^(){}<>'
# (removed / from default WORDCHARS)

Commands Don't Save to History

Check:

bash
# Is history enabled?
echo $SAVEHIST

# Are you starting commands with a space? (HIST_IGNORE_SPACE)
 echo "This won't save"
echo "This will save"

Comparison: Emacs Mode vs Vi Mode

FeatureEmacs ModeVi Mode
Learning curveEasier (familiar Ctrl shortcuts)Steeper (modal editing)
EfficiencyGood for most usersBetter for Vim experts
Muscle memoryWorks everywhereConflicts with non-vi tools
RecommendationStart hereSwitch once you master Vim

Bottom line: Use emacs mode unless you're already a Vim power user. The Ctrl-based shortcuts work in most text fields (browsers, IDEs, etc.), while vi mode only helps in the shell.

Power User Workflow

My recommended progression:

  1. Week 1: Master basic shortcuts from Terminal Keyboard Shortcuts

    • Ctrl+A/E, Ctrl+W, Ctrl+R
  2. Week 2: Install syntax highlighting and autosuggestions

    bash
    brew install zsh-syntax-highlighting zsh-autosuggestions
    
  3. Week 3: Configure history substring search

    • Type docker run, press ↑ to cycle through docker run commands
  4. Week 4: Learn edit-command-line (Ctrl+X Ctrl+E)

    • Edit complex one-liners in Neovim
  5. Advanced: Consider vi mode if you're a Vim expert

Related: