# 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
| Feature | Emacs Mode | Vi Mode |
|---------|------------|---------|
| **Learning curve** | Easier (familiar Ctrl shortcuts) | Steeper (modal editing) |
| **Efficiency** | Good for most users | Better for Vim experts |
| **Muscle memory** | Works everywhere | Conflicts with non-vi tools |
| **Recommendation** | Start here | Switch 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:**
- [[Terminal Keyboard Shortcuts]] - Complete shortcut reference
- [[Dev Environment Stack]] - How shell fits into the bigger picture
- [[Neovim/Neovim - The Vim Ecosystem]] - If you want to go all-in on vi mode