Reference
*noethervim.txt* NoetherVim distribution user guide
Author: Chiarandini
License: MIT
CONTENTS *noethervim-contents*
1. Introduction ........................ |noethervim|
2. Installation ........................ |noethervim-install|
3. Quickstart .......................... |noethervim-quickstart|
4. User configuration .................. |noethervim-user-config|
4.1 Directory structure ............. |noethervim-user-dir|
4.2 Options ......................... |noethervim-user-options|
4.3 Keymaps ......................... |noethervim-user-keymaps|
4.4 Autocommands .................... |noethervim-user-autocmds|
4.5 Highlights ...................... |noethervim-user-highlights|
4.6 Bundle data table ............... |noethervim-user-config-data|
4.7 Plugins ......................... |noethervim-user-plugins|
4.8 LSP servers ..................... |noethervim-user-lsp|
4.9 Imperative overrides ............ |noethervim-user-overrides|
5. Bundles ............................. |noethervim-bundles|
6. Inspection .......................... |noethervim-inspect|
7. Comparison .......................... |noethervim-diff|
8. Toggling ............................ |noethervim-toggle|
9. Keymap namespaces ................... |noethervim-keymaps|
9.1 Filetype keymaps ................ |noethervim-ftplugin|
9.2 Statusline ...................... |noethervim-statusline|
10. Commands ............................ |noethervim-commands|
11. Completion sources .................. |noethervim-completion|
12. Health check ........................ |noethervim-health|
13. FAQ ................................. |noethervim-faq|
1. Introduction
Section titled “1. Introduction”*noethervim* *neothervim*
NoetherVim is an opinionated Neovim distribution built on lazy.nvim. It
ships sensible defaults, 30+ core plugins, and 39 opt-in bundles, with a
user configuration system that survives upstream updates.
This help file covers in-editor usage and reference. For design rationale,
system requirements, and installation, see the README in the project root.
2. Installation
Section titled “2. Installation”*noethervim-install* *neothervim-install*
*noethervim-nvim-appname*
*noethervim-uninstall*
For initial installation (backup paths, the bootstrap `curl`, NVIM_APPNAME
side-by-side trials, and uninstall / reset commands), see the README in
the project root. Once Neovim is running, this help file is the reference.
Updating: ~
Run inside Neovim. This updates the distro and all plugins: >vim
:Lazy update
3. Quickstart
Section titled “3. Quickstart”*noethervim-quickstart*
Four commands, in the order worth running them: ~
SearchLeader+? Every active keymap, grouped by namespace. `<CR>` on
a line jumps to where it was defined.
See |noethervim-guide|.
`:checkhealth noethervim`
Confirms your terminal, tools and config are in order.
See |noethervim-health|.
SearchLeader+cb Enable bundles. See |noethervim-bundle-toggle|.
SearchLeader+ct Write `lua/user/` config templates. Start with
`user/config.example.lua`, which is an annotated list
of everything the distribution lets you change about
its own behavior. See |noethervim-templates|.
Both the bundle and template flows show a diff prompt before writing.
The keymap prefixes are the fastest thing to learn: |noethervim-keymaps|.
A longer walkthrough for a first session, including the prefixes and the
defaults worth reviewing, lives in `docs/onboarding/first-session.md` in the
project root; `docs/onboarding/mathematicians.md` continues it for LaTeX
work.
4. User configuration
Section titled “4. User configuration”*noethervim-user-config*
NoetherVim is designed so that the source code is its own documentation.
Browse core source files with SearchLeader+cf or `:NoetherVim files`, and
live-grep them with SearchLeader+cg. All source files open read-only to
prevent accidental edits. See |noethervim-inspect| for the full set of
navigation commands.
NoetherVim loads user override files automatically after each core module.
User files live in your config directory's `lua/user/` folder and are
never touched by upstream updates.
*noethervim-load-order*
Loading happens in three stages. ~
Stage 1 collects plugin specs. Nothing runs yet; your `user/plugins/`
files are merged last, so your `opts` win over both the distro's and any
bundle's. A bundle sits in between: it can change a core plugin's `opts`,
and you can change a bundle's.
Stage 2 runs the configuration files, distro first and yours second at
every step:
noethervim/options.lua -> user/options.lua
noethervim/lsp/*.lua -> user/lsp/*.lua
noethervim/keymaps.lua \
noethervim/toggles.lua > user/keymaps.lua
noethervim/autocmds.lua -> user/autocmds.lua
noethervim/highlights.lua \
colorscheme > user/highlights.lua
user/overrides/*.lua (last-resort hooks)
Stage 3 is deferred until after the screen is drawn: the extra commands,
and each lazy-loaded plugin whenever its trigger fires.
*noethervim-override-timing*
What stage 3 means for overrides: ~
`user/overrides/*.lua` is the last thing in stage 2, not the last thing
overall. It wins against everything the distribution sets in stage 2, and
against a plugin's `keys` entries: once you remap one of those, it stays
remapped whether or not the plugin later loads.
It does NOT win against stage 3, which is mainly a lazy-loaded plugin's
own `config` function and buffer-local LSP keymaps applied on
`LspAttach`. Those run later and overwrite you.
For those, override at the layer that runs at the same time: an
`LspAttach` autocmd for LSP keymaps, or a `config` wrapper in your
`user/plugins/` spec for a plugin's own bindings.
------------------------------------------------------------------------------
4.1 DIRECTORY STRUCTURE *noethervim-user-dir*
~/.config/nvim/lua/user/
|-- config.lua Config data table (|noethervim-user-config-data|)
|-- options.lua vim.o overrides
|-- keymaps.lua Keymap overrides and additions
|-- autocmds.lua Autocommand additions
|-- highlights.lua Highlight overrides (after colorscheme)
|-- lsp/ LSP server config overrides
| `-- lua_ls.lua e.g., override lua_ls workspace settings
|-- plugins/ Personal plugins and distro opts overrides
| `-- snacks.lua e.g., override snacks.nvim opts
`-- overrides/ Imperative after-hooks (last resort)
`-- *.lua
Template files are provided in the `templates/user/` directory of the
NoetherVim repo. Copy the ones you need to your `lua/user/` and
uncomment the relevant lines.
------------------------------------------------------------------------------
4.2 OPTIONS *noethervim-user-options*
Create `lua/user/options.lua`. It runs after `noethervim/options.lua`,
so any global value you set overwrites the distro default: >lua
-- lua/user/options.lua
vim.o.scrolloff = 8 -- default: 4
vim.o.autochdir = true -- default: false
*noethervim-option-layers*
Note: this holds for "genuinely" global options. Filetype-scoped options set
by the ftplugin layer (`formatoptions`, `comments`, `commentstring`, and often
`textwidth`, `tabstop`, `shiftwidth`) are only seeded in `options.lua`; an
ftplugin runs after it and sets the effective per-buffer value. Neovim's own
bundled ftplugins do this too, so setting one of these in `user/options.lua`
does not take effect for filetypes whose ftplugin sets it. To change the
effective value, use the filetype layer: a `FileType` autocmd in
`lua/user/autocmds.lua`, or an `after/ftplugin/<ft>.lua`. For example, to stop
comment-leader continuation on <Enter> and o/O across filetypes: >lua
vim.api.nvim_create_autocmd("FileType", {
callback = function()
vim.opt_local.formatoptions:remove({ "r", "o" })
end,
})
Notable non-default choices: ~
`swapfile` is OFF; `undofile` is ON. NoetherVim favors persistent
undo over swap-based crash recovery; in the author's experience,
swap's "file changed on disk" prompts produced more false positives
than they prevented lost work. Your mileage may differ; if you
want swap back (e.g. on flaky SSH / VM sessions), set
`vim.o.swapfile = true` in `user/options.lua`.
`autowrite` ON, `autowriteall` OFF, `autoread` ON. Buffer-switch
commands (`:next`, `:make`, `<C-^>`, `:buffer`) auto-save unsaved
edits; reload commands (`:e`) do not, so running `:e` on a modified
buffer errors with `E37: No write since last change`.
The `autowriteall=false` guard prevents a specific data-loss
scenario: if a buffer is modified while an external process also
edits the file on disk, `autowriteall=true` would silently write
the stale buffer first when you `:e`, overwriting the external
changes, then reloading the result, destroying the external edits
with no warning. With `autowriteall=false`, `:e` errors instead,
forcing an explicit choice (`:e!` discards buffer edits; `:w!`
overwrites disk). Treat `:e` as "load file into buffer," not
"reload regardless of state."
Flip with `vim.o.autowriteall = true` in `user/options.lua` if you
prefer the classic "save everything automatically" behavior.
See `noethervim/options.lua` for all defaults.
------------------------------------------------------------------------------
4.3 KEYMAPS *noethervim-user-keymaps*
Create `lua/user/keymaps.lua`. It runs after core keymaps and toggles: >lua
-- Override a core keymap (last-write wins)
vim.keymap.set("n", ";", ";") -- revert ; to default
-- Remove a core keymap entirely
vim.keymap.del("n", "<C-a>")
-- Add personal keymaps
vim.keymap.set("n", "<space>ev", "<cmd>e $MYVIMRC<cr>", { desc = "edit vimrc" })
NoetherVim keymap philosophy:
SearchLeader fuzzy navigation / search (default: <Space>)
<Leader> global actions
<LocalLeader> filetype-specific actions
<C-w> window navigation / management
[x / ]x prev / next directional navigation
[ox / ]ox toggle option on / off
*g:mapsearchleader*
g:mapsearchleader ~
The key prefix for all search/navigation keymaps. Defaults to `<Space>`.
Works like |mapleader|; set it in your init.lua BEFORE `lazy.setup()`: >lua
vim.g.mapsearchleader = "<C-s>" -- or ";", "\\", etc.
All keymaps in the SearchLeader namespace (find, grep, LSP, git, config,
diagnostics, etc.) will use the new prefix. which-key popups and the
keymap diff picker automatically reflect the change.
When unset, all search keymaps use `<Space>` (the default).
------------------------------------------------------------------------------
4.4 AUTOCOMMANDS *noethervim-user-autocmds*
Create `lua/user/autocmds.lua`. New autocommands are additive.
To override a core autocmd, clear its augroup first: >lua
-- Clear a core augroup and replace it
vim.api.nvim_create_augroup("noethervim_q_close", { clear = true })
-- Then re-create with your preferred list
*noethervim-q-close*
q-to-close: ~
A bare `q` closes the window in filetypes you cannot usefully edit (help,
man, lazy, mason, checkhealth, undotree, diff, and friends). Editable
filetypes are excluded on purpose, because `q` there would shadow macro
recording, so `oil`, `markdown` and the like are left alone.
To ADD a filetype, set `q_close_filetypes` in `lua/user/config.lua`; see
|noethervim-user-config-data|. To REMOVE one of the defaults, clear the
`noethervim_q_close` augroup and re-create the autocmd from
`require("noethervim.util.filetypes").q_close` minus the entries you do
not want. `templates/user/autocmds.example.lua` has the full snippet.
Core augroups (clear any of these to disable or replace the feature): ~
`noethervim_q_close` q-to-close in non-editing windows
`noethervim_profiles` the writing / code filetype profiles
`noethervim_lastplace` restore last cursor position on read
`noethervim_autoread` reload buffers when focus or edits change
`noethervim_term` terminal window tweaks (no numbers, scrolloff)
`noethervim_diff_cleanup` auto diffoff when a diff half closes
`noethervim_oil_float` Oil dual-pane navigation
*noethervim-filetype-profiles*
Filetype profiles (writing and code): ~
NoetherVim applies two FileType autocmds that layer opinionated
settings on top of the globals:
`noethervim_writing` Matches: tex, markdown, norg, text, gitcommit,
gitsendemail, mail, rst, typst. Sets `wrap`,
`linebreak`, `spell`, `conceallevel = 2`, appends
`t` to `formatoptions` (auto-wrap at textwidth),
and hides list chars. Also binds `<C-l>` in
insert mode for spell-fix. Wrapped lines get a
marker column (see |noethervim-wrap-marker|).
`noethervim_code` Matches: every filetype NOT in the writing list,
structured-text set (json, jsonc, yaml, toml),
or special-buffer set (help, man, lspinfo, query,
qf, oil, terminal, snacks_*, lazy, mason,
checkhealth, notify, Trouble, dap / dap-float /
dapui_*). Sets `list` on so trailing whitespace and
tabs are visible, and `wrap` off, the counterpart to
the writing profile turning it on. `[ow` therefore
lasts until the window shows a buffer again, the
same as `]ow` in a writing buffer.
`formatoptions` omits `t` globally, so typing past
textwidth in code does NOT auto-break mid-line;
formatters handle line length instead. When
`spell_in_code = true` is set in
|noethervim-user-config-data|, also turns on
`spell`; spellcheck is then scoped to comments
and strings via treesitter @spell captures.
Use `[os` / `]os` to toggle per-buffer and `zg`
to add a word to your spellfile. For CamelCase
identifier splitting, append `camel` to
`spelloptions` yourself in
`lua/user/options.lua`; the distro leaves
`spelloptions` untouched.
*noethervim-hard-wrap*
The writing profile hard wraps: `formatoptions+t` breaks a line at
'textwidth' as you type. That is what you want for prose in version
control, but it means a phrase can end up split across two lines, and
`/brown fox` then finds nothing when the wrap falls between the words.
Enable the `wrapsearch` bundle to make `/` and `?` match across the
break; see |noethervim-bundles|. To stop hard wrapping instead, remove
`t` from `formatoptions` in an `after/ftplugin/<ft>.lua`.
FileType autocmds fire AFTER `ftplugin/*.lua`, so these profiles win
over same-named ftplugin settings. To extend the lists (e.g. treat
`vimwiki` as writing, or skip the code profile for a custom filetype),
set `writing_filetypes` / `non_code_filetypes` in `lua/user/config.lua`;
see |noethervim-user-config-data|.
Both profiles are applied by a single augroup, `noethervim_profiles`. To
override the whole profile, clear it and re-create: >lua
vim.api.nvim_create_augroup("noethervim_profiles", { clear = true })
-- then register your own FileType autocmd
The global `wrap` default is OFF; the writing profile re-enables it
for writing filetypes. Global `list` is OFF; the code profile re-enables
it for code filetypes. Listchars are styled in `noethervim/options.lua`.
*noethervim-wrap-marker*
Wherever 'wrap' is on, a continuation row is marked `↳` in the number
column, so a line that ran past the window edge is never mistaken for a
line of its own. Writing buffers wrap by default and so always show it; a
code buffer shows it from the moment you press `[ow`, and stops when you
press `]ow`.
The marker column replaces 'number' while it is up, which is why it draws
the line numbers itself and why `]on` still turns them off, leaving the
marker on its own. It costs no text cell, so continuation rows stay lined
up under the text they continue. 'showbreak' is left empty.
Turning wrap off in a prose buffer swaps the marker column for the code
layout's edge markers: `›` and `‹` where a line runs past the window, which
Vim draws only while 'list' is on, so tabs and trailing space become visible
with them. Prose that scrolls sideways has left the profile's assumption.
A 'statuscolumn' you define in `lua/user/options.lua` is never touched,
and switches the marker off for that window: >lua
vim.o.statuscolumn = "%l "
To go back to an inline 'showbreak' arrow, set both in
`lua/user/options.lua`: >lua
vim.o.showbreak = "↳"
vim.o.statuscolumn = "%s%=%l "
Setting 'statuscolumn' to an empty string does NOT opt out: empty means
"nothing set yet", so the writing profile fills it in. Give it a value of
your own, or clear the `noethervim_profiles` augroup entirely.
------------------------------------------------------------------------------
4.5 HIGHLIGHTS *noethervim-user-highlights*
Create `lua/user/highlights.lua`. This file runs AFTER the colorscheme
is applied, so your overrides are not wiped by the theme: >lua
vim.api.nvim_set_hl(0, "Normal", { bg = "#1d2021" })
vim.api.nvim_set_hl(0, "SnacksDashboardHeader", { fg = "#89b4fa" })
*noethervim-colorscheme-tweaks*
Highlight tweaks: ~
Plain `nvim_set_hl` calls are lost when you switch colorschemes. Use the
tweak helper instead; tweaks re-apply automatically on every colorscheme
change. This is a core feature and needs no bundle: >lua
-- lua/user/highlights.lua
require("noethervim.util.colorscheme").tweak({
Comment = { italic = true },
CursorLine = { bg = "#1a1a2e" },
LineNr = { fg = "#555555" },
})
Common highlight groups to tweak: ~
`Normal` Main text and background
`Comment` Code comments (try `italic = true`)
`CursorLine` Line the cursor is on
`LineNr` Line numbers
`Visual` Visual selection
`Search` Search matches
`DiagnosticError` Error diagnostics
`DiagnosticWarn` Warning diagnostics
`@keyword` Treesitter keywords
`@function` Treesitter functions
`@string` Treesitter strings
------------------------------------------------------------------------------
4.6 CONFIG DATA TABLE *noethervim-user-config-data*
Create `lua/user/config.lua`. It returns a single table of values that
`noethervim.setup()` and individual bundles read at runtime. This is
NoetherVim's single user-facing configuration surface: colorscheme,
statusline overrides, vault paths, feature flags, filetype lists.
Keys you do not set fall back to the distro default; the file is
optional and missing keys are not an error.
Note: this file is for personal preferences. Arbitrary plugin overrides go in
`lua/user/plugins/` instead (see |noethervim-user-plugins|).
Which surface to reach for: ~
`config.lua` Values NoetherVim's own code branches on, with no
`vim.o` or plugin-`opts` home of their own.
`user/options.lua` Anything with a `vim.o` / `vim.opt` equivalent.
`user/plugins/` Anything a plugin exposes through `opts`. Plugin
options are not repeated in `config.lua`; go
straight to the spec.
`vim.g.*` Leaders and the dashboard opt-out, set in
`init.lua`. These have to be in place before
plugins load, which is earlier than `config.lua`
is read.
>lua
-- lua/user/config.lua
return {
colorscheme = "gruvbox",
colorscheme_persistence = false,
statusline = {
edge_style = "round",
colors = {},
},
obsidian_vault = "~/Documents/MyVault/",
completion_style = "supertab",
blink_conservative_filetypes = { "tex", "latex" },
blink_conservative_size_kb = 500,
drop = false,
writing_filetypes = { "vimwiki", "quarto" },
non_code_filetypes = { "csv" },
q_close_filetypes = { "oil" },
spell_in_code = true,
}
Current keys: ~
`colorscheme` Colorscheme applied at startup unless
persistence has restored a previously
picked one. Default: `"gruvbox"`, which
the distribution ships and pins. The
dashboard and statusline fallback
colors are chosen against it, so a
fresh install looks coherent without
any configuration. Naming a scheme that
is not installed warns on startup and
falls back to gruvbox.
`colorscheme_persistence` If true, restore the colorscheme last
picked with SearchLeader+C, in
preference to `colorscheme` above. Set
false to make `colorscheme`
authoritative. Default: true.
`:checkhealth noethervim` reports which
of the two set the active scheme.
`statusline_enabled` Set to `false` to skip NoetherVim's
heirline statusline, tabline and
winbar entirely, so a replacement
(lualine, mini.statusline) dropped into
`lua/user/plugins/` takes over with no
conflict. Default: `true`.
`statusline.edge_style` Shape of the colored mode block at
the left of the statusline. See
|noethervim-statusline-edge-style|.
Default: `"round"`.
`statusline.colors` Heirline color-table overrides.
See |noethervim-statusline|.
`statusline.extra_right` Extra heirline component specs
appended to the right side of the
main statusline.
`statusline.tab_modified_indicator`
Glyph rendered on a tab containing
unsaved changes. Default `" ●"`.
Common alternatives: `" [+]"`,
`" *"`, `" "`, `" ◉"`.
`statusline.mode_background` Set to `false` to keep one statusline
background in every mode. On by default:
the bar shifts to blue in insert, so the
mode is legible from the shape of the bar
and not only from the mode chip, which
still changes colour either way.
`statusline.filetype_profile` Set to `true` to render a marker for
the filetype profile (writing or code)
that claimed the current buffer: a
pencil in blue for writing, angle
brackets in purple for code, and
nothing at all for a buffer neither
profile claims.
Clicking it reports the profile, the
detected filetype, and the live values
of the options the profile sets, so
per-buffer toggles (`[ow`, `]os`, ...)
show up too. `<C-w>sf` turns it on and
off in a running session; this sets
where it starts. See
|noethervim-filetype-profiles|.
Default: `false`.
`statusline.git_click` Function called when the git
branch/status block is clicked. The
default opens lazygit when it is on
PATH and falls back to snacks'
git-status picker otherwise; lazygit
is optional, not a prerequisite.
`obsidian_vault` Path to your Obsidian vault. Read by
the `obsidian` bundle. Default:
`~/obsidian/`.
`completion_style` Tab-key philosophy for the
completion menu: `"snippet"` (default,
Tab is reserved for LuaSnip jumps),
`"supertab"` (Tab accepts visible
menu item, falls back to snippet
jump), or `"navigate"` (Tab cycles
the menu without accepting).
`blink_conservative_filetypes` Filetypes where keyword-triggered
completion is suppressed. `<C-Space>`
and LSP trigger characters (e.g. `\`
in LaTeX) still work. Default:
`{ "tex", "latex" }`.
`blink_conservative_size_kb` Files larger than this (in KB) also
get conservative mode, regardless of
filetype. Default: `500`.
`drop` Set to `false` to disable the
seasonal `drop.nvim` animations from
the `eye-candy` bundle. Default:
enabled.
`writing_filetypes` Extra filetypes added to the writing
profile (wrap, linebreak, spell,
conceallevel=2, formatoptions+t).
Additive: defaults stay in place.
See |noethervim-filetype-profiles|.
Default: `{}`.
`non_code_filetypes` Extra filetypes that skip both the
writing and code profiles; their own
ftplugin / buffer settings take over.
Additive: defaults stay in place.
Default: `{}`.
`q_close_filetypes` Extra filetypes where a bare `q`
closes the window. Additive: defaults
stay in place. `{ "oil" }` is the
common addition. See
|noethervim-q-close|. Default: `{}`.
`spell_in_code` If `true`, the code profile turns on
`spell`. Spellcheck is scoped to
comments and strings via treesitter
`@spell` captures; identifiers are not
flagged. `spelloptions` is left
untouched; add `camel` yourself if
you want CamelCase splitting. See
|noethervim-filetype-profiles|.
Default: `false`.
`toggle_feedback` Channel for the confirmation message
emitted when a bracket-prefix toggle
(`[ow`, `]os`, etc.) fires.
`"notify"` (default) routes through
`vim.notify` and is picked up as a
snacks toast. `"echo"` uses
`nvim_echo` for the classic one-line
cmdline message (still recorded in
`:messages`). `"off"` suppresses the
message entirely.
See `templates/user/config.example.lua` for the annotated template.
------------------------------------------------------------------------------
4.7 PLUGINS *noethervim-user-plugins*
Create files in `lua/user/plugins/`. Each file returns a lazy.nvim spec
table. This is where you both add new plugins and adjust the ones the
distro already ships.
To add a new plugin, write a normal lazy.nvim spec: >lua
-- lua/user/plugins/pantran.lua
return {
{ "potamides/pantran.nvim",
cmd = "Pantran",
opts = { default_engine = "argos" },
},
}
To override a plugin the distro already loads, use the EXACT same
repository string, and lazy.nvim deep-merges `opts` tables automatically: >lua
-- lua/user/plugins/snacks.lua
return {
{ "folke/snacks.nvim",
opts = {
picker = { layout = { preset = "vertical" } },
},
},
}
You only need to specify the fields you want to change. Core defaults
fill in everything else.
To add extra keymaps to an existing plugin: >lua
{ "folke/snacks.nvim",
keys = {
{ "<space>fg", function() Snacks.picker.grep() end, desc = "live grep" },
},
}
lazy.nvim unions `keys`, `cmd`, `event`, and `ft` tables from all specs
for the same plugin.
If you prefer keeping all keymaps in one place, set them in
`lua/user/keymaps.lua` instead. That file loads early, before lazy-loaded
plugins, so map to a `<cmd>...<cr>` (which triggers the load) or wrap the
call, (function() ... end) rather than referencing a plugin function that may
not be loaded yet.
Array-valued opts: ~ *noethervim-user-plugins-arrays*
`vim.tbl_deep_extend` (the function lazy.nvim uses to merge `opts`) does
NOT extend sequential arrays; it REPLACES them. Keys like
`ensure_installed` (Mason / treesitter), `formatters_by_ft.<ft>` (conform),
and `linters_by_ft.<ft>` (nvim-lint) all behave this way.
Two patterns to extend an array key without replacing it: >lua
-- Pattern 1: list the full set you want.
-- (you must track upstream additions yourself.)
{ "neovim/nvim-lspconfig",
opts = { ensure_installed = { "lua_ls", "basedpyright", "gopls" } } }
-- Pattern 2: opts as a function, mutating in place.
-- Picks up upstream additions automatically; preferred when the
-- upstream list is long or distro-maintained.
{ "neovim/nvim-lspconfig",
opts = function(_, opts)
opts.ensure_installed = opts.ensure_installed or {}
table.insert(opts.ensure_installed, "gopls")
end }
Pattern 2 receives the resolved upstream `opts` table as the second
argument. Mutating it (rather than returning a new table) lets you apend your
entries at runtime.
NOTE: if you specify `config = function()` in your override spec, it
REPLACES the upstream config function entirely. Only do this as a last
resort.
See `templates/user/plugins/example.lua` for more examples.
------------------------------------------------------------------------------
4.8 LSP SERVERS *noethervim-user-lsp*
Create files in `lua/user/lsp/`. This is where you both add new server
configs and adjust the ones the distro already ships
(see `lua/noethervim/lsp/` for the bundled list).
To add a new server, call `vim.lsp.config()` and `vim.lsp.enable()`: >lua
-- lua/user/lsp/gleam.lua
vim.lsp.config("gleam", {
cmd = { "gleam", "lsp" },
filetypes = { "gleam" },
root_markers = { "gleam.toml", ".git" },
})
vim.lsp.enable("gleam")
To adjust a server the distro already configures, call `vim.lsp.config()`
again with the same name, and Neovim deep-merges the tables: >lua
-- lua/user/lsp/lua_ls.lua
vim.lsp.config("lua_ls", {
settings = {
Lua = {
workspace = { library = { "/my/custom/lib" } },
},
},
})
-- No need to call vim.lsp.enable() again.
------------------------------------------------------------------------------
4.9 IMPERATIVE OVERRIDES *noethervim-user-overrides*
Create files in `lua/user/overrides/`. These run at the very end of
`noethervim.setup()`, after everything else has loaded. Use this only
when opts merging and module hooks are insufficient: >lua
-- lua/user/overrides/custom-diagnostics.lua
vim.diagnostic.config({ virtual_text = { prefix = ">>" } })
"After everything else" means everything in stage 2 of
|noethervim-load-order|. Overrides also beat plugin `keys` entries: once
you remap one, lazy.nvim leaves it alone, before and after the plugin
loads.
They do NOT beat stage 3: a lazy-loaded plugin's own `config` function,
or buffer-local LSP keymaps applied on `LspAttach`. Those run later and
win. For a plugin's own bindings use |noethervim-user-plugins| (spec
merging), which lazy.nvim times correctly; for LSP keymaps use your own
`LspAttach` autocmd. See |noethervim-override-timing|.
5. Bundles
Section titled “5. Bundles”*noethervim-bundles* *neothervim-bundles*
Bundles are opt-in plugin groups. They live under
`lua/noethervim/bundles/<category>/<name>.lua`; if you are using the init.lua
template, enable them by uncommenting the corresponding `import` line in your
`init.lua`: >lua
{ import = "noethervim.bundles.languages.latex" },
{ import = "noethervim.bundles.tools.debug" },
*noethervim-bundle-toggle*
You can also toggle bundles from the picker without editing `init.lua`
by hand. Open `:NoetherVim bundles` (or SearchLeader+cb) and:
<C-y> Enable bundle. Tries to uncomment its `import` line in
`init.lua`; if the line is missing, proposes inserting a
new one at the end of the `spec = { ... }` table. A
floating diff window shows the exact change before any
file is written, with [y]es / [n]o confirmation.
<C-x> Disable bundle. Re-comments the active `import` line,
again behind a diff prompt.
<CR> Open the bundle source file (unchanged).
<C-o> Seed a user override for this bundle in
`user/plugins/<name>.lua`, listing the upstream spec's repo
strings as commented stubs. Same as opening the source and
running `:NoetherVim override`. See |noethervim-commands|.
<F1> List every key the picker binds. Press <F1> or <Esc> to
dismiss it; <Esc> only closes the picker itself once the
list is gone. Works in every snacks picker, not just this
one; see |noethervim-picker-help|.
If `init.lua` has been restructured beyond recognition (multi-file
specs, no detectable `spec = {` block), <C-y> falls back to copying the
import line to the `+` and `"` registers and notifies you to paste it
manually.
After accepting a change, restart Neovim to load the bundle (or use
`:Lazy reload` if you know what you are doing).
*noethervim-stale-imports*
If an enabled bundle has been removed or renamed upstream, lazy.nvim
emits a `No specs found for module "..."` error during spec resolution.
The stock `init.lua.example` bootstrap calls
`require("noethervim.util").buffer_notify()` immediately before
`lazy.setup(...)` so these errors surface as `snacks.notifier` notifications
after VimEnter rather than landing on the cmdline as ErrorMsg.
Error-level startup notifications stay on screen until dismissed, and say
so: the toast carries `<Esc> dismiss` and, for a stale import, the key
that opens `init.lua` (read from your keymaps, so a rebind shows yours).
`:checkhealth noethervim` reports the same errors under "Spec errors",
along with the current path when a bundle was renamed rather than removed,
and flags an override left without a spec to extend under
"Stranded overrides".
Available bundles (grouped by category): ~
languages/ ~
`c-cpp` clangd for C and C++, plus c/cpp treesitter parsers.
Requires: compile_commands.json for good results.
`rust` rustaceanvim: enhanced Rust beyond rust-analyzer.
`go` go.nvim: Go test gen, struct tags, interface impl.
`java` nvim-jdtls: proper Java LSP (requires special setup).
`python` venv-selector.nvim: virtual environment switching.
`latex` VimTeX, img-clip, bibtex picker, LaTeX textobjects.
`<LocalLeader>lf` makes the PDF viewer follow the
cursor until pressed again.
Some of its snippets expect a particular preamble;
which ones, and what it looks like, are written up at
https://nathanaelsrawley.com/noethervim/guides/latex-setup/
Requires: latexmk, TeX distribution.
For citations, see the `zotero` bundle under writing/.
`web-dev` JS/TS template string + inline color preview.
tools/ ~
`debug` nvim-dap + UI. Language-agnostic: the Neovim Lua
adapter is built in, and the Python, Go, JS/TS and
C/C++ adapters come from their own language bundles
when both bundles are enabled.
`test` neotest test runner. Language-agnostic in the same
way: Python, Go, Rust, Java, Jest and Vitest adapters
come from their own language bundles when both
bundles are enabled. See |noethervim-test-adapters|.
`repl` iron.nvim interactive REPL.
`task-runner` overseer.nvim + compiler.nvim.
`database` vim-dadbod + UI + SQL completion via blink.cmp.
`http` kulala.nvim HTTP/REST/gRPC/GraphQL client.
`git` Fugitive, Flog, Fugit2, diffview, git-conflict.
`ai` CodeCompanion (Anthropic, OpenAI, Gemini, Ollama, …).
Default: Anthropic. Override adapter in user/plugins/.
Requires: API key env var for cloud providers.
`refactoring` Extract function/variable/block.
`octo` GitHub PRs / issues / reviews via the `gh` CLI.
`<C-w>O` opens the PR list. Requires `gh` (and
a one-time `gh auth login`).
`nvim-dev` Neovim config development: :StartupTime, :Luapad,
vimls LSP for .vim files.
navigation/ ~
`harpoon` Fast per-project file marks.
`flash` Enhanced f/t and / motions with jump labels.
Note: the flash bundle remaps `S` in normal mode from the
core global-substitute shortcut to a flash jump. The core
`S` behavior remains accessible via `:%s/`.
`projects` Project switcher via snacks.picker.
`editing-extras` Argument marking (argmark) + decorative comment boxes.
`yanky` Yank ring: cycle through paste history with
`<C-p>`/`<C-n>` after a paste; SearchLeader+y opens
a fuzzy picker over the full history.
writing/ ~
`markdown` render-markdown, markdown-preview, tables, math,
img-clip (image paste from clipboard).
`obsidian` Obsidian vault integration (obsidian.nvim).
Recommended: also enable the markdown bundle.
Configure vault path in lua/user/config.lua.
`neorg` .norg wiki / note-taking.
`zotero` Citation picker over a local Zotero library, for tex,
markdown, quarto, typst, org and asciidoc.
`<LocalLeader>z` opens it. Requires: Zotero, sqlite3.
`wrapsearch` Make `/` and `?` match across hard-wrapped lines, so
a phrase split by a wrap is still findable. The
writing profile hard-wraps by default, so this is its
counterpart. `g/` searches verbatim for one search.
See |noethervim-filetype-profiles|.
`presentation` Slide presentations (presenting.nvim) + showkeys.
terminal/ ~
`better-term` Named terminal windows.
`tmux` Tmux window naming.
Note: vim-tmux-navigator remaps `<C-h/j/k/l>` to navigate
both Neovim splits and tmux panes seamlessly. This
overrides the core window-navigation shortcuts (which do
the same thing for Neovim splits only).
`remote-dev` distant.nvim SSH editing.
ui/ ~
`colorscheme` Nine themes beyond the gruvbox core already ships.
Mainstream picks: catppuccin, tokyonight, rose-pine,
kanagawa. Long tail: onedark, nord, everforest,
nightfox, solarized. All lazy; only the active one
loads, but the nine cost ~18MB on disk, which is why
they are opt-in.
Switching themes (SearchLeader+C), persistence and
highlight tweaks are core features and work without
this bundle. See |noethervim-colorscheme-tweaks|.
`eye-candy` Animations, scrollbar, code block visualizer.
`minimap` Sidebar minimap with diagnostics and git signs.
`helpview` Rendered :help pages.
`tableaux` noethervim-tableaux: animated mathematical dashboard
scenes (Sieve, Collatz, Lorenz, Game of Life, …).
practice/ ~
`training` Vim motion and typing practice (vim-be-good,
speedtyper, typr).
`hardtime` Motion habit trainer.
Each bundle file contains full documentation (plugins, keymaps,
commands, and requirements) in its header comment. Browse them with
`:NoetherVim bundles` or SearchLeader+cb.
*noethervim-test-adapters*
Test bundle: ~
Keymaps live under `<Leader>t`, the sibling of `<Leader>d` for debug: ~
`<Leader>tt` run the nearest test
`<Leader>tf` run the current file
`<Leader>ta` run every test under the working directory
`<Leader>tl` re-run the last test
`<Leader>tq` stop the running test
`<Leader>ts` toggle the summary tree
`<Leader>to` show output for the nearest test
`<Leader>tO` toggle the output panel
`<Leader>tw` toggle watch mode on the current file
`<Leader>td` debug the nearest test (needs the `debug` bundle too)
Adapters: ~
The `test` bundle installs neotest and no adapters. An adapter arrives
from its language bundle, and only when both bundles are enabled:
`languages/python` pytest / unittest, via neotest-python
`languages/go` go test, via neotest-golang
`languages/rust` cargo test, via rustaceanvim's own adapter
`languages/java` JUnit, via neotest-java
`languages/web-dev` Jest and Vitest
So `test` on its own gives you the UI and nothing to run: `:Neotest run`
finds no tests. The `debug` bundle is arranged the same way.
For a language with no bundle, append to `adapters` from `user/plugins/`,
using the function form of `opts`. An adapter is an object its plugin
constructs, so the `require` has to run when neotest loads, not while specs
are being collected: >lua
{ "nvim-neotest/neotest",
dependencies = { "some/neotest-adapter" },
opts = function(_, opts)
table.insert(opts.adapters, require("neotest-adapter")({}))
end }
*noethervim-inline-math*
Inline math (markdown bundle): ~
The `markdown` bundle typesets `$...$` and `$$...$$` and draws the result
over the source, in markdown buffers. Each equation is set by pdflatex or
tectonic and converted by ImageMagick, then cached, so only its first
appearance pays for the run. It needs a terminal that speaks the kitty
graphics protocol (kitty, WezTerm, Ghostty).
Move the cursor onto an equation's line to see the source again, unless
'concealcursor' covers the mode you are in.
`[om` Render math in this buffer. In a tex or typst
buffer this is the opt-in: the bundle claims
markdown and leaves other filetypes to the PDF
or preview they already have.
`]om` Stop rendering math, in every buffer. Reach for
this in a document where typesetting each equation
costs more than reading the source.
Configuring noethervim-tex (latex bundle): ~
The `latex` bundle includes `noethervim-tex`, which provides LaTeX snippets,
treesitter navigation, preamble and figure completion sources, theorem-tag
colouring, and a spell dictionary of common mathematical terms. Configure it
via opts override in `user/plugins/`: >lua
{ "Chiarandini/NoetherVim-tex", opts = {
-- Both off by default: `conventions` needs preamble declarations
-- yours may not have, `acronyms` is one writer's vocabulary.
snippets = { conventions = true, acronyms = true },
preamble = { folders = { "preamble", "~/my/preambles/" } },
extra_snippet_paths = { "~/shared-snippets/" },
textobjects = false,
} }
See `:help noethervim-tex` for the full option list.
*noethervim-snippet-stop*
*noethervim-spell-add*
Adding a word: ~
`zg` adds the word under the cursor to your spellfile, and with it the
possessive, which Vim does not derive for a lowercase entry and which you
would otherwise come back and add by hand. `zG` does the same for this
session only. Both report what they wrote, and adding a word twice does
nothing.
Vim already accepts the capitalised and all-caps forms of a lowercase
entry, so those are never written. Nor is the lowercase form of a
capitalised one: that would stop a proper noun being flagged in lowercase,
and names are most of what gets added by hand.
`<C-l>` in insert mode replaces the nearest misspelling behind the cursor
with the first suggestion, leaving the cursor where you were typing.
Getting out of a snippet: ~
`<C-u>` Stop every snippet in the buffer (insert, select)
`<Leader>U` Same, from normal mode
`:LuaSnipStop` Same, as a command
`<Leader>u` Unlink only the snippet you are in
`<C-u>` only stops snippets when there are snippets to stop. With none
active and no marks left behind, it is Vim's own "delete what you have
typed on this line", untouched.
Stopping is not the same as unlinking. Unlinking drops one snippet and
hands the cursor to its neighbor; LuaSnip's extmarks stay behind, so the
buffer can end up with no active snippet but still carry marks that
highlight text and interfere with later expansions. Stopping unlinks
everything and clears those marks. Reach for it when a buffer still
behaves as though a snippet were active. Buffer text is never touched,
and snippets expand normally afterwards.
*noethervim-snippet-reenter*
Getting back into one: ~
`<Leader>j` Jump back into the snippet under the cursor
Tabbing past the last placeholder finishes a snippet, and normally that is
the end of it. `<Leader>j` reopens one you have already left, so a
placeholder you filled in wrongly can be filled in again without expanding
the snippet a second time. Put the cursor anywhere in the snippet's text
and press it; `<Tab>` and `<S-Tab>` then move between its placeholders as
they did the first time. A snippet nested inside another is reached the
same way.
Snippets stay reachable for as long as the buffer is open. If the cursor
is not in one, `<Leader>j` says so and does nothing.
*noethervim-snippets*
Writing custom snippets: ~
LuaSnip loads user snippets from `stdpath("config")/LuaSnip/<filetype>/`.
Create a directory for the target filetype (e.g. `LuaSnip/tex/`,
`LuaSnip/python/`) and add `.lua` files inside it; each file should
return a snippet list. LuaSnip auto-loads them alongside any
plugin-provided snippets.
Saving a snippet file puts it to work straight away. That holds for a file
you have just created for a filetype you had no snippets for before, and it
holds in every other Neovim you have open at the time. Nothing needs
restarting.
SearchLeader+es (`:LuaSnipEdit`) jumps to a snippet file for the current
filetype. Every registered file is listed, yours and the ones plugins
ship, so the picker doubles as a way to read how a bundled snippet is
built.
Entries read as owner and filename, for example:
~/.config/nvim · preamble.lua
NoetherVim-Tex · commands.lua
The filetype was chosen a prompt earlier, so the `LuaSnip/<ft>/` segment
every entry shares is left out. The owner is the lazy.nvim plugin name, or
the tree the file came from when it is one of yours.
A file owned by a plugin opens with 'readonly' set: a snippet saved there
is lost on the next |:Lazy| update, and in a `dev` checkout it dirties that
repository and then loads twice alongside your own copy. Use `:w!` when you
do mean to edit a plugin you maintain.
When you have no snippet file of your own for the filetype, the list also
offers one to create, marked `(new)`:
~/.config/nvim · rust.lua (new)
Choosing it writes a skeleton and opens it. Do not empty the file: a Lua
file that returns nil errors the next time LuaSnip loads that filetype.
Writing the new file registers it immediately, so its snippets work in the
same session without a restart.
In a buffer with no 'filetype' there is nothing filetype-specific to edit,
so the prompt offers only `all` and warns. Neovim maps `.rs` to `rust`,
not `.rust`, so a scratch file named `tmp.rust` has no filetype at all;
`:setfiletype` fixes it.
For LaTeX snippets, `noethervim-tex` exposes helper functions: >lua
local helper = require("noethervim-tex").luasnip_helper
local tex_utils = helper.tex_utils -- in_mathzone, in_text, etc.
local get_visual = helper.get_visual_node
6. Inspection
Section titled “6. Inspection”*noethervim-inspect*
Browse and search NoetherVim source code from within the editor.
All commands are available as `:NoetherVim <subcommand>` and most have
keymaps under the SearchLeader+c prefix (default: `<Space>c`; see
|g:mapsearchleader|). Source files open as non-modifiable to prevent
accidental edits to the distribution.
SearchLeader+cf `:NoetherVim files` Browse NoetherVim source
SearchLeader+cg `:NoetherVim grep` Live grep NoetherVim source
SearchLeader+cu (snacks) Browse user config files
SearchLeader+cb `:NoetherVim bundles` Browse bundle source files
SearchLeader+ct `:NoetherVim templates` Write user-config templates
`<Leader>e` `:NoetherVim override` Edit/create user override
SearchLeader+cL (snacks) Lazy plugin list
SearchLeader+? `:NoetherVim keymap-guide` Keymap namespace reference
`<Leader>i` (inspect) Open init.lua for editing
Run `:NoetherVim status` to see which user override files are active.
*noethervim-templates*
Templates: ~
SearchLeader+ct `:NoetherVim templates`
Merge the bundled `templates/user/<name>.example.lua` files into your config
as `lua/user/<name>.lua`. The picker shows each template with `[new]` or
`[exists]` status; press `<C-y>` on a row to write it. A floating diff
window shows the exact change before any file is created or overwritten:
`y` or `<CR>` accepts, `n` rejects, `q` or `<Esc>` cancels.
On accept the new file opens for editing, which is what you wanted the
template for.
`<CR>` in the picker opens the source template read-only, so you can read
one without copying anything.
If the destination directory does not exist (e.g. `lua/user/plugins/`),
it is created on accept.
*noethervim-guide*
Keymap namespace guide: ~
SearchLeader+? `:NoetherVim keymap-guide`
Opens a reference buffer showing all active keymaps organized by
NoetherVim's namespace philosophy:
SearchLeader fuzzy navigation and search (sub-grouped by topic)
`[` / `]` directional navigation and option toggles (paired)
`<C-w>` window and panel management
`g` goto and LSP actions
`Z` buffer management
`<Leader>` global actions
Normal / Insert / Visual / Command-line mode sections
The guide is dynamic: it reads the current keymap state, so it reflects
active bundles, user additions, and buffer-local LSP keymaps.
Press `q` to close. Press `<CR>` on any keymap line to jump to its
source definition in the distribution or user config.
*noethervim-picker-help*
Picker key list: ~
Every snacks picker binds `<F1>` to a list of the keys it defines, in both
normal and insert mode, and says so in its title. Snacks also binds `?`,
but only in normal mode; since pickers open with the prompt in insert
mode, `?` typed there goes into the query instead.
Where `<CR>` does something other than open what you picked, the prompt
says which action it is -- `copy` in the registers picker, `git checkout`
in the branch picker. Pickers that open a file or jump to a line leave the
prompt bare, since naming the obvious would bury the ones worth reading.
Dismiss the list with `<F1>` again or with `<Esc>`. `<Esc>` closes the
picker itself only once the list is gone, so with the list open it takes
two presses to leave.
The list shows normal-mode and insert-mode mappings together, and where a
key exists in both the normal-mode entry is the one displayed. Entries
like `q`, `j`, `k`, `gg`, `G`, `/` and `<C-w>H/J/K/L` therefore apply to
the results window rather than the prompt; reach it with `<A-w>`.
*noethervim-picker-notifications*
Notification history: ~
SearchLeader+fn lists past notifications. `<CR>` opens the one under the
cursor in a float: the full message, wrapped, sized to fit, bordered in
the colour of its level. `q` closes it.
The float's filetype is `noethervim-notification`, so it answers to the
q-to-close set (|noethervim-q-close|) and skips both filetype profiles.
7. Comparison
Section titled “7. Comparison”*noethervim-diff*
Compare your user overrides against NoetherVim defaults.
SearchLeader+ck `:NoetherVim diff keymaps`
Smart picker showing all keymaps annotated with:
`[CORE]`: unchanged NoetherVim default
`[USER]`: keymap added by your user/keymaps.lua
`[OVERRIDE]`: core keymap you changed
`[DELETED]`: core keymap you removed
A second column names the bundle a keymap came from
(`latex`, `debug`, `yanky`, ...) and stays blank for
core and user keymaps. It disappears entirely when no
bundle is enabled. The bundle name is searchable, so
typing `latex` narrows the list to that bundle.
`<CR>` opens the file and line that defined the key.
Keymaps set by third-party plugins (surround, mini.ai,
marks, ...) are hidden by default, since they are not
NoetherVim's to explain; `<C-x>` toggles them in and
out. On one of those, `<CR>` opens the spec file that
installs the plugin rather than the plugin's own
source: the spec is where you add `opts` or `keys` to
change or disable the key, and the plugin's tree is
overwritten on update. When the plugin cannot be
identified, `<CR>` says so instead of guessing.
Neovim's own defaults and `<Plug>` handles are never
listed. Use SearchLeader+fk for a plain search over
every key currently mapped.
Buffer-local keymaps, marked `(buffer)`, are those of
the buffer the picker was opened from, so LSP keymaps
appear only when a server is attached there.
Plugins that have not loaded yet have not registered
their keymaps, so the list grows as a session goes on.
SearchLeader+co `:NoetherVim diff options`
Smart picker showing all tracked options (the set of
options NoetherVim explicitly configures) annotated with:
`[CORE]`: unchanged NoetherVim default
`[OVERRIDE]`: option you changed in user/options.lua
SearchLeader+ca `:NoetherVim diff autocmds`
Smart picker showing every augroup NoetherVim
declares, annotated with:
`[CORE]`: unchanged NoetherVim default
`[OVERRIDE]`: augroup you replaced from `lua/user/`
`[CLEARED]`: augroup you emptied and did not refill
`[INACTIVE]`: not in use this session, because the
bundle is off or the plugin has not loaded. Expected,
not a problem.
`[USER]`: augroup you added
Overriding an autocommand means clearing its augroup
and re-registering, so this is the view that tells you
whether that worked. `<CR>` opens the file and line
that registered the handler.
SearchLeader+cd `:NoetherVim diff`
Smart picker listing all NoetherVim modules across
Core, Plugin, Bundle, and LSP categories. Each entry
shows whether a user override exists. Selecting a
module opens the upstream file (readonly) and user
override in a side-by-side vertical split.
Also accepts a direct argument:
`:NoetherVim diff snacks`
8. Toggling
Section titled “8. Toggling”*noethervim-toggle*
Disable all user overrides for debugging.
Environment variable (before starting Neovim): >bash
NOETHERVIM_NO_USER=1 nvim
Vim global (set in init.lua before lazy.setup): >lua
vim.g.noethervim_no_user = true
When active, all `user/` module hooks, LSP overrides, and
`user/overrides/` files are skipped. Plugin spec merging from
`user/plugins/` is also skipped (via the conditional import in
init.lua.example).
This is NOT a runtime toggle; restart Neovim to apply.
Check current status: `:NoetherVim status`
*noethervim-dashboard*
Disabling the startup dashboard: ~
The Snacks dashboard shown on empty-arg startup (`nvim` with no file)
can be turned off with a single flag in your init.lua, set before
`lazy.setup`: >lua
vim.g.noethervim_dashboard = false
With the flag set, `nvim` opens straight into an empty buffer. Set it
to `true` or leave unset to keep the dashboard (default).
*noethervim-auto-install*
Declining toolchain auto-install: ~
Enabling a bundle is how you ask for the tools it drives. A language
bundle names its language server, formatter, linter and debug adapter,
and NoetherVim fetches through Mason whatever is missing, the same way
language servers have always arrived. Enabling `languages/rust` together
with `tools/debug` gets you codelldb; `languages/python` gets you black.
To decline, set this in your init.lua before `lazy.setup`: >lua
vim.g.noethervim_auto_install = false
Nothing is then fetched on your behalf. Bundles still declare what they
need, `:checkhealth noethervim` still names anything missing, and
`:Mason` still installs on request. Set this when a toolchain is managed
outside the editor, by Nix, by system packages, or by a project-local
environment, where a second copy under Mason is at best redundant and at
worst a version you did not choose.
Language servers are unaffected: they arrive through `ensure_installed`
and are not gated by this flag.
9. Keymap namespaces
Section titled “9. Keymap namespaces”*noethervim-keymaps* *neothervim-keymaps*
NoetherVim groups keymaps by prefix. Press any prefix and wait for
which-key to show available actions, or use SearchLeader+fk to search
all keymaps interactively.
This section is the complete list. The bindings that replaced a Vim
default are argued for individually, with a standalone snippet for
each, at:
https://nathanaelsrawley.com/noethervim/guides/notable-keybindings/
The which-key popup appears after a 1500 ms delay by default. To
change this, add to `lua/user/plugins/whichkey.lua`: >lua
return {
{ "folke/which-key.nvim",
-- Remember that setting `config` would override, not extend,
-- the behavior, and passing `opts= {...}` would override the
-- default optoins
opts = function(_, opts)
vim.o.timeoutlen = 500
opts.delay = 500
return opts
end,
},
}
Prefixes: ~
*noethervim-space*
*noethervim-mapsearchleader*
`SearchLeader` Search & navigation (default: `<Space>`)
f(ind) g(rep) G(it) l(sp) d(iagnostics)
c(onfig) D(ebug) e(ditor files)
o(bsidian) w(iki)
Configurable via |g:mapsearchleader|.
*noethervim-leader*
`<Leader>` (\) Global actions
y/Y/p/P (clipboard) f(ormat) h(unk) d(ebug)
t(est) r(un/REPL) R(efactor) m(inimap) b(ox)
a(i) z(en) H(arpoon add)
*noethervim-localleader*
`<LocalLeader>` (,) Filetype-specific actions
LaTeX: vimtex commands, image paste
Neorg: wiki navigation
*noethervim-ctrl-w*
`<C-w>` Windows & panels
l(azy) s(tatusline) t(erminal) F(ugit2)
Q (Trouble diagnostics) <C-q> (quickfix)
[d / ]d (diffview) <C-h> (harpoon) <C-e> (explorer)
<C-o> (Oil)
*noethervim-g*
`g` LSP & go-to
gd(efinition) gD(eclaration) gR(eferences float)
gt(ype def) gi(mplementation) gs(ignature)
gl(diagnostics) go(utline) gy(ank type at cursor)
gr* = Neovim 0.12 defaults (grr/grn/gra…)
gz / gZ (capitalize, see |noethervim-case|)
SearchLeader+li / +lO (call hierarchy in/out)
*noethervim-brackets*
`[x` / `]x` Directional navigation
d(iagnostic) t(ab) b(uffer) f(ile)
q(uickfix / Trouble when open)
T(odo) e(xchange line) <Space>(blank line)
*noethervim-option-toggles*
`[ox` / `]ox` Toggle options
w(rap) s(pell) n(umbers) r(elative) h(lsearch)
L(SP) b(ackground) c(ursorline) l(ist) t(extwidth)
i(gnorecase) I(lluminate) T(reeSitter)
D(irty whitespace/tidy) G(uide column/deadcolumn)
G is off by default; toggle persists across sessions
(stored in stdpath("state")).
`[oC`/`]oC` toggle blink.cmp completion on/off
`[om`/`]om` toggle inline math
(|noethervim-inline-math|)
*noethervim-Z*
`Z` Closing things, arranged as a grid. Rows say how
much you are willing to lose; columns say what you
are closing. Vim's own `ZZ` and `ZQ` are the bottom
and top of the first column.
this window everything this buffer
force `ZQ` :q! `ZW` :qa! `ZE` :bd!
refuse dirty `ZA` :q `ZS` :qa `ZD` :bd
save first `ZZ` :x `ZX` :wa|qa! `ZC` :w|bd
`ZQ` and `ZZ` are Vim defaults, listed for the shape.
`ZW` is the "kill nvim" key; `ZX` is the one that saves
every buffer it can before forcing out.
`ZR` (scratch sweep) deletes unnamed scratch buffers.
*noethervim-running*
Running code: ~
`<Leader>rf` Run the current file
`<Leader>rp` Run the project around it
`<Leader>rc` Run the current file in a floating window
`<Leader>rT` Send the run command to a betterTerm terminal
What each language runs is one table, so the four agree. `rf` and `rp`
need the task-runner bundle; `rc` and `rT` are core, and `rT` also
needs the better-term bundle.
The difference between `rf` and `rp` is the project: in a Cargo crate
`rf` runs `cargo run`, and on a loose .rs file it compiles that file
alone. `rp` runs the project's own entry point (cargo, go.mod, npm,
Maven, make) and does nothing when there is no project around the
buffer. Interpreted languages have `rf` only.
Version managers are honored: an interpreter is resolved through
mise, asdf, pyenv, rbenv, nodenv or goenv for the buffer's directory
before it runs, so a project pinned to an older Python gets that one.
Other notable keymaps: ~
Normal: ~
`<C-a>` / `<C-x>` Enhanced increment/decrement (numbers, booleans,
dates, operators, semver, via dial.nvim)
`g<C-a>`/`g<C-x>` Sequential increment/decrement in visual selection
`<C-/>` Toggle comment (builtin gc/gcc, all modes)
`<C-S-r>` Comment lines and paste uncommented copy below
`gz` / `gZ` Capitalize (see |noethervim-case|)
Visual: ~
`gC` Invert comment per-line. Unlike `gc` (which picks
one direction for the whole range based on the
majority state), `gC` toggles each selected line
independently: so a mixed selection ends up with
every line in the *opposite* state.
`s` Substitute without polluting register
`S` Global search/replace (:%s/)
`;` Command-line (replaces :)
`n` / `N` Consistent search direction: `n` always forwards,
`N` always backwards
`-` Highlight word under cursor + count
`L` Fold-peek (nvim-ufo); falls through to the default
L motion (jump to last visible line) when not on
a closed fold.
`<F2>` Rename (with count, LSP)
Visual: ~
`Y` Yank selection to clipboard
`P` Paste clipboard (keep registers)
`p` Paste over (keep register)
`il` / `al` Line text objects (|noethervim-line-objects|)
`<Down>` / `<Up>` Move block
Insert: ~
`<M-BS>` Delete word backward
`<C-v>` Paste from clipboard
`<C-=>` Expression register
Command-line: ~
`<C-l>` Insert cwd
`<C-y>` Yank cmdline to clipboard
`<C-o>` Redirect output to buffer
*noethervim-line-objects*
Line text objects ~
Vim has no text object for a line. `V` and `yy` are linewise, so what they
put in a register comes back as a whole new line, and there is no operator
target that stops at the line break.
`al` The whole line, up to but not including the break.
`il` The line without its indentation: first non-blank
through last non-blank.
Both take an operator and both work on a Visual selection.
`yal` Yank a line as text, to drop inside another one
with `p` instead of below it.
`dal` Empty a line and keep it: the line stays, its
contents go.
`dil` The same, with the indentation left in place, ready
to type the line again.
`al` does nothing on an empty line, where the break is all there is.
Both take a register prefix, so `"_dal` clears a line without disturbing
the unnamed register and `"ayal` puts one in register `a`.
*noethervim-case*
Capitalization operators ~
Vim ships three case operators -- `gu`, `gU` and `g~` -- and no way to raise
only the leading letter. NoetherVim adds the missing pair:
`gz`{motion} Title Case: raise the first letter of every word.
`gZ`{motion} Sentence case: raise the first letter of the region.
Both work in Visual mode on the selection, take a count, and repeat with
`.`. `gziw`, `gzip`, `gz$` and `gZ3w` all do what they read like.
Title Case has the unshifted key because it is the one with no short
manual equivalent. Raising a single leading letter is already two keys --
`~` on the character, or `guiw~` for a word -- while Title Casing a run by
hand is one `~` per word.
Both are conservative: every character after the first is left exactly as
typed. `gZ` on `the PDE of LaTeX notes` gives `The PDE of LaTeX notes`,
not `The pde of latex notes` -- which is what the usual `guiw~` workaround
would leave you with.
`gz` leaves the short function words lowercase unless they fall first or
last in the run, so `of mice and men` becomes `Of Mice and Men`. The list
lives in `util/case.lua` and can be replaced from `lua/user/`: >lua
-- lua/user/overrides/case.lua
local case = require("noethervim.util.case")
case.minor_words = {} -- capitalize every word
case.minor_words = { ["über"] = true } -- or supply your own set
In tex buffers a leading control sequence is stepped over rather than
walked into, so `gZ` on `\emph{the cat}` gives `\emph{The cat}` and not
`\Emph{the cat}`. An accent macro is followed through -- `\'etale` becomes
`\'Etale` -- and a bare macro is treated as the word it renders as, so
`\LaTeX is great` is left alone.
There is deliberately no `gzz` line-wise double. Mapping an operator and
its doubled form together makes every `gz` wait out 'timeoutlen' first,
which is the same annoyance `gc`/`gcc` is known for. Use `gzV` or `gzip`.
Shadowed defaults: ~
*noethervim-shadowed*
These core keymaps replace standard Vim behavior. The original
functionality is available through the alternatives listed.
`s` Substitute without register
(default: same as `cl`)
`S` Global `:%s/` replace
(default: same as `cc`)
`;` Command-line (replaces `:`)
(default: repeat last f/F/t/T motion)
`n` / `N` Always forward / always backward
(default: next / prev in search direction: after
`?pat`, default `n` goes up)
`|` Vertical split on a scratch buffer
(default: go to screen column)
`_` Horizontal split on a scratch buffer
(default: go N-1 lines down, first non-blank)
`+` New tab on a scratch buffer
(default: go to next line, first non-blank)
`<C-w><C-q>` Open quickfix
(default: close window: use `<C-w>q` or `:q`)
`<C-w>Q` Toggle Trouble diagnostics panel
`<C-w>t` Toggle a 12-line terminal along the bottom. Reuses
the same terminal buffer each time rather than
opening a new one, and drops you straight into
terminal mode. `<Esc><Esc>` leaves terminal mode.
(default: go to top window: use `1<C-w>w`)
*noethervim-resize-arrows*
`<Arrow>` Resize current window: push the near edge in the
arrow direction (grow). Falls back to `hjkl` cursor
motion when the tab has a single non-floating window
(nothing to resize against) or the current window is
a float. Counts pass through, so `5<Down>` jumps 5
lines via the `j` remap.
(default: one-cell / one-line motion: use `hjkl`)
`<S-Arrow>` Resize current window: pull the far edge in the
arrow direction (shrink). No-op when no neighbor
exists on the moving edge.
(default: same one-cell / one-line motion)
*noethervim-semicolon*
Note on `;`: If you lean on `f{char}` / `t{char}` motions with `;` / `,`
to repeat them, revert in `lua/user/keymaps.lua`:
>lua
vim.keymap.set({ "n", "v" }, ";", ";", { desc = "repeat f/t motion" })
If you don't reach for `;` / `,` repeat often, give the swap a try.
The cmdline becomes a one-key hop and the habit sticks fast.
------------------------------------------------------------------------------
9.1 FILETYPE KEYMAPS *noethervim-ftplugin*
These keymaps activate only in their respective filetype, except for the
"all filetypes" group below which applies globally.
all filetypes (insert): ~
`<S-CR>` Smart newline (smart-enter.nvim). Continues the
structure at the cursor: Markdown lists, and LaTeX
environments, etc. Presets are appended when the
Markdown and LaTeX bundles are enabled.
Note: smart-enter's `<S-CR>` needs a terminal that sends it distinctly
from `<CR>` (Kitty, WezTerm, Ghostty, Neovide, most GUIs); some send
plain `<CR>` for both.
tex: ~
`:PDF` Open the compiled PDF in the system viewer. This,
`yP` and Oil's `gP` treat the PDF as a file and
need no viewer configured; `<LocalLeader>lv` is
the one that drives your viewer and syncs
`yP` Put the compiled PDF on the system clipboard
`<LocalLeader>lv` View the PDF at the cursor. Says so first when
the PDF is behind the buffer, since SyncTeX
resolves against the last build
`<LocalLeader>lf` Toggle the PDF following the cursor
`<LocalLeader>P` Paste an image from the clipboard as a figure
`<C-S-c>` Citation from a .bib file (insert)
`@` Preamble fragment completion, at the start of a
line above `\begin{document}`
`]g` `[g` Next / previous theorem environment; `]p` `[p`
for proofs, `]x` `[x` for examples, and `]P` `]X`
for their `\end`
`ig` `ag` A paragraph that stops at display math,
environments, `\item` and sectioning
`zg` `zw` `z=` Spell add / mark wrong / suggest, understanding
LaTeX accents: on `K\"ahler` these read the
decoded word rather than the fragment
`<S-CR>` Smart newline in environments (insert)
`<C-l>` Auto-fix nearest spelling error (insert)
`<LocalLeader>vw` VimTeX word count
`gd` Jump to the label under the cursor (`\cref`,
`\ref`, `\eqref`, ...) via the label cache;
LSP definition when not on a prefixed label
`<C-]>` Same label jump through the tag stack, so
`<C-t>` returns. Works across subfiles and
nested sub-books
`:VimtexToggleMain` Switch between compiling the current subfile
and the full project. Subfile projects
(`subfiles.cls`) start in subfile mode
`:VimtexStop` Stop compilation AND disarm the on-save
auto-compile for the project; the next manual
compile re-arms it. `:VimtexStopAll` does the
same for every open project
markdown: ~
`<S-CR>` Smart newline in lists (insert)
`<C-l>` Auto-fix nearest spelling error (insert)
qf (quickfix): ~
`q` Close quickfix window
`<CR>` Jump to entry, leave the list open
`<S-CR>` Jump to entry and close the list
`<C-j>` / `<C-n>` Navigate down
`<C-p>` Navigate up
oil (file explorer, `<C-w><C-o>` or `:Oil`): ~
`g?` Show all Oil keymaps (including defaults)
`<C-p>` Preview the entry under the cursor in a split, and
keep it in step as you move. Press it again to
close. The split side follows the window shape;
set `preview_split` in a user override to pin it.
`gf` Fuzzy find files in current directory
`gG` Live grep in current directory
`gt` Open a .tex file from this directory (latex
bundle). A lone match opens; with several, a
picker appears where `<CR>` opens the file and
`<S-CR>` puts the Oil cursor on it without
opening, ready for the usual Oil keys
`gP` Open a .pdf from this directory in the system
viewer (latex bundle). With several, a list
appears to choose from
`gV` / `g|` Second pane beside this one, for moving files
between directories. Both open where you are;
navigate each with Oil as usual. `<C-h>` / `<C-l>`
switch panes, `q` or either key again leaves.
A float gives two floats and a split gives two
splits, so the pair matches what you were in.
To copy: yank an entry's line in one pane, put it
in the other, `:w` to commit -- Oil's ordinary
edit-the-buffer model, now with both ends visible.
`gd` Toggle detail view (permissions, size, mtime)
`gX` Open directory in system file browser
`gS` Create symlink in current directory
`gz` Zip entry under cursor (normal) or selection (visual)
Uses `zip` on macOS/Linux, `Compress-Archive` on Windows
`gZ` Unzip .zip entry (normal) or selection (visual)
Uses `unzip` on macOS/Linux, `Expand-Archive` on Windows
`g.` Toggle hidden files
`g\` Toggle trash
`Y` (normal) Copy file under cursor to system clipboard
`Y` (visual) Copy selected files to system clipboard
(skips "../" and unsaved lines)
`yp` `yd` `yn` Yank full path / parent dir / name to unnamed reg
`Yp` `Yd` `Yn` Same, but to the system clipboard ("+)
------------------------------------------------------------------------------
9.2 STATUSLINE *noethervim-statusline*
NoetherVim uses heirline.nvim for its statusline. Toggle components
with the `<C-w>s` prefix:
`<C-w>sg` Toggle git component
`<C-w>sp` Toggle PDF size indicator
`<C-w>sl` Toggle LSP component
`<C-w>sf` Toggle the filetype-profile marker. Starts from
`statusline.filetype_profile`; see
|noethervim-user-config-data|.
`<C-w>sP` Split the path: project-relative directory as its
own component, bare filename beside it
`<C-w>s<C-p>` Toggle PDF mode
Clickable components: ~
Every statusline component below responds to mouse clicks:
Diagnostics count Open buffer diagnostics picker
File name Open current directory in system file browser
Modified indicator Diff unsaved changes against saved version
Deleted-file flag Offer to write the buffer back (see below)
New-file flag Report that nothing is at that path yet (see below)
Scratch flag Offer to save the buffer under a name
Directory indicator Explain the cwd mismatch, offer `:lcd` (see below)
LSP indicator Open `:LspInfo`
Git branch/status Open lazygit in a terminal
Update indicator Open Lazy plugin manager
*noethervim-statusline-mode-chip*
Mode chip: ~
The colored block at the left of the bar names the current mode, and
hands its space to a counter when there is one to show.
Search: the match under the cursor and how many the pattern has in the
buffer, as `3/12`. It clears with the highlighting, on `<Esc>` or `]oh`.
Confirmed substitute (`:s/old/new/gc`): the prompt you are answering and
how many the pass will offer, as `3/12`. The total is fixed when the run
starts, and skipping with `n` advances the count the same as replacing.
Neither counter is computed in buffers over 50000 lines.
*noethervim-statusline-buffer-file*
Buffer and file: ~
A buffer and the file it is named after are two different things, and
they come apart more often than the editor usually admits: the file can
be deleted, or changed underneath you, or never have existed. Vim says
so once, on the cmdline, at the moment it happens, and then the buffer
looks like any other.
The flag slot left of the filename holds that state instead, for as
long as it is true. Between them the flags answer one question, which
is whether what you are looking at exists anywhere but this buffer:
Deleted the file was there and is gone
New the file is not there yet
Modified the file is there and differs from the buffer
Read-only the buffer will not write back
Scratch there is no file, and no name to write to
The directory indicator (below) answers the neighbouring question, of
where a relative `:w` or `:e` would land.
*noethervim-statusline-deleted*
Deleted-file flag: ~
A red marker appears in the flag slot, left of the filename, when the
file behind the buffer is deleted, whether through Oil, `rm` in a
terminal, or another program. The buffer stays intact and holds the
only surviving copy of the contents.
Click it to write the buffer back and recreate the file.
The flag clears on the next write or re-read.
*noethervim-statusline-new-file*
New-file flag: ~
A marker in the same slot when the buffer has a name but nothing is at
that path yet, as after `:e notes/draft.md` on a file that does not
exist. The buffer is the only copy, the same as for a deleted file, so
closing without writing loses it.
Click it for the full path. `:w` creates the file, and any parent
directories it needs.
The flag clears on the first write.
*noethervim-statusline-directory*
Directory indicator: ~
A small marker appears left of the filename when the buffer's
directory is not the window's working directory. Dimmed when the
file is somewhere under cwd, which is ordinary project navigation;
in the warning color when it is outside cwd entirely.
The marker is about relative paths, not saving in general. Neovim's
working directory belongs to the window (falling back to
the tab, then the global one), never to the buffer, so:
:e docs/a.md cwd stays at the project root
:w draft.md writes <root>/draft.md,
not <root>/docs/draft.md
A plain `:w` is never affected. Neovim expands a buffer's name to an
absolute path when the buffer is created, so it always writes back
to the file it read, whatever cwd has done since.
Click the marker for the two paths and a `[L]` action that runs
`:lcd` to the buffer's directory. `:lcd` rather than `:cd` on
purpose: `:cd` from a window holding a window-local or tab-local
directory discards both. `:pwd` and `haslocaldir()` report which
scope is currently in force.
Setting 'autochdir' makes cwd follow the buffer, which silences this
marker permanently. It also makes every relative path resolve
against whichever file you last visited, which is its own way to
open or create a file somewhere unexpected. NoetherVim leaves
'autochdir' off, matching Neovim's default; `[oa` / `]oa` toggle it.
VimTeX compile indicator (latex bundle): ~
The compile indicator follows the relevant project automatically.
For \input children, it walks vimtex's state list to find the
parent project so the status surfaces even when the buffer is
a fragment. For subfile projects (`subfiles.cls`) it compiler
statusline component marks `[parent]` / `[subfile]` / `[parent+sub]`
so it's clear which side is compiling. While compiling it appends a rough
percent against the last successful PDF size (cached at
`stdpath('state')/noethervim/vimtex_baseline.json`). On success the
compile duration is sent to fidget under the `vimtex` group; the
statusline shows a persistent `compiled ✓` until the next compile.
Snippet jump indicator: ~
While a LuaSnip snippet is active, the statusline shows which
tabstop you are on out of how many, flanked by arrows for the
directions that lead to another tabstop: a left arrow when an
earlier tabstop exists, a right arrow when a later one does. So
`1/3` with only a right arrow means two tabstops still to fill.
The indicator disappears once the snippet is finished or left.
The count describes the innermost group that actually holds more
than one tabstop. Dynamic and choice nodes build groups of their
own, so this is what you want in both directions: `:defn`, whose
four tabstops are each wrapped in a dynamic node, reads `1/4`
through `4/4`, and a `mat:3*3` matrix, whose nine cells all live
inside a single dynamic node, reads `1/9` through `9/9`.
Expanding a second snippet inside a tabstop of the first switches
the indicator to the inner snippet's own count while you fill it,
then returns to the outer snippet's numbering when you jump out.
Customizing the statusline: ~
These keys apply to the default heirline statusline; if you replace the
statusline (for example with lualine), they have no effect.
Customize statusline colors and extra components in `lua/user/config.lua`: >lua
-- lua/user/config.lua
return {
statusline = {
edge_style = "round", -- see edge styles below
colors = { ... }, -- override heirline color table
extra_right = { ... }, -- heirline component specs
},
}
Available color keys (from `util/palette.lua`): ~
`mode_n`, `mode_i`, `mode_v`, `mode_c`, `mode_t`, `mode_R`,
`green`, `blue`, `orange`, `red`, `purple`, `default_gray`,
`lazy_updates`, `text_gray`, `profile_writing`, `profile_code`
A `mode_` key names one mode and moves only that indicator. The mode
variations are reachable the same way, by their short name:
`mode_niI`, `mode_cv`, `mode_Rv`. Setting `green` instead moves the
normal-mode indicator and everything else green on the bar with it.
*noethervim-statusline-themes*
Every component asks the palette for a role rather than for a colour,
so overriding a key here moves everything that plays that role at once,
and the bar keeps working when you change theme.
Gruvbox is the theme the palette is tuned against, by hand, with each
role given a value chosen for it. Under any other colorscheme the roles
are derived from standard highlight groups instead: `String` for green,
`Function` for blue, `DiagnosticError` for red, and so on. That is a
best effort, and on themes that paint those groups unusually the result
is legible rather than considered -- gruvbox itself paints `Function`
green and `Statement` red, which is why the two filetype-profile roles
are their own keys rather than reusing `blue` and `purple`.
If a role lands badly on the theme you use, override it here; that is
what these keys are for.
Example, changing the normal-mode indicator to blue: >lua
-- lua/user/config.lua
return {
statusline = {
colors = { mode_n = "#458588" },
},
}
*noethervim-statusline-edge-style*
Edge style ~
The colored mode block at the left of the statusline (and, for some
styles, an opening endcap on the right ruler block) follows the
`statusline.edge_style` key in `lua/user/config.lua`. All glyphs come
from the Nerd Font private use area, so a Powerline-capable terminal
font is required.
`round` (default) Rounded mode bubble, flush right edge.
`slant` Slanted endcaps leaning right, so the mode block is a
right-leaning parallelogram; right ruler also slanted.
`slant_left` The mirror of it, leaning left.
`slant_in` Edges lean towards each other, making the block a
trapezoid rather than a parallelogram.
`slant_out` Edges lean apart, the other trapezoid.
`pointy` Triangle endcaps; right ruler gets a triangle opener.
`straight` No endcap glyphs; mode block renders as a plain
rectangle, right edge stays flush.
`bubbly` Rounded endcaps on both sides (left mode block and
right ruler block).
An endcap at a section's left edge is filled on its right, and one at
the right edge is filled on its left. Two that lean the same way give a
parallelogram, two that lean opposite ways give a trapezoid, which is
the whole of the shape space and why the slant family has four members.
Switching styles takes effect on the next Neovim launch (the bar is
assembled in heirline's `config` callback). Picking an unknown name
falls back to `round` and emits a `vim.notify` warning during setup.
For custom component authoring, see |heirline-cookbook|.
*noethervim-statusline-busy-override*
Greedy Busy-component takeover ~
The Busy component (animated spinner while `vim.bo.busy > 0`) can be
greedily claimed by a bundle or by user config via
`register_busy_override`. The override supplies a label, highlight and
optional click handler, and temporarily replaces the default spinner
rendering. The most recently registered override that returns a
non-nil spec wins (last-write-wins), so user config overrides bundle
defaults naturally: >lua
require("noethervim.statusline").register_busy_override(function()
-- return nil to yield; return a spec to claim the slot:
return {
icon = nil, -- falls back to the spinner
label = "ai", -- shown after icon
hl = { fg = "#c678dd", bold = true },
on_click = function() ... end, -- mouse handler
}
end)
The animation timer only ticks while something is driving
`vim.bo.busy` > 0, so overrides wanting animation should also
increment busy on the relevant buffer.
*noethervim-statusline-error-boundary*
Error recovery ~
If any statusline component raises an error, the bar degrades to a
`statusline recovering...` marker instead of letting the traceback
replace the entire statusline. The full error is written to
|:messages| for diagnosis. After a cooldown, the bar auto-retries;
if the underlying state has cleared, normal rendering resumes.
Override the cooldown with `vim.g.heirline_recovery_ms` (default
`1000`). The same protection applies to the tabline, winbar, and
statuscolumn.
------------------------------------------------------------------------------
9.3 TABLINE *noethervim-tabline*
NoetherVim ships a tab-based tabline (not a bufferline). Each tab shows
its focused buffer's file icon, `project: filename` label (derived from
git root), and a `●` modified indicator. Tabs have per-tab close
buttons and shrink gracefully when many tabs are open.
Tab management keymaps: ~
`+` Open a new tab on an empty scratch buffer
`<C-w><A-h>` Move current tab left
`<C-w><A-l>` Move current tab right
`[t` / `]t` Previous / next tab
`>t` / `<t` Move current tab right / left
*noethervim-tabline-bufferline*
Switching to a bufferline ~
NoetherVim defaults to a tabline because tabs and buffers serve
different purposes in Vim's model: tabs are layout viewports, buffers
are open files. Reaching for a bufferline to manage open files often
signals that a fuzzy-finder or |:ls| would be a more effective habit.
That said, if you prefer a bufferline, replacing the tabline is
straightforward. See the example in
`templates/user/plugins/bufferline.example.lua`, or drop this into your
`lua/user/plugins/` directory: >lua
{ "akinsho/bufferline.nvim",
event = "UIEnter",
dependencies = "nvim-tree/nvim-web-devicons",
opts = {},
config = function(_, opts)
require("bufferline").setup(opts)
-- Disable heirline's tabline so bufferline owns the area.
vim.o.tabline = ""
vim.o.showtabline = 2
end,
}
10. Commands
Section titled “10. Commands”*noethervim-commands*
`:NoetherVim` [subcommand] Inspection and comparison commands.
See |noethervim-inspect| and |noethervim-diff|.
Run with no subcommand to print the full
subcommand list with descriptions.
`:NoetherVim override` Open the user override file corresponding to
the current buffer's NoetherVim source file.
Creates the file (and parent directories) if it
does not exist, seeding it with minimal
boilerplate. Opens the override in a vertical
split so you can read the source alongside your
customization.
Path mapping:
`plugins/<name>.lua` -> `user/plugins/<name>.lua`
`bundles/<cat>/<name>.lua` -> `user/plugins/<name>.lua`
`lsp/<name>.lua` -> `user/lsp/<name>.lua`
`options.lua` etc. -> `user/<name>.lua`
`ftplugin/<ft>.lua` -> `<config>/ftplugin/<ft>.lua`
For internal modules (util/, sources/) or files
outside the NoetherVim tree, a warning is shown.
For a plugin or bundle, the seeded file lists
every repo string the upstream spec declares, as
commented-out stubs. lazy.nvim merges by repo
string, so those are the keys an override
attaches to; uncomment the one you want.
`:NoetherVim bundles` binds `<C-o>` to the same
thing, so you can seed an override straight from
the catalogue without opening the source first.
`:NoetherVim override!` Copy the upstream file wholesale instead of
seeding stubs. A last resort, and the file says
so in its own header: a full copy shadows every
spec in the original, not just the ones you meant
to change, so later upstream edits to any of them
stop reaching you, and a copied
`config = function()` no longer deep-merges the
way `opts` does. An override that already exists
is never overwritten, with or without the bang.
*noethervim-override-drift*
Both forms record which distribution file the
override was written against, and a hash of its
contents at that moment. The record lives in
`stdpath('state')/noethervim/override-base.json`,
never in the override itself: files under
`lua/user/` are yours to rewrite, reformat or
strip comments from, and a marker comment would
stop working the moment you did.
`:checkhealth noethervim` rehashes the upstream
file and reports overrides whose upstream has
moved on since, under "Override drift". Without
it an upstream fix simply never arrives: the
override keeps winning, and nothing says so.
Compare one with `:NoetherVim diff` {name},
which opens upstream and your override side by
side in diff mode. Once you have read the
change, run `:NoetherVim override` again from
the upstream file to re-baseline it and clear
the warning. Deleting the override drops its
record on the next health check.
Overrides written by hand are not reported:
there is no baseline to compare them against.
Run `:NoetherVim override` from the upstream
file to start tracking one.
The record is per-machine. A configuration
copied to a second machine arrives with no
baselines, so nothing is reported there until
an override is created or re-baselined on it.
`:Reset[!]` Close all buffers, open dashboard.
Use ! to force (discard unsaved changes).
`:Redir` {cmd} Redirect command or shell output to a
scratch buffer. Example: `:Redir !ls -la`
`:DiffOrig` Diff the current buffer against the file
on disk. Opens a vertical split with the
on-disk version and enters diff mode on
both sides. Useful when the buffer has
unsaved changes or the file was modified
externally. Close the scratch split with
`:q` when done.
`:LuaSnipEdit` Open a snippet file for the current filetype.
See |noethervim-snippets|.
`:LuaSnipStop` Stop every snippet in the buffer and clear
LuaSnip's leftover marks. See
|noethervim-snippet-stop|.
`:LspRename` LSP rename with instance count notification.
`:NoetherVimHighlightUnderCursor`
Print every highlight layer covering the
cursor: legacy syntax, treesitter
captures, LSP semantic tokens, and ALL
extmarks on the row (including virtual
text from inlay hints, diagnostics,
gitsigns blame, and nvim-dap-virtual-text).
Useful for tweaking and debugging; the output
names the exact group so you can override it in
`user/highlights.lua`.
Web search command: ~ *noethervim-web-search*
`:Search` {query} Search the web with the current default engine.
`:Search` {engine} {query} Search the web with a specific engine.
`:Search set` {engine} Set the default search engine for this session.
`:Search ?` Report the engine currently in use, with the
full list of engines (the active one marked).
`:Search set` with no engine does the same.
Available engines: brave (default), duckduckgo, google, ecosia,
github, startpage, reddit, stackoverflow, wikipedia, youtube.
Tab-completion is available for engine names and the `set` / `?`
subcommands.
11. Completion sources
Section titled “11. Completion sources”*noethervim-completion*
NoetherVim uses blink.cmp for completion. The default keybindings are
designed around a "Tab jumps snippets, C-n/p navigates the menu" model.
See |noethervim-completion-custom| below to change this.
Default keybindings: ~
*noethervim-completion-keys*
Insert mode: ~
`<C-Space>` Show menu / toggle documentation.
Falls back to native |i_CTRL-N| when blink is
disabled (|noethervim-option-toggles| `]oC`).
`<C-n>` Next item
`<C-p>` Previous item
`<C-y>` Accept selected item
`<C-e>` Dismiss menu
`<Tab>` Jump to next snippet node (or expand snippet)
`<S-Tab>` Jump to previous snippet node
`<C-b>` Scroll documentation up
`<C-f>` Scroll documentation down
Cmdline mode: ~
`<Tab>` Select and insert next item (cycles through matches)
`<S-Tab>` Select and insert previous item
`<C-y>` Accept (inherited from insert mode)
Tab key philosophy preset: ~
*noethervim-completion-style*
Set `completion_style` in `lua/user/config.lua` to switch the entire Tab
behavior without writing any keymap overrides:
`"snippet"` (default) Tab is reserved for LuaSnip jumps. Pick from
the menu with C-n/C-p and accept with C-y. Snippet
expansion and menu navigation never compete for Tab.
`"supertab"` Tab accepts the highlighted item (auto-selecting the
top one if none is selected) and inserts a trailing
space. Falls back to snippet_forward, then to literal
Tab. IDE muscle memory.
`"navigate"` Tab cycles forward through the menu (= C-n) without
accepting; C-y or <CR> commits. S-Tab cycles backward.
Matches the classic nvim-cmp default and most VSCode
setups.
Example: >lua
-- lua/user/config.lua
return { completion_style = "supertab" }
The two philosophies people mostly go for are "supertab" and "snippet". The
"navigate" is here mostly for users coming from older nvim-cmp configs. You
can accept AI completion (Copilot/Codeium) using Tab in AI plugin's
accept_word in your `lua/user/keymaps.lua` to shadow whichever preset you
picked.
Customizing individual keys: ~
*noethervim-completion-custom*
Override in `user/plugins/` using opts merging. Each key maps to a list
of actions; blink tries them in order and stops at the first that
succeeds.
Tab to accept (instead of snippet jump): >lua
{ "saghen/blink.cmp", opts = {
keymap = {
["<Tab>"] = { "accept", "snippet_forward", "fallback" },
["<S-Tab>"] = { "snippet_backward", "fallback" },
},
} }
Enter to accept, Tab/S-Tab to navigate menu: >lua
{ "saghen/blink.cmp", opts = {
keymap = {
["<CR>"] = { "accept", "fallback" },
["<Tab>"] = { "select_next", "fallback" },
["<S-Tab>"] = { "select_prev", "fallback" },
},
} }
Use a blink preset (replaces all keymaps): >lua
{ "saghen/blink.cmp", opts = {
keymap = { preset = "super-tab" },
} }
Cmdline-only overrides (insert mode keymaps stay unchanged): >lua
{ "saghen/blink.cmp", opts = {
cmdline = {
keymap = {
preset = "inherit",
["<Tab>"] = { "accept", "fallback" },
},
},
} }
Custom completion sources: ~
NoetherVim ships three custom blink.cmp completion sources.
images ~
Triggered when the entire line is "." in a .tex file. Lists image
files from ./images/ and inserts a complete \begin{figure}...\end{figure}
block.
todos ~
Triggered by typing "@" at the start of a line. Inserts a
language-appropriate comment prefix + keyword (TODO, FIXME, BUG, etc.).
Supported filetypes: lua, vim, tex, python, typescript, javascript.
user_config ~
Active only in `lua/user/config.lua`. Opening a quote after a field
that takes one of a fixed set of strings lists that set:
`colorscheme` every scheme installed right now, so it grows when
the ui.colorscheme bundle is enabled
`edge_style` the statusline edge presets
Both are read at the moment you ask, not from a list kept here, so
what you are offered is what the distribution will accept.
12. Health check
Section titled “12. Health check”*noethervim-health*
Run `:checkhealth noethervim` to verify your setup. Checks include:
- Required tools: git, rg (ripgrep), fd
- Optional tools: node, zoxide, lazygit, tree-sitter
- Terminal: true color, `<S-CR>` support, Nerd Font (heuristic, see below)
- LaTeX toolchain (if latex bundle enabled)
- Snippets: how many are loaded per filetype, where your own live, and a
warning when a trigger is defined more than once (which shows up as a
duplicated entry in the completion menu)
- Template version: warns if your init.lua is outdated
- Override drift: warns when a file you overrode has changed upstream
(see |noethervim-override-drift|)
- User override status: lists loaded user modules
- Configuration: user config dir, plugin dir, vault paths
- Active bundles, spec errors, option drift, override conflicts
- LSP servers and feature flags
*noethervim-health-terminal*
About the terminal section: ~
These results are a guess. A terminal cannot be asked what it supports,
so the check goes by `$TERM` and `$TERM_PROGRAM`, both of which can be
wrong, especially through a multiplexer. An unrecognized terminal is
reported as unknown rather than broken.
What it is checking for is whether `<S-CR>` arrives as its own key
instead of a plain `<CR>`. Several keymaps need that, notably the
list-continuation and environment-aware `<S-CR>` bindings. Terminals
known to support it include kitty, Ghostty, WezTerm, Alacritty, foot and
iTerm2 3.5+. Apple Terminal does not. To test it yourself, press `<S-CR>`
in insert mode inside a markdown list: a new list item means it works.
Under tmux, add `set -g extended-keys on`, otherwise `<S-CR>` never
reaches Neovim regardless of the terminal.
Nerd Font presence cannot be detected at all. The check prints sample
glyphs instead; if they render as boxes, the font is missing.
13. FAQ
Section titled “13. FAQ”*noethervim-faq* *neothervim-faq*
Q: How do I add a new LSP server?~
A: Create `lua/user/lsp/<server>.lua` with `vim.lsp.config()` and
`vim.lsp.enable()` calls. Add the server to Mason's ensure_installed
via a plugin override:
>lua
{ "neovim/nvim-lspconfig",
opts = { ensure_installed = { "lua_ls", "basedpyright", ..., "gopls" } } }
`ensure_installed` is an array, so the table-form override REPLACES
the distro list, so include every server you want loaded. To extend
the distro list instead, see |noethervim-user-plugins-arrays|.
Q: How do I add a linter?~
A: NoetherVim uses nvim-lint alongside conform.nvim. Most linting is
provided by LSP servers; nvim-lint fills the gap for non-LSP tools.
Add linters via opts override in `user/plugins/`:
>lua
{ "mfussenegger/nvim-lint",
opts = { linters_by_ft = { sh = { "shellcheck" }, yaml = { "yamllint" } } } }
`linters_by_ft.<ft>` is also an array; same caveat applies. See
|noethervim-user-plugins-arrays|.
*noethervim-diagnostic-display*
Q: The error message beside my cursor is cut off. Can I see all of it?~
A: `gl` opens the full text in a float; `]d` and `[d` jump to the next
or previous diagnostic and open it there. The inline text is a
one-line preview, clipped where the window ends.
To let it continue over the lines below instead:
>lua
-- lua/user/plugins/diagnostics.lua
return {
{ "rachartier/tiny-inline-diagnostic.nvim",
opts = { options = { overflow = { mode = "wrap" } } } },
}
The continuation is drawn on top of the lines below, so while the
cursor rests there a long message hides two or three lines of code.
For a renderer that pushes those lines down rather than covering
them, swap in Neovim's own:
>lua
-- lua/user/plugins/diagnostics.lua
return {
{ "rachartier/tiny-inline-diagnostic.nvim", enabled = false },
{ "neovim/nvim-lspconfig",
opts = { diagnostic = {
virtual_lines = { current_line = true },
} } },
}
Q: How do I install a formatter or debugger?~
A: Both are managed by Mason, the same as LSP servers. Use `:Mason`
to browse and install interactively. Formatters referenced by
conform are installed automatically:
>lua
{ "stevearc/conform.nvim",
opts = { formatters_by_ft = { sh = { "shfmt" } } } }
For debuggers, enable the `debug` bundle plus the bundle for the
language you are debugging: `languages.python` brings debugpy,
`languages.go` brings delve, `languages.web-dev` brings
vscode-js-debug, `languages.c-cpp` brings codelldb. Neither half
installs a debugger on its own, so enabling `debug` alone costs
nothing but the UI.
Test adapters pair the same way; see |noethervim-test-adapters|.
*noethervim-formatting-core*
Q: Which filetypes can I format without enabling anything?~
A: Thirteen, out of the box and with no bundle: `lua`, `python`, `bib`,
`javascript`, `javascriptreact`, `typescript`, `typescriptreact`,
`css`, `html`, `json`, `yaml`, `markdown` and `sh`.
This is deliberate rather than an oversight about where language
configuration belongs. Formatting a Python file is something you are
entitled to expect from an editor you just installed, so it is core's
to provide; a bundle deepens a subject rather than making a common
file type work at all. The formatter binaries still come from Mason on
first use, so nothing is downloaded for a language you never open.
To change one, or to add a filetype, override conform in
`user/plugins/`. `formatters_by_ft` is a table of arrays, so naming a
filetype replaces its list rather than adding to it: >lua
-- lua/user/plugins/conform.lua
return {
{ "stevearc/conform.nvim",
opts = { formatters_by_ft = {
python = { "ruff_format" }, -- replaces black
rust = { "rustfmt" }, -- adds a filetype core does not claim
} } },
}
See |noethervim-user-plugins-arrays| for why the array does not merge.
Q: Does NoetherVim format on save?~
A: No; auto-formatting is deliberately off. Use `<Leader>ff` for
explicit formatting (conform.nvim with LSP fallback). To opt into
save-time formatting, override conform in `user/plugins/`:
>lua
{ "stevearc/conform.nvim",
opts = {
format_on_save = { timeout_ms = 500, lsp_format = "fallback" },
} }
Q: How do I change a picker keymap?~
A: NoetherVim uses snacks.picker as the core UI. Default picker keymaps live
in bundles (e.g. `bundles/languages/latex.lua`) or core plugin files (e.g.
`plugins/snacks.lua`). Override them in `user/keymaps.lua` or by specifying
`keys` in your plugin override spec.
Q: Why does my override have no effect?~
A: Check `:NoetherVim status` to verify your files are loaded. Check
`:NoetherVim diff keymaps` or `:NoetherVim diff options` to see what
changed. If using `NOETHERVIM_NO_USER`, your overrides are disabled.
Q: How do I completely replace a plugin's config?~
A: Specify `config = function(_, opts) ... end` in your override spec.
This replaces the upstream config entirely. Note in this case you
must call the plugin's setup yourself.
Q: Can I disable a plugin from core or a bundle?~
A: Use `cond = false` (or `enabled = false`) in your `user/plugins/` spec:
>lua
{ "some/plugin", cond = false }
Both work: lazy.nvim resolves these on the merged plugin (last-write-
wins), and user fragments are always last. `cond = false` keeps the
plugin in lazy's disabled list (visible in `:Lazy`); `enabled = false`
removes it entirely.
For bundles, comment out the bundle's `import` line in your
init.lua to disable the entire bundle.
Q: Why doesn't `p` paste from my system clipboard?~
A: By design. NoetherVim keeps `y` and `p` bound to Vim's unnamed
register so transient edits (`ddp`, `xp`, `ciwp`) don't go to the
OS clipboard. By default, there are keybindings for when you want the
system clipboard:
Normal, Visual <leader>y / <leader>Y yank (motion / line)
<leader>p / <leader>P paste (after / before)
Visual Y / P shorthand yank / paste
Insert <C-v> paste (see caveat)
Cmdline <C-y> yank cmdline to clipboard
<C-r>* insert clipboard (built-in)
Any mode (nvim defualt) "*y / "*p unmapped register access
To opt into the "everything through OS clipboard" default,
override in `lua/user/options.lua`:
>lua
vim.o.clipboard = "unnamedplus"
Caveat: insert `<C-v>` shadows Vim's "insert next char literally"
default. Substitutes:
- `<C-q>`: literal-insert in most terminals
- `<C-r>+`: insert clipboard register literally
- cmdline `<C-v>{char}` is unshadowed; pairs well with `:verbose`
when introspecting keymaps that contain literal control
sequences
Q: How do I change the default colorscheme?~
A: Set `colorscheme` in `lua/user/config.lua`:
>lua
-- lua/user/config.lua
return { colorscheme = "tokyonight" }
When the `colorscheme` bundle is enabled, your SearchLeader+C pick
is persisted and takes priority over the configured default on
subsequent launches. To reset the saved pick, delete
`stdpath("data") .. "/noethervim_colorscheme"`.
Q: Why is spell check on in markdown / tex / gitcommit files?~
A: Writing filetypes (tex, markdown, norg, text, gitcommit, rst, typst)
get a "writing profile" that enables wrap, linebreak, spell, and
`conceallevel = 2`. See |noethervim-filetype-profiles| for the
full picture. Toggle spell per buffer with `[os` / `]os`. To
disable the profile entirely, clear the augroup in
`lua/user/autocmds.lua`:
>lua
vim.api.nvim_create_augroup("noethervim_writing", { clear = true })
Q: How do I restore my last session?~
A: Sessions auto-save per working directory via persistence.nvim.
From the dashboard, press `r`. From anywhere else:
>vim
:lua require("persistence").load({ last = true })
Omit `{ last = true }` to load the session for the current CWD
instead of the most recent one.
Q: I typed `:NeotherVim` and it worked?~
A: Yes. Years of typing `neo-` will do that. The alias is there so you
don't have to think about it. `:help neothervim` and
`:checkhealth neothervim` work too. I did consider
naming the distribution `NeotherVim`, but this felt it would
inadvertently induce the Mandela-effect forevermore on the
reading of the name `Noether`.
Q: What is vim-abolish and how do I use it?~
A: NoetherVim ships a fork of vim-abolish that adds context-aware
expansion. The fork exposes three features:
1. `:Subvert`: case-preserving find/replace (facility->building
preserves Facility->Building, FACILITY->BUILDING)
2. `:Abolish`: smart auto-correct (`:Abolish teh the` corrects all
case variants in insert mode)
3. Coercion operators: `crs` snake_case, `crm` MixedCase,
`crc` camelCase, `cru` UPPER_CASE, `cr-` dash-case
Add personal typo corrections in `lua/user/plugins/abolish.lua`.
Context gating: in code buffers, :Abolish corrections only fire when
the cursor is inside a comment or @spell-tagged region (typical
string content). Prose buffers (tex, markdown, ...) always expand.
Use `[oA` to force unconditional expansion in the current buffer
and `]oA` to return to context-gated. To opt a specific :Abolish
line out of gating entirely, pass `-expr=` explicitly:
`:Abolish -expr= teh the` always expands
Q: Can I make my `leader` and `searchleader` the same?~
A: Yes; simply map both to the same key in your `init.lua` file. You can map
your LocalLeader to the same key as well.