# Neovim config for remote machines

This is my Neovim configuration, published so that I, or an agent working on my behalf, can put it on any machine I SSH into with one command. The config is frozen: the lockfile is the contract, and the files here are byte-for-byte what I run locally.

```sh
curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh
```

If `nvim` is missing or older than 0.11, add `--install-nvim` and the script fetches the official release tarball into `~/.local/opt/nvim` (no sudo, no package manager):

```sh
curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh -s -- --install-nvim
```

For a machine with no git or no outbound network beyond this download, install the plugin-free variant instead:

```sh
curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh -s -- --minimal
```

## Files

- [`/nvim/install.sh`](/nvim/install.sh): POSIX `sh` installer. Idempotent; backs up any config it replaces.
- [`/nvim/init.lua`](/nvim/init.lua): the full config. Bootstraps [lazy.nvim](https://github.com/folke/lazy.nvim) and declares every plugin, LSP server, keymap, and option.
- [`/nvim/lazy-lock.json`](/nvim/lazy-lock.json): pinned plugin commits. The installer runs `Lazy! restore`, not `sync`, so you get exactly these.
- [`/nvim/syntax/baml.vim`](/nvim/syntax/baml.vim): fallback highlighting for [BAML](https://boundaryml.com) files until the LSP attaches.
- [`/nvim/init.min.lua`](/nvim/init.min.lua): the `--minimal` variant. Same options and keymaps, built-in colorscheme, netrw instead of a tree plugin, `:find`/`:grep` instead of Telescope, and native LSP completion for any server already on `PATH`.

## Requirements

Hard requirements for the full config: `nvim` 0.11 or newer (the config uses `vim.lsp.config`, `vim.lsp.enable`, and `vim.uv`), `git` (lazy.nvim clones plugins), and `curl` or `wget` for the download. The minimal variant needs only `nvim` 0.11+ and `curl` or `wget`.

Optional tools, and what each unlocks:

- `cc` and `make`: compile `telescope-fzf-native` and LuaSnip's jsregexp. Without them Telescope falls back to its Lua sorter.
- `tree-sitter` CLI plus a C compiler: nvim-treesitter's `main` branch installs the C, C++, CMake, and Make parsers through it. Without it, highlighting comes from regex syntax files and LSP semantic tokens.
- `rg` (ripgrep): Telescope `live_grep` and the minimal config's `:grep`.
- `node`, `cargo`, `go`, `java`, `python3`: toolchains for the language servers Mason installs (`lua_ls`, `clangd`, `pyright`, `rust_analyzer`, `hls`, `html`, `ts_ls`, `cssls`, `jdtls`, `gopls`). Servers whose toolchain is absent simply fail to install in `:Mason` and the rest keep working.

The installer prints which optional tools it found so you can decide whether the machine needs more.

## What the installer does

1. Checks for `nvim` ≥ 0.11. With `--install-nvim`, downloads the `stable` tarball for the detected OS and architecture from the Neovim GitHub releases into `~/.local/opt/nvim` and links `~/.local/bin/nvim`. It prints a note if that directory is not on `PATH`.
2. Downloads `init.lua`, `lazy-lock.json`, and `syntax/baml.vim` (or just `init.min.lua` with `--minimal`) into a temporary directory.
3. If `$XDG_CONFIG_HOME/nvim` (default `~/.config/nvim`) exists and its `init.lua` differs, moves it to `nvim.bak.<timestamp>`. If it already matches, it refreshes in place with no backup. It never deletes anything.
4. Copies the files into place.
5. Runs `nvim --headless "+Lazy! restore" +qa` to install the pinned plugins, then counts the directories in `$XDG_DATA_HOME/nvim/lazy` against the lockfile. Skip this with `--no-sync`.
6. Reports optional tooling.

`--dry-run` prints every step without writing. The script writes only to the config directory, Neovim's data directory, and, with `--install-nvim`, `~/.local/opt/nvim` and `~/.local/bin/nvim`.

Language servers are installed by Mason on the first interactive launch, not by the installer, because `mason-lspconfig` installs them asynchronously and a headless `+qa` would cut that short. Open `nvim` once and let `:Mason` finish, or run `:MasonInstall` for a specific server.

## Verify

```sh
nvim --headless +checkhealth +qa
nvim --headless "+Lazy! check" +qa
```

Both should exit 0. To check that a remote machine is on the published config rather than a drifted copy:

```sh
curl -fsSL https://www.mfserna.dev/nvim/init.lua | diff - ~/.config/nvim/init.lua
curl -fsSL https://www.mfserna.dev/nvim/lazy-lock.json | diff - ~/.config/nvim/lazy-lock.json
```

No output means no drift.

## Manual install

For a machine where piping to `sh` is not acceptable, the files are plain static downloads:

```sh
mkdir -p ~/.config/nvim/syntax
curl -fsSL https://www.mfserna.dev/nvim/init.lua -o ~/.config/nvim/init.lua
curl -fsSL https://www.mfserna.dev/nvim/lazy-lock.json -o ~/.config/nvim/lazy-lock.json
curl -fsSL https://www.mfserna.dev/nvim/syntax/baml.vim -o ~/.config/nvim/syntax/baml.vim
nvim --headless "+Lazy! restore" +qa
```

## What is in the config

Gruvbox, lualine, bufferline, nvim-tree, Telescope with fzf-native, nvim-cmp with LuaSnip and friendly-snippets, gitsigns, toggleterm, which-key, Comment.nvim, indent-blankline, and nvim-autopairs. LSP goes through nvim-lspconfig and Mason, with rustaceanvim and crates.nvim for Rust, nvim-jdtls for Java, clangd flags for C and C++, and a hand-wired BAML server. Leader is space; `<leader>f` finds, `<leader>g` is git, `<leader>l` is LSP, `<leader>r` is Rust, `<leader>c` shells out to the Cursor CLI, and `Ctrl-\` toggles a terminal. Tabs are four spaces, folds come from treesitter and start open, diagnostics show in a float on hover rather than as virtual text, and search highlighting clears itself when the cursor leaves the match.

This page is part of the [mfserna.dev document surface](/docs). It has a markdown sibling at [`/nvim.md`](/nvim.md) and is listed in [`/api/v1/pages`](/api/v1/pages) and [`/openapi.json`](/openapi.json).
