DEV Community

Cover image for I Built a Pure-Zsh Prompt Engine That Renders in 0.6ms (Without Rust or C++)
Cason Adams
Cason Adams

Posted on

I Built a Pure-Zsh Prompt Engine That Renders in 0.6ms (Without Rust or C++)

For years, developers optimizing their shell prompts have faced a binary dilemma:

  1. Starship: Beautiful, portable, and feature-packed, but written in Rust. Every single prompt render forks an external process (starship prompt). On macOS security layers or large monorepos, process invocation latency adds up.
  2. Powerlevel10k: Blazing fast, but relies on a compiled C++ companion binary (gitstatusd) and requires a generated configuration file (~/.p10k.zsh) that often exceeds 1,600 lines of uppercase global variables (POWERLEVEL9K_*).

I wanted something different:

  • 100% Pure Zsh: Zero compiled binaries, zero C++ daemons, zero external dependencies. Runs on any platform with Zsh 5.8+ (macOS, Linux, BSDs, Alpine/musl, Termux).
  • Sub-millisecond synchronous rendering: Renders in under 0.7 ms.
  • Zero subshell forks on the hot path: No $(...), no backticks, no piping through sed/awk.
  • Clean declarative configuration: Concise Zsh arrays instead of hundreds of global environment variables.

That project is zline. Here is how it works under the hood.


1. The Core Performance Invariant: Zero Subshell Forks

In shell scripting, subshells are expensive:

# This forks a new process:
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)

# This forks another process:
formatted=$(echo "$current_branch" | tr '[:lower:]' '[:upper:]')
Enter fullscreen mode Exit fullscreen mode

If your prompt executes 4 to 8 subshells every time you press Enter, prompt rendering latency quickly jumps to 15-50 ms. On systems with endpoint security scanners (e.g., macOS Endpoint Security or Windows Defender on WSL), process execution overhead can be even higher.

In zline, the synchronous render path has a strict rule enforced by automated CI audits: 0 subshells allowed.

