Skip to content

F-Sy-H

Z-Shell logo

Feature-rich, interactive syntax highlighting for Zsh.

ZUnit status License

Features

  • Highlights Zsh syntax as a command line is edited.
  • Provides command-specific chroma highlighters for tools such as Git, Docker, grep, and make.
  • Supports shipped and user-defined themes through fsh_theme.
  • Inspects command-specific highlighters and their health through fsh_chroma.
  • Highlights nested command substitutions, arithmetic, strings, paths, and shell control structures.

A command line highlighted with an F-Sy-H theme

Requirements

  • Zsh 5.8 or newer
  • An interactive Zsh Line Editor session for live highlighting

Portable shell contract

  • Project identifier: fsh
  • Authoritative entrypoint: F-Sy-H.plugin.zsh
  • Public functions: fsh_chroma, fsh_theme, and fsh_plugin_unload
  • Public configuration context: :fsh:config
  • Autoload paths: functions/, completions/, and the private chroma/

The plugin has no public aliases or public parameters. Persistent implementation state and callbacks use the private _fsh_ prefix. Native completion naming is the required exceptions: completions for fsh_chroma and fsh_theme are _fsh_chroma and _fsh_theme.

Repository layout

  • F-Sy-H.plugin.zsh is the only entrypoint.
  • lib/ contains private code sourced eagerly by the entrypoint and is not on fpath.
  • functions/ contains one autoload function per file.
  • chroma/ contains private command-specific autoload functions.
  • completions/ contains native completion functions. The plugin makes this directory available on fpath but never calls compinit.
  • themes/ and share/ contain declarative INI data.
  • tests/integration/ and tests/unit/ contain executable integration profiles and ZUnit specifications, respectively.

Owned shell state

  • Public functions: fsh_chroma, fsh_theme, and fsh_plugin_unload
  • Private persistent functions and parameters: names beginning with _fsh_
  • Direct module requests: zsh/parameter, zsh/system, optional zsh/nearcolor, and interactive-only zsh/zleparameter
  • Hook: _fsh_preexec_hook in preexec_functions
  • Widgets: _fsh_check_path_handler_widget, _fsh_widget_* wrappers, and temporary fsh-orig-* saved-widget names

The unload function tracks modules loaded transitively during initialization and lazy plugin operations. It only claims modules that were not loaded before the plugin.

Installation

Zi

zi light z-shell/F-Sy-H

Zi is the reference plugin manager for Z-Shell documentation and validation.

Direct source

git clone https://github.com/z-shell/F-Sy-H.git ~/path/to/f-sy-h
source ~/path/to/f-sy-h/F-Sy-H.plugin.zsh

Other plugin managers

Managers that source conventional *.plugin.zsh entrypoints can load z-shell/F-Sy-H. Detailed manager-specific examples live in the F-Sy-H wiki guide.

Version 2 migration

This layout intentionally uses the Zsh Plugin Standard version 2 contract as a clean interface. Existing configurations need these changes:

  • Replace fast-theme and the f-sy-h alias with fsh_theme.
  • Replace FAST_WORK_DIR with zstyle ':fsh:config' work-dir ....
  • Replace ZSH_HIGHLIGHT_MAXLENGTH with zstyle ':fsh:config' max-length ....
  • Replace FAST_THEME_MANAGER_DISABLED=1 with zstyle ':fsh:config' theme-manager disabled.
  • Replace direct mutation of plugin globals with the documented settings below.
  • Reapply a theme with fsh_theme; executable legacy theme cache files are not loaded.

Legacy functions, aliases, parameters, and executable cache formats are not retained as a second compatibility interface.

Migrating from zsh-syntax-highlighting

F-Sy-H is not configuration-compatible with zsh-syntax-highlighting. Remove ZSH_HIGHLIGHT_STYLES and ZSH_HIGHLIGHT_HIGHLIGHTERS configuration when switching plugins. F-Sy-H does not read or translate either parameter. If one is already declared when F-Sy-H loads, the plugin prints one migration diagnostic without reading or changing its values.

Zsh requires associative arrays to be declared before assigning an element. For example, zsh-syntax-highlighting documents this sequence:

typeset -A ZSH_HIGHLIGHT_STYLES
ZSH_HIGHLIGHT_STYLES[comment]='fg=201'

Without the typeset -A line, Zsh reports assignment to invalid subscript range at the assignment itself. If that assignment appears before the F-Sy-H source or manager command, F-Sy-H has not run yet and cannot intercept the error. Remove the legacy block instead of moving it after the F-Sy-H load.

Replace the legacy controls as follows:

zsh-syntax-highlighting F-Sy-H replacement
ZSH_HIGHLIGHT_MAXLENGTH=1000 zstyle ':fsh:config' max-length 1000
ZSH_HIGHLIGHT_HIGHLIGHTERS=(main) Main syntax highlighting is integrated and always active.
Add brackets to ZSH_HIGHLIGHT_HIGHLIGHTERS zstyle ':fsh:config' bracket-highlighting enabled
Add pattern, regexp, cursor, root, or line No direct equivalent. Remove the entry or implement the behavior outside F-Sy-H.
ZSH_HIGHLIGHT_STYLES[...] Copy and edit an F-Sy-H INI theme, then apply it with fsh_theme.

