- 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.
- Zsh 5.8 or newer
- An interactive Zsh Line Editor session for live highlighting
- Project identifier:
fsh - Authoritative entrypoint:
F-Sy-H.plugin.zsh - Public functions:
fsh_chroma,fsh_theme, andfsh_plugin_unload - Public configuration context:
:fsh:config - Autoload paths:
functions/,completions/, and the privatechroma/
The plugin has no public aliases or public parameters. Persistent implementation state and callbacks use the private _fsh_ prefix, except for the compatibility callback below. Native completions for fsh_chroma and fsh_theme are _fsh_chroma and _fsh_theme.
The compatibility callback _zsh_highlight is a maintainer-approved namespace exception for issue #141. It prevents zsh-history-substring-search from erasing syntax highlights in either load order. It is not a public configuration interface and does not restore other legacy names. F-Sy-H leaves this callback untouched when ZSH_HIGHLIGHT_VERSION is declared.
On unload, F-Sy-H restores the prior callback or removes its own, preserving newer replacements. If history-substring-search loaded afterward and still needs the callback, unload leaves a standalone fallback that clears highlights on printable input, with no dependency on F-Sy-H. Widgets added or replaced by other plugins after loading remain theirs.
F-Sy-H.plugin.zshis the only entrypoint.lib/contains private code sourced eagerly by the entrypoint and is not onfpath.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 onfpathbut never callscompinit.themes/andshare/contain declarative INI data.tests/integration/andtests/unit/contain executable integration profiles and ZUnit specifications, respectively.
- Public functions:
fsh_chroma,fsh_theme, andfsh_plugin_unload - Private persistent functions and parameters: names beginning with
_fsh_ - Compatibility callback:
_zsh_highlight, subject to the exception above - Direct module requests:
zsh/parameter,zsh/system, optionalzsh/nearcolor, and interactive-onlyzsh/zleparameter - Hook:
_fsh_preexec_hookinpreexec_functions - Widgets:
_fsh_widget_*wrappers and temporary_fsh_orig-*saved-widget names; asynchronous callbacks are plainzle -Fhandlers, not widgets
The unload function tracks modules loaded transitively during initialization and lazy plugin operations. It only claims modules that were not loaded before the plugin.
zi light z-shell/F-Sy-HZi is the reference plugin manager for Z-Shell documentation and validation.
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.zshManagers 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.
This layout intentionally uses the Zsh Plugin Standard version 2 contract as a clean interface. Existing configurations need these changes:
- Replace
fast-themeand thef-sy-halias withfsh_theme. - Replace
FAST_WORK_DIRwithzstyle ':fsh:config' work-dir .... - Replace
ZSH_HIGHLIGHT_MAXLENGTHwithzstyle ':fsh:config' max-length .... - Replace
FAST_THEME_MANAGER_DISABLED=1withzstyle ':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.
Apart from the documented _zsh_highlight callback for history-substring-search, legacy functions, aliases, parameters, and executable cache formats are not retained as a second compatibility interface.
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.
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' git-message-length 72
zstyle ':fsh:config' theme-manager enabled
zstyle ':fsh:config' bracket-highlighting enabled
zstyle ':fsh:config' color-preview 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-HThe settings are:
work-dir: scalar path, default${XDG_CACHE_HOME:-$HOME/.cache}/f-sy-h.max-length: non-negative integer, default1000.git-message-length: non-negative integer, default72. Thegitchroma marks the part of a commit, merge, or tag subject line beyond this many characters with theincorrect-subtlestyle. Only the first-m,--message VALUE, or--message=VALUEargument is checked; later ones are body paragraphs.0disables the check.theme-manager: boolean-like scalar, defaultenabled.bracket-highlighting: boolean-like scalar, defaultenabled.color-preview: boolean-like scalar, defaultenabled. A#rrggbb,#rgb, orrgb(rr,gg,bb)literal delimited by the token boundary or non-alphanumeric characters is shown on its own color, with a black or white foreground chosen for contrast. The preview covers only the literal and is applied after the token's ordinary style, so quoted arguments keep their configured style.path-blocklist: array of Zsh patterns excluded from path probing, empty by default.chroma-opt-in: array containingvim,which, or both, empty by default. Thevimchroma reads.viminfoand displays recent files. Thewhichchroma runs multiple command-discovery tools while highlighting.chroma-cache-seconds: non-negative lifetime for asynchronous chroma lookup results, default5.chroma-timeout-seconds: positive time budget for an asynchronous chroma worker, default2. When a pending lookup is revisited after this budget, its worker is cancelled, the lookup is disabled for the session, and one ZLE warning is reported.
For boolean-like settings, disabled, false, no, off, and 0 disable
the feature; any other value enables it.
At or below max-length, changed buffers receive a full parse. Cursor-only
updates can reuse the main highlighting while updating brackets and regions.
Buffers above the limit skip highlighting.
Theme file examples and additional usage guidance are documented in the wiki guide.
List registered command highlighters and classify files that are not directly registered:
fsh_chroma listThe 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.
hub and lab use generic coverage; their older dedicated sources are retired.
The unmaintained zmanage mapping is removed. Git repository queries and manual
page lookups use the shared asynchronous worker. Tokens remain neutral until
valid knowledge arrives and the next key press repaints the line; the worker
callback never runs a widget between key presses, so LASTWIDGET and the kill
and yank state stay untouched. Failed refreshes preserve the last valid result.
Makefile variable expansion and static local include, -include, and
sinclude discovery stay in the current shell without subprocesses. A refresh
parses at most 16,384 characters across 32 files and eight include levels.
Dynamic, unsupported, cyclic, missing mandatory, or over-budget input skips
target validation so highlighting remains responsive and does not report false
errors. Cache entries are scoped to the selected Makefile and invalidated when
an included input changes.
Check registry reachability, declarative definitions, active theme styles, and session-disabled asynchronous lookups:
fsh_chroma doctorAdd 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 --listThe 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 --galleryPreview a theme on the next command line:
fsh_theme --test cleanPreviewing 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 cleanIf Tinty or another Base16 manager sets your terminal's ANSI 16-color palette, select the shipped adaptive theme:
fsh_theme base16This 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.
Interactive loading:
- adds
functions/,completions/, andchroma/tofpathwhen absent; - wraps existing ZLE widgets and creates the path-check handler widget;
- registers
_fsh_preexec_hookinpreexec_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.
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-zsh-highlight-compat.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-color-preview-regions.zsh
zsh -f tests/integration/test-async-chroma.zsh
zsh -f tests/integration/test-theme-validator.zsh
zsh -f tools/validate-themes.zsh
zunitThe 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. Three outcomes remain test failures: empty
highlighting, a lifecycle refresh on the widget path (in steady state or in
the first parse that materializes a chroma), and failure to skip a buffer
above the default limit. While _fsh_preexec_hook is installed, the
lifecycle accounting for a materialized chroma waits for that hook, so it runs
between command lines; without the hook it runs at once. Either way it records
only the functions and parameters the chroma added, so changes the caller made
since loading are never mistaken for the plugin's own. The lifecycle profile
checks every chroma registered by default against the full accounting.
Parameters are compared by an in-process signature (type and attribute words,
element count, and each element quoted) rather than by typeset -p output,
so loading and accounting fork only to list the loaded modules.
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.
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.
Contributions follow the Z-Shell organization guidance. F-Sy-H retains the upstream BSD-3-Clause terms in LICENSE. It is licensing class L3 (third-party derived code) under the organization licensing policy.
