CASH: Choose from Available Search Highlights
This plugin adds additional search registers to Neovim. Normally when you perform a search in (Neo-)Vim, your previous search is overwritten. Cash.nvim provides you with 9 "cash registers" (haha) that you can use to store multiple searches at once. Highlighting and jump functionality is handled separately for each cash register.
Use ?<number> (1-9), or F1-F9, to select a
cash register. This gives you 9 individual searches that can be highlighted
simultaneously.
681a776b-95b8-4fe6-9753-d6af3462d5a4.mov
Lazy.nvim config:
{
'nullromo/cash.nvim',
opts = {}, -- specify options here
config = function(_, opts)
local cash = require('cash')
cash.setup(opts)
end,
}With rocks.nvim:
:Rocks install cash.nvimEverything below is also available inside Neovim as vimdoc:
:help cash.nvim. The docs have more details than this README.
With Cash.nvim, you can perform searches normally, and they will show up normally. Start a search with /, *, #, g*, or g# (but not ?; see below) from normal mode, then use n and N to navigate through the highlighted matches.
By default, your working cash register is cash register 1. Every time you search, the contents of cash register 1 will update to match your search terms. You will jump between instances of the search term stored in cash register 1.
To switch to a different cash register, press ? followed by a single digit. This will change the working cash register to the specified number. For example, use ?2 to switch to cash register 2.
By default, pressing ? brings up a chooser showing all nine cash registers in their own colors, so you can see which number is the one you want.
╭─ Choose a cash register ───────────────╮
│ ▸ 1 foo 2? bar 3 \<baz\> │
│ 4 · 5 · 6 · │
│ 7 · 8 · 9 TODO │
╰────────────────────────────────────────╯
The ▸ marks the working cash register and · marks an empty one. The ?
after a number marks the cash register that is highlighting the text under the
cursor, which is the one that ? switches to.
The look of the chooser can be customized via the chooser.style option.
The function keys do the same job without the chooser. F1 through F9 select cash registers 1 through 9, and F10 selects the cash register that is highlighting the text under the cursor.
The mapKeys option can be used to disable these sets of keymaps. Both are on
by default.
Once you change cash registers, the search highlighting of the old cash register will remain on the screen. You can then perform a new search independent of the previous one. Any search you perform will always overwrite the contents of the working cash register.
Jumping normally jumps between occurrences that match the contents of the
working cash register, skipping over matches for other cash registers. If you
want to jump between matches for a different cash register other than the
working one, either switch back to that cash register and start jumping, or use
the includeInSearch option.
Sometimes you can see the color you want but you don't know the number of the cash register it belongs to. Move the cursor onto text that a cash register is highlighting and press ?? or F10 to switch to that cash register.
:Cash here and require('cash').setCashRegisterUnderCursor() do the same
thing.
By default, n and N jump between the matches of the
working cash register only. Switching includeInSearch on for another cash
register causes jumps to match the contents of that cash register as well.
n visits whichever match comes next from among the search patterns in
all cash registers that have includeInSearch enabled.
require('cash').setIncludeInSearch(2, true) -- include register 2 matches when pressing n/N
require('cash').toggleIncludeInSearch(2) -- toggle whether or not to include register 2For example, say cash register 1 holds foo and cash register 2 holds bar. If
both registers 1 and 2 have includeInSearch = true, then n walks
through every foo and every bar in whatever order they appear. They keep
their own colors while it happens. Including a cash register changes where
n/N go, not what is highlighted. The match under the
cursor is always colored with the CurSearch highlight group.
The working cash register is always included, no matter what its own
includeInSearch setting says.
Each cash register keeps its own case sensitivity. If one register holds \Cfoo
and another holds bar, the first stays case-sensitive and the second still
follows ignorecase.
:Cash opens a popup showing all nine cash registers at once: their contents,
their colors, which ones n/N will visit, and how many
matches each one has in the buffer you came from.
Other possible actions from the drawer are listed in the table below.
| Key | Does |
|---|---|
| j / k | Move between cash registers. |
| any edit | Edit the cash register under the cursor. Edits here work like any regular buffer. |
| dd | Empty the highlighted cash register. It clears the row rather than removing it, because there are always nine registers. |
| Space | Toggle includeInSearch for the cash register under the cursor. |
| Enter | Select the highlighted cash register and close. |
| Tab | Select the highlighted cash register and stay open. |
| ] / [ | Swap the highlighted cash register with the one below / above. Contents move, but colors stay put, so this is one way to re-color a search. |
| q / Esc | Apply and close the drawer. |
| ? | Show or hide the detail pane (shows more information about the highlighted cash register). |
| Ctrl-c | Close the drawer and undo everything changed since it was opened. |
The detail pane can be opened from the drawer by pressing ?. It provides more details about the highlighted cash register.
╭─ Details ────────────────────────────╮
│ cash register 3 │
│ │
│ contents \<baz\> │
│ match pattern \C\<baz\> │
│ include in search no │
│ selected no │
│ │
│ matching window IDs 1000 1001 │
╰──────────────────────────────────────╯
contentsis the contents of the cash register.match patternis what Vim actually matches on, with the case flag inserted on the left.include in searchanswers whether n and N will visit this cash register. It always readsyesfor the selected register .matching window IDslists the windows in which that cash register's pattern actually occurs.
Set drawer.detailPane = true to have the detail pane always appear with the
drawer.
Note: The detail pane appears next to the drawer, so it needs a window at least 102 columns wide in order to open.
If you have telescope.nvim,
:Telescope cash_registers lists all nine cash registers and filters them as
you type. Selecting one makes it the working cash register, the same as
?n does.
Selecting is all the picker does. Editing patterns, swapping registers and toggling include-in-search are done from the cash drawer.
Telescope finds the picker on the runtimepath, so Cash.nvim has to be loaded
before :Telescope cash_registers works. Telescope reports Unknown command
while it cannot find the extension, which is worth knowing if your plugin
manager loads Cash.nvim lazily. There is no need to call
require('telescope').load_extension() yourself.
There is a static heads-up display for the current cash register that you can
enable or put into your statusline. Setting indicator = { show = true }, or
using the :Cash indicator command will display the indicator.
The indicator has 2 styles: 'current' (which shows the search set) and
'strip' (which shows all nine cash registers).
❰1▸35❱ 'current', with 1 and 5 included in the search as well
❰1▸2 3 4 5 6 7 8 9❱ 'strip', with 2 the current cash register
Every cash register in the search set is highlighted in its own color, and the
▸ marks the current one.
Cash.nvim does not modify your statusline, winbar or tabline. To put the
indicator label in one of these lines, use require('cash').statusline() as an
expression. For example:
vim.o.statusline = "%f %m%=%{%v:lua.require'cash'.statusline()%}"Note that the %{% %} form matters here, since Vim needs to redraw the status
line to update it.
For a statusline plugin that takes text and a highlight group separately,
require('cash').label() hands back both:
-- lualine
sections = {
lualine_x = {
{
function()
return require('cash').label({ style = 'current' }).text
end,
color = function()
return require('cash').label().group
end,
},
},
}Cash.nvim takes a single user command, with verbs.
| Command | Does |
|---|---|
:Cash |
Open the cash drawer. |
:Cash use {n} |
Select cash register n, the same as ?n. |
:Cash here |
Work in the cash register highlighting the text under the cursor, as ?? does. |
:Cash where |
Print the working cash register and what it holds. |
:Cash include {n} |
Add cash register n to the search set (includeInSearch = true). |
:Cash exclude {n} |
Remove cash register n from the search set. (includeInSearch = false). |
:Cash toggle {n} |
Toggle whether or not cash register n is included in the search set. |
:Cash clear [{n}] |
Empty cash register n, or the current working cash register if the n arg is omitted. |
:Cash reset |
Empty all nine cash registers and select cash register 1. |
:Cash hide |
Hide all search highlights. The same as :nohlsearch (:noh). The registers keep their contents. |
:Cash show |
Bring search highlights back. They will also come back when using n/N. |
:Cash autohide [on|off|toggle] |
Change whether search highlights clear as soon as the cursor moves. Toggles if no argument is given. |
:Cash indicator [on|off|toggle] |
Show or hide the indicator. Toggles if no argument is given. |
Cash.nvim will respect the ignorecase option, but the case sensitivity can be
overridden in the search pattern as normal using \c or \C (see :help /\c).
To clear the contents of the working cash register, use :Cash clear (or the
require('cash').clearCashRegister() function). This will also set Vim's search
to an empty string. Pass a number, as in :Cash clear 3, to empty a different
cash register instead.
To clear all cash registers and reset the plugin to its initial state, use
:Cash reset (or the require('cash').resetCashRegisters() function). This
will set Vim's search register to an empty string and clear the contents of all
cash registers.
Vim remembers your last search pattern from one session to the next, in the shada file. Similarly, Cash.nvim remembers all nine cash registers. If you quit and start Neovim again, all your cash registers will come back the way you left them.
Nothing is highlighted straight away. A restored cash register lights up when your next search or your next n turns highlighting on, and not before. This is exactly what Vim already does with the search pattern it restores.
A few things worth knowing:
- The plugin state is written on the way out, so a clean exit of Neovim keeps
them and a crash does not. This is the same thing Vim does for
@/and your search history. - There is one global copy of the plugin state shared by every instance of Neovim on the machine. Starting Neovim in another project gives you the cash registers you last quit with.
Set persistCashRegisters = false to start every session with nine empty cash
registers instead.
Cash.nvim will overwrite the default behavior of the ? key, and will
remap the F1-F10 keys. Set mapKeys.questionMark = false
or mapKeys.functionKeys = false to prevent this. Unlike with * and
#, mappings already present for the function keys will be replaced
rather than wrapped.
Cash.nvim also maps n and N in normal mode, so that they
can jump between the matches of more than one cash register (see
Include in Search). Whenever the search set is a single
cash register, n and N use their native (Neo-)Vim
functions. Set manageJumps = false to make includeInSearch a no-op and leave
the n and N keys alone entirely.
If you map n yourself (vim.keymap.set('n', 'n', 'nzz') is a common
one), your mapping replaces Cash.nvim's and include-in-search will silently stop
working. Map require('cash').nextMatch / require('cash').previousMatch if
you want to maintain Cash.nvim's intended behavior. If centering is all you
need, the centerAfterSearch option already does that for you.
The options are annotated for lua-language-server, so
an editor with the correct setup will offer you the option names and flag
invalid values. Options written apart from the call (like Lazy.nvim's opts
field) are not handed to setup where you write them, so you can annotate the
type there, like this:
{
'nullromo/cash.nvim',
---@type cash.Options
opts = {
chooser = { style = 'asdf' }, -- 'asdf' is flagged here, since it's invalid
},
config = function(_, opts)
require('cash').setup(opts)
end,
}{
-- clear all highlighting as soon as the cursor moves
autoNoHighlight = false,
-- center the window after every search jump: /, *, #, n, N, and switching
-- to another cash register
centerAfterSearch = true,
-- customize the cash register chooser that ? opens
chooser = {
-- 'grid', 'strip' or 'none'
style = 'grid',
-- where on screen the chooser appears ('center', 'bottom-right',
-- 'bottom', 'bottom-left', 'left', 'top-left', 'top', 'top-right', or
-- 'right')
position = 'center',
-- the chooser's border, in any form nvim_open_win accepts
border = 'rounded',
},
-- color settings
colors = {
-- default colors for foreground and background (used for highlight
-- groups where fg/bg are not specified)
defaultBG = constants.colors.roninYellow,
defaultFG = constants.colors.sumiInk0,
-- define colors for highlight groups 1-9
highlightColors = {
{ bg = constants.colors.roninYellow },
{ bg = constants.colors.springBlue },
{ bg = constants.colors.sakuraPink },
{ bg = constants.colors.springGreen },
{ bg = constants.colors.autumnYellow },
{ bg = constants.colors.oniViolet },
{ bg = constants.colors.autumnGreen },
{ bg = constants.colors.autumnRed },
{ bg = constants.colors.waveBlue2, fg = constants.colors.fujiWhite },
},
},
-- control whether or not using * or # from normal mode will jump to the
-- next occurrence. Vim will jump by default; this plugin disables the jump
-- by default
disableStarPoundJump = true,
-- the cash drawer, which :Cash opens
drawer = {
-- where on screen the drawer appears ('center', 'bottom-right',
-- 'bottom', 'bottom-left', 'left', 'top-left', 'top', 'top-right', or
-- 'right')
position = 'center',
-- the drawer's border, in any form nvim_open_win accepts
border = 'rounded',
-- whether the detail pane is already open when the drawer appears
detailPane = false,
},
-- the indicator: a small window saying which cash register is the working
-- one
indicator = {
-- whether it is on screen at all
show = false,
-- 'current' for just the search set, 'strip' for all nine registers
style = 'current',
-- where on screen the indicator appears ('center', 'bottom-right',
-- 'bottom', 'bottom-left', 'left', 'top-left', 'top', 'top-right', or
-- 'right')
position = 'bottom-right',
-- what is displayed in the indicator: 'number', 'pattern', or
-- 'number-and-pattern'
display = 'number',
-- how wide the indicator may be, brackets included. The pattern will
-- be truncated if necessary
maxWidth = 30,
-- what symbols surround the indicator: the name of one of the pairs
-- listed in the options table below, or { left = ..., right = ... }
brackets = 'heavy-angle',
},
-- let this plugin own n and N, so that they can jump between the matches
-- of every cash register in the search set
manageJumps = true,
-- which keys this plugin maps
mapKeys = {
-- <F1> through <F9> for the nine cash registers and <F10> for the
-- register under the cursor. No chooser appears for these
functionKeys = true,
-- ? for the chooser and ?? for the register under the cursor
questionMark = true,
},
-- carry the cash registers from one Neovim to the next in the shada file,
-- the way Vim already remembers your last search
persistCashRegisters = true,
-- leave vim's hlsearch setting alone. This plugin overrides hlsearch by
-- default
respectHLSearch = false,
}| Option | Data Type | Default | Description |
|---|---|---|---|
centerAfterSearch |
boolean | true |
Each time you arrive at a match, Cash.nvim will center the current window on it for you. This covers /, *, #, g*, g#, switching cash registers, n, and N. A search that finds nothing leaves the window alone. If you don't like this behavior, you can disable it by setting this option to false. |
colors.defaultBG and colors.defaultFG |
string ('#RRGGBB') |
see above | These will be the highlight background and foreground, respectively, for highlight colors that do not have a bg or fg color specified, respectively. |
colors.highlightColors |
list of 9 { bg = string, fg = string } items |
see above | This is a table of 9 values, each with a bg and fg field. These define the highlight colors for each of the 9 available cash registers. If a bg or fg value is not specified in one of these entries, then the colors.defaultBG/colors.defaultFG color will be used. Colors should be of the form '#RRGGBB'. |
disableStarPoundJump |
boolean | true |
By default, Vim will jump you to the next occurrence of a search term if you initiate the search using *, #, g*, or g#. Cash.nvim disables this by default. You can preserve Vim's default behavior by setting this option to false. A count (e.g. 3*) jumps either way. |
manageJumps |
boolean | true |
Cash.nvim maps n and N so that they can jump between the matches of every cash register in the search set. With only one cash register in the search set, the mapping uses Vim's default behavior, so nothing changes until you turn includeInSearch on for more than one cash register. Set this to false to leave the keys alone, which also turns includeInSearch into a no-op. |
mapKeys.functionKeys |
boolean | true |
Map the nine cash registers to the function keys (F1-F9) and map F10 as well. No chooser appears for these. |
mapKeys.questionMark |
boolean | true |
Map ? to the chooser and ?? to the cash register under the cursor. Set this to false to leave ? alone. |
persistCashRegisters |
boolean | true |
Carry the cash registers from one Neovim to the next in the shada file, the way Vim already remembers your last search. All nine search patterns, their includeInSearch values, and the working cash register all come back. Set this to false to start every session with nine empty cash registers. |
respectHLSearch |
boolean | false |
In order to enable search highlighting for the current search, you need to enable the hlsearch Vim option. Cash.nvim does this automatically, but if you want your hlsearch setting to be left as-is, then you can set this option to true. |
autoNoHighlight |
boolean | false |
Clear every cash register's highlighting as soon as the cursor moves. The cursor movement made by the search itself does not count. Switchable with :Cash autohide. |
chooser.style |
'grid', 'strip' or 'none' |
'grid' |
What the ? chooser looks like. 'grid' lays the registers out like a numpad and shows what each one holds; 'strip' is one line of numbers; 'none' turns the chooser popup off. |
chooser.position, drawer.position, and indicator.position |
string | 'center' for chooser and drawer; 'bottom-right' for indicator |
Where these elements appear: 'top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom' or 'bottom-right'. |
drawer.detailPane |
boolean | false |
Whether the drawer's detail pane is already open when it appears. Needs a window at least 102 columns wide to function. |
indicator.show |
boolean | false |
Whether or not the indicator is on screen. Switchable with :Cash indicator. |
indicator.style |
'current' or 'strip' |
'current' |
What the indicator looks like. 'current' shows the search set; 'strip' shows all nine cash registers. Either way the search set is highlighted and ▸ marks the working cash register. |
indicator.display |
'number', 'pattern' or 'number-and-pattern' |
'number' |
What is displayed in the indicator: ❰3❱, ❰foo❱, or ❰3 foo❱. The number is the whole search set when there is more than one cash register in it, so ❰1 ▸3 5❱ and ❰1 ▸3 5 foo❱, where foo is what the working cash register holds. |
indicator.maxWidth |
number | 30 |
How wide the indicator may be, brackets included. The pattern will be truncated with ~ if necessary. The numbers are never cut, so a very small maxWidth may not be respected. |
indicator.brackets |
string or table | 'heavy-angle' |
What symbols are use around the indicator. Either the name of a pair: 'ascii' [ ], 'angle' ‹ ›, 'heavy-angle' ❰ ❱, 'box-light' │ │, 'box-heavy' ┃ ┃, 'small-cap' ▏ ▕, 'large-cap' ▌ ▐, 'short-corner' ⌜ ⌟, 'tall-corner' 「 」, 'double-square' ⟬ ⟭, 'white-square' ⟦ ⟧, or a pair of your own { left = '(', right = ')' }. |
chooser.border and drawer.border |
string or table | 'rounded' |
Popup border settings for the chooser and drawer, in any form nvim_open_win accepts. |
Here are some other searching tips that are not part of Cash.nvim's functionality, but might be useful.
When searching in Vim, \| is the "or" operator, meaning the pattern foo\|bar
will match occurrences of foo and occurrences of bar. This mapping allows
you to search for something, then press + to start searching for
something else in addition. It works by starting a new search that begins with
the contents of the old search register plus a \| at the end.
vim.keymap.set('n', '+', '/<C-r>/\\|')By default, the + key in Vim just moves the cursor down 1 line. It is very similar to j, so it's not that useful. For this reason, + is a good candidate for remapping.
Centering is built in—see the centerAfterSearch option. Anything else you want
to happen after each jump with n/N should wrap the API
functions, so that the search set is still taken into account. For example, here
is a mapping that puts the match at the top of the window instead of the middle.
local cash = require('cash')
vim.keymap.set('n', 'n', function()
cash.nextMatch()
vim.cmd('normal! zt')
end)
vim.keymap.set('n', 'N', function()
cash.previousMatch()
vim.cmd('normal! zt')
end)Do not use the usual vim.keymap.set('n', 'n', 'nzz') for this. That mapping
calls Vim's built-in n, which knows nothing about the search set, so
n would only ever be able to visit the working cash register.
:checkhealth cashNothing in Cash.nvim raises an error when the colors don't render, when a cash register stops painting, or when a keymap conflicts. The healthcheck can help diagnose these problems. It reports:
- Neovim version and
termguicolors. - Whether
setuphas been called. - Every option that isn't set to its default.
- Map origins for keys that Cash.nvim maps. A plugin that loads after Cash.nvim and maps one of these keys can replace the mapping.
- Whether the
CashRegisterhighlight groups still exist. The setup function creates them, and creates them again whenever a colorscheme loads, so anything else that clears them is reported here. - Whether the cash registers can be saved, and what the last session left behind.
The healthcheck does not modify anything.
See LICENSE and CONTRIBUTING.md.
I am very open to feedback and criticism.
To say thanks with some cash, sponsor me on GitHub or use @Kyle-Kovacs on Venmo. Your donation is appreciated!