Many common main highlighter style names map directly to F-Sy-H theme keys:

zsh-syntax-highlighting style F-Sy-H theme key
unknown-token, reserved-word, alias, suffix-alias, global-alias Same name
builtin, function, command, precommand, hashed-command Same name
commandseparator, path, path_pathseparator, globbing Same name
history-expansion, single-hyphen-option, double-hyphen-option Same name
back-quoted-argument, single-quoted-argument, double-quoted-argument Same name
dollar-quoted-argument, assign, redirection, comment, default Same name

Other zsh-syntax-highlighting keys do not have a one-to-one mapping. F-Sy-H uses more specific keys for arithmetic, loops, case blocks, here strings, bracket levels, directories, subcommands, and command option arguments. Start from a shipped theme so those F-Sy-H-specific styles retain valid fallbacks:

theme_dir=${XDG_CONFIG_HOME:-$HOME/.config}/f-sy-h
mkdir -p -- "$theme_dir"
fsh_theme --copy-shipped-theme default "$theme_dir/migrated"
# Edit "$theme_dir/migrated.ini", then apply it:
fsh_theme "$theme_dir/migrated.ini"

Theme INI values use red,bold for a foreground and bg:blue for a background. The corresponding zsh-syntax-highlighting forms are fg=red,bold and bg=blue. Run fsh_theme --help for theme commands and see the configuration section below for the full :fsh:config interface.

Configuration

All ordinary settings use :fsh:config. Set them before loading the plugin:

zstyle ':fsh:config' work-dir "${XDG_CACHE_HOME:-$HOME/.cache}/f-sy-h"
zstyle ':fsh:config' max-length 1000
zstyle ':fsh:config' theme-manager enabled
zstyle ':fsh:config' bracket-highlighting enabled
zstyle ':fsh:config' path-blocklist '/private/*' '/mnt/slow/**'
zstyle ':fsh:config' chroma-opt-in vim
zstyle ':fsh:config' chroma-cache-seconds 5
zstyle ':fsh:config' chroma-timeout-seconds 2
zi light z-shell/F-Sy-H

The settings are:

  • work-dir: scalar path, default ${XDG_CACHE_HOME:-$HOME/.cache}/f-sy-h.
  • max-length: non-negative integer, default 1000.
  • theme-manager: boolean-like scalar, default enabled.
  • bracket-highlighting: boolean-like scalar, default enabled.
  • path-blocklist: array of Zsh patterns excluded from path probing, empty by default.
  • chroma-opt-in: array containing vim, which, or both, empty by default. The vim chroma reads .viminfo and displays recent files. The which chroma runs multiple command-discovery tools while highlighting.
  • chroma-cache-seconds: non-negative lifetime for asynchronous chroma lookup results, default 5.
  • chroma-timeout-seconds: positive time budget for an asynchronous chroma worker, default 2. A worker that exceeds it is disabled for the session and reports one ZLE warning.

For boolean-like settings, disabled, false, no, off, and 0 disable the feature; any other value enables it.

At or below max-length, edits to independent simple command lists can reuse highlighting before a parser-confirmed top-level semicolon or newline. Quoting, redirections, assignments, aliases, chroma, control structures, changed theme or shell context, and other ambiguous input use a full parse. Bracket and string highlighting still scan the complete buffer. Buffers above the limit remain unhighlighted rather than switching to a degraded highlighting mode.

Theme file examples and additional usage guidance are documented in the wiki guide.

Usage

List registered command highlighters and classify files that are not directly registered:

fsh_chroma list

The KIND column distinguishes command-specific dedicated handlers from the shared generic fallback. The generic handler highlights the first non-option word as a subcommand, but it does not validate that word or parse command-specific options. A generic registry entry therefore does not represent the same depth of coverage as a dedicated handler. STATUS reports whether the target is ready, missing, or intentionally disabled.

Check registry reachability, declarative definitions, active theme styles, and session-disabled asynchronous lookups:

fsh_chroma doctor

Add a nine-run median of end-to-end highlighting time for one command line:

fsh_chroma doctor --sample 'docker image rm deadbeef'

The sample runs outside active ZLE, so it does not start asynchronous lookup workers. It measures the complete highlighting pass, not isolated chroma function time. doctor returns status 0 when healthy and 1 when it finds a problem; invalid command usage returns status 2.

List available themes:

fsh_theme --list

The listing includes each theme's intended background, whether its palette is fixed or terminal-owned, and a short description.

Compare every shipped theme using the same highlighted sample:

fsh_theme --gallery

Preview a theme on the next command line:

fsh_theme --test clean

Previewing does not change the active theme or saved theme files. Inspection commands (--help, --info, --palette, --list, --gallery, and --show) also leave the theme work directory untouched. The gallery preserves an existing one-shot preview as well as the active and saved themes. fsh_theme --show reports both the active and session-startup theme names and source paths.

Apply a theme:

fsh_theme clean

Match a Tinted Theming terminal palette

