January 7, 2023·6 min read·engineeringfishhistoryabbreviationsergonomics

fish 3.6 makes history and abbreviations easier to inspect

fish 3.6 adds a searchable history pager and programmable abbreviation expansion. Both changes keep command construction visible in the interactive editor.

A shell history search is useful when part of a command is remembered but its exact form is not. Showing one candidate at a time makes the search depend on repeated navigation. fish 3.6, released on January 7, makes Control-R open a searchable history pager with syntax highlighting. The same release expands abbreviations into a programmable command-editing facility.

These changes share a practical concern with what the user can inspect before execution. History search presents candidate commands. Abbreviations replace typed tokens with the text they mean. Neither requires treating a short spelling as the final explanation of what the shell will execute. The command line remains available for inspection and editing after the shortcut has supplied its text.

I like fish's emphasis on a useful interactive shell without extensive configuration. A history pager belongs in that default experience because retrieving an old command is ordinary terminal work. The abbreviation changes are more configurable, but their result still appears in the editor where it can be understood as a normal command.

History search has several forms

The interactive documentation at 3.6.0 distinguishes full-command history search from token history search. Up and down search command lines, using the text already entered as a filter when the command line is nonempty. Alt-Up and Alt-Down instead search history elements matching the token under the cursor.

The new Control-R pager offers another view over remembered commands. It shows a limited page of entries and allows the search text to be refined. Repeated Control-R advances to another page, while Control-S returns to the previous page. Search is case-insensitive unless the search string contains an uppercase character.

The version-pinned release notes identify the new history-pager input function for custom bindings. That matters for people who customize their keymaps. The default shortcut and the editing operation have separate identities, so a different key can invoke the same pager behavior.

History retrieval does not require remembering an exact prefix. The existing full-command search accepts commands containing the entered text. The pager makes the candidate set visible, which can help when several commands share a service name, path, or option. Syntax highlighting then helps distinguish their structures before selection.

The documentation also describes the privacy behavior of a leading space. A command prefixed with a space is not stored on disk, although it remains available for immediate recall until the next command executes. This is separate from filtering the display. A pager searches the history that exists, while storage policy determines what can enter that history in the first place.

Abbreviations expand during editing

The 3.6 abbreviation manual defines an abbreviation as typed text replaced with a longer phrase. Expansion happens on Space or Enter. It applies to interactive commands, not scripts. Control-Space inserts a space without triggering expansion under the default bindings.

A literal command abbreviation remains simple. The following declaration makes gco expand into the full command text.

abbr --add gco git checkout

This is an editor convenience. The expanded text is visible, and scripts do not acquire a dependency on the abbreviation being defined. A script needs the actual command or a function deliberately used as part of its program. The distinction keeps an interactive typing preference from silently becoming a new scripting language requirement.

By default, an abbreviation expands only in command position. Version 3.6 can also expand a matching token anywhere on the command line with --position anywhere. That permits shortcuts for arguments or command suffixes. The position setting is part of the definition, so a command abbreviation does not automatically start transforming matching arguments.

Anywhere expansion needs a correspondingly deliberate name. A token used as a normal argument can now qualify for replacement if its abbreviation allows that position. Control-Space provides an escape from expansion, but choosing a narrowly useful token reduces the occasions where that escape is necessary.

Cursor placement makes templates editable

An abbreviation can specify where the cursor should land after expansion. --set-cursor uses a marker in the replacement text, removes it, and positions the cursor at its first occurrence. The default marker is %, with an explicit alternative available through the option's value.

For example, the manual uses a pager suffix that leaves the cursor before the pipe. The inserted text can then follow the command being constructed.

abbr -a L --position anywhere --set-cursor "% | less"

The marker belongs to the expansion definition, not the final command. That permits a small template to place its editable part somewhere other than the end. The behavior is useful for commands whose variable portion comes before a fixed suffix, since the shortcut can insert both while leaving the user at the relevant position.

I prefer this visible expansion to an opaque shortcut when the command is still being assembled. It saves typing while preserving a command line that can be changed before execution. The benefit comes from reducing editing steps, without requiring the user to remember a second representation of the completed command.

Functions compute replacement text

The new --function option names a fish function that receives the matching token as an argument. If the function succeeds, its output becomes the expansion. If it returns a failure status, fish leaves the token unchanged. Literal replacement text and a function are alternative ways to define the result.

The release notes demonstrate a history-expansion convention through that mechanism. This definition makes !! expand to the latest history item wherever the token occurs.

function last_history_item
    echo $history[1]
end

abbr -a !! --position anywhere --function last_history_item

This is an opt-in abbreviation, not a new general rule that every fish command interprets exclamation marks as history syntax. The function provides the replacement through the abbreviation system. Its output becomes visible command text under the same editing rules as other expansions.

Regular-expression matching extends the input side of that system. --regex uses PCRE2 syntax and must match the whole token. A single definition can recognize a family of inputs, while the function interprets which member matched. If several abbreviations match, the last one added takes precedence.

The release notes give repeated dots as an example. A regular expression recognizes two or more dots, and a function produces a cd command with the corresponding number of parent-directory components. The token length becomes an argument to ordinary fish code. The shell does not need a separate built-in syntax for each shorthand family.

Definitions move into ordinary configuration

The added expressiveness comes with a persistence change. New abbreviations are no longer stored in universal variables. Existing universal abbreviations are imported, but new definitions should go in config.fish. The short -r option also changes from rename to regex, making the long --rename spelling important for scripts that rename abbreviations.

Function-based expansion helps explain why configuration files are a natural location. A computed abbreviation depends on executable fish code as well as its pattern and position settings. Keeping those definitions together makes the dependency between the function and abbreviation visible. abbr --show prints definitions in a form suitable for import and export.

An expansion function can decline a token even after the regular expression accepts it. That separates recognizing a possible shorthand from deciding that a replacement is appropriate. The function's exit status supplies that decision, while standard output supplies the replacement on success. A definition should therefore keep incidental diagnostic text out of successful standard output, since that text would become part of the command line.

The command manual also retains separate operations for listing names, querying whether names exist, and erasing definitions. abbr --list answers which names are present, while abbr --show provides the fuller definitions. These are different inspection tasks now that an abbreviation can have a pattern, an expansion function, and a cursor marker beyond a simple pair of words.

The release also adds fish_delta, which shows changes to fish configuration from its defaults. The function implementation compares user functions and completions with their default counterparts and includes configuration files in its report. It gives a user a way to inspect accumulated customization alongside the new default interaction behavior.

fish 3.6 also changes the completion pager to fill half the terminal on its first display, replacing the previous four-row initial view. Like the history pager, this makes more candidates visible during selection. For both retrieval and command construction, the release gives the editor more useful information before the command is executed.