Everything on the critical render path relies strictly on native Zsh builtins and parameter expansion:

  • Instead of sed or tr: Native parameter substring replacement ${var//search/replace}.
  • Instead of basename or dirname: Path modifiers ${var:t} and ${var:h}.
  • Instead of wc -l: Array length expansions ${#array} and math evaluation $(( ... )).
  • Instead of cut: Splitting expansion ${(s/:/)var}.
  • Instead of join: Joining expansion ${(j: :)items}.

2. Two-Phase Git Status Detection

Git repositories are the primary bottleneck for shell prompts. Running git status synchronously in a 500k-file repository can freeze your prompt for seconds.

zline uses a two-phase architecture:

Phase 1: Direct File System Read (0.07 ms)

Before touching the git binary, zline checks .git/HEAD directly using pure Zsh file descriptors:

_zline_git_read_head() {
  emulate -L zsh
  local dir="$1"
  local git_path="${dir}/.git"

  # Handle .git worktrees / submodules
  if [[ -f "$git_path" ]]; then
    local line
    read -r line < "$git_path" 2>/dev/null
    if [[ "$line" == "gitdir: "* ]]; then
      local gd="${line#gitdir: }"
      [[ "$gd" != /* ]] && gd="${dir}/${gd}"
      git_path="$gd"
    fi
  fi

  local head_file="${git_path}/HEAD"
  [[ -r "$head_file" ]] || return 1

  local head
  read -r head < "$head_file" 2>/dev/null
  if [[ "$head" == "ref: refs/heads/"* ]]; then
    REPLY="${head#ref: refs/heads/}"
  elif [[ -n "$head" ]]; then
    REPLY="${head[1,7]}" # Detached commit SHA
  fi

  # Check merge / rebase / cherry-pick states
  if [[ -f "${git_path}/MERGE_HEAD" ]]; then
    REPLY="${REPLY}|MERGING"
  elif [[ -d "${git_path}/rebase-merge" ]]; then
    REPLY="${REPLY}|REBASE"
  fi
}
Enter fullscreen mode Exit fullscreen mode

Reading and parsing .git/HEAD in pure Zsh executes in 0.072 ms. Your branch name, detached state, and rebase/merge status appear instantly on every keystroke.

Phase 2: Asynchronous Non-Blocking Worker via zle -F

To calculate dirty status, untracked counts, and ahead/behind commits without freezing the terminal, zline starts a persistent background worker:

  1. Communication happens over named pipes (FIFOs) using POSIX mkfifo.
  2. The background worker queries git status --porcelain=v2 --branch off the UI thread.
  3. When results arrive, Zsh receives an event via its native file descriptor event listener:
zle -F "$_zline_worker_res_fd" _zline_worker_zle_handler
Enter fullscreen mode Exit fullscreen mode

When the worker finishes, Zsh immediately triggers the handler, updates the internal Git cache, and calls zle reset-prompt. No CPU polling, no timer loops, and zero prompt lag.


3. Compile Once, Render in Microseconds

Shell prompts typically parse user options on every render loop. If your configuration allows flags like --shorten 1 --anchor git, re-parsing those strings on every precmd burns CPU cycles.

zline separates compilation from rendering:

[~/.zshrc Evaluated]
        │
        ▼
   zline init
        │
        ├── Tokenize & Parse Flags Once
        ├── Build Static Argument Arrays
        └── Compile Zsh Wordcode (.zwc)
        │
[Interactive Shell Active]
        │
        ▼
  Every Enter Key ──▶ Direct Function Calls via Pre-compiled Arrays (< 0.7 ms)
Enter fullscreen mode Exit fullscreen mode

At shell startup (zline init), segment definitions are tokenized into indexed function references:

typeset -ga _zline_compiled_left_names=('dir' 'git' 'newline' 'prompt_char')
typeset -ga _zline_compiled_left_args=('shorten=1 anchor=git' 'clean=2 dirty=3' '' '')
Enter fullscreen mode Exit fullscreen mode

During active typing, the render engine simply loops through pre-indexed arrays and invokes segment functions without calling zparseopts or evaluating regular expressions.

Additionally, zline compile compiles all engine source files into Zsh Wordcode (.zwc) via zcompile -R, skipping text-parsing overhead on subsequent terminal launches.


4. Replacing 1,600 Lines of Config with Declarative Arrays

Powerlevel10k configurations often require a massive ~/.p10k.zsh full of global environment variables:

# Powerlevel10k approach:
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(dir vcs newline prompt_char)
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
typeset -g POWERLEVEL9K_DIR_ANCHOR_FILES=(.git)
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=2
typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=3
typeset -g POWERLEVEL9K_STATUS_OK=false
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=2
# ... hundreds more lines ...
Enter fullscreen mode Exit fullscreen mode

In zline, configuration is declared via readable arrays directly in your ~/.zshrc:

# zline approach:
zline preset powerline --transient

zline_left=(
  'dir --shorten 1 --anchor git'
  'git --clean 2 --dirty 3'
  newline
  prompt_char
)

zline_right=(
  'status --hide-zero'
  'exec_time --min 2'
)

zline init
Enter fullscreen mode Exit fullscreen mode

5. Base16 First: Harmonious by Default

Hardcoded hex colors (#ff5555, #8be9fd) look great until you change your terminal theme from Dracula to Tokyo Night, leaving your prompt colors mismatched.

zline standardizes on the Base16 / Term16 ANSI color space (colors 0 through 15). Each segment references standard semantic terminal color slots:

  • 2: Success / Clean VCS (Terminal Green)
  • 3: Warning / Dirty VCS (Terminal Yellow)
  • 1: Error / Failure (Terminal Red)
  • 4: Primary / Directory (Terminal Blue)
  • 8: Connecting lines / Frames (Bright Black / Muted Gray)

Whether you switch your terminal emulator between Catppuccin, Nord, Gruvbox, or Solarized, zline adapts automatically with zero configuration edits.


6. Real-World Benchmarks

Measured on an Apple Silicon arm64 machine running Zsh 5.9 over 1,000 iterations:

Benchmark Operation zline Latency Target SLA Status
Sync Prompt Render (Powerline) 0.66 ms < 1.50 ms PASS
Sync Prompt Render (Lean) 0.52 ms < 1.00 ms PASS
Directory Shortening & Anchor 0.10 ms < 0.15 ms PASS
Synchronous Git HEAD Reader 0.072 ms < 0.15 ms PASS
Instant Prompt Load 0.057 ms < 0.50 ms PASS

Compared to typical external prompts spawning processes on every return key (15-30 ms), zline stays well below the 1-millisecond mark.


7. Modern Terminal Integration (OSC Protocols)

Beyond basic prompt drawing, zline includes native support for modern terminal emulator escape sequences:

  • OSC 133 (Semantic Shell Integration): Emits A, B, C, and D escape markers so terminals like Kitty, iTerm2, WezTerm, and Ghostty know exactly where commands begin, end, and output.
  • OSC 7 (Working Directory Reporting): Guarantees new terminal tabs open in the current directory.
  • OSC 8 (Hyperlinks): Turns file paths into clickable links.
  • Transient Prompt: Collapses historical multi-line prompts down to a clean ❯ glyph on Enter, keeping terminal scrollback clean.

Quickstart

You can test zline in seconds.

Manual Setup

git clone https://github.com/casonadams/zline.git ~/.zline
Enter fullscreen mode Exit fullscreen mode

Add this to your ~/.zshrc:

source ~/.zline/zline.zsh

zline preset powerline --transient
zline init
Enter fullscreen mode Exit fullscreen mode

Or test presets interactively in your current shell:

zline preset lean
zline preset catppuccin
zline preset rainbow
Enter fullscreen mode Exit fullscreen mode

There is also an interactive web playground where you can preview configurations in real time: casonadams.github.io/zline.


Takeaways

Writing performant shell plugins doesn't always require compiling to Rust or pulling down C++ helper daemons. By respecting shell architecture—eliminating subshells, reading state directly via native parameter expansions, and leveraging Zsh features like zle -F and .zwc wordcode compilation—you can build tools that are lightweight, maintainable, and fast.

Check out the project on GitHub: github.com/casonadams/zline. Feedback, benchmarks, and PRs are welcome!

Top comments (0)