Personal Neovim configuration with a small project CLI for bootstrap tasks.
- OS: Ubuntu 22.04 LTS, Ubuntu 24.04 LTS
- Shell: Bash 5+
- Neovim: 0.11+ (0.12 recommended)
Installed automatically by install deps:
| Package | Purpose |
|---|---|
git |
Mandatory — clones plugins via lazy.nvim; Neovim refuses to load plugins without it |
make |
Builds the native telescope-fzf-native.nvim sorter |
gcc |
C compiler — make alone is not enough, the sorter's Makefile calls cc |
curl |
Plugin download, nvm install |
ripgrep |
Telescope live grep |
fd-find |
Telescope file finder |
xsel / xclip |
System clipboard integration (X11) |
wl-clipboard |
System clipboard integration (Wayland) |
lolcat |
Colored CLI output |
gnupg |
Verifies the mise installer signature (install mise) |
Both X11 and Wayland clipboard tools are always installed, regardless of the
current session type — install deps may run over SSH or before any
graphical session starts, so there's no reliable way to detect which one
will actually be used later. Neovim's own clipboard provider picks the
right tool at runtime based on the active session.
If the config was obtained without git already present (e.g. copied or
mounted rather than cloned), Neovim will refuse to load plugins and print a
clear error on startup until install deps (or sudo apt install git) is run.
Required by Mason-managed LSP servers (TypeScript, ESLint, and others).
Not installed by install deps by default.
Without npm on PATH, Neovim does not ask Mason for the language servers that are npm
packages (jsonls, ts_ls, vue_ls, svelte, intelephense, pyright, bashls,
yamlls, dockerls) instead of failing to install them at every startup. It says nothing
at startup; switching a profile on with :NvimProfile lists the servers it left out, and
nvim-config doctor shows the whole list under "Language servers that need npm", saying
whether npm was found. The other servers (lua_ls, rust_analyzer) and the linters and
formatters are not affected. The list lives in lua/lsp_requirements.lua, which doctor
reads.
- Minimum version: Node 18+
- Recommended: manage Node via nvm or fnm rather than via apt — the apt package is often outdated and may conflict with an existing Node setup.
To install nvm and Node LTS automatically as part of the bootstrap:
./bin/nvim-config install deps --with-node
./bin/nvim-config install all --with-node # nvim, deps, config symlink, tree-sitter CLIThe --with-node flag installs nvm v0.39.7 then runs nvm install --lts.
nvm goes where its own installer puts it: ~/.nvm, or $XDG_CONFIG_HOME/nvm when
XDG_CONFIG_HOME is set. An exported NVM_DIR wins, and an existing ~/.nvm is kept. The install is skipped
when nvm is already there.
The repository includes a .tmux.conf with settings required for Neovim to work
correctly inside tmux and byobu:
| Setting | Purpose |
|---|---|
focus-events on |
Lets Neovim receive focus events (autoread, LSP hover on focus) |
default-terminal screen-256color |
Enables 256-colour support in the tmux pane |
terminal-overrides Tc |
Enables true colour (24-bit) so the colorscheme renders correctly |
Without these settings, the colorscheme may appear degraded and autoread/LSP focus-based features will not trigger inside a tmux session.
To activate this configuration, inject a source-file entry into ~/.tmux.conf:
./bin/nvim-config install tmuxThis appends source-file /path/to/neovim-config/.tmux.conf to your existing
~/.tmux.conf (creating it if absent) and is idempotent — running it multiple
times has no effect. Your existing tmux configuration is preserved.
If ~/.tmux.conf is a symlink (typically into a dotfiles repository), the command stops with an error and
leaves the target alone: it would otherwise edit a file that belongs to another repository. The message
gives the source-file line to add to the file the link points to.
lazy.nvim— plugin manager (bootstrapped automatically on first startup)mason.nvim+mason-lspconfig.nvim— LSP server managementnvim-treesitter— syntax parsing with an explicit parser baseline
bats-core— required to run the test suite (tests/run)
Complete setup sequence for a new machine.
./bin/nvim-config install nvimWith no flag, this cascades interactively based on what's available:
mise if present on PATH → otherwise prompts to
install it (curl download, GPG signature verified against the official mise
release key — any missing prerequisite, i.e. curl or gnupg, is listed
and installed via apt in the same step) → declined or unavailable, uses
snap if present → otherwise prompts to install snapd → declined, falls
back to apt. Prompts auto-decline (no blocking, Ctrl+C always aborts)
outside an interactive terminal, under --quiet, or under --dry-run.
Force a specific method — skips the interactive cascade entirely:
./bin/nvim-config install nvim --mise # installs mise first if missing
./bin/nvim-config install nvim --snap # installs snapd first if missing
./bin/nvim-config install nvim --apt # requires apt (never bootstrapped)--mise/--snap bootstrap their own package manager (and, for mise, any
missing prerequisite like gnupg) if it's missing — passing the flag is
already an explicit choice, so no further prompt is needed. --apt still
errors if apt itself isn't present (that signals the wrong OS entirely,
nothing to install).
After a fresh mise install, mise needs to be activated in your shell rc
for itself and any mise-managed tool (like nvim) to be found on PATH —
see mise's shell-specific activation
docs.
The CLI detects whether the activation line is already present and, if
not, offers to add it to ~/.bashrc / ~/.zshrc / ${XDG_CONFIG_HOME:-~/.config}/fish/config.fish
(based on $SHELL) and tells you to reload your shell afterwards. Two
flags bypass this prompt for automated/non-interactive installs, where
the default prompt already silently declines (no TTY) but an automation
author may still want a definite outcome either way:
./bin/nvim-config install mise --activate # add the line unattended
./bin/nvim-config install mise --no-activate # skip it, log the manual commandSnap is recommended when not using mise — it always provides the latest stable release with classic confinement (full host access, no sandbox restrictions):
sudo snap install nvim --classicMinimum required version: 0.11+. The LSP setup uses vim.lsp.config / vim.lsp.enable, which
do not exist in 0.10 — there the configuration fails at startup. Verified by starting it, with its
plugins, on 0.10.4 (fails), 0.11.0, 0.11.7 and 0.12.0 (clean start, parsers and highlighting work).
0.12 is recommended: the main branch of nvim-treesitter, which this config uses, officially
requires it, so 0.11 works but is not a combination upstream supports. On an older Neovim the configuration now stops at once with a one-line message
(This configuration requires Neovim 0.11 or newer (found 0.10.4)) instead of a Lua error.
Check with nvim --version.
Alternatively, via apt (version depends on the Ubuntu release):
sudo apt install neovimgit clone https://github.com/eviweb/neovim-config.git ~/path/to/neovim-config
cd ~/path/to/neovim-config./bin/nvim-config install depsAdd --with-node to also install nvm and Node.js LTS (required for TypeScript,
ESLint, and other Node-based LSP servers):
./bin/nvim-config install deps --with-node./bin/nvim-config install configThis creates a symlink ${XDG_CONFIG_HOME:-~/.config}/nvim → /path/to/neovim-config, the directory Neovim reads
its configuration from.
./bin/nvim-config install tmuxRequired for correct colours and focus events when running Neovim inside tmux or byobu. See Dependencies — tmux / byobu integration for details.
Open Neovim:
nvimOn first startup, lazy.nvim bootstraps itself and installs all declared plugins
automatically. This may take a minute. install all also fetches the
tree-sitter CLI (into vendor/tree-sitter/) beforehand — nvim-treesitter
needs it on PATH to build parsers; without it, parser installs fail with
ENOENT ... 'tree-sitter' on first launch. Once complete:
- Run
:Lazyto review plugin status - Run
:Masonto open the LSP server manager and install servers for your languages —lua_ls(Lua, standalone binary) is pre-configured and installed automatically;jsonls(JSON) is too, but only ifnpmis present, sincejson-lspis an npm package (see Node.js — Node.js is opt-in, runinstall deps --with-nodefirst if you need it). The same goes for every language server that is an npm package, whichever profile asks for it - Restart Neovim after Mason finishes
./bin/nvim-config update # nvim + plugins
./bin/nvim-config update nvim # Neovim only
./bin/nvim-config update plugins # plugins only
./bin/nvim-config update dap-adapters # Mason DAP packages for the active profile
./bin/nvim-config update tree-sitter # tree-sitter CLI (vendor/tree-sitter/) — also run by `install all`# mise (auto-detected if Neovim was installed via mise)
mise use -g neovim@latest
# Snap
sudo snap refresh nvim
# apt
sudo apt upgrade neovimFrom inside Neovim:
:Lazy syncOr headlessly from the terminal:
nvim --headless -u init.lua +"Lazy! sync" +qaLazy sync installs missing plugins, updates existing ones, and removes unused
ones. The lazy-lock.json file pins exact plugin versions — commit it to lock
your plugin state across machines.
./bin/nvim-config uninstall config # remove the nvim config symlink (only if it points here)
./bin/nvim-config uninstall nvim # remove Neovim (detects mise/snap/apt, asks for confirmation)
./bin/nvim-config uninstall mise # remove the mise binary + its shell activation lines
./bin/nvim-config uninstall deps # never removes apt packages — prints the manual command instead
./bin/nvim-config uninstall all # config, nvim, then mise (deps stays informational, see above)uninstall deps is intentionally informational only: the packages installed by
install deps (git, make, gcc, curl, ripgrep, fd-find, xsel,
xclip, wl-clipboard, lolcat, gnupg) are shared system dependencies that
other tools may also rely on — nvim-config never runs apt remove on your
behalf. uninstall all follows the same policy and leaves them untouched.
uninstall nvim and uninstall mise each ask for confirmation before removing
anything. Without a TTY or under --quiet they decline, so automated runs never
hang or remove something unexpectedly. --dry-run skips the prompt (nothing runs,
so there is nothing to consent to) and prints the plan instead — for example
mise unuse -g neovim then mise uninstall neovim@<version>, sudo snap remove nvim
or sudo apt remove -y neovim, depending on how Neovim was installed. The other
prompts of this CLI (install) offer alternatives rather than ask for consent, so
they still decline under --dry-run.
Profiles enable per-project tooling — only the LSP servers, treesitter parsers, null-ls sources, and plugins relevant to the current project are activated.
Available profiles: web, php, laravel (extends php), rust, python,
shell, devops, termux, ai.
Profiles are detected automatically at startup, from the directory Neovim was launched in:
| Marker | Profile |
|---|---|
package.json |
web |
composer.json |
php |
composer.json + laravel/framework |
laravel |
Cargo.toml |
rust |
pyproject.toml, setup.py, setup.cfg or requirements.txt |
python |
.shellcheckrc, a root-level *.sh or *.bash file, or tests/*.bats |
shell |
Dockerfile, compose.y*ml, docker-compose.y*ml or .github/workflows/ |
devops |
TERMUX_VERSION environment variable (set by Termux) |
termux |
ai is opt-in and never auto-detected: it only turns on the AI plugins (avante,
codecompanion, codeium, mcphub) when you pin it with profile set ai.
| Profile | LSP | Treesitter parsers | Debug adapter |
|---|---|---|---|
web |
ts_ls, vue_ls, svelte |
tsx, vue, svelte |
js-debug-adapter |
php |
intelephense |
php |
php-debug-adapter (Xdebug) |
laravel |
via php |
blade |
via php |
rust |
rust_analyzer |
rust, toml |
codelldb |
python |
pyright |
python, toml |
debugpy |
shell |
bashls (needs npm) |
— (bash is global) |
— |
devops |
yamlls, dockerls (need npm) |
dockerfile |
— |
termux |
lua_ls only |
— | none (no DAP, no OAuth plugins) |
ai |
— | — | — |
Profiles also bring their formatters (conform.nvim), linters (nvim-lint) and
neotest adapters. A profile is a Lua table in lua/profiles/<name>.lua; besides
lsp_servers, plugins, dap_mason_packages and the tool hooks it may declare
treesitter_parsers and mason_packages. Without a parser a language gets no treesitter highlighting,
folds or textobjects (af, ]m, ]a…). Parsers declared by the active profiles
are installed in the background at startup, and also when a profile is switched on
at runtime.
For any other language, Neovim asks before installing its parser: opening a go or java
file with no parser installed shows a menu Install / Not now / Never for go. Installing
downloads the grammar and compiles it (the tree-sitter CLI, a C compiler, curl and
tar, and the network), then turns highlighting on in the buffers already open, without a
restart. Not now, or dismissing the menu, only silences that language until the next
session. Never writes the language to treesitter-declined in Neovim's state directory
(${XDG_STATE_HOME:-~/.local/state}/nvim/), one language per line; delete the line to be
asked again. Nothing is asked when it could not work (a tool is missing), for windows that
are not files, for the languages installed at startup, or without a user interface.
Lints shell scripts with shellcheck (on save and when
leaving insert mode) and formats them with shfmt. Both
are expected on PATH — nvim-config doctor lists them:
sudo apt install shellcheck shfmtIt applies to every buffer Neovim calls sh: .sh, .bash, .bats and scripts with a
shell shebang. shfmt runs on demand (<Space>f), not on save, unlike every other
formatter here: its defaults (tabs, its own redirect and alignment style) rewrite most of
an existing script the first time it is saved — about half the lines of this repository's
own bin/nvim-config. It reads .editorconfig when one covers the file.
With npm available it also brings bash-language-server (bashls): completion, hover, go
to definition and symbols. The server runs shellcheck itself, which would show every
diagnostic twice next to nvim-lint's, so that part of it is switched off
(bashIde.shellcheckPath = '' in lua/config/lsp.lua). Without npm you have shellcheck and
shfmt, as before.
Lints Dockerfiles with hadolint and GitHub Actions
workflows with actionlint. Neither is in apt, so
the profile declares them in mason_packages — a profile field for Mason-installed tools,
next to dap_mason_packages — and Neovim installs them in the background at startup, or
when you switch the profile on with :NvimProfile devops (needs network; skipped on
Termux, where Mason has no ARM binaries). Mason's bin directory is on Neovim's PATH
but not on your shell's, so nvim-config doctor looks there too.
actionlint reports errors on any YAML that is not a workflow, so workflow files
(.github/workflows/*.yml|yaml) get the compound filetype yaml.github (set in
lua/options.lua). Neovim plugins split a compound filetype on the dot: yaml keeps its
own linters and formatters and only github adds actionlint.
With npm available it also brings two language servers. yaml-language-server (yamlls)
validates YAML against the schemas of SchemaStore.nvim (Compose files and others); it does
not attach to workflow files, whose filetype is yaml.github, so they stay with actionlint.
dockerfile-language-server (dockerls) completes and checks Dockerfiles; the three rules
hadolint also checks (several CMD, ENTRYPOINT or HEALTHCHECK) are left to hadolint, and
it keeps its own, such as the case of instructions. Without npm you have hadolint and
actionlint, as before.
Pin a profile for the current project by writing .nvim-profile at the project root:
./bin/nvim-config profile set web
./bin/nvim-config profile set web,php # several profiles, separated by commas
./bin/nvim-config profile unsetprofile set checks every name against the profiles that exist and refuses an unknown one (exit 2, with the list of
valid names): Neovim itself ignores an unknown name without a word, so a typo would otherwise write a file that does
nothing. Neovim reads the first line of .nvim-profile as a comma-separated list, ignoring spaces around the names;
profile list, profile info, update dap-adapters and status read it the same way, so they show every pinned
profile.
Switch the active profile without restarting Neovim (null-ls updates immediately; its language servers, treesitter parsers and Mason tools install in the background; plugin changes require a restart):
:NvimProfile webA language server that is already installed is enabled at once. A missing one is installed
by Mason, and mason-lspconfig enables it as soon as the install succeeds, so it attaches to
the buffers that are already open without a restart. The notice lists the servers being
installed, and the ones left out because npm is missing. On Termux, where Mason has no ARM
binaries, nothing is installed.
Or use the Telescope picker: <Leader>fp — shows active (●) / inactive (○) profiles.
Profile-driven DAP adapters are installed automatically via Mason at Neovim startup. To install or refresh them from the terminal:
./bin/nvim-config update dap-adapters./bin/nvim-config profile list # list profiles, marks active with *
./bin/nvim-config profile detect # detect for the current directory
./bin/nvim-config profile set <name> # write .nvim-profile
./bin/nvim-config profile unset # remove .nvim-profile
./bin/nvim-config profile create <name> # generate a new profile template
./bin/nvim-config profile info # active profile + LSP servers + DAP packagesTwo interchangeable UI variants are available. The variant is chosen at Neovim
startup and defaults to classic.
| Variant | Stack |
|---|---|
classic |
telescope, which-key, trouble |
modern |
snacks.nvim (picker, notifier, dashboard) |
Both variants share: lualine statusline, bufferline, nvim-navic breadcrumb, and all installed colorschemes.
./bin/nvim-config ui set classic # write .nvim-ui at the config root
./bin/nvim-config ui set modern
./bin/nvim-config ui unset # remove .nvim-ui (reverts to classic).nvim-ui is gitignored — the setting is local to the machine.
14 dark colorschemes are available. The active theme is persisted in .nvim-theme (gitignored) and defaults to nightfox.
| Family | Variants |
|---|---|
| nightfox | nightfox, nordfox, duskfox, terafox, carbonfox |
| catppuccin | catppuccin-mocha, catppuccin-macchiato, catppuccin-frappe |
| tokyonight | tokyonight-night, tokyonight-storm, tokyonight-moon |
| kanagawa | kanagawa-wave, kanagawa-dragon |
| gruvbox | gruvbox-material |
Switch from inside Neovim (with tab-completion):
:Theme catppuccin-mochaOr via the CLI:
./bin/nvim-config theme set catppuccin-mocha
./bin/nvim-config theme unset # revert to nightfoxCursor-like AI sidebar for code chat and inline edits. Authenticates via Claude Pro/Max subscription (browser OAuth, no API key required):
<Leader>aa " ask
<Leader>ae " edit selection (visual mode)
<Leader>at " toggle sidebarFree inline AI completion, active alongside LSP suggestions ([AI] label in the completion menu). Activate once:
:Codeium AuthInstall the CLIs, then toggle their dedicated floating terminals from inside Neovim:
./bin/nvim-config install claude # Claude Code CLI
./bin/nvim-config install codex # OpenAI Codex CLI
./bin/nvim-config install gemini # Google Gemini CLI| Key | Agent |
|---|---|
<Leader>tC |
Claude Code |
<Leader>tX |
Codex |
<Leader>tG |
Gemini |
Requires Node.js / npm. Use install deps --with-node if not already installed.
./bin/nvim-config status # Neovim version, UI variant, theme, active profile
./bin/nvim-config doctor # check required and optional binaries, and the npm language servers
./bin/nvim-config keymaps # open keymaps reference in $PAGER- Vim/Neovim basics — new to Neovim? Start here: modes, motions, windows, command-line — none of it specific to this config
- Usage guide — LSP, completion, diagnostics, git, testing, debugging, AI tools, and more
- Keymaps cheatsheet — all custom mappings and plugin shortcuts
- Plugin quick-reference — concise tables per plugin
- Cheatsheets — basics, editing, git, LSP, profiles
yazi (terminal file manager with image, video and PDF previews) opens in a
floating window through yazi.nvim: <Leader>- at the current
file, :Yazi cwd in the working directory. It complements neo-tree (<Leader>n) and oil (-), which keep
opening directories. The plugin is loaded only when the yazi binary is on PATH, checked once at
startup: install yazi (for example mise use -g yazi@latest, or follow the
official instructions) and restart Neovim; nothing installs it
for you. :checkhealth yazi verifies the versions, nvim-config doctor lists it as an optional tool.
:Cheat opens a sheet in a floating window (<Leader>hc for the picker; q or <Esc> closes). A topic
is a <name>.md file found in these places, from lowest to highest precedence — on a name clash the last
one wins, so you can replace a built-in sheet with your own:
| Source | Where | Appears |
|---|---|---|
| Built-in | docs/cheatsheets/ |
always |
| Profile | the cheatsheets directories a profile declares, e.g. docs/profile-cheatsheets/shell/ |
only while that profile is active (also after :NvimProfile) |
| Your own | ~/.config/nvim-cheatsheets/ (next to the nvim config directory, XDG_CONFIG_HOME aware) |
always |
| Extra | each directory of $NVIM_CHEATSHEET_DIRS, colon-separated like PATH |
always |
Missing directories, subdirectories and non-markdown files are ignored. A profile declares its sheets with
cheatsheets = { 'docs/profile-cheatsheets/<name>' } (relative to the config root, or absolute); the
shell and devops profiles each ship one.
./bin/nvim-config --help
./bin/nvim-config --version
./bin/nvim-config --dry-run installEvery run also writes what it prints to a log file, without colors:
${XDG_STATE_HOME:-~/.local/state}/nvim-config/logs/nvim-config-YYYY-MM-DD.log. There is one file per day and
every run is appended to it; a first line Run: nvim-config <version> <arguments> (file only, never printed)
marks where each run starts. Change the directory with --log-dir <path> or the NVIM_CONFIG_LOG_DIR
environment variable (both win over XDG_STATE_HOME); --no-log turns the file off.
Colors appear only on a terminal. --no-color, or a non-empty NO_COLOR environment variable
(https://no-color.org), turns them off. Errors always go to stderr, even with --quiet.
--dry-run shows what a command would do and changes nothing: no file written, no link created,
no package installed. What it prints is phrased as a plan ("Would link ...", "Would install bash
completion to ..."), never as a finished action.
Supported shells: bash, zsh, fish
Show the completion script:
./bin/nvim-config --show-completion bash
./bin/nvim-config --show-completion zsh
./bin/nvim-config --show-completion fishInstall completion for the current user:
./bin/nvim-config --install-completion bash
./bin/nvim-config --install-completion zsh
./bin/nvim-config --install-completion fishInstalled paths:
| Shell | Path |
|---|---|
| bash | ${XDG_DATA_HOME:-~/.local/share}/bash-completion/completions/nvim-config |
| zsh | ${XDG_DATA_HOME:-~/.local/share}/zsh/completions/_nvim-config |
| fish | ${XDG_CONFIG_HOME:-~/.config}/fish/completions/nvim-config.fish |
For zsh, add that directory to your fpath before calling compinit:
fpath=("${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions" $fpath)
autoload -Uz compinit && compinitEarlier versions installed the zsh completion in ~/.zfunc. If a ~/.zfunc/_nvim-config is still there, the
install warns about it: nothing loads it any more, remove it.
Fish picks up completions from $XDG_CONFIG_HOME/fish/completions/ automatically — no extra setup required.
Domain-specific bats files contain grep-based tests that verify structural invariants without needing a running Neovim process: deprecated options, removed plugins, namespace migrations, and similar one-way ratchets.
| File | Domain |
|---|---|
tests/cli.bats |
CLI commands, install, update, completion |
tests/lsp.bats |
LSP, mason, nvim-cmp, treesitter, null-ls |
tests/ui.bats |
UI plugins: telescope, neo-tree, lualine, trouble, which-key, bufferline, variant system |
tests/docs.bats |
README and keymaps cheatsheet coverage |
tests/profiles.bats |
Profile system, CLI profile commands, NvimProfile switcher |
tests/ci.bats |
CI workflow guards (triggers, pinned and verified downloads) |
tests/cheatsheets.bats |
:Cheat sources: built-in, profile, user directories, precedence |
tests/startup.bats verifies that every require() call in init.lua, plugins.lua, and bootstrap.lua has a matching file on disk. These tests catch "file deleted but require() not updated" regressions at the source level.
tests/startup.bats also runs nvim --headless against the core modules
(options.lua, keymaps.lua) to detect hard Lua errors at startup.
Snap confinement behaviour:
| Confinement | Effect |
|---|---|
| Classic | Full host access — headless tests run normally |
| Strict / devmode | Sandbox restrictions — headless tests are skipped automatically |
To force-enable or force-disable regardless of environment:
NVIM_HEADLESS_TESTS_SKIP=0 bash tests/run # force enable
NVIM_HEADLESS_TESTS_SKIP=1 bash tests/run # force disabletests/run hands bats a minimal environment, as a clean CI runner has: PATH, HOME, TERM, TMPDIR
(when set), NVIM_HEADLESS_TESTS_SKIP (when set) and the C.UTF-8 locale. Anything else your shell exports
(XDG_*, NVM_DIR, VIMINIT, SHELL, NO_COLOR...) is dropped, so a test cannot pass here and fail in CI
because of it.
tests/run is offline and takes under a minute. tests/full-startup is the one test that starts the
real configuration with every plugin: it clones them at the commits pinned in lazy-lock.json (about
200 MB) into a throwaway directory, then fails if Neovim reports an error or if the config was not
actually read. It never touches your own ~/.config/nvim or ~/.local/share/nvim.
tests/full-startup # uses nvim from PATH
NVIM=/path/to/nvim tests/full-startup # a specific versionCI runs it on the oldest supported Neovim and on the one used by the Test job (job Startup, not a
required check, since it depends on the network).
Neovim installed via Snap exists in two confinement modes:
- Classic — full host filesystem access, no sandbox restrictions. Headless tests run normally. This is the recommended install mode for development use.
- Strict / devmode — sandboxed environment. Some workflows that depend on external agents, sockets, or mounted key material may behave differently from the host system. Headless startup tests are skipped automatically in this mode.
Check the active confinement mode with:
snap list nvimThe Notes column shows classic for classic confinement; empty means strict.
Bootstrap and plugin wiring are covered by static regression tests regardless of confinement mode, and by headless smoke tests where the environment permits.
See CONTRIBUTING.md. Found a security issue? See
SECURITY.md instead of opening a public issue.