From 20d151ca72613068f7e00e8f1e2e0092944492e1 Mon Sep 17 00:00:00 2001 From: SimonKimDev Date: Sat, 17 Oct 2026 04:07:29 -0700 Subject: [PATCH] Add Rust support with rustaceanvim - rustaceanvim v9 runs the toolchain's rust-analyzer (not Mason's) with clippy as the checker, and finds Mason's codelldb for debugging. mason-nvim-dap installs codelldb but leaves its setup to rustaceanvim. - rust and toml treesitter parsers; neotest gets rustaceanvim's adapter; dap-virtual-text covers Rust. - after/ftplugin/rust.lua: :make runs cargo build into quickfix, and rr / rd / re open runnables, debuggables and the error explanation. - Smoke test checks Rust buffers get treesitter highlighting and that rust_analyzer isn't also enabled through lspconfig. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 90315f97-7c82-4bc1-9bf1-2f8460782554 --- .github/copilot-instructions.md | 4 ++-- README.md | 3 ++- after/ftplugin/rust.lua | 12 ++++++++++++ lazy-lock.json | 1 + lua/simonkdev/lazy/debugger/dap-virtual-text.lua | 2 +- lua/simonkdev/lazy/debugger/mason-nvim-dap.lua | 6 +++++- lua/simonkdev/lazy/rust.lua | 8 ++++++++ lua/simonkdev/lazy/testing.lua | 1 + lua/simonkdev/lazy/treesitter.lua | 2 ++ tests/smoke.lua | 10 +++++++++- 10 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 after/ftplugin/rust.lua create mode 100644 lua/simonkdev/lazy/rust.lua diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f8e0061..76268e2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,12 +1,12 @@ # Copilot instructions -Personal Neovim config (Lua, lazy.nvim) for Neovim 0.12+, used on Windows with pwsh for C#/.NET, Go and Vite/React work. CI checks are described in `README.md`. +Personal Neovim config (Lua, lazy.nvim) for Neovim 0.12+, used on Windows with pwsh for C#/.NET, Go, Rust and Vite/React work. CI checks are described in `README.md`. ## Layout - `init.lua` loads `lua/simonkdev/`: `options.lua`, `keymaps.lua`, `autocmds.lua`, then `lazy_init.lua`, which bootstraps lazy.nvim. - Plugin specs live in `lua/simonkdev/lazy/`, with `lsp/` and `debugger/` subfolders. Each file returns a lazy.nvim spec. -- Language servers are installed and enabled by mason-lspconfig (`lazy/lsp/mason.lua`) and configured with `vim.lsp.config` (`lazy/lsp/lspconfig.lua`). +- Language servers are installed and enabled by mason-lspconfig (`lazy/lsp/mason.lua`) and configured with `vim.lsp.config` (`lazy/lsp/lspconfig.lua`). The exception is Rust: rustaceanvim (`lazy/rust.lua`) runs the toolchain's rust-analyzer, so never add `rust_analyzer` to mason-lspconfig. - Per-filetype settings go in `after/ftplugin/.lua`. - `lazy-lock.json` pins every plugin's commit. `tests/smoke.lua` is the headless startup test. - Treesitter uses nvim-treesitter's `main` branch: it only installs parsers (`lazy/treesitter.lua`), and Neovim's built-in treesitter does highlighting, started per buffer from a FileType autocmd. There is no `nvim-treesitter.configs`. diff --git a/README.md b/README.md index a2003f0..96b9e7b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Other than small retouch the setup is virtually identitcal to Josean's setup, ch - Neovim 0.12 or later. - A C compiler on `PATH` (e.g. gcc from MinGW) to build treesitter parsers and telescope-fzf-native. nvim-treesitter also needs the `tree-sitter` CLI, which Mason installs; on a fresh machine parsers install once Mason has it. +- For Rust, rust-analyzer from your toolchain: `rustup component add rust-analyzer`. rustaceanvim starts it (and Mason's codelldb for debugging); don't install rust-analyzer through Mason. ## Updates @@ -18,7 +19,7 @@ Other than small retouch the setup is virtually identitcal to Josean's setup, ch Every pull request (and push to `main`) runs `.github/workflows/ci.yml`: - **StyLua**: `stylua --check .` using `.stylua.toml`. Fix locally with `stylua .`. -- **Startup smoke test (Windows)**: installs every plugin from `lazy-lock.json` and every treesitter parser into empty directories, opens a Lua and a C# buffer, loads all plugins, and fails on any error. It also checks wiring that wouldn't raise an error: every mason-lspconfig server name resolves to an LSP config, Go/TSX/Lua buffers get the expected formatter and treesitter highlighting, files without a formatter get trailing whitespace trimmed (patches excepted), and web filetypes use 2-space indents. Mason package download failures are ignored because they depend on the network, not the config; language servers aren't installed in headless runs, so they're never started. +- **Startup smoke test (Windows)**: installs every plugin from `lazy-lock.json` and every treesitter parser into empty directories, opens a Lua and a C# buffer, loads all plugins, and fails on any error. It also checks wiring that wouldn't raise an error: every mason-lspconfig server name resolves to an LSP config, Go/TSX/Lua/Rust buffers get the expected formatter and treesitter highlighting, files without a formatter get trailing whitespace trimmed (patches excepted), and web filetypes use 2-space indents. Mason package download failures are ignored because they depend on the network, not the config; language servers aren't installed in headless runs, so they're never started. Run the smoke test locally from this directory: diff --git a/after/ftplugin/rust.lua b/after/ftplugin/rust.lua new file mode 100644 index 0000000..f59bab0 --- /dev/null +++ b/after/ftplugin/rust.lua @@ -0,0 +1,12 @@ +-- :make (b) runs `cargo build` and loads rustc's errors into the quickfix list +vim.cmd.compiler("cargo") +vim.opt_local.makeprg = "cargo build" + +local function map(lhs, subcommand, desc) + vim.keymap.set("n", lhs, function() + vim.cmd.RustLsp(subcommand) + end, { buffer = true, desc = desc }) +end +map("rr", "runnables", "Rust: Runnables") +map("rd", "debuggables", "Rust: Debuggables") +map("re", "explainError", "Rust: Explain Error") diff --git a/lazy-lock.json b/lazy-lock.json index af96f13..4789b09 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -46,6 +46,7 @@ "nvim-ts-context-commentstring": { "branch": "main", "commit": "6141a40173c6efa98242dc951ed4b6f892c97027" }, "nvim-web-devicons": { "branch": "master", "commit": "ec309a0eaff6ea967e58c275996155ab5c16938d" }, "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, + "rustaceanvim": { "branch": "main", "commit": "9cb125de42c0256ed1bc1f9ded8d8f0381317a17" }, "substitute.nvim": { "branch": "main", "commit": "0d2077398552f069abccbd964a26dd7cd26882cd" }, "telescope-fzf-native.nvim": { "branch": "main", "commit": "b25b749b9db64d375d782094e2b9dce53ad53a40" }, "telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" }, diff --git a/lua/simonkdev/lazy/debugger/dap-virtual-text.lua b/lua/simonkdev/lazy/debugger/dap-virtual-text.lua index 687f50d..c9bc285 100644 --- a/lua/simonkdev/lazy/debugger/dap-virtual-text.lua +++ b/lua/simonkdev/lazy/debugger/dap-virtual-text.lua @@ -3,6 +3,6 @@ return { dependencies = { "mfussenegger/nvim-dap", }, - ft = { "go", "cs" }, + ft = { "go", "cs", "rust" }, opts = {}, } diff --git a/lua/simonkdev/lazy/debugger/mason-nvim-dap.lua b/lua/simonkdev/lazy/debugger/mason-nvim-dap.lua index 6ed43b8..c01674b 100644 --- a/lua/simonkdev/lazy/debugger/mason-nvim-dap.lua +++ b/lua/simonkdev/lazy/debugger/mason-nvim-dap.lua @@ -7,8 +7,12 @@ return { "chrome-debug-adapter", "js-debug-adapter", "coreclr", + "codelldb", -- Rust, through rustaceanvim + }, + handlers = { + -- rustaceanvim registers codelldb and Rust's debug configurations itself (lazy/rust.lua) + codelldb = function() end, }, - handlers = {}, }, event = "VeryLazy", } diff --git a/lua/simonkdev/lazy/rust.lua b/lua/simonkdev/lazy/rust.lua new file mode 100644 index 0000000..5c4e58c --- /dev/null +++ b/lua/simonkdev/lazy/rust.lua @@ -0,0 +1,8 @@ +-- Rust: rustaceanvim starts and configures rust-analyzer itself, so don't also enable it through mason-lspconfig. +-- It runs the rust-analyzer on PATH (`rustup component add rust-analyzer`), which matches the toolchain, and +-- debugs with Mason's codelldb, which it finds on its own. Clippy is the on-save checker when it's installed. +return { + "mrcjkb/rustaceanvim", + version = "^9", + lazy = false, -- a filetype plugin that already loads lazily +} diff --git a/lua/simonkdev/lazy/testing.lua b/lua/simonkdev/lazy/testing.lua index 86e870c..cbe4c90 100644 --- a/lua/simonkdev/lazy/testing.lua +++ b/lua/simonkdev/lazy/testing.lua @@ -17,6 +17,7 @@ return { dap_settings = { type = "coreclr" }, }), require("neotest-golang")(), -- debugs tests through nvim-dap-go + require("rustaceanvim.neotest"), -- debugs tests through codelldb }, status = { virtual_text = true }, output = { diff --git a/lua/simonkdev/lazy/treesitter.lua b/lua/simonkdev/lazy/treesitter.lua index 5ab4467..b762323 100644 --- a/lua/simonkdev/lazy/treesitter.lua +++ b/lua/simonkdev/lazy/treesitter.lua @@ -21,6 +21,8 @@ return { "gomod", "gosum", "gowork", + "rust", + "toml", "css", "html", "json", diff --git a/tests/smoke.lua b/tests/smoke.lua index 95b888f..b9ff6b9 100644 --- a/tests/smoke.lua +++ b/tests/smoke.lua @@ -166,6 +166,8 @@ local function run() for _, name in ipairs({ "gopls", "ts_ls", "lua_ls", "csharp_ls" }) do check(vim.tbl_contains(servers, name), name .. " is missing from mason-lspconfig ensure_installed") end + -- rustaceanvim runs rust-analyzer; a second client from lspconfig would duplicate every result. + check(not vim.lsp.is_enabled("rust_analyzer"), "rust_analyzer is enabled alongside rustaceanvim") for _, name in ipairs(servers) do local config = vim.lsp.config[name] check(config and config.cmd ~= nil, "no LSP config named " .. name) @@ -173,7 +175,13 @@ local function run() -- Scratch buffers get FileType without BufEnter, so nvim-lint doesn't try to run eslint_d. local conform = require("conform") - local expected = { go = "goimports", typescriptreact = "prettier", lua = "stylua", text = "trim_whitespace" } + local expected = { + go = "goimports", + typescriptreact = "prettier", + lua = "stylua", + rust = "trim_whitespace", -- after rust-analyzer's rustfmt + text = "trim_whitespace", + } for ft, formatter in pairs(expected) do local buf = vim.api.nvim_create_buf(true, true) vim.bo[buf].filetype = ft