A down-to-earth Neovim distribution: fast, modular, and ready for everyday work.
TerraNvim is a Neovim configuration you can use as a complete IDE-like setup
or as a base for your own. Languages come as language packs: one data file
per language that you switch on with :LangEnable. Nothing language-specific
is hard-wired, and startup stays around 25 ms.
- Language packs for Lua, Bash, JSON, YAML, TOML, Markdown, Docker, C/C++ (embedded-ready), Go, Python, Rust, TypeScript/JavaScript, HTML/CSS, Swift, Kotlin, Solidity, LaTeX and Typst. Each pack bundles LSP, formatting, linting, debugging, tests and keymaps. Adding a language is one file (docs/languages.md).
- Batteries on, switchable: language servers start automatically, formatting and linting run on save, and missing tools for enabled languages install on first use. Every behaviour is a setting, and most are toggles.
- Modern stack: lazy.nvim, snacks.nvim (picker, explorer, terminal,
lazygit, dashboard), blink.cmp,
vim.lsp.configwith nvim-lspconfig as data, conform, nvim-lint, nvim-treesitter (main), nvim-dap, neotest, diffview-plus, gitsigns, noice, which-key, flash, trouble. - AI, opt-in: Claude Code integration (claudecode.nvim) and a terminal for any AI CLI (sidekick.nvim). Both stay off until you enable them.
- Neovim 0.12 native where it is good enough: default LSP keymaps,
incremental selection,
:restart,:Undotree,winborder, EditorConfig. - Hardened defaults: no modelines, no project-local config execution, pinned plugins, writes only under Neovim's own data directories.
- Neovim 0.12+, git, a C compiler,
tree-sitterCLI ≥ 0.26.1 (builds parsers),rgandfd. - Optional:
lazygit, a Nerd Font, and per language the toolchain itself (go,cargo,node, Xcode, JDK 17+, ...). Language servers and tools come from Mason automatically.
mv ~/.config/nvim ~/.config/nvim.bak 2>/dev/null # keep an existing config
git clone https://github.com/txaty/TerraNvim ~/.config/nvim
nvim # lazy.nvim bootstraps itself and installs the pinned pluginsTo try it next to your current config, use a separate app name (it keeps its own config, plugins and state):
git clone https://github.com/txaty/TerraNvim ~/.config/terranvim
NVIM_APPNAME=terranvim nvimThen in Neovim:
<leader>Lp(Space, L, p): enable the languages you use.- Open a file of each. Tools install in the background, and the server attaches when its install finishes.
:checkhealth core.lang: see what each enabled language has.
Setting up a machine non-interactively:
nvim --headless "+Lazy! restore" +qa
nvim --headless "+LangEnable go python rust typescript" "+LangInstall!" +qa| Command | |
|---|---|
<leader>Lp / :LangPanel |
Enable/disable, install, options |
:LangEnable / :LangDisable / :LangToggle {pack...} |
Persisted per machine |
:LangOption {pack} {option} {value} |
e.g. typescript server tsc, python server pyright, cpp query_driver /opt/homebrew/bin/arm-none-eabi-* |
:LangInstall[!] [pack...] |
Install Mason tools and parsers now |
:checkhealth core.lang / <leader>Lh |
Per-pack status |
Enabled on a fresh install: lua, bash, json, yaml, toml, markdown. The full table, the schema and a walkthrough for adding a language are in docs/languages.md.
Settings live in lua/core/settings.lua. Override
them in lua/user/settings.lua (gitignored; copy
lua/user/settings.example.lua):
return {
format = { on_save = false },
langs = { default = { "lua", "go", "python" }, options = { typescript = { server = "tsc" } } },
theme = { dark = "kanagawa-wave", light = "kanagawa-lotus" },
}| Setting | Default | |
|---|---|---|
lsp.auto_start |
true |
Start servers of enabled packs |
format.on_save / format.timeout_ms |
true / 1000 |
Toggle at runtime: <leader>uf, per buffer <leader>uF |
lint.enabled |
true |
Toggle: <leader>ul |
install.auto |
true |
Install missing tools/parsers of enabled packs on first use |
langs.default / langs.options / langs.hint_disabled |
see above | Fresh-install packs, per-pack option defaults, hint for disabled packs |
session.persistence |
true |
Default for :SessionToggle (per-directory sessions) |
ai.enabled |
false |
Default for :AIToggle |
cleanup.auto |
false |
Daily sweep of stale logs/swap/undo/views (:CleanupNvim anytime) |
editorconfig |
true |
Honour .editorconfig |
theme.dark / theme.light |
catppuccin-mocha / catppuccin-latte |
Fallbacks for <leader>cd / <leader>cl |
Your own plugins go in lua/user/plugins/*.lua (lazy.nvim specs). Your own
language packs go in lua/user/langs/*.lua.
Press <leader> (Space) and wait: which-key shows everything, including
language-specific keys in buffers of that language. Highlights:
| Key | Key | ||
|---|---|---|---|
<leader>ff / fg |
Find files / grep | <C-n> |
Explorer |
s |
Flash jump | <C-\> |
Terminal (your $SHELL) |
gd, grr, gri, grn, gra, K |
LSP (Neovim defaults) | <leader>lf |
Format |
<leader>gg |
lazygit | <leader>gvo |
Diffview |
<leader>db / dc |
Breakpoint / debug | <leader>tn |
Nearest test |
<leader>cc |
Theme picker | <leader>qr |
:restart |
The full reference is docs/keymaps.md, which also lists what changed in the 2026-10 modernization.
<leader>ai (or :AIEnable) and accept the restart prompt. Then:
- Claude Code (
<leader>ac): runs theclaudeCLI in a split, connected over Claude Code's IDE protocol. Claude sees your selection and open files, and its edits open as diffs:<leader>aaaccepts,<leader>adrejects. - sidekick (
<C-.>,<leader>ak): a persistent terminal for any AI CLI (claude, codex, gemini, ...) with helpers to send the current file/selection/diagnostics.
37 themes: 12 maintained plugins, each with dark and light variants, and the
built-in low-saturation txaty pair. <leader>cc opens a picker with
live preview; <leader>cd / <leader>cl switch to your last dark / light
theme. The choice persists, including themes set with a plain :colorscheme.
distant.nvim was removed (unmaintained). Neovim 0.12 can attach your local UI
to a Neovim running on another machine. Use Unix sockets in private
directories: a TCP --listen port is unauthenticated and lets any local user
on either machine run code as you.
# on the host
mkdir -p -m 700 ~/.cache/nvim-remote
nvim --headless --listen ~/.cache/nvim-remote/nvim.sock
# locally: forward the socket, then attach with :connect
mkdir -p -m 700 ~/.cache/nvim-remote
ssh -N -L ~/.cache/nvim-remote/host.sock:/home/you/.cache/nvim-remote/nvim.sock host
nvim "+connect $HOME/.cache/nvim-remote/host.sock"Mounting with sshfs also works.
Opening a file in a repository you just cloned should not run code that the
repository ships. TerraNvim treats every project as untrusted until you
run :TrustProject (<leader>Lt), using Neovim's own trust database
(:trust).
In untrusted projects:
-
Language servers, formatters and linters come from Mason or your
PATH, never from the project'snode_modules/.bin. -
Tools whose project configuration is code don't run:
- luacheck (
.luacheckrcis Lua); - prettier (JS configs and plugins);
- the eslint, tailwindcss and Solidity (Hardhat) servers (they load project JS);
- markdownlint-cli2 (
.cjsconfigs); - solhint (plugins);
- the workspace TypeScript SDK.
A notice names what was skipped. LSP formatting is used instead where it exists.
- luacheck (
-
Toolchains that build the project are not gated: rust-analyzer runs build scripts and proc macros, SwiftPM evaluates
Package.swift, kotlin-lsp imports Gradle/Maven builds, and go may fetch toolchains. Enabling those language packs means accepting that, as in any editor.
Hardened defaults:
- No modelines and no
.nvim.lua/.exrcexecution (exrc=false,secure). 'shell'is pinned to/bin/shfor:!and plugins. Interactive terminals use your$SHELLonly after validating it.- Plugins are pinned in
lazy-lock.jsonand never auto-update (checker.enabled = false). - Network access:
- on the first start (missing plugins);
- on first use of an enabled language: missing Mason tools and parsers
(
install.auto = falseturns this off); - from language servers themselves, e.g. jsonls, yamlls and taplo fetching
the schema a file names (
"$schema": "<url>") or that a catalogue maps it to; - once per blink.cmp version, to download its prebuilt fuzzy matcher;
- when you run
:Lazy,:Masonor:LangInstall; - from AI plugins, once you enable them.
- Persisted state is written only under
stdpath("data"|"state"|"cache"), never through symlinks. - External openers (
<leader>mo,<leader>io) ask before launching.
After updating plugins: review git diff lazy-lock.json, check new build
hooks, and grep the updated plugins for os.execute, io.popen and
loadstring.
:Lazy update |
Update plugins (review and commit lazy-lock.json) |
:TSUpdate |
Update treesitter parsers |
:Mason |
Update tools |
:checkhealth |
Everything; :checkhealth core.lang, vim.lsp, which-key, vim.deprecated in particular |
:Lazy profile |
Startup profile |
make deps # new machine / CI: pinned plugins, then every pack's tools (network)
make check # lint (stylua + luacheck), headless smoke tests, startup budget
make fmt # format Lua
make test # smoke tests with default, all and no language packsscripts/smoke.sh starts this checkout headlessly with isolated state and
checks:
- startup is free of errors;
- every language pack is valid;
- enabled packs register their servers, formatters, linters, DAP configs and keymaps;
- nothing writes to the lockfile or persisted state.
Contributor and coding-agent notes are in AGENTS.md; CHANGELOG.md records notable changes.
Ideas borrowed from LazyVim, AstroNvim, NvChad and kickstart.nvim. MIT licensed.