If Tinty or another Base16 manager sets your terminal's ANSI 16-color palette, select the shipped adaptive theme:

fsh_theme base16

This theme uses the terminal-owned foreground, background, and ANSI colors, so changing the terminal scheme also changes F-Sy-H without generating or downloading another theme file.

Lifecycle and side effects

Interactive loading:

  • adds functions/, completions/, and chroma/ to fpath when absent;
  • wraps existing ZLE widgets and creates the path-check handler widget;
  • registers _fsh_preexec_hook in preexec_functions;
  • loads the Zsh modules needed by highlighting; and
  • defines the documented functions and private state above.

Non-interactive loading defines the shell API but does not change widgets or install the preexec hook. Repeated sourcing is a no-op after a successful load.

fsh_plugin_unload removes plugin-owned hooks, widgets, functions, parameters, modules, and fpath entries. It restores state captured before the first load only while the installed value remains unchanged. A widget, function, or parameter changed after loading is preserved.

Loading performs no network request and does not create the cache directory. The explicit fsh_theme command creates storage only when it needs to write theme state. Preview and inspection do not create it. Saved state uses current_theme.ini, theme_overlay.ini, and secondary_theme.local.ini. These files are parsed as data. Legacy writable *.zsh theme caches are deliberately ignored and never sourced.

Verification

From the repository root:

zsh -f -n F-Sy-H.plugin.zsh lib/*.zsh functions/* completions/* chroma/*
zsh -f tests/integration/test-plugin-entrypoint.zsh
zsh -f tests/integration/test-plugin-lifecycle.zsh noninteractive
zsh -f -i tests/integration/test-plugin-lifecycle.zsh interactive
zsh -f tests/integration/test-function-completion.zsh
zsh -f tests/integration/test-git-chroma-regions.zsh
zsh -f tests/integration/test-passive-safety.zsh
zsh -f tests/integration/test-hostile-autoloads.zsh
zsh -f tests/integration/test-highlight-performance.zsh
zsh -f tests/integration/test-theme-persistence.zsh
zsh -f tests/integration/test-chroma-registry.zsh
zsh -f tests/integration/test-chroma-regions.zsh
zsh -f tests/integration/test-async-chroma.zsh
zsh -f tests/integration/test-theme-validator.zsh
zsh -f tools/validate-themes.zsh
zunit

The highlight-performance profile measures nine parses of representative, delimiter-free single commands at 173 and 1,000 characters after one warm-up run. Pull-request CI compares the medians with the base revision on the same runner and updates a PR comment with the relative difference. Hardware timing does not gate the build; empty highlighting, steady-state lifecycle refreshes, and failure to skip a buffer above the default limit remain test failures.

tools/validate-themes.zsh validates all shipped themes by default and accepts explicit INI paths as arguments. It emits one JSON Lines record per result or diagnostic using schema fsh-theme-validation/v1, and exits non-zero if any record has status set to error. Each record's nearcolor256 object maps the theme's distinct truecolor style literals to the xterm-256 indices selected by the installed Zsh zsh/nearcolor module. It is empty when the theme has no truecolor styles; an unavailable module produces a structured nearcolor-unavailable error.

Every shipped theme declares a [theme] rendering contract and a one-line description. Fixed-palette themes use palette = xterm-256 with exact foreground and background #rrggbb values. Their resolved ordinary styles must reach a contrast ratio of 4.5:1; unknown-token, incorrect-subtle, and matherr must reach 7:1. palette = terminal-ansi16 is adaptive and restricts colors to terminal-owned ANSI indices 0 through 15 instead of claiming a fixed contrast ratio. Rendering metadata and descriptions remain optional for external themes, preserving existing user themes. A supplied rendering contract receives the same validation as a shipped theme.

That contract also keeps semantically opposed styles distinguishable. The validator compares resolved rendering state rather than raw INI text, so named and indexed equivalents, backgrounds, attributes, none, and reverse are canonicalized before comparison. Single- and double-hyphen options, string and numeric option arguments, and subcommands and string option arguments must differ. Command and builtin styles, function and command styles, and alias and suffix-alias styles may intentionally share a rendering. Overlays are checked as partial customizations, so standalone overlay validation does not enforce these pair relationships against an unknown base theme.

Fixed-palette themes also keep correct-subtle and incorrect-subtle separated under the published Machado, Oliveira, and Fernandes severity-1.0 protanopia, deuteranopia, and tritanopia simulation matrices. The validator converts each simulated foreground and background pair to CIE 1976 Lab* and requires at least one channel to retain a project-defined distance of 20.0. Terminal-owned ANSI palettes and standalone overlays cannot make a fixed color claim, so this check does not apply to them. This regression guard is not a WCAG conformance claim or a substitute for a non-color correctness cue.

The ZUnit command requires the repository's pinned ZUnit toolchain.

Documentation and support

Release model

Contributions integrate on main. F-Sy-H is consumed directly from Git; version tags identify reviewed snapshots and do not introduce a separate package registry.

Contributing and license

Contributions follow the Z-Shell organization guidance. This project is distributed under the terms in LICENSE.