CLI tool
zsh-users/zsh-history-substring-search avatar
zsh-users/zsh-history-substring-search

zsh-history-substring-search: six install paths, four binding recipes, and one variable nobody explains

🐠 ZSH port of Fish history search (up arrow)

3,118 stars171 forksShellLicense varies

At a glance

What is it?
zsh-users' plugin is a clean-room port of the Fish shell's substring history search for zsh, four root files and no license file beside them. Five of its six install paths stop before the key bindings, and the sixth is the only one that shows them.
Who is it for?
Worth installing if you keep retyping the middle of long commands, and cheap to remove since it is two files and one source line. Pick Homebrew or Oh-my-zsh for a maintained path, copy the bindkey lines from the antigen example because the other five install sections never give you any, and source zsh-syntax-highlighting first if you use it.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 57 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • zsh-users/zsh-history-substring-search3,118 ★ · +11
Star history
Repository2026-09-242026-11-14
zsh-users/zsh-history-substring-search3.1k3.1k

11 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A clean-room port of one Fish feature, needing zsh 4.3

The whole project is one feature borrowed from another shell. It is described as a clean-room implementation of the Fish shell's history search, where you type any part of any command from history and then press chosen keys, such as the UP and DOWN arrows, to cycle through matches. The requirement is a single line: ZSH 4.3 or newer.

The provenance is declared rather than implied. Six link definitions sit under the opening paragraph, pointing at the Fish shell site, a 2009 message on the zsh-users mailing list, the fizsh project on sourceforge, a pull request against oh-my-zsh, this repository, and zsh-syntax-highlighting. In the text you can read, only two of the six are actually cited: the Fish link and the syntax-highlighting one. The other four are there for material further down.

Clean-room means the behaviour was written from the description rather than copied from Fish's source, which is why the notes that follow are about terminals and key codes rather than about algorithms.

Two working files at the root, and no license file next to them

The repository root holds four entries: .gitignore, README.md, zsh-history-substring-search.plugin.zsh, and zsh-history-substring-search.zsh.

Two of those do the work. The .zsh file is the script you source into an interactive session. The .plugin.zsh file is the shim that plugin managers load, and it is the reason the same repository works under six different install paths without six different instructions. There is no build step, no test directory, and no packaging manifest of its own.

What is missing is as interesting as what is there. Nothing in the project's own metadata names a license, and no LICENSE file appears at the root beside either script. For a two-file shell script that people clone into their own dotfile repository, that gap is the one thing to settle before the code travels anywhere it might be redistributed.

Six install paths, and five of them stop before the key bindings

Installation is documented six times, once per plugin manager, and the paths differ in what they leave you to do.

Homebrew appends a source line to your .zshrc:

code
brew install zsh-history-substring-search
echo 'source $(brew --prefix)/share/zsh-history-substring-search/zsh-history-substring-search.zsh' >> ~/.zshrc

Fig is one click. Oh-my-zsh wants a clone into the custom plugins directory, an entry in the plugins array, and exec zsh. Zinit wants a snippet, a load line, and an ice that calls a config function. zplug is a single line. Only antigen shows the rest of the job:

code
# zsh-history-substring-search configuration
bindkey '^[[A' history-substring-search-up # or '\eOA'
bindkey '^[[B' history-substring-search-down # or '\eOB'
HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE=1

Those lines belong after antigen apply, because the bundle call rewrites the configuration that follows it. The other five paths load the script and stop, leaving the bindings to a reader who has already guessed them.

The arrow keys are ambiguous, and cat -v is documented as unreliable

Binding the arrows is the part of this project that needs its own procedure, and the procedure admits its own first step can fail.

Run cat -v in a terminal, press UP and DOWN, and note what appears; press Control and C to finish. The note warns that in some cases cat -v shows the wrong key codes, and that the correct ones come from pressing Control and V together with UP or DOWN at the prompt. From there, four binding recipes are offered:

code
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
bindkey "$terminfo[kcuu1]" history-substring-search-up
bindkey "$terminfo[kcud1]" history-substring-search-down
bindkey -M emacs '^P' history-substring-search-up
bindkey -M emacs '^N' history-substring-search-down
bindkey -M vicmd 'k' history-substring-search-up
bindkey -M vicmd 'j' history-substring-search-down

The first pair is what most terminals report, the second asks terminfo instead, and the last two cover EMACS and VI keymaps. One inconsistency survives: the binding comment writes the alternative as '\eOA', while the prose says users have found [OA and [OB correct even when those were not the values observed. Same sequence, two notations.

zsh-syntax-highlighting has to be sourced before the script

Loading order is a stated requirement, not a preference. If you use zsh-syntax-highlighting, the instruction is to load it before this script:

code
source zsh-syntax-highlighting.zsh
source zsh-history-substring-search.zsh

The reason is visible in the configuration section. The script highlights the matched substring itself: HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND defines how the query is highlighted inside a matching command, and its default value renders the query in bold white text on a magenta background. The reference for the kinds of values the variable accepts is the Character Highlighting section of the zshzle(1) man page, which is where the line editor's own highlighting vocabulary is defined.

So the plugin draws its own highlight through the line editor. Anything that redraws the buffer afterwards has the last word, which is why the highlighting plugin is asked to go first rather than last.

Up means older, down means newer, and the same key moves lines first

The search functions are defined by age, not by direction on screen. Type any part of any previous command, and history-substring-search-up selects the nearest command that both contains the query and is older than the current one in your command history. history-substring-search-down selects the nearest that contains the query and is newer. Control and U aborts the search.

Multi-line commands change what the keys do. If a matching command spans more than one line, pressing LEFT moves the cursor away from the end of the command, and from there the up key moves the cursor to the line above rather than searching. Pressing it again at the first line starts another search. Down behaves the same way downwards, and searching resumes once the cursor passes the last line.

One key, two meanings, selected by cursor position, and no configuration variable appears in sight for any of it.

Three tags, and the one called latest and greatest is the oldest

The release history is three tags deep: v1.1.0 dated 2023-07-28, v1.0.1 dated 2017-02-28, and between them v1.0.2 dated 2019-10-26, carrying the release title Latest and greatest. The title belongs to the middle release, three years older than the newest one, so the boast is part of the tag record rather than a claim about current code. The most recent push is dated 2026-09-18.

The configuration section introduces the variables the script defines and says you may override their default values. Two are named before the text ends: HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND, whose default highlights in bold white on magenta, and HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND, whose description starts and stops there. A third setting, HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE, appears only inside the antigen example with a value of 1 and no explanation anywhere around it.

So two of the knobs are documented, one is not, and the most interesting one is the one nobody describes.

Editorial conclusion

Worth installing if you keep retyping the middle of long commands, and cheap to remove since it is two files and one source line. Pick Homebrew or Oh-my-zsh for a maintained path, copy the bindkey lines from the antigen example because the other five install sections never give you any, and source zsh-syntax-highlighting first if you use it. Before vendoring the script into anything you ship, settle the licensing question yourself: the metadata records no license and no license file sits at the root.

Frequently asked questions

how to use zsh history substring search

Source zsh-history-substring-search.zsh in an interactive session, then bind keys to the two functions. history-substring-search-up picks the nearest older command containing what you typed, history-substring-search-down picks the nearest newer one, and Control and U aborts. If you use zsh-syntax-highlighting, source it first.

how to install zsh history substring search

Six paths are documented: Homebrew with brew install plus a source line appended to ~/.zshrc, Fig, Oh-my-zsh through a clone into the custom plugins directory, zplug with one line, Zinit, and antigen. Only the antigen section also prints the bindkey lines, and those have to come after antigen apply.

How do I search through my zsh history?

This plugin matches on a substring rather than a prefix. You type any part of any previous command and press the up key for the nearest older match or the down key for the nearest newer one. It is a clean-room port of the same feature in the Fish shell and needs zsh 4.3 or newer.

Where is zsh history kept?

This repository does not say. It defines two line editor widgets that walk the history zsh already holds in the running session, and nothing about where that history is stored on disk, how it is written, or how long it is kept.

How long does zsh history last?

Nothing here covers retention. The settings it documents concern highlighting, namely HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND, which defaults to bold white text on a magenta background, and HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND, plus the HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE value that appears in the antigen example without explanation.

What license does zsh-history-substring-search use?

Nothing in the project's own metadata names a license, and the root holds only .gitignore, README.md, zsh-history-substring-search.plugin.zsh and zsh-history-substring-search.zsh, with no license file beside them. Whether the script can be redistributed or vendored has to be settled outside this repository.

Official sources

  1. Issues
  2. README
  3. Releases
  4. zsh-users/zsh-history-substring-search on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zsh-users-zsh-history-substring-search.svg)](https://hysenlabs.com/projects/zsh-users-zsh-history-substring-search)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/zsh-users-zsh-history-substring-search.svg)](https://hysenlabs.com/projects/zsh-users-zsh-history-substring-search)

Was this useful?