From e073982fb361cb8c8e299dca33e5f77568a9b907 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Tue, 4 Nov 2025 17:40:54 +0100 Subject: [PATCH 1/2] ci: use lux for type checking --- .github/workflows/tests.yml | 10 +- .luarc.json | 13 ++- flake.nix | 3 +- lua/rustaceanvim/cargo.lua | 21 ++-- lua/rustaceanvim/commands/diagnostic.lua | 98 ++++++++++--------- lua/rustaceanvim/commands/external_docs.lua | 6 +- lua/rustaceanvim/commands/hover_range.lua | 7 +- lua/rustaceanvim/config/check.lua | 4 +- lua/rustaceanvim/rust_analyzer.lua | 7 ++ lux.lock | 101 ++++++++++++++++++++ lux.toml | 4 + 11 files changed, 203 insertions(+), 71 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1d4a4121..bcd0e0fa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,9 +39,9 @@ jobs: run: | lx --nvim test - # TODO: Not stable in Lux yet. - # For now, we use Nix for type checking - # - name: Type checks - # run: | - # lx --nvim check + - name: Type checks + run: | + lx --nvim check --warnings-as-errors + env: + VIMRUNTIME: /home/runner/nvim-stable/share/nvim/runtime diff --git a/.luarc.json b/.luarc.json index 8a557ed8..020f4d04 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,10 +1,17 @@ { + "diagnostics": { + "enable": true, + "disable": [ + "annotation-usage-error" + ] + }, "workspace": { "library": [ "$VIMRUNTIME", ".lux/5.1/test_dependencies/5.1/01a3c364614bddff7370223a5a9c4580f8e62d144384148444c518ec5367a59b-mediator_lua@1.1.2-0/src", ".lux/5.1/test_dependencies/5.1/287e827f4a088d41bba04af5f61a13614346c16fe8150eb7c4246e67d6fd163e-lua-term@0.8-1/src", ".lux/5.1/test_dependencies/5.1/316ac0b30e04e86a253d64886f3b110bd0508267474e6b58a3b973bd6857dbf4-penlight@1.14.0-3/src", + ".lux/5.1/test_dependencies/5.1/36c63cd0c043eb0fbc7d5ecd75907926b7136414cef7703dcd5784a61e6a728e-nvim-dap@0.10.0-1/src", ".lux/5.1/test_dependencies/5.1/455cd98d50c6191a9685cffcda4ce783efbb957934625e134c39f43bd5df6818-luassert@1.9.0-1/src", ".lux/5.1/test_dependencies/5.1/47b12edcdc032232157ace97bddf34bddd17f6f458095885e62bbd602ad9e9ec-luasystem@0.6.3-1/src", ".lux/5.1/test_dependencies/5.1/4e9592a499c9ced4f8ce366db9db7d9c0dd1424ea8d4c8c16c1550ea3a61a696-say@1.4.1-3/src", @@ -12,8 +19,12 @@ ".lux/5.1/test_dependencies/5.1/6ce29c2c535c40246c386c056f24689344cddb56ec397473931431e6b67694d2-say@1.4.1-3/src", ".lux/5.1/test_dependencies/5.1/832fd9862ce671c0c9777855d4c8b19f9ad9c2679fb5466c3a183785a51b76b0-luafilesystem@1.8.0-1/src", ".lux/5.1/test_dependencies/5.1/a6c5176043cb3de56336b7de119443dbb3d9e024be1d50e06289ad4b4959a2da-lua_cliargs@3.0.2-1/src", + ".lux/5.1/test_dependencies/5.1/acdfde00d122aac481c18c906d483478bb536741beb025becc11782a075d125b-luassert@1.9.0-1/src", + ".lux/5.1/test_dependencies/5.1/b54df892b93931d9062f7bb8887a2ae7e6ce116e50d9aca0cf690256d7ac05b6-neotest@5.13.1-1/src", ".lux/5.1/test_dependencies/5.1/d85464dc58c62460a1ecb14e6ac773ae615a66a8224b26ceb25d954c6b05ca74-nlua@0.3.2-1/src", - ".lux/5.1/test_dependencies/5.1/e4f17b9e67313bbd5e90f425672fc8998dd0bfa43335f7c57ed2de7a799e07a6-dkjson@2.8-1/src" + ".lux/5.1/test_dependencies/5.1/de206edd51dbfc1bbe56869d6729cf4a6e6e70e7b2e107093c0f6520c142d79b-nvim-nio@1.10.1-1/src", + ".lux/5.1/test_dependencies/5.1/e4f17b9e67313bbd5e90f425672fc8998dd0bfa43335f7c57ed2de7a799e07a6-dkjson@2.8-1/src", + ".lux/5.1/test_dependencies/5.1/e6419c49ea30f1be0576a4def3982181470d4f3961a28360a17aa9396db68ff2-plenary.nvim@scm-1/src" ] } } \ No newline at end of file diff --git a/flake.nix b/flake.nix index eef096fb..80bd976d 100755 --- a/flake.nix +++ b/flake.nix @@ -148,7 +148,8 @@ pkgs.statix pkgs.nixd alejandra - lua-language-server + pkgs.emmylua-ls + # lua-language-server stylua editorconfig-checker markdownlint-cli diff --git a/lua/rustaceanvim/cargo.lua b/lua/rustaceanvim/cargo.lua index 448a20a7..87b381b8 100644 --- a/lua/rustaceanvim/cargo.lua +++ b/lua/rustaceanvim/cargo.lua @@ -47,10 +47,10 @@ local function get_cargo_metadata(path, callback) end) else local sc = vim - .system(cmd, { - cwd = vim.uv.fs_stat(path) and path or cargo_crate_dir or vim.fn.getcwd(), - }) - :wait() + .system(cmd, { + cwd = vim.uv.fs_stat(path) and path or cargo_crate_dir or vim.fn.getcwd(), + }) + :wait() return on_exit(sc) end end @@ -74,11 +74,11 @@ local function default_get_root_dir(file_name, callback) ---@return string | nil root_dir local function root_dir(cargo_crate_dir, cargo_metadata) return cargo_metadata and cargo_metadata.workspace_root - or cargo_crate_dir - or vim.fs.dirname(vim.fs.find({ 'rust-project.json' }, { - upward = true, - path = path, - })[1]) + or cargo_crate_dir + or vim.fs.dirname(vim.fs.find({ 'rust-project.json' }, { + upward = true, + path = path, + })[1]) end if callback then get_cargo_metadata(path, function(cargo_crate_dir, cargo_metadata) @@ -107,7 +107,8 @@ local function get_mb_active_client_root(file_name) item = os.normalize_path_on_windows(item) if file_name:sub(1, #item) == item then local clients = rust_analyzer.get_active_rustaceanvim_clients() - return clients and #clients > 0 and clients[#clients].config.root_dir or nil + local client = clients[#clients] + return client and client.config.root_dir or nil end end end diff --git a/lua/rustaceanvim/commands/diagnostic.lua b/lua/rustaceanvim/commands/diagnostic.lua index bb53a1a3..fbbfe68e 100644 --- a/lua/rustaceanvim/commands/diagnostic.lua +++ b/lua/rustaceanvim/commands/diagnostic.lua @@ -62,14 +62,15 @@ function M.explain_error(cycle_diagnostic) end local diagnostics = vim - .iter(vim.diagnostic.get(0, {})) - ---@param diagnostic vim.Diagnostic - :filter(function(diagnostic) - return diagnostic.code ~= nil - and diagnostic.source == 'rustc' - and diagnostic.severity == vim.diagnostic.severity.ERROR - end) - :totable() + .iter(vim.diagnostic.get(0, {})) + :filter( + ---@param diagnostic vim.Diagnostic + function(diagnostic) + return diagnostic.code ~= nil + and diagnostic.source == 'rustc' + and diagnostic.severity == vim.diagnostic.severity.ERROR + end) + :totable() if #diagnostics == 0 then vim.notify('No explainable errors found.', vim.log.levels.INFO) return @@ -171,15 +172,15 @@ function M.explain_error_current_line() -- get matching diagnostics from current line local diagnostics = vim - .iter(vim.diagnostic.get(0, { - lnum = cursor_position[1] - 1, - })) - :filter(function(diagnostic) - return diagnostic.code ~= nil - and diagnostic.source == 'rustc' - and diagnostic.severity == vim.diagnostic.severity.ERROR - end) - :totable() + .iter(vim.diagnostic.get(0, { + lnum = cursor_position[1] - 1, + })) + :filter(function(diagnostic) + return diagnostic.code ~= nil + and diagnostic.source == 'rustc' + and diagnostic.severity == vim.diagnostic.severity.ERROR + end) + :totable() -- no matching diagnostics on current line if #diagnostics == 0 then @@ -241,7 +242,7 @@ end local function render_ansi_code_diagnostic(rendered_diagnostic) -- adopted from https://stackoverflow.com/questions/48948630/lua-ansi-escapes-pattern local lines = - vim.split(rendered_diagnostic:gsub('[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]', ''), '\n', { trimempty = true }) + vim.split(rendered_diagnostic:gsub('[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]', ''), '\n', { trimempty = true }) local float_preview_lines = vim.deepcopy(lines) table.insert(float_preview_lines, 1, '---') table.insert(float_preview_lines, 1, '1. Open in split') @@ -288,12 +289,12 @@ local function render_ansi_code_diagnostic(rendered_diagnostic) vim.api.nvim_feedkeys( vim.api.nvim_replace_termcodes( 'lua vim.api.nvim_set_current_win(' - .. winnr - .. ')' - .. [[]] - .. 'lua vim.api.nvim_win_set_cursor(' - .. winnr - .. ',{1,0})', + .. winnr + .. ')' + .. [[]] + .. 'lua vim.api.nvim_win_set_cursor(' + .. winnr + .. ',{1,0})', true, false, true @@ -308,12 +309,13 @@ end ---@param cycle_diagnostic (fun(opts?: vim.diagnostic.JumpOpts): vim.Diagnostic?) function M.render_diagnostic(cycle_diagnostic) local diagnostics = vim - .iter(vim.diagnostic.get(0, {})) - ---@param diagnostic vim.Diagnostic - :filter(function(diagnostic) - return get_rendered_diagnostic(diagnostic) ~= nil - end) - :totable() + .iter(vim.diagnostic.get(0, {})) + :filter( + ---@param diagnostic vim.Diagnostic + function(diagnostic) + return get_rendered_diagnostic(diagnostic) ~= nil + end) + :totable() if #diagnostics == 0 then vim.notify('No renderable diagnostics found.', vim.log.levels.INFO) return @@ -391,12 +393,13 @@ function M.render_diagnostic_current_line() -- get rendered diagnostics from current line ---@type string[] local rendered_diagnostics = vim - .iter(get_diagnostics_current_line()) - ---@param diagnostic vim.Diagnostic - :map(function(diagnostic) - return get_rendered_diagnostic(diagnostic) - end) - :totable() + .iter(get_diagnostics_current_line()) + :map( + ---@param diagnostic vim.Diagnostic + function(diagnostic) + return get_rendered_diagnostic(diagnostic) + end) + :totable() -- if no renderable diagnostics on current line if #rendered_diagnostics == 0 then @@ -420,17 +423,18 @@ function M.related_diagnostics() end ---@type lsp.Location[] local locations = vim - .iter(get_diagnostics_at_cursor()) - ---@param diagnostic vim.Diagnostic - :map(function(diagnostic) - return vim.tbl_get(diagnostic, 'user_data', 'lsp', 'relatedInformation') - end) - :flatten() - ---@param related_info rustaceanvim.diagnostic.RelatedInfo - :map(function(related_info) - return related_info.location - end) - :totable() + .iter(get_diagnostics_at_cursor()) + :map( + ---@param diagnostic vim.Diagnostic + function(diagnostic) + return vim.tbl_get(diagnostic, 'user_data', 'lsp', 'relatedInformation') + end) + :flatten() + ---@param related_info rustaceanvim.diagnostic.RelatedInfo + :map(function(related_info) + return related_info.location + end) + :totable() if #locations == 0 then vim.notify('No related diagnostics found.', vim.log.levels.INFO) return diff --git a/lua/rustaceanvim/commands/external_docs.lua b/lua/rustaceanvim/commands/external_docs.lua index 50acc634..96e5d009 100644 --- a/lua/rustaceanvim/commands/external_docs.lua +++ b/lua/rustaceanvim/commands/external_docs.lua @@ -2,14 +2,14 @@ local M = {} function M.open_external_docs() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client(0) + if not client then return end ra.buf_request( 0, 'experimental/externalDocs', - vim.lsp.util.make_position_params(0, clients[1].offset_encoding or 'utf-8'), + vim.lsp.util.make_position_params(0, client.offset_encoding or 'utf-8'), function(_, response) local url if response['local'] and vim.uv.fs_stat(vim.uri_to_fname(response['local'])) then diff --git a/lua/rustaceanvim/commands/hover_range.lua b/lua/rustaceanvim/commands/hover_range.lua index 6d982474..47ed85e0 100644 --- a/lua/rustaceanvim/commands/hover_range.lua +++ b/lua/rustaceanvim/commands/hover_range.lua @@ -56,8 +56,11 @@ local function get_visual_selected_range() return make_lsp_position(row1, math.min(col1, col2), row1, math.max(col1, col2)) end ----@type lsp.Handler -local function handler(_, result, _) +---@param _err lsp.ResponseError? +---@param result unknown +---@param _context lsp.HandlerContext +---@diagnostic disable-next-line: unused-local +local function handler(_err, result, _context) if not (result and result.contents) then return end diff --git a/lua/rustaceanvim/config/check.lua b/lua/rustaceanvim/config/check.lua index 23c0df46..4f05efc6 100644 --- a/lua/rustaceanvim/config/check.lua +++ b/lua/rustaceanvim/config/check.lua @@ -7,10 +7,10 @@ local M = {} ---@param name string Argument name ---@param value unknown Argument value ---@param validator vim.validate.Validator ---- - (`string|string[]`): Any value that can be returned from |lua-type()| in addition to +--- * (`string|string[]`): Any value that can be returned from |lua-type()| in addition to --- `'callable'`: `'boolean'`, `'callable'`, `'function'`, `'nil'`, `'number'`, `'string'`, `'table'`, --- `'thread'`, `'userdata'`. ---- - (`fun(val:any): boolean, string?`) A function that returns a boolean and an optional +--- * (`fun(val:any): boolean, string?`) A function that returns a boolean and an optional --- string message. ---@param optional? boolean Argument is optional (may be omitted) ---@param message? string message when validation fails diff --git a/lua/rustaceanvim/rust_analyzer.lua b/lua/rustaceanvim/rust_analyzer.lua index 2eb88a3d..19ec6300 100644 --- a/lua/rustaceanvim/rust_analyzer.lua +++ b/lua/rustaceanvim/rust_analyzer.lua @@ -48,6 +48,13 @@ M.get_active_rustaceanvim_clients = function(bufnr, filter) return clients end +---@param bufnr number | nil 0 for the current buffer, `nil` for no buffer filter +---@param filter? rustaceanvim.lsp.get_clients.Filter +---@return vim.lsp.Client | nil +M.find_active_rustaceanvim_client = function(bufnr, filter) + return M.get_active_rustaceanvim_clients(bufnr, filter)[1] +end + ---@param method string LSP method name ---@param params table|nil Parameters to send to the server ---@param handler? lsp.Handler See |lsp-handler| diff --git a/lux.lock b/lux.lock index 5bf43fcf..b05e651c 100644 --- a/lux.lock +++ b/lux.lock @@ -59,6 +59,24 @@ "source": "sha256-4zAt0GgQEkg9toaUaDn3ST3RvjLUDsuOzrKi9lhq0fQ=" } }, + "36c63cd0c043eb0fbc7d5ecd75907926b7136414cef7703dcd5784a61e6a728e": { + "name": "nvim-dap", + "version": "0.10.0-1", + "pinned": false, + "opt": false, + "dependencies": [], + "constraint": "==0.10.0", + "binaries": [], + "source": "luarocks_rockspec+https://luarocks.org/", + "source_url": { + "type": "url", + "url": "https://github.com/mfussenegger/nvim-dap/archive/0.10.0.zip" + }, + "hashes": { + "rockspec": "sha256-1I+b1kcgY/5G4EogluprGiVJ27xG8tHDykJSH4IDDj4=", + "source": "sha256-S3Q810Zph3f3a/qIRe9j0L0DWHZWi4oxPLD5ZsWo1XE=" + } + }, "455cd98d50c6191a9685cffcda4ce783efbb957934625e134c39f43bd5df6818": { "name": "luassert", "version": "1.9.0-1", @@ -212,6 +230,48 @@ "source": "sha256-wL3qBQ8Lu3q8DK2Kaeo1dgzIHd8evaxFYJg47CcQiSg=" } }, + "acdfde00d122aac481c18c906d483478bb536741beb025becc11782a075d125b": { + "name": "luassert", + "version": "1.9.0-1", + "pinned": false, + "opt": false, + "dependencies": [ + "4e9592a499c9ced4f8ce366db9db7d9c0dd1424ea8d4c8c16c1550ea3a61a696" + ], + "constraint": null, + "binaries": [], + "source": "luarocks_rockspec+https://luarocks.org/", + "source_url": { + "type": "git", + "url": "https://github.com/lunarmodules/luassert.git", + "ref": "v1.9.0" + }, + "hashes": { + "rockspec": "sha256-rTPvF/GK/jMnH/q4wbwTCGBFELWh+JcvHeOCFAbIf64=", + "source": "sha256-jjdB95Vr5iVsh5T7E84WwZMW6/5H2k2R/ny2VBs2l3I=" + } + }, + "b54df892b93931d9062f7bb8887a2ae7e6ce116e50d9aca0cf690256d7ac05b6": { + "name": "neotest", + "version": "5.13.1-1", + "pinned": false, + "opt": false, + "dependencies": [ + "e6419c49ea30f1be0576a4def3982181470d4f3961a28360a17aa9396db68ff2", + "de206edd51dbfc1bbe56869d6729cf4a6e6e70e7b2e107093c0f6520c142d79b" + ], + "constraint": "==5.13.1", + "binaries": [], + "source": "luarocks_rockspec+https://luarocks.org/", + "source_url": { + "type": "url", + "url": "https://github.com/nvim-neotest/neotest/archive/366175e1e74ff112787f9adbfb072eeaedd2a88e.zip" + }, + "hashes": { + "rockspec": "sha256-Njk2ADfp0OEfiALnpgQTjctQQZzjRiGjcmpN1XaKwLA=", + "source": "sha256-aQeUSK31oatiuotbmKJ7nyLTlm7eAMf1zrZVDUYicAY=" + } + }, "d85464dc58c62460a1ecb14e6ac773ae615a66a8224b26ceb25d954c6b05ca74": { "name": "nlua", "version": "0.3.2-1", @@ -232,6 +292,24 @@ "source": "sha256-Gep+0WGkNPJ/7acaIVfHpi+/wHoUevlly3JRc0G+hBU=" } }, + "de206edd51dbfc1bbe56869d6729cf4a6e6e70e7b2e107093c0f6520c142d79b": { + "name": "nvim-nio", + "version": "1.10.1-1", + "pinned": false, + "opt": false, + "dependencies": [], + "constraint": null, + "binaries": [], + "source": "luarocks_rockspec+https://luarocks.org/", + "source_url": { + "type": "url", + "url": "https://github.com/nvim-neotest/nvim-nio/archive/21f5324bfac14e22ba26553caf69ec76ae8a7662.zip" + }, + "hashes": { + "rockspec": "sha256-QL5OCZFBGixecdEoriGck4iG83tjM09ewYbWVSbcfa4=", + "source": "sha256-5x+iHduNdyVjS6+OrqfDh17g9o2tP2YFFqKEsK6Z5zw=" + } + }, "e4f17b9e67313bbd5e90f425672fc8998dd0bfa43335f7c57ed2de7a799e07a6": { "name": "dkjson", "version": "2.8-1", @@ -249,10 +327,33 @@ "rockspec": "sha256-arasJeX7yQ2Rg70RyepiGNvLdiyQz8Wn4HXrdTEIBBg=", "source": "sha256-JOjNO+uRwchh63uz+8m9QYu/+a1KpdBHGBYlgjajFTI=" } + }, + "e6419c49ea30f1be0576a4def3982181470d4f3961a28360a17aa9396db68ff2": { + "name": "plenary.nvim", + "version": "scm-1", + "pinned": false, + "opt": false, + "dependencies": [ + "acdfde00d122aac481c18c906d483478bb536741beb025becc11782a075d125b" + ], + "constraint": null, + "binaries": [], + "source": "luarocks_rockspec+https://luarocks.org/", + "source_url": { + "type": "git", + "url": "https://github.com/nvim-lua/plenary.nvim", + "ref": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" + }, + "hashes": { + "rockspec": "sha256-r0jP0L52Bjijhhwn/6j9FhToew/BGCKTVtDoCCw25ik=", + "source": "sha256-9Un7ekhBxcnmFE1xjCCFTZ7eqIbmXvQexpnhduAg4M0=" + } } }, "entrypoints": [ + "36c63cd0c043eb0fbc7d5ecd75907926b7136414cef7703dcd5784a61e6a728e", "56b98be57b1a97b869fd8ded0d2c0b9ce0b6b052e2d5abf84d060748617b2c90", + "b54df892b93931d9062f7bb8887a2ae7e6ce116e50d9aca0cf690256d7ac05b6", "d85464dc58c62460a1ecb14e6ac773ae615a66a8224b26ceb25d954c6b05ca74" ] } diff --git a/lux.toml b/lux.toml index e0cdd49e..ef4b118e 100644 --- a/lux.toml +++ b/lux.toml @@ -16,3 +16,7 @@ type = "builtin" [test] type = "busted-nlua" + +[test_dependencies] +nvim-dap = "0.10.0-1" +neotest = "5.13.1-1" From 143cea92fb2fc2d60f2b6c4d567c99a4c9912e28 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Tue, 4 Nov 2025 18:03:42 +0100 Subject: [PATCH 2/2] fix: type safety improvements --- .github/workflows/tests.yml | 2 +- .luarc.json | 3 + flake.lock | 44 ----- flake.nix | 68 +------ lua/rustaceanvim/cache.lua | 7 +- lua/rustaceanvim/cached_commands.lua | 9 +- lua/rustaceanvim/cargo.lua | 20 +- .../commands/code_action_group.lua | 54 +++--- lua/rustaceanvim/commands/crate_graph.lua | 2 +- lua/rustaceanvim/commands/debuggables.lua | 15 +- lua/rustaceanvim/commands/diagnostic.lua | 175 ++++++++++-------- lua/rustaceanvim/commands/expand_macro.lua | 6 +- lua/rustaceanvim/commands/hover_range.lua | 16 +- lua/rustaceanvim/commands/init.lua | 17 +- lua/rustaceanvim/commands/join_lines.lua | 21 ++- lua/rustaceanvim/commands/move_item.lua | 26 ++- lua/rustaceanvim/commands/parent_module.lua | 6 +- .../commands/rebuild_proc_macros.lua | 4 +- lua/rustaceanvim/commands/rustc_unpretty.lua | 29 +-- lua/rustaceanvim/commands/ssr.lua | 9 +- lua/rustaceanvim/commands/syntax_tree.lua | 6 +- lua/rustaceanvim/commands/view_ir.lua | 7 +- .../commands/workspace_symbol.lua | 20 +- lua/rustaceanvim/config/internal.lua | 18 +- lua/rustaceanvim/dap.lua | 46 +++-- lua/rustaceanvim/executors/background.lua | 7 +- lua/rustaceanvim/executors/neotest.lua | 1 + lua/rustaceanvim/executors/quickfix.lua | 2 + lua/rustaceanvim/executors/termopen.lua | 1 + lua/rustaceanvim/executors/toggleterm.lua | 2 + lua/rustaceanvim/executors/vimux.lua | 1 + lua/rustaceanvim/health.lua | 7 +- lua/rustaceanvim/hover_actions.lua | 12 +- lua/rustaceanvim/lsp/init.lua | 49 +++-- lua/rustaceanvim/neotest/init.lua | 45 +++-- lua/rustaceanvim/neotest/trans.lua | 5 +- lua/rustaceanvim/overrides.lua | 4 +- lua/rustaceanvim/runnables.lua | 14 +- lua/rustaceanvim/rust_analyzer.lua | 29 ++- lua/rustaceanvim/rustc.lua | 6 +- lua/rustaceanvim/test.lua | 55 +++--- lux.lock | 19 ++ lux.toml | 1 + 43 files changed, 467 insertions(+), 423 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bcd0e0fa..a8688897 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,5 +43,5 @@ jobs: run: | lx --nvim check --warnings-as-errors env: - VIMRUNTIME: /home/runner/nvim-stable/share/nvim/runtime + VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim-version }}/share/nvim/runtime diff --git a/.luarc.json b/.luarc.json index 020f4d04..12408f76 100644 --- a/.luarc.json +++ b/.luarc.json @@ -5,6 +5,9 @@ "annotation-usage-error" ] }, + "runtime": { + "version": "Lua 5.1" + }, "workspace": { "library": [ "$VIMRUNTIME", diff --git a/flake.lock b/flake.lock index a265b218..c557fd65 100644 --- a/flake.lock +++ b/flake.lock @@ -55,33 +55,6 @@ "type": "github" } }, - "gen-luarc": { - "inputs": { - "flake-parts": [ - "flake-parts" - ], - "git-hooks": [ - "git-hooks" - ], - "luvit-meta": "luvit-meta", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1755304025, - "narHash": "sha256-xVKfjFwc0zMbLMjLTiHz+0llggkjs93SmHkhaa9S3M4=", - "owner": "mrcjkb", - "repo": "nix-gen-luarc-json", - "rev": "1865b0ebb753ae5324d7381b1fa8c98c04ec7509", - "type": "github" - }, - "original": { - "owner": "mrcjkb", - "repo": "nix-gen-luarc-json", - "type": "github" - } - }, "git-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -125,22 +98,6 @@ "type": "github" } }, - "luvit-meta": { - "flake": false, - "locked": { - "lastModified": 1705776742, - "narHash": "sha256-zAAptV/oLuLAAsa2zSB/6fxlElk4+jNZd/cPr9oxFig=", - "owner": "Bilal2453", - "repo": "luvit-meta", - "rev": "ce76f6f6cdc9201523a5875a4471dcfe0186eb60", - "type": "github" - }, - "original": { - "owner": "Bilal2453", - "repo": "luvit-meta", - "type": "github" - } - }, "neovim-nightly-overlay": { "inputs": { "flake-parts": "flake-parts_2", @@ -227,7 +184,6 @@ "root": { "inputs": { "flake-parts": "flake-parts", - "gen-luarc": "gen-luarc", "git-hooks": "git-hooks", "neovim-nightly-overlay": "neovim-nightly-overlay", "nixpkgs": "nixpkgs_2", diff --git a/flake.nix b/flake.nix index 80bd976d..12bd5c70 100755 --- a/flake.nix +++ b/flake.nix @@ -13,15 +13,6 @@ neovim-nightly-overlay.url = "github:nix-community/neovim-nightly-overlay"; - gen-luarc = { - url = "github:mrcjkb/nix-gen-luarc-json"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-parts.follows = "flake-parts"; - git-hooks.follows = "git-hooks"; - }; - }; - vimcats = { url = "github:mrcjkb/vimcats"; inputs = { @@ -37,7 +28,6 @@ nixpkgs, flake-parts, git-hooks, - gen-luarc, vimcats, ... }: let @@ -65,53 +55,6 @@ ci-overlay = import ./nix/ci-overlay.nix {inherit neovim-nightly;}; - luarc-plugins = with pkgs.lua51Packages; - [ - nvim-nio - ] - ++ (with pkgs.vimPlugins; [ - neotest - nvim-dap - ]); - - luarc-nightly = pkgs.mk-luarc { - nvim = neovim-nightly; - plugins = luarc-plugins; - }; - - luarc-stable = pkgs.mk-luarc { - nvim = pkgs.neovim-unwrapped; - plugins = luarc-plugins; - disabled-diagnostics = [ - "undefined-doc-name" - "undefined-doc-class" - "redundant-parameter" - "invisible" - ]; - }; - - type-check-nightly = git-hooks.lib.${system}.run { - src = self; - hooks = { - lua-ls = { - enable = true; - settings.configuration = luarc-nightly; - }; - }; - }; - - type-check-stable = git-hooks.lib.${system}.run { - src = self; - hooks = { - lua-ls = { - enable = true; - settings = { - configuration = luarc-stable; - }; - }; - }; - }; - pre-commit-check = git-hooks.lib.${system}.run { src = self; hooks = { @@ -143,13 +86,15 @@ shellHook = '' ${pre-commit-check.shellHook} ''; + env = { + EMMYLUALS_CONFIG = ".luarc.json"; + }; buildInputs = with git-hooks.packages.${system}; [ pkgs.lux-cli pkgs.statix pkgs.nixd - alejandra pkgs.emmylua-ls - # lua-language-server + alejandra stylua editorconfig-checker markdownlint-cli @@ -163,7 +108,6 @@ inherit system; overlays = [ ci-overlay - gen-luarc.overlays.default plugin-overlay ]; }; @@ -186,10 +130,6 @@ checks = { formatting = pre-commit-check; - inherit - type-check-stable - type-check-nightly - ; }; }; flake = { diff --git a/lua/rustaceanvim/cache.lua b/lua/rustaceanvim/cache.lua index 9f6741fe..2d8285ac 100644 --- a/lua/rustaceanvim/cache.lua +++ b/lua/rustaceanvim/cache.lua @@ -11,8 +11,9 @@ store-success-output = true ---@return string path to nextest.toml file function M.nextest_config_path() - local cache_dir = vim.fs.joinpath(vim.fn.stdpath('cache'), 'rustaceanvim') - local config_path = vim.fs.joinpath(cache_dir, 'nextest_1.toml') + local nvim_cache_dir = vim.fn.stdpath('cache') ---@as string + local rustaceanvim_cache_dir = vim.fs.joinpath(nvim_cache_dir, 'rustaceanvim') + local config_path = vim.fs.joinpath(rustaceanvim_cache_dir, 'nextest_1.toml') -- Check if file already exists local stat = vim.uv.fs_stat(config_path) @@ -21,7 +22,7 @@ function M.nextest_config_path() end -- Create cache directory if it doesn't exist - vim.fn.mkdir(cache_dir, 'p') + vim.fn.mkdir(rustaceanvim_cache_dir, 'p') -- Write the config file local file = io.open(config_path, 'w') diff --git a/lua/rustaceanvim/cached_commands.lua b/lua/rustaceanvim/cached_commands.lua index f9f54518..863119f7 100644 --- a/lua/rustaceanvim/cached_commands.lua +++ b/lua/rustaceanvim/cached_commands.lua @@ -6,9 +6,9 @@ local M = {} local cache = { ---@type rustaceanvim.RARunnableArgs | nil last_debuggable = nil, - ---@type rustaceanvim.RARunnablesChoice + ---@type rustaceanvim.RARunnablesChoice | nil last_runnable = nil, - ---@type rustaceanvim.RARunnablesChoice + ---@type rustaceanvim.RARunnablesChoice | nil last_testable = nil, } @@ -54,8 +54,9 @@ end ---@param choice rustaceanvim.RARunnablesChoice ---@param executableArgsOverride? string[] local function override_executable_args_if_set(choice, executableArgsOverride) - if type(executableArgsOverride) == 'table' and #executableArgsOverride > 0 then - choice.runnables[choice.choice].args.executableArgs = executableArgsOverride + local runnable = choice.runnables[choice.choice] + if type(executableArgsOverride) == 'table' and #executableArgsOverride > 0 and runnable then + runnable.args.executableArgs = executableArgsOverride end end diff --git a/lua/rustaceanvim/cargo.lua b/lua/rustaceanvim/cargo.lua index 87b381b8..b2a26b8e 100644 --- a/lua/rustaceanvim/cargo.lua +++ b/lua/rustaceanvim/cargo.lua @@ -28,7 +28,7 @@ local function get_cargo_metadata(path, callback) return callback and callback(cargo_crate_dir) or cargo_crate_dir end local ok, cargo_metadata_json = pcall(vim.fn.json_decode, sc.stdout) - if ok and cargo_metadata_json then + if ok and type(cargo_metadata_json) == 'table' then return callback and callback(cargo_crate_dir, cargo_metadata_json) or cargo_crate_dir, cargo_metadata_json else vim.notify( @@ -47,10 +47,10 @@ local function get_cargo_metadata(path, callback) end) else local sc = vim - .system(cmd, { - cwd = vim.uv.fs_stat(path) and path or cargo_crate_dir or vim.fn.getcwd(), - }) - :wait() + .system(cmd, { + cwd = vim.uv.fs_stat(path) and path or cargo_crate_dir or vim.fn.getcwd(), + }) + :wait() return on_exit(sc) end end @@ -74,11 +74,11 @@ local function default_get_root_dir(file_name, callback) ---@return string | nil root_dir local function root_dir(cargo_crate_dir, cargo_metadata) return cargo_metadata and cargo_metadata.workspace_root - or cargo_crate_dir - or vim.fs.dirname(vim.fs.find({ 'rust-project.json' }, { - upward = true, - path = path, - })[1]) + or cargo_crate_dir + or vim.fs.dirname(vim.fs.find({ 'rust-project.json' }, { + upward = true, + path = path, + })[1]) end if callback then get_cargo_metadata(path, function(cargo_crate_dir, cargo_metadata) diff --git a/lua/rustaceanvim/commands/code_action_group.lua b/lua/rustaceanvim/commands/code_action_group.lua index be101a31..e11ab58b 100644 --- a/lua/rustaceanvim/commands/code_action_group.lua +++ b/lua/rustaceanvim/commands/code_action_group.lua @@ -33,7 +33,7 @@ function _M.apply_action(action, client, ctx) if action.command then local command = type(action.command) == 'table' and action.command or action local fn = vim.lsp.commands[command.command] - if fn then + if type(fn) == 'function' then fn(command, ctx) end end @@ -50,11 +50,12 @@ function _M.on_user_choice(action_item) end local ctx = action_item.ctx local client = vim.lsp.get_client_by_id(ctx.client_id) - local action = action_item.action - local code_action_provider = client and client.server_capabilities.codeActionProvider if not client then return end + local action = action_item.action + local server_capabilities = client.server_capabilities + local code_action_provider = server_capabilities and server_capabilities.codeActionProvider if not action.edit and type(code_action_provider) == 'table' and code_action_provider.resolveProvider then client:request('codeAction/resolve', action, function(err, resolved_action) ---@cast resolved_action rustaceanvim.RACodeAction|rustaceanvim.RACommand @@ -80,7 +81,7 @@ local function compute_width(action_items, is_group) for _, value in pairs(action_items) do local action = value.action - local text = action.title + local text = action.title or '' if is_group and action.group then text = action.group .. config.tools.code_actions.group_icon @@ -118,7 +119,7 @@ end ---@class rustaceanvim.RACodeActionResult ---@field result? rustaceanvim.RACodeAction[] | rustaceanvim.RACommand[] ----@param results { [number]: rustaceanvim.RACodeActionResult } +---@param results table ---@param ctx lsp.HandlerContext local function on_code_action_results(results, ctx) local cur_win = vim.api.nvim_get_current_win() @@ -148,7 +149,7 @@ local function on_code_action_results(results, ctx) for _, value in ipairs(action_items) do local action = value.action -- Some clippy lints may have newlines in them - action.title = string.gsub(action.title, '[\n\r]+', ' ') + action.title = action.title and string.gsub(action.title, '[\n\r]+', ' ') if action.group then if not _M.state.actions.grouped[action.group] then _M.state.actions.grouped[action.group] = { actions = {}, idx = nil } @@ -162,8 +163,8 @@ local function on_code_action_results(results, ctx) if vim.tbl_count(_M.state.actions.grouped) == 0 and config.tools.code_actions.ui_select_fallback then ---@param item rustaceanvim.CodeActionItem local function format_item(item) - local title = item.action.title:gsub('\r\n', '\\r\\n') - return title:gsub('\n', '\\n') + local title = item.action.title and item.action.title:gsub('\r\n', '\\r\\n') + return title and title:gsub('\n', '\\n') end local select_opts = { prompt = 'Code actions:', @@ -243,7 +244,8 @@ function _M.codeactionify_window_buffer(winnr, bufnr) end local function on_secondary_enter_press() - local line = vim.api.nvim_win_get_cursor(_M.state.secondary.winnr)[1] + local winnr = _M.state.secondary.winnr + local line = winnr and vim.api.nvim_win_get_cursor(winnr)[1] ---@type grouped_actions_tbl | nil local active_group = nil @@ -293,29 +295,32 @@ function _M.cleanup() end function _M.on_cursor_move() - local line = vim.api.nvim_win_get_cursor(_M.state.primary.winnr)[1] + local primary_winnr = _M.state.primary.winnr + local line = primary_winnr and vim.api.nvim_win_get_cursor(primary_winnr)[1] for _, value in pairs(_M.state.actions.grouped) do if value.idx == line then _M.state.active_group_index = line - if _M.state.secondary.winnr then - ui.close_win(_M.state.secondary.winnr) + local secondary_winnr = _M.state.secondary.winnr + if secondary_winnr then + ui.close_win(secondary_winnr) _M.state.secondary.clear() end _M.state.secondary.geometry = compute_width(value.actions, false) _M.state.secondary.bufnr = vim.api.nvim_create_buf(false, true) - local secondary_winnr = vim.api.nvim_open_win(_M.state.secondary.bufnr, false, { + local primary_width = _M.state.primary.geometry and _M.state.primary.geometry.width + secondary_winnr = vim.api.nvim_open_win(_M.state.secondary.bufnr, false, { relative = 'win', - win = _M.state.primary.winnr, + win = primary_winnr, width = _M.state.secondary.geometry.width, height = #value.actions, focusable = true, border = config.tools.float_win_config.border, - row = line - 2, - col = _M.state.primary.geometry.width + 1, + row = line and line - 2 or 0, + col = primary_width and primary_width + 1 or 0, }) _M.state.secondary.winnr = secondary_winnr vim.wo[secondary_winnr].signcolumn = 'no' @@ -389,19 +394,22 @@ _M.state = { }, } ----@param make_range_params fun(bufnr: integer, offset_encoding: string):{ range: table } +---@param make_range_params fun(bufnr: integer, offset_encoding: string?):{ range: table } _M.code_action_group = function(make_range_params) + local cursor_line = vim.api.nvim_win_get_cursor(0)[1] + ---@type vim.diagnostic.GetOpts + local get_opts = { + lnum = cursor_line and cursor_line - 1 or 0, + } local context = { - diagnostics = vim.lsp.diagnostic.from(vim.diagnostic.get(0, { - lnum = vim.api.nvim_win_get_cursor(0)[1] - 1, - })), + diagnostics = vim.lsp.diagnostic.from(vim.diagnostic.get(0, get_opts)), } - local clients = vim.lsp.get_clients { bufnr = 0 } - if #clients == 0 then + local client = (vim.lsp.get_clients { bufnr = 0 })[1] + if not client then return end - local params = make_range_params(0, clients[1].offset_encoding) + local params = make_range_params(0, client.offset_encoding) ---@diagnostic disable-next-line: inject-field params.context = context diff --git a/lua/rustaceanvim/commands/crate_graph.lua b/lua/rustaceanvim/commands/crate_graph.lua index 14fc2d9b..7e402d72 100644 --- a/lua/rustaceanvim/commands/crate_graph.lua +++ b/lua/rustaceanvim/commands/crate_graph.lua @@ -12,7 +12,7 @@ end ---@param backend string | nil ---@param output string | nil ---@param pipe string | nil ----@return fun(err: string, graph: string) +---@return lsp.Handler local function handler_factory(backend, output, pipe) backend = backend or config.tools.crate_graph.backend output = output or config.tools.crate_graph.output diff --git a/lua/rustaceanvim/commands/debuggables.lua b/lua/rustaceanvim/commands/debuggables.lua index 4c1505b6..b65e0b2b 100644 --- a/lua/rustaceanvim/commands/debuggables.lua +++ b/lua/rustaceanvim/commands/debuggables.lua @@ -104,11 +104,12 @@ local function ui_select_debuggable(debuggables, executableArgsOverride) if #options == 0 then return end - vim.ui.select(options, { prompt = 'Debuggables', kind = 'rust-tools/debuggables' }, function(_, choice) - if choice == nil then + vim.ui.select(options, { prompt = 'Debuggables', kind = 'rust-tools/debuggables' }, function(_, choice_idx) + if choice_idx == nil then return end - local args = debuggables[choice].args + local choice = debuggables[choice_idx] + local args = choice and choice.args dap_run(args) end) end @@ -120,6 +121,7 @@ local function add_debuggables_to_nvim_dap(debuggables) return end local rt_dap = require('rustaceanvim.dap') + dap.configurations = dap.configurations or {} dap.configurations.rust = dap.configurations.rust or {} -- To prevent parallel 'cargo build" processes, we -- iterate over the debuggables using a recursive function. @@ -153,12 +155,13 @@ local function debug_at_cursor_position(debuggables, executableArgsOverride) return end debuggables = ra_runnables.apply_exec_args_override(executableArgsOverride, debuggables) - local choice = ra_runnables.get_runnable_at_cursor_position(debuggables) - if not choice then + local choice_idx = ra_runnables.get_runnable_at_cursor_position(debuggables) + if not choice_idx then vim.notify('No debuggable targets found for the current position.', vim.log.levels.ERROR) return end - local args = debuggables[choice].args + local choice = debuggables[choice_idx] + local args = choice and choice.args dap_run(args) end diff --git a/lua/rustaceanvim/commands/diagnostic.lua b/lua/rustaceanvim/commands/diagnostic.lua index fbbfe68e..98a7931a 100644 --- a/lua/rustaceanvim/commands/diagnostic.lua +++ b/lua/rustaceanvim/commands/diagnostic.lua @@ -62,15 +62,16 @@ function M.explain_error(cycle_diagnostic) end local diagnostics = vim - .iter(vim.diagnostic.get(0, {})) - :filter( + .iter(vim.diagnostic.get(0, {})) + :filter( ---@param diagnostic vim.Diagnostic - function(diagnostic) - return diagnostic.code ~= nil - and diagnostic.source == 'rustc' - and diagnostic.severity == vim.diagnostic.severity.ERROR - end) - :totable() + function(diagnostic) + return diagnostic.code ~= nil + and diagnostic.source == 'rustc' + and diagnostic.severity == vim.diagnostic.severity.ERROR + end + ) + :totable() if #diagnostics == 0 then vim.notify('No explainable errors found.', vim.log.levels.INFO) return @@ -141,10 +142,13 @@ function M.explain_error(cycle_diagnostic) _window_state.float_winnr = winnr set_close_keymaps(bufnr) set_split_open_keymap(bufnr, winnr, function() - -- set filetype to rust for syntax highlighting - vim.bo[_window_state.latest_scratch_buf_id].filetype = 'rust' - -- write the expansion content to the buffer - vim.api.nvim_buf_set_lines(_window_state.latest_scratch_buf_id, 0, 0, false, markdown_lines) + local latest_scratch_buf_id = _window_state.latest_scratch_buf_id + if latest_scratch_buf_id then + -- set filetype to rust for syntax highlighting + vim.bo[latest_scratch_buf_id].filetype = 'rust' + -- write the expansion content to the buffer + vim.api.nvim_buf_set_lines(latest_scratch_buf_id, 0, 0, false, markdown_lines) + end end) if config.tools.float_win_config.auto_focus then @@ -172,15 +176,15 @@ function M.explain_error_current_line() -- get matching diagnostics from current line local diagnostics = vim - .iter(vim.diagnostic.get(0, { - lnum = cursor_position[1] - 1, - })) - :filter(function(diagnostic) - return diagnostic.code ~= nil - and diagnostic.source == 'rustc' - and diagnostic.severity == vim.diagnostic.severity.ERROR - end) - :totable() + .iter(vim.diagnostic.get(0, { + lnum = cursor_position[1] or 1 - 1, + })) + :filter(function(diagnostic) + return diagnostic.code ~= nil + and diagnostic.source == 'rustc' + and diagnostic.severity == vim.diagnostic.severity.ERROR + end) + :totable() -- no matching diagnostics on current line if #diagnostics == 0 then @@ -212,10 +216,13 @@ function M.explain_error_current_line() _window_state.float_winnr = winnr set_close_keymaps(bufnr) set_split_open_keymap(bufnr, winnr, function() - -- set filetype to rust for syntax highlighting - vim.bo[_window_state.latest_scratch_buf_id].filetype = 'rust' - -- write the expansion content to the buffer - vim.api.nvim_buf_set_lines(_window_state.latest_scratch_buf_id, 0, 0, false, markdown_lines) + local latest_scratch_buf_id = _window_state.latest_scratch_buf_id + if latest_scratch_buf_id then + -- set filetype to rust for syntax highlighting + vim.bo[latest_scratch_buf_id].filetype = 'rust' + -- write the expansion content to the buffer + vim.api.nvim_buf_set_lines(latest_scratch_buf_id, 0, 0, false, markdown_lines) + end end) if config.tools.float_win_config.auto_focus then @@ -242,7 +249,7 @@ end local function render_ansi_code_diagnostic(rendered_diagnostic) -- adopted from https://stackoverflow.com/questions/48948630/lua-ansi-escapes-pattern local lines = - vim.split(rendered_diagnostic:gsub('[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]', ''), '\n', { trimempty = true }) + vim.split(rendered_diagnostic:gsub('[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]', ''), '\n', { trimempty = true }) local float_preview_lines = vim.deepcopy(lines) table.insert(float_preview_lines, 1, '---') table.insert(float_preview_lines, 1, '1. Open in split') @@ -281,20 +288,23 @@ local function render_ansi_code_diagnostic(rendered_diagnostic) _window_state.float_winnr = winnr set_close_keymaps(bufnr) set_split_open_keymap(bufnr, winnr, function() - local chan_id = vim.api.nvim_open_term(_window_state.latest_scratch_buf_id, {}) - vim.api.nvim_chan_send(chan_id, vim.trim(rendered_diagnostic)) + local latest_scratch_buf_id = _window_state.latest_scratch_buf_id + if latest_scratch_buf_id then + local chan_id = vim.api.nvim_open_term(latest_scratch_buf_id, {}) + vim.api.nvim_chan_send(chan_id, vim.trim(rendered_diagnostic)) + end end) if config.tools.float_win_config.auto_focus then vim.api.nvim_set_current_win(winnr) vim.api.nvim_feedkeys( vim.api.nvim_replace_termcodes( 'lua vim.api.nvim_set_current_win(' - .. winnr - .. ')' - .. [[]] - .. 'lua vim.api.nvim_win_set_cursor(' - .. winnr - .. ',{1,0})', + .. winnr + .. ')' + .. [[]] + .. 'lua vim.api.nvim_win_set_cursor(' + .. winnr + .. ',{1,0})', true, false, true @@ -309,13 +319,14 @@ end ---@param cycle_diagnostic (fun(opts?: vim.diagnostic.JumpOpts): vim.Diagnostic?) function M.render_diagnostic(cycle_diagnostic) local diagnostics = vim - .iter(vim.diagnostic.get(0, {})) - :filter( + .iter(vim.diagnostic.get(0, {})) + :filter( ---@param diagnostic vim.Diagnostic - function(diagnostic) - return get_rendered_diagnostic(diagnostic) ~= nil - end) - :totable() + function(diagnostic) + return get_rendered_diagnostic(diagnostic) ~= nil + end + ) + :totable() if #diagnostics == 0 then vim.notify('No renderable diagnostics found.', vim.log.levels.INFO) return @@ -376,7 +387,7 @@ local function get_diagnostics_current_line() local win_id = vim.api.nvim_get_current_win() local cursor_position = vim.api.nvim_win_get_cursor(win_id) return vim.diagnostic.get(0, { - lnum = cursor_position[1] - 1, + lnum = cursor_position[1] or 1 - 1, }) end @@ -393,21 +404,22 @@ function M.render_diagnostic_current_line() -- get rendered diagnostics from current line ---@type string[] local rendered_diagnostics = vim - .iter(get_diagnostics_current_line()) - :map( + .iter(get_diagnostics_current_line()) + :map( ---@param diagnostic vim.Diagnostic - function(diagnostic) - return get_rendered_diagnostic(diagnostic) - end) - :totable() + function(diagnostic) + return get_rendered_diagnostic(diagnostic) + end + ) + :totable() -- if no renderable diagnostics on current line - if #rendered_diagnostics == 0 then + local rendered_diagnostic = rendered_diagnostics[1] + if not rendered_diagnostic then vim.notify('No renderable diagnostics found.', vim.log.levels.INFO) return end - local rendered_diagnostic = rendered_diagnostics[1] render_ansi_code_diagnostic(rendered_diagnostic) end @@ -417,47 +429,52 @@ end function M.related_diagnostics() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end ---@type lsp.Location[] local locations = vim - .iter(get_diagnostics_at_cursor()) - :map( + .iter(get_diagnostics_at_cursor()) + :map( ---@param diagnostic vim.Diagnostic - function(diagnostic) - return vim.tbl_get(diagnostic, 'user_data', 'lsp', 'relatedInformation') - end) - :flatten() - ---@param related_info rustaceanvim.diagnostic.RelatedInfo - :map(function(related_info) - return related_info.location - end) - :totable() + function(diagnostic) + return vim.tbl_get(diagnostic, 'user_data', 'lsp', 'relatedInformation') + end + ) + :flatten() + ---@param related_info rustaceanvim.diagnostic.RelatedInfo + :map(function(related_info) + return related_info.location + end) + :totable() if #locations == 0 then vim.notify('No related diagnostics found.', vim.log.levels.INFO) return end - local quickfix_entries = vim.lsp.util.locations_to_items(locations, clients[1].offset_encoding) + ---@type vim.quickfix.entry[] + local quickfix_entries = vim.lsp.util.locations_to_items(locations, client.offset_encoding) if #quickfix_entries == 1 then local item = quickfix_entries[1] - ---@diagnostic disable-next-line: undefined-field - local b = item.bufnr or vim.fn.bufadd(item.filename) - -- Save position in jumplist - vim.cmd.normal { "m'", bang = true } - -- Push a new item into tagstack - local tagstack = { { tagname = vim.fn.expand(''), from = vim.fn.getpos('.') } } - local current_window_id = vim.api.nvim_get_current_win() - vim.fn.settagstack(vim.fn.win_getid(current_window_id), { items = tagstack }, 't') - vim.bo[b].buflisted = true - local window_id = vim.fn.win_findbuf(b)[1] or current_window_id - vim.api.nvim_win_set_buf(window_id, b) - vim.api.nvim_win_set_cursor(window_id, { item.lnum, item.col - 1 }) - vim._with({ win = window_id }, function() - -- Open folds under the cursor - vim.cmd.normal { 'zv', bang = true } - end) + local bufnr = item.bufnr or item.filename and vim.fn.bufadd(item.filename) + if bufnr then + -- Save position in jumplist + vim.cmd.normal { "m'", bang = true } + -- Push a new item into tagstack + local tagstack = { { tagname = vim.fn.expand(''), from = vim.fn.getpos('.') } } + local current_window_id = vim.api.nvim_get_current_win() + vim.fn.settagstack(vim.fn.win_getid(current_window_id), { items = tagstack }, 't') + vim.bo[bufnr].buflisted = true + local window_id = vim.fn.win_findbuf(bufnr)[1] or current_window_id + vim.api.nvim_win_set_buf(window_id, bufnr) + local lnum = item.lnum or 0 + local col = item.col or 1 + vim.api.nvim_win_set_cursor(window_id, { lnum, col - 1 }) + vim._with({ win = window_id }, function() + -- Open folds under the cursor + vim.cmd.normal { 'zv', bang = true } + end) + end else vim.fn.setqflist({}, ' ', { title = 'related diagnostics', items = quickfix_entries }) vim.cmd([[ botright copen ]]) diff --git a/lua/rustaceanvim/commands/expand_macro.lua b/lua/rustaceanvim/commands/expand_macro.lua index cbb7536a..c4908bb4 100644 --- a/lua/rustaceanvim/commands/expand_macro.lua +++ b/lua/rustaceanvim/commands/expand_macro.lua @@ -68,11 +68,11 @@ end --- Sends the request to rust-analyzer to expand the macro under the cursor function M.expand_macro() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = vim.lsp.util.make_position_params(0, clients[1].offset_encoding or 'utf-8') + local params = vim.lsp.util.make_position_params(0, client.offset_encoding or 'utf-8') ra.buf_request(0, 'rust-analyzer/expandMacro', params, handler) end diff --git a/lua/rustaceanvim/commands/hover_range.lua b/lua/rustaceanvim/commands/hover_range.lua index 47ed85e0..7b02a713 100644 --- a/lua/rustaceanvim/commands/hover_range.lua +++ b/lua/rustaceanvim/commands/hover_range.lua @@ -5,7 +5,7 @@ local M = {} ---@class rustaceanvim.hover_range.State local _state = { - ---@type integer + ---@type integer | nil winnr = nil, } @@ -41,11 +41,11 @@ local function get_visual_selected_range() if not p1 then return nil end - local row1 = p1[2] - local col1 = p1[3] + local row1 = p1[2] or 0 + local col1 = p1[3] or 0 local p2 = vim.api.nvim_win_get_cursor(0) - local row2 = p2[1] - local col2 = p2[2] + local row2 = p2[1] or 0 + local col2 = p2[2] or 0 if row1 < row2 then return make_lsp_position(row1, col1, row2, col2) @@ -121,11 +121,11 @@ end function M.hover_range() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = vim.lsp.util.make_range_params(0, clients[1].offset_encoding or 'utf-8') + local params = vim.lsp.util.make_range_params(0, client.offset_encoding or 'utf-8') ---@diagnostic disable-next-line: inject-field params.position = get_visual_selected_range() params.range = nil diff --git a/lua/rustaceanvim/commands/init.lua b/lua/rustaceanvim/commands/init.lua index 1b6c4f0c..eaec2f3e 100644 --- a/lua/rustaceanvim/commands/init.lua +++ b/lua/rustaceanvim/commands/init.lua @@ -8,12 +8,14 @@ local M = {} local rust_lsp_cmd_name = 'RustLsp' local rustc_cmd_name = 'Rustc' ----@class rustaceanvim.command_tbl +---@class rustaceanvim.command_tbl_entry ---@field impl fun(args: string[], opts: vim.api.keyset.user_command) The command implementation ---@field complete? fun(subcmd_arg_lead: string): string[] Command completions callback, taking the lead of the subcommand's arguments ---@field bang? boolean Whether this command supports a bang! ----@type rustaceanvim.command_tbl[] +---@alias rustaceanvim.command_tbl table + +---@type rustaceanvim.command_tbl local rustlsp_command_tbl = { codeAction = { impl = function(_, opts) @@ -259,6 +261,7 @@ local rustlsp_command_tbl = { end, }, flyCheck = { + ---@param args rustaceanvim.flyCheckCommand[] impl = function(args) local cmd = args[1] or 'run' require('rustaceanvim.commands.fly_check')(cmd) @@ -271,7 +274,7 @@ local rustlsp_command_tbl = { }, view = { impl = function(args) - if not args or #args == 0 then + if not args or not args[1] then vim.notify("Expected argument: 'mir' or 'hir'", vim.log.levels.ERROR) return end @@ -300,16 +303,18 @@ local rustlsp_command_tbl = { }, } ----@type rustaceanvim.command_tbl[] +---@type rustaceanvim.command_tbl local rustc_command_tbl = { unpretty = { + ---@param args rustaceanvim.rustcir.level[] impl = function(args) local err_msg = table.concat(require('rustaceanvim.commands.rustc_unpretty').available_unpretty, ' | ') - if not args or #args == 0 then + if not args or not args[1] then vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) return end local arg = args[1]:lower() + ---@cast arg rustaceanvim.rustcir.level local available = false for _, value in ipairs(require('rustaceanvim.commands.rustc_unpretty').available_unpretty) do if value == arg then @@ -401,7 +406,7 @@ end --- Delete the `:RustLsp` command function M.delete_rust_lsp_command() - if vim.cmd[rust_lsp_cmd_name] then + if vim.cmd[rust_lsp_cmd_name] ~= nil then pcall(vim.api.nvim_del_user_command, rust_lsp_cmd_name) end end diff --git a/lua/rustaceanvim/commands/join_lines.lua b/lua/rustaceanvim/commands/join_lines.lua index 6cb8ab4c..d1441b1e 100644 --- a/lua/rustaceanvim/commands/join_lines.lua +++ b/lua/rustaceanvim/commands/join_lines.lua @@ -11,8 +11,15 @@ local function modify_params(params) return params end +---@param client_id integer +---@return string +local function offset_encoding(client_id) + local client = vim.lsp.get_client_by_id(client_id) + return client and client.offset_encoding or 'utf-8' +end + local function handler(_, result, ctx) - vim.lsp.util.apply_text_edits(result, ctx.bufnr, vim.lsp.get_client_by_id(ctx.client_id).offset_encoding or 'utf-8') + vim.lsp.util.apply_text_edits(result, ctx.bufnr, offset_encoding(ctx.client_id)) end local ra = require('rustaceanvim.rust_analyzer') @@ -20,22 +27,22 @@ local ra = require('rustaceanvim.rust_analyzer') --- Sends the request to rust-analyzer to get the TextEdits to join the lines --- under the cursor and applies them (for use in visual mode) function M.join_lines_visual() - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = modify_params(vim.lsp.util.make_given_range_params(nil, nil, 0, clients[1].offset_encoding or 'utf-8')) + local params = modify_params(vim.lsp.util.make_given_range_params(nil, nil, 0, client.offset_encoding or 'utf-8')) ra.buf_request(0, 'experimental/joinLines', params, handler) end --- Sends the request to rust-analyzer to get the TextEdits to join the lines --- under the cursor and applies them function M.join_lines() - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = modify_params(vim.lsp.util.make_range_params(0, clients[1].offset_encoding or 'utf-8')) + local params = modify_params(vim.lsp.util.make_range_params(0, client.offset_encoding or 'utf-8')) ra.buf_request(0, 'experimental/joinLines', params, handler) end diff --git a/lua/rustaceanvim/commands/move_item.lua b/lua/rustaceanvim/commands/move_item.lua index 8a44931d..c155a70f 100644 --- a/lua/rustaceanvim/commands/move_item.lua +++ b/lua/rustaceanvim/commands/move_item.lua @@ -10,12 +10,17 @@ local function text_edit_line_range_diff(prev_text_edit, text_edit) end ---@param text_edits rustaceanvim.lsp.TextEdit[] +---@return uinteger[] local function extract_cursor_position(text_edits) + if not text_edits[1] then + return {} + end + ---@type uinteger[] local cursor = { text_edits[1].range.start.line } local prev_text_edit for _, text_edit in ipairs(text_edits) do if text_edit.newText and text_edit.insertTextFormat == 2 and not cursor[2] then - cursor[1] = cursor[1] + (prev_text_edit and text_edit_line_range_diff(prev_text_edit, text_edit) or 0) + cursor[1] = cursor[1] or 0 + (prev_text_edit and text_edit_line_range_diff(prev_text_edit, text_edit) or 0) local snippet_pos_start = string.find(text_edit.newText, '%$0') local lines = vim.split(string.sub(text_edit.newText, 1, snippet_pos_start), '\n') local line_count = #lines @@ -31,6 +36,13 @@ local function extract_cursor_position(text_edits) return cursor end +---@param client_id integer +---@return string +local function offset_encoding(client_id) + local client = vim.lsp.get_client_by_id(client_id) + return client and client.offset_encoding or 'utf-8' +end + -- move it baby ---@param text_edits? rustaceanvim.lsp.TextEdit[] ---@param ctx lsp.HandlerContext @@ -41,11 +53,7 @@ local function handler(_, text_edits, ctx) local cursor = extract_cursor_position(text_edits) local overrides = require('rustaceanvim.overrides') overrides.snippet_text_edits_to_text_edits(text_edits) - vim.lsp.util.apply_text_edits( - text_edits, - ctx.bufnr, - vim.lsp.get_client_by_id(ctx.client_id).offset_encoding or 'utf-8' - ) + vim.lsp.util.apply_text_edits(text_edits, ctx.bufnr, offset_encoding(ctx.client_id)) vim.api.nvim_win_set_cursor(0, cursor) end @@ -53,11 +61,11 @@ end ---@param direction 'Up' | 'Down' function M.move_item(direction) local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = vim.lsp.util.make_range_params(0, clients[1].offset_encoding or 'utf-8') + local params = vim.lsp.util.make_range_params(0, client.offset_encoding or 'utf-8') ---@diagnostic disable-next-line: inject-field params.direction = direction ra.buf_request(0, 'experimental/moveItem', params, handler) diff --git a/lua/rustaceanvim/commands/parent_module.lua b/lua/rustaceanvim/commands/parent_module.lua index 8ba69793..96727ba9 100644 --- a/lua/rustaceanvim/commands/parent_module.lua +++ b/lua/rustaceanvim/commands/parent_module.lua @@ -21,11 +21,11 @@ end --- Sends the request to rust-analyzer to get the parent modules location and open it function M.parent_module() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = vim.lsp.util.make_position_params(0, clients[1].offset_encoding or 'utf-8') + local params = vim.lsp.util.make_position_params(0, client.offset_encoding or 'utf-8') ra.buf_request(0, 'experimental/parentModule', params, handler) end diff --git a/lua/rustaceanvim/commands/rebuild_proc_macros.lua b/lua/rustaceanvim/commands/rebuild_proc_macros.lua index cd79d2b0..b94e8992 100644 --- a/lua/rustaceanvim/commands/rebuild_proc_macros.lua +++ b/lua/rustaceanvim/commands/rebuild_proc_macros.lua @@ -1,9 +1,9 @@ local M = {} ----@param err string | nil +---@param err lsp.ResponseError? local function handler(err, _, _) if err then - vim.notify('Error rebuilding proc macros: ' .. err) + vim.notify('Error rebuilding proc macros: ' .. vim.inspect(err)) return end end diff --git a/lua/rustaceanvim/commands/rustc_unpretty.lua b/lua/rustaceanvim/commands/rustc_unpretty.lua index c2112535..4939fb5c 100644 --- a/lua/rustaceanvim/commands/rustc_unpretty.lua +++ b/lua/rustaceanvim/commands/rustc_unpretty.lua @@ -41,20 +41,20 @@ local latest_buf_id = nil ---@param buf integer|nil ---@return integer, integer, integer, integer local function get_vim_range(range, buf) - ---@type integer, integer, integer, integer + ---@type integer?, integer?, integer?, integer? local srow, scol, erow, ecol = unpack(range) - srow = srow + 1 - scol = scol + 1 - erow = erow + 1 + srow = srow or 0 + 1 + scol = scol or 0 + 1 + erow = erow or 0 + 1 - if ecol == 0 then + if not ecol or ecol == 0 then -- Use the value of the last col of the previous row instead. erow = erow - 1 if not buf or buf == 0 then ---@diagnostic disable-next-line: assign-type-mismatch ecol = vim.fn.col { erow, '$' } - 1 else - ecol = #vim.api.nvim_buf_get_lines(buf, erow - 1, erow, false)[1] + ecol = #api.nvim_buf_get_lines(buf, erow - 1, erow, false)[1] end ecol = math.max(ecol, 1) end @@ -65,7 +65,7 @@ end ---@param node TSNode local function get_rows(node) local start_row, _, end_row, _ = get_vim_range({ ts.get_node_range(node) }, 0) - return vim.api.nvim_buf_get_lines(0, start_row - 1, end_row, true) + return api.nvim_buf_get_lines(0, start_row - 1, end_row, true) end ---@param sc vim.SystemCompleted @@ -80,17 +80,17 @@ local function handler(sc) ui.delete_buf(latest_buf_id) -- create a new buffer - latest_buf_id = vim.api.nvim_create_buf(false, true) -- not listed and scratch + latest_buf_id = api.nvim_create_buf(false, true) -- not listed and scratch -- split the window to create a new buffer and set it to our window ui.split(true, latest_buf_id) - local lines = vim.split(sc.stdout, '\n') + local lines = vim.split(sc.stdout or '', '\n') -- set filetype to rust for syntax highlighting vim.bo[latest_buf_id].filetype = 'rust' -- write the expansion content to the buffer - vim.api.nvim_buf_set_lines(latest_buf_id, 0, 0, false, lines) + api.nvim_buf_set_lines(latest_buf_id, 0, 0, false, lines) end ---@return boolean @@ -113,15 +113,18 @@ function M.rustc_unpretty(level) local text local cursor = api.nvim_win_get_cursor(0) - local pos = { math.max(cursor[1] - 1, 0), cursor[2] } + local pos = { math.max(cursor[1] or 1 - 1, 0), cursor[2] } local cline = api.nvim_get_current_line() if not string.find(cline, 'fn%s+') then + ---@type integer[] local temp = vim.fn.searchpos('fn ', 'bcn', vim.fn.line('w0')) - pos = { math.max(temp[1] - 1, 0), temp[2] } + pos = { math.max(temp[1] or 0 - 1, 0), temp[2] or 0 } end - local node = ts.get_node { pos = pos } + ---@type vim.treesitter.get_node.Opts + local opts = { pos = pos } + local node = ts.get_node(opts) if node == nil or node:type() ~= 'function_item' then vim.notify('no function found or function is incomplete', vim.log.levels.ERROR) diff --git a/lua/rustaceanvim/commands/ssr.lua b/lua/rustaceanvim/commands/ssr.lua index 4eee1085..08362113 100644 --- a/lua/rustaceanvim/commands/ssr.lua +++ b/lua/rustaceanvim/commands/ssr.lua @@ -26,12 +26,13 @@ end ---@param query? string ---@param make_range_params fun(bufnr: integer, offset_encoding: string):{ range: table } local function ssr(query, make_range_params) - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = vim.lsp.util.make_position_params(0, clients[1].offset_encoding or 'utf-8') - local range = make_range_params(0, clients[1].offset_encoding or 'utf-8').range + local offset_encoding = client.offset_encoding or 'utf-8' + local params = vim.lsp.util.make_position_params(0, offset_encoding) + local range = make_range_params(0, offset_encoding).range if not query then vim.ui.input({ prompt = 'Enter query: ' }, function(input) query = input diff --git a/lua/rustaceanvim/commands/syntax_tree.lua b/lua/rustaceanvim/commands/syntax_tree.lua index 4c6948e6..b6067130 100644 --- a/lua/rustaceanvim/commands/syntax_tree.lua +++ b/lua/rustaceanvim/commands/syntax_tree.lua @@ -33,11 +33,11 @@ end function M.syntax_tree() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = vim.lsp.util.make_range_params(0, clients[1].offset_encoding or 'utf-8') + local params = vim.lsp.util.make_range_params(0, client.offset_encoding or 'utf-8') ra.buf_request(0, 'rust-analyzer/viewSyntaxTree', params, handler) end diff --git a/lua/rustaceanvim/commands/view_ir.lua b/lua/rustaceanvim/commands/view_ir.lua index 4f588a90..a8b077cf 100644 --- a/lua/rustaceanvim/commands/view_ir.lua +++ b/lua/rustaceanvim/commands/view_ir.lua @@ -41,12 +41,11 @@ end ---@param level rustaceanvim.ir.level function M.viewIR(level) - local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = rl.find_active_rustaceanvim_client() + if not client then return end - local position_params = vim.lsp.util.make_position_params(0, clients[1].offset_encoding or 'utf-8') + local position_params = vim.lsp.util.make_position_params(0, client.offset_encoding or 'utf-8') rl.buf_request(0, 'rust-analyzer/view' .. level, position_params, function(...) return handler(level, ...) end) diff --git a/lua/rustaceanvim/commands/workspace_symbol.lua b/lua/rustaceanvim/commands/workspace_symbol.lua index 286c535d..58439e89 100644 --- a/lua/rustaceanvim/commands/workspace_symbol.lua +++ b/lua/rustaceanvim/commands/workspace_symbol.lua @@ -34,7 +34,7 @@ local function query_from_input() end ---@param searchScope WorkspaceSymbolSearchScope ----@param args? unknown[] +---@param args? string[] function M.workspace_symbol(searchScope, args) local searchKind = default_search_kind local query @@ -45,17 +45,19 @@ function M.workspace_symbol(searchScope, args) end args = {} end - if #args > 0 and M.WorkspaceSymbolSearchKind[args[1]] then - searchKind = args[1] + local arg = args[1] + if arg and M.WorkspaceSymbolSearchKind[arg] then + searchKind = M.WorkspaceSymbolSearchKind[arg] table.remove(args, 1) end - if #args == 0 then - query = query_from_input() - if not query then - return - end - else + arg = args[1] + if arg then query = args[1] + else + query = query_from_input() + end + if not query or not searchKind then + return end rl.any_buf_request('workspace/symbol', get_params(searchScope, searchKind, query)) end diff --git a/lua/rustaceanvim/config/internal.lua b/lua/rustaceanvim/config/internal.lua index 708499b0..f4a5ffb0 100644 --- a/lua/rustaceanvim/config/internal.lua +++ b/lua/rustaceanvim/config/internal.lua @@ -4,11 +4,14 @@ local executors = require('rustaceanvim.executors') local os = require('rustaceanvim.os') local server_config = require('rustaceanvim.config.server') +---@type rustaceanvim.Config local RustaceanConfig local rustaceanvim = vim.g.rustaceanvim or {} local rustaceanvim_opts = type(rustaceanvim) == 'function' and rustaceanvim() or rustaceanvim +---@alias rustaceanvim.rust-analyzer.StartCmd string[] | fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient + ---Wrapper around |vim.fn.exepath()| that returns the binary if no path is found. ---@param binary string ---@return string the full path to the executable or `binary` if no path is found. @@ -23,7 +26,7 @@ end ---@field quiescent boolean inactive? ---@field message string | nil --- ----@param dap_adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | rustaceanvim.disable +---@param dap_adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | rustaceanvim.disable | fun():(rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | rustaceanvim.disable) ---@return boolean local function should_enable_dap_config_value(dap_adapter) local adapter = types.evaluate(dap_adapter) @@ -277,10 +280,11 @@ local RustaceanDefaultConfig = { end ---@cast cmd string[] local rs_bin = cmd[1] - return vim.fn.executable(rs_bin) == 1 + return rs_bin ~= nil and vim.fn.executable(rs_bin) == 1 end, - ---@type string[] | fun():(string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient) + ---@type string[] | fun():rustaceanvim.rust-analyzer.StartCmd cmd = function() + ---@diagnostic disable-next-line: return-type-mismatch return { exepath_or_binary('rust-analyzer'), '--log-file', RustaceanConfig.server.logfile } end, @@ -346,7 +350,9 @@ local RustaceanDefaultConfig = { else local has_mason, mason_registry = pcall(require, 'mason-registry') local ok, err = pcall(function() + ---@diagnostic disable-next-line: need-check-nil, unnecessary-if if has_mason and mason_registry.is_installed('codelldb') then + ---@diagnostic disable-next-line: need-check-nil local codelldb_package = mason_registry.get_package('codelldb') local mason_codelldb_path if require('mason.version').MAJOR_VERSION > 1 then @@ -411,6 +417,7 @@ mason.nvim threw an error while trying to detect codelldb: return false end local adapter = types.evaluate(RustaceanConfig.dap.adapter) + ---@diagnostic disable-next-line: cast-type-mismatch ---@cast adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | rustaceanvim.disable return adapter ~= false and is_lldb_adapter(adapter) end, @@ -421,6 +428,7 @@ mason.nvim threw an error while trying to detect codelldb: return false end local adapter = types.evaluate(RustaceanConfig.dap.adapter) + ---@diagnostic disable-next-line: cast-type-mismatch ---@cast adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | rustaceanvim.disable if adapter == false then return false @@ -439,8 +447,10 @@ for _, executor in pairs { 'executor', 'test_executor', 'crate_test_executor' } and rustaceanvim_opts.tools[executor] and type(rustaceanvim_opts.tools[executor]) == 'string' then + local executor_key = rustaceanvim_opts.tools[executor] rustaceanvim_opts.tools[executor] = - assert(executors[rustaceanvim_opts.tools[executor]], 'Unknown RustaceanExecutor') + ---@diagnostic disable-next-line: undefined-field + assert(executors[executor_key], 'Unknown rustaceanvim.executor_alias') end end diff --git a/lua/rustaceanvim/dap.lua b/lua/rustaceanvim/dap.lua index eeadcd56..a7b55e5d 100644 --- a/lua/rustaceanvim/dap.lua +++ b/lua/rustaceanvim/dap.lua @@ -39,7 +39,7 @@ end local ok, _ = pcall(require, 'dap') if not ok then return { - ---@param on_error fun(err:string) + ---@param on_error? fun(err:string) start = function(_, _, _, on_error) on_error = on_error or scheduled_error on_error('nvim-dap not found.') @@ -67,12 +67,12 @@ local function get_cargo_args_from_runnables_args(runnable_args) return cargo_args end ----@type string +---@type string | nil local rustc_commit_hash ---@param callback fun(rustc_commit_hash:string) local function get_rustc_commit_hash(callback) - if rustc_commit_hash then + if rustc_commit_hash ~= nil then return callback(rustc_commit_hash) end vim.system({ 'rustc', '--version', '--verbose' }, nil, function(sc) @@ -88,12 +88,12 @@ local function get_rustc_commit_hash(callback) end) end ----@type string +---@type string | nil local rustc_sysroot ---@param callback fun(rustc_sysroot:string) local function get_rustc_sysroot(callback) - if rustc_sysroot then + if rustc_sysroot ~= nil then return callback(rustc_sysroot) end vim.system({ 'rustc', '--print', 'sysroot' }, nil, function(sc) @@ -141,7 +141,7 @@ local source_maps = {} ---See https://github.com/vadimcn/codelldb/issues/204 ---@param workspace_root? string local function generate_source_map(workspace_root) - if not workspace_root or source_maps[workspace_root] then + if not workspace_root or source_maps[workspace_root] ~= nil then return end get_rustc_commit_hash(function(commit_hash) @@ -170,7 +170,7 @@ local init_commands = {} ---@param workspace_root? string local function get_lldb_commands(workspace_root) - if not workspace_root or init_commands[workspace_root] then + if not workspace_root or init_commands[workspace_root] ~= nil then return end get_rustc_sysroot(function(sysroot) @@ -179,7 +179,7 @@ local function get_lldb_commands(workspace_root) return end local script_import = 'command script import "' .. script .. '"' - local commands_file = vim.fs.joinpath(rustc_sysroot, 'lib', 'rustlib', 'etc', 'lldb_commands') + local commands_file = vim.fs.joinpath(sysroot, 'lib', 'rustlib', 'etc', 'lldb_commands') local file = io.open(commands_file, 'r') local workspace_root_cmds = {} if file then @@ -198,7 +198,7 @@ end ---@param key string ---@param segments string[] ---@param sep string ----@return {[string]: string} | string[] +---@return table local function format_environment_variable(adapter, key, segments, sep) ---@diagnostic disable-next-line: missing-parameter local existing = vim.uv.os_getenv(key) @@ -207,14 +207,14 @@ local function format_environment_variable(adapter, key, segments, sep) return adapter.type == 'server' and { [key] = value } or { key .. '=' .. value } end ----@type {[string]: rustaceanvim.EnvironmentMap} +---@type table local environments = {} -- Most succinct description: https://github.com/bevyengine/bevy/issues/2589#issuecomment-1753413600 ---@param adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config ---@param workspace_root string | nil local function add_dynamic_library_paths(adapter, workspace_root) - if not workspace_root or environments[workspace_root] then + if not workspace_root or environments[workspace_root] ~= nil then return end vim.system({ 'rustc', '--print', 'target-libdir' }, { cwd = workspace_root }, function(sc) @@ -295,6 +295,7 @@ function M.start(args, verbose, callback, on_error) callback = dap.run end local adapter = types.evaluate(config.dap.adapter) + ---@diagnostic disable-next-line: cast-type-mismatch --- @cast adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | rustaceanvim.disable if adapter == false then on_error('Debug adapter disabled or not found.') @@ -375,18 +376,23 @@ function M.start(args, verbose, callback, on_error) local _, dap_config = next(dap.configurations.rust or {}) local local_config = types.evaluate(config.dap.configuration) + ---@diagnostic disable-next-line: cast-type-mismatch --- @cast local_config rustaceanvim.dap.client.Config | boolean ---@diagnostic disable-next-line: param-type-mismatch - local final_config = local_config ~= false and vim.deepcopy(local_config) or vim.deepcopy(dap_config) - --- @cast final_config rustaceanvim.dap.client.Config + local final_config = local_config ~= false + ---@cast local_config rustaceanvim.dap.client.Config + ---@diagnostic disable-next-line: generic-constraint-mismatch + and vim.deepcopy(local_config) + or vim.deepcopy(dap_config or {}) + ---@cast final_config rustaceanvim.dap.client.Config local err - ok, err = pcall(vim.validate, { - type = { final_config.type, 'string' }, - name = { final_config.name, 'string' }, - request = { final_config.request, 'string' }, - }) + ok, err = pcall(function() + vim.validate('rustaceanvim.dap.client.Config.type', final_config.type, 'string') + vim.validate('rustaceanvim.dap.client.Config.name', final_config.name, 'string') + vim.validate('rustaceanvim.dap.client.Config.request', final_config.request, 'string') + end) if not ok then on_error(([[ DAP client config validation failed. @@ -407,6 +413,7 @@ If you have specified a custom configuration, see ":h rustaceanvim.dap.client.Co final_config.args = args.executableArgs or {} local environment = args.workspaceRoot and environments[args.workspaceRoot] final_config = next(environment or {}) ~= nil + ---@diagnostic disable-next-line: param-type-not-match and vim.tbl_deep_extend('force', final_config, { env = environment }) or final_config @@ -414,16 +421,19 @@ If you have specified a custom configuration, see ":h rustaceanvim.dap.client.Co -- lldb specific entries final_config = args.workspaceRoot and next(init_commands or {}) ~= nil + ---@diagnostic disable-next-line: param-type-not-match and vim.tbl_deep_extend('force', final_config, { initCommands = init_commands[args.workspaceRoot] }) or final_config local source_map = args.workspaceRoot and source_maps[args.workspaceRoot] final_config = source_map and next(source_map or {}) ~= nil + ---@diagnostic disable-next-line: param-type-not-match and vim.tbl_deep_extend('force', final_config, { sourceMap = format_source_map(adapter, source_map) }) or final_config elseif string.find(final_config.type, 'probe%-rs') ~= nil then -- probe-rs specific entries + final_config.coreConfigs = final_config.coreConfigs or {} final_config.coreConfigs[1].programBinary = final_config.program end diff --git a/lua/rustaceanvim/executors/background.lua b/lua/rustaceanvim/executors/background.lua index 30e3a7b1..3d400007 100644 --- a/lua/rustaceanvim/executors/background.lua +++ b/lua/rustaceanvim/executors/background.lua @@ -31,12 +31,7 @@ end M.execute_command = function(command, args, cwd, opts) ---@type rustaceanvim.ExecutorOpts opts = vim.tbl_deep_extend('force', { bufnr = 0 }, opts or {}) - if vim.fn.has('nvim-0.10.0') ~= 1 then - vim.schedule(function() - vim.notify_once("the 'background' executor is not recommended for Neovim < 0.10.", vim.log.levels.WARN) - end) - return - end + ---@cast opts.bufnr integer vim.diagnostic.reset(diag_namespace, opts.bufnr) local is_single_test = args[1] == 'test' diff --git a/lua/rustaceanvim/executors/neotest.lua b/lua/rustaceanvim/executors/neotest.lua index e71a6a9b..8781c951 100644 --- a/lua/rustaceanvim/executors/neotest.lua +++ b/lua/rustaceanvim/executors/neotest.lua @@ -8,6 +8,7 @@ local M = {} M.execute_command = function(_, _, _, opts) ---@type rustaceanvim.TestExecutor.Opts opts = vim.tbl_deep_extend('force', { bufnr = 0 }, opts or {}) + ---@cast opts.bufnr integer if type(opts.runnable) ~= 'table' then vim.notify('rustaceanvim neotest executor called without a runnable. This is a bug!', vim.log.levels.ERROR) end diff --git a/lua/rustaceanvim/executors/quickfix.lua b/lua/rustaceanvim/executors/quickfix.lua index 674792dc..d2fbaddc 100644 --- a/lua/rustaceanvim/executors/quickfix.lua +++ b/lua/rustaceanvim/executors/quickfix.lua @@ -21,6 +21,8 @@ end ---@type rustaceanvim.Executor local M = { execute_command = function(command, args, cwd, opts) + opts = opts or {} + -- open quickfix copen() -- go back to the previous window diff --git a/lua/rustaceanvim/executors/termopen.lua b/lua/rustaceanvim/executors/termopen.lua index af10c4aa..1b3e6781 100644 --- a/lua/rustaceanvim/executors/termopen.lua +++ b/lua/rustaceanvim/executors/termopen.lua @@ -4,6 +4,7 @@ local latest_buf_id = nil ---@type rustaceanvim.Executor local M = { execute_command = function(command, args, cwd, opts) + opts = opts or {} local shell = require('rustaceanvim.shell') local ui = require('rustaceanvim.ui') local commands = {} diff --git a/lua/rustaceanvim/executors/toggleterm.lua b/lua/rustaceanvim/executors/toggleterm.lua index b65e362a..0e694945 100644 --- a/lua/rustaceanvim/executors/toggleterm.lua +++ b/lua/rustaceanvim/executors/toggleterm.lua @@ -1,6 +1,7 @@ ---@type rustaceanvim.Executor local M = { execute_command = function(command, args, cwd, opts) + opts = opts or {} local ok, term = pcall(require, 'toggleterm.terminal') if not ok then vim.schedule(function() @@ -10,6 +11,7 @@ local M = { end local shell = require('rustaceanvim.shell') + ---@diagnostic disable-next-line: need-check-nil term.Terminal :new({ dir = cwd, diff --git a/lua/rustaceanvim/executors/vimux.lua b/lua/rustaceanvim/executors/vimux.lua index 73462015..90be9cc4 100644 --- a/lua/rustaceanvim/executors/vimux.lua +++ b/lua/rustaceanvim/executors/vimux.lua @@ -3,6 +3,7 @@ local shell = require('rustaceanvim.shell') ---@type rustaceanvim.Executor local M = { execute_command = function(command, args, cwd, opts) + opts = opts or {} local envs = '' for k, v in pairs(opts.env) do envs = envs .. k .. "='" .. v .. "' " diff --git a/lua/rustaceanvim/health.lua b/lua/rustaceanvim/health.lua index c3a8d4e6..f0e47b53 100644 --- a/lua/rustaceanvim/health.lua +++ b/lua/rustaceanvim/health.lua @@ -72,7 +72,7 @@ local check_installed = function(dep) end if is_executable(binary) then local handle = io.popen(binary .. ' --version') - if handle then + if handle ~= nil then local binary_version, error_msg = handle:read('*a') handle:close() if error_msg then @@ -90,7 +90,7 @@ local check_installed = function(dep) return false, binary, 'Unable to determine version.' end end - return false, binaries[1], 'Could not find an executable binary.' + return false, binaries[1] or 'undefined', 'Could not find an executable binary.' end ---@param dep rustaceanvim.ExternalDependency @@ -195,6 +195,7 @@ function health.check() h.start('Checking external dependencies') local adapter = types.evaluate(config.dap.adapter) + ---@diagnostic disable-next-line: cast-type-mismatch ---@cast adapter rustaceanvim.dap.executable.Config | rustaceanvim.dap.server.Config | boolean ---@return string @@ -204,7 +205,7 @@ function health.check() return default end local cmd = types.evaluate(config.server.cmd) - if not cmd or #cmd == 0 then + if not cmd or not cmd[1] then return default end return cmd[1] diff --git a/lua/rustaceanvim/hover_actions.lua b/lua/rustaceanvim/hover_actions.lua index de8a95b4..cf205612 100644 --- a/lua/rustaceanvim/hover_actions.lua +++ b/lua/rustaceanvim/hover_actions.lua @@ -5,7 +5,7 @@ local M = {} ---@class rustaceanvim.hover_actions.State local _state = { - ---@type integer + ---@type integer | nil winnr = nil, ---@type unknown commands = nil, @@ -18,7 +18,7 @@ end local function execute_rust_analyzer_command(action, ctx) local fn = vim.lsp.commands[action.command] - if fn then + if type(fn) == 'function' then fn(action, ctx) end end @@ -126,7 +126,7 @@ function M.handler(_, result, ctx) -- run the command under the cursor vim.keymap.set('n', '', function() - local line = vim.api.nvim_win_get_cursor(winnr)[1] + local line = vim.api.nvim_win_get_cursor(winnr)[1] ---@as integer run_command(ctx, line) end, { buffer = bufnr, noremap = true, silent = true }) vim.keymap.set('n', 'RustHoverAction', function() @@ -138,11 +138,11 @@ end --- Sends the request to rust-analyzer to get hover actions and handle it function M.hover_actions() local ra = require('rustaceanvim.rust_analyzer') - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end - local params = lsp_util.make_position_params(0, clients[1].offset_encoding or 'utf-8') + local params = lsp_util.make_position_params(0, client.offset_encoding or 'utf-8') ra.buf_request(0, 'textDocument/hover', params, M.handler) end diff --git a/lua/rustaceanvim/lsp/init.lua b/lua/rustaceanvim/lsp/init.lua index 7f64befe..7c122544 100644 --- a/lua/rustaceanvim/lsp/init.lua +++ b/lua/rustaceanvim/lsp/init.lua @@ -47,7 +47,7 @@ local function find_vscode_settings(bufname) if vim.tbl_isempty(found_dirs) then return settings end - local vscode_dir = found_dirs[1] + local vscode_dir = found_dirs[1] ---@as string local results = vim.fn.glob(vim.fs.joinpath(vscode_dir, 'settings.json'), true, true) if vim.tbl_isempty(results) then return settings @@ -152,7 +152,7 @@ end ---@field name string ---@field filetypes string[] ---@field capabilities table ----@field handlers lsp.Handler[] +---@field handlers table ---@field on_init function ---@field on_attach function ---@field on_exit function @@ -163,9 +163,13 @@ end M.start = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() local bufname = vim.api.nvim_buf_get_name(bufnr) - local ra_config = vim.lsp.config[ra_client_name] or {} + + ---@type vim.lsp.ClientConfig? + ---@diagnostic disable-next-line: undefined-field + local ra_config = vim.lsp.config[ra_client_name] + -- NOTE: We deep copy to prevent shared state between rust-analyzer clients - local client_config = vim.tbl_deep_extend('force', vim.deepcopy(config.server), ra_config) + local client_config = vim.tbl_deep_extend('force', vim.deepcopy(config.server), ra_config or {}) ---@type rustaceanvim.lsp.StartConfig local lsp_start_config = vim.tbl_deep_extend('force', {}, client_config) cargo.get_config_root_dir(client_config, bufname, function(root_dir) @@ -240,12 +244,13 @@ Starting rust-analyzer client in detached/standalone mode (with reduced function -- end if type(rust_analyzer_cmd) == 'table' then - if #rust_analyzer_cmd == 0 then + local rust_analyzer_bin = rust_analyzer_cmd[1] + if not rust_analyzer_bin then vim.notify('rust-analyzer command is not set!', vim.log.levels.ERROR) return end - if vim.fn.executable(rust_analyzer_cmd[1]) ~= 1 then - vim.notify(('%s is not executable'):format(rust_analyzer_cmd[1]), vim.log.levels.ERROR) + if vim.fn.executable(rust_analyzer_bin) ~= 1 then + vim.notify(('%s is not executable'):format(rust_analyzer_bin), vim.log.levels.ERROR) return end end @@ -257,7 +262,8 @@ Starting rust-analyzer client in detached/standalone mode (with reduced function local custom_handlers = {} custom_handlers['experimental/serverStatus'] = server_status.handler - lsp_start_config.handlers = vim.tbl_deep_extend('force', custom_handlers, lsp_start_config.handlers or {}) + local handlers = vim.tbl_deep_extend('force', custom_handlers, lsp_start_config.handlers or {}) + lsp_start_config.handlers = handlers local commands = require('rustaceanvim.commands') local old_on_init = lsp_start_config.on_init @@ -322,11 +328,12 @@ M.stop = function(bufnr, filter) for _, client in ipairs(clients) do server_status.reset_client_state(client.id) end + return clients else ---@cast clients vim.lsp.Client server_status.reset_client_state(clients.id) + return { clients } end - return clients end ---Restart the LSP client. @@ -363,11 +370,12 @@ M.set_target_arch = function(bufnr, target) restart(bufnr, { exclude_rustc_target = target }, function(client) rustc.with_rustc_target_architectures(function(rustc_targets) if rustc_targets[target] then - local ra = client.config.settings['rust-analyzer'] or {} + local settings = client.config.settings + local ra = settings and settings['rust-analyzer'] or {} ---@diagnostic disable-next-line: inject-field ra.cargo = ra.cargo or {} ra.cargo.target = target - client:notify('workspace/didChangeConfiguration', { settings = client.config.settings }) + client:notify('workspace/didChangeConfiguration', { settings = settings }) return else vim.schedule(function() @@ -422,14 +430,14 @@ local function rust_analyzer_user_cmd(opts) M.set_target_arch(nil, target_arch) elseif cmd == RustAnalyzerCmd.config then local ra_settings_str = vim.iter(fargs):join(' ') - ---@diagnostic disable-next-line: param-type-mismatch - local f = load('return ' .. ra_settings_str) - ---@diagnostic disable-next-line: param-type-mismatch + local f = load('return ' .. ra_settings_str) ---@as fun() local ok, ra_settings = pcall(f) - if not ok or type(ra_settings) ~= 'table' then + if ok and type(ra_settings) == 'table' then + ---@cast ra_settings table + M.set_config(ra_settings) + else return vim.notify('RustAnalyzer config: invalid Lua table.\n' .. ra_settings_str, vim.log.levels.ERROR) end - M.set_config(ra_settings) end end @@ -441,9 +449,12 @@ vim.api.nvim_create_user_command('RustAnalyzer', rust_analyzer_user_cmd, { ---@type RustAnalyzerCmd[] local commands = #clients == 0 and { 'start' } or { 'stop', 'restart', 'reloadSettings', 'target', 'config' } if cmdline:match('^RustAnalyzer%s+%w*$') then - return vim.tbl_filter(function(command) - return command:find(arg_lead) ~= nil - end, commands) + return vim + .iter(commands) + :filter(function(command) + return command:find(arg_lead) ~= nil + end) + :totable() end end, }) diff --git a/lua/rustaceanvim/neotest/init.lua b/lua/rustaceanvim/neotest/init.lua index f39c5af8..a47a5cb8 100644 --- a/lua/rustaceanvim/neotest/init.lua +++ b/lua/rustaceanvim/neotest/init.lua @@ -119,15 +119,15 @@ NeotestAdapter.discover_positions = function(file_path) for runnable in vim .iter(runnables) - ---@param runnable rustaceanvim.RARunnable - :filter(function(runnable) + ---@param r rustaceanvim.RARunnable + :filter(function(r) -- Exclude snapshot tests that overwrite snapshots - return runnable.label:match('Update%sTests') == nil + return r.label:match('Update%sTests') == nil end) do local pos = trans.runnable_to_position(file_path, runnable) if pos then - max_end_row = math.max(max_end_row, pos.range[3]) + max_end_row = math.max(max_end_row, pos.range[3] or 0) if pos.type ~= 'dir' then table.insert(positions, pos) end @@ -136,29 +136,29 @@ NeotestAdapter.discover_positions = function(file_path) ---@diagnostic disable-next-line: cast-type-mismatch ---@cast runnables rustaceanvim.RARunnable[] - ---@type { [string]: neotest.Position } + ---@type table local tests_by_name = {} ---@type table - local namespaces = {} + local namespaces_tbl = {} for _, pos in pairs(positions) do if pos.type == 'test' then tests_by_name[pos.name] = pos elseif pos.type == 'namespace' then - namespaces[pos.id] = pos + namespaces_tbl[pos.id] = pos end end - namespaces = vim.tbl_values(namespaces) - ---@cast namespaces rustaceanvim.neotest.Position[] + ---@type rustaceanvim.neotest.Position[] + local namespaces_list = vim.tbl_values(namespaces_tbl) -- sort namespaces by name from longest to shortest - table.sort(namespaces, function(a, b) + table.sort(namespaces_list, function(a, b) return #a.name > #b.name end) ---@type { [string]: rustaceanvim.neotest.Position[] } local positions_by_namespace = {} -- group tests by their longest matching namespace - for _, namespace in ipairs(namespaces) do + for _, namespace in ipairs(namespaces_list) do if namespace.name ~= '' then ---@type string[] local child_keys = vim.tbl_filter(function(name) @@ -177,11 +177,11 @@ NeotestAdapter.discover_positions = function(file_path) end -- nest child namespaces in their parent namespace - for i, namespace in ipairs(namespaces) do + for i, namespace in ipairs(namespaces_list) do ---@type rustaceanvim.neotest.Position? local parent = nil -- search remaning namespaces for the longest matching parent namespace - for _, other_namespace in ipairs { unpack(namespaces, i + 1) } do + for _, other_namespace in ipairs { unpack(namespaces_list, i + 1) } do if vim.startswith(namespace.name, other_namespace.name .. '::') then parent = other_namespace break @@ -191,6 +191,7 @@ NeotestAdapter.discover_positions = function(file_path) local namespace_name = namespace.name local children = positions_by_namespace[namespace_name] -- strip parent namespace + "::" + children[1] = children[1] or {} children[1].name = children[1].name:sub(#parent.name + 3, #namespace_name) table.insert(positions_by_namespace[parent.name], children) positions_by_namespace[namespace_name] = nil @@ -234,7 +235,7 @@ NeotestAdapter.discover_positions = function(file_path) path = file_path, range = { 0, 0, max_end_row, 0 }, -- use the shortest namespace for the file runnable - runnable = #namespaces > 0 and namespaces[#namespaces].runnable or nil, + runnable = namespaces_list[#namespaces_list] and namespaces_list[#namespaces_list].runnable or nil, } table.insert(sorted_positions, 1, file_pos) @@ -260,6 +261,7 @@ end ---@param run_args neotest.RunArgs ---@return neotest.RunSpec|nil ---@private +---@async function NeotestAdapter.build_spec(run_args) local supported_types = { 'test', 'namespace', 'file', 'dir' } local tree = run_args and run_args.tree @@ -295,11 +297,13 @@ function NeotestAdapter.build_spec(run_args) future.set_error(err) end) local ok, strategy = pcall(future.wait) + ---@type neotest.RunSpec? local run_spec if not ok then ---@cast strategy string lib.notify(strategy, vim.log.levels.ERROR) run_spec = { + command = {}, cwd = cwd, context = context, } @@ -351,8 +355,8 @@ end function NeotestAdapter.results(spec, strategy_result) ---@type table local results = {} - ---@type rustaceanvim.neotest.RunContext local context = spec.context + ---@cast context rustaceanvim.neotest.RunContext local ctx_pos_id = context.pos_id ---@type string if strategy_result.code == 0 then @@ -365,14 +369,17 @@ function NeotestAdapter.results(spec, strategy_result) ---@type rustaceanvim.Diagnostic[] local diagnostics + ---@type string | nil local output_content = '' + ---@type string | nil local junit_xml = '' if context.is_cargo_test then local success success, output_content = pcall(function() return lib.files.read(strategy_result.output) end) - if not success then + ---@cast output_content string | nil + if not success or not output_content then vim.notify('Failed to read output file', vim.log.levels.ERROR) return results end @@ -384,13 +391,17 @@ function NeotestAdapter.results(spec, strategy_result) vim.fs.joinpath(context.workspace_root or vim.fn.getcwd(), 'target', 'nextest', 'rustaceanvim', 'junit.xml') ) end) - if not success then + ---@cast junit_xml string | nil + if not success or not junit_xml then vim.notify('Failed to read junit.xml file', vim.log.levels.ERROR) return results end diagnostics = require('rustaceanvim.test').parse_nextest_diagnostics(junit_xml, 0) end + ---@cast output_content string + ---@cast junit_xml string + if not vim.tbl_contains({ 'file', 'test', 'namespace' }, context.type) then return results end diff --git a/lua/rustaceanvim/neotest/trans.lua b/lua/rustaceanvim/neotest/trans.lua index b3fddd0c..abbdf140 100644 --- a/lua/rustaceanvim/neotest/trans.lua +++ b/lua/rustaceanvim/neotest/trans.lua @@ -7,9 +7,8 @@ local function get_test_path(runnable) return #executableArgs > 0 and executableArgs[1] or nil end ----@overload fun(file_path: string, test_path: string | nil) ----@overload fun(file_path: string, runnable: rustaceanvim.RARunnable) ----@return string +---@overload fun(file_path: string, test_path: string | nil):string +---@overload fun(file_path: string, runnable: rustaceanvim.RARunnable):string function M.get_position_id(file_path, runnable) local test_path = runnable if type(runnable) == 'table' then diff --git a/lua/rustaceanvim/overrides.lua b/lua/rustaceanvim/overrides.lua index 20c12637..848dd98a 100644 --- a/lua/rustaceanvim/overrides.lua +++ b/lua/rustaceanvim/overrides.lua @@ -98,10 +98,10 @@ function M.maybe_nextest_transform(args) } local indexes_to_remove_reverse_order = {} for i, arg in ipairs(executable_args) do - if nextest_unsupported_flags[arg] then + if nextest_unsupported_flags[arg] ~= nil then table.insert(indexes_to_remove_reverse_order, 1, i) end - if move_to_nextest_args_flags[arg] then + if move_to_nextest_args_flags[arg] ~= nil then table.insert(indexes_to_remove_reverse_order, 1, i) table.insert(nextest_args, arg) end diff --git a/lua/rustaceanvim/runnables.lua b/lua/rustaceanvim/runnables.lua index 75c6c075..dd4b5ac5 100644 --- a/lua/rustaceanvim/runnables.lua +++ b/lua/rustaceanvim/runnables.lua @@ -77,7 +77,7 @@ function M.get_command(runnable) local dir = args.workspaceRoot local env = args.environment - local ret = vim.list_extend({}, args.cargoArgs or {}) + local ret = vim.list_extend({}, args.cargoArgs or {}) ---@as string[] ret = vim.list_extend(ret, args.cargoExtraArgs or {}) table.insert(ret, '--') ret = vim.list_extend(ret, args.executableArgs or {}) @@ -99,6 +99,7 @@ end ---@return string|nil dir ---@return table | nil env local function getCommand(choice, runnables) + ---@diagnostic disable-next-line: param-type-not-match, return-type-mismatch return M.get_command(runnables[choice]) end @@ -198,15 +199,15 @@ local function is_within_range(position, targetRange) return targetRange.start.line <= position.line and targetRange['end'].line >= position.line end ----@param runnables rustaceanvim.RARunnable +---@param runnables rustaceanvim.RARunnable[] ---@return integer | nil choice function M.get_runnable_at_cursor_position(runnables) - local clients = ra.get_active_rustaceanvim_clients(0) - if #clients == 0 then + local client = ra.find_active_rustaceanvim_client() + if not client then return end ---@type lsp.Position - local position = vim.lsp.util.make_position_params(0, clients[1].offset_encoding or 'utf-8').position + local position = vim.lsp.util.make_position_params(0, client.offset_encoding or 'utf-8').position ---@type integer|nil, integer|nil local choice, fallback for idx, runnable in ipairs(runnables) do @@ -239,7 +240,8 @@ local function mk_cursor_position_handler(executableArgsOverride) end M.run_command(choice, runnables) local cached_commands = require('rustaceanvim.cached_commands') - if is_testable(runnables[choice]) then + local selected = runnables[choice] + if selected and is_testable(selected) then cached_commands.set_last_testable(choice, runnables) end cached_commands.set_last_runnable(choice, runnables) diff --git a/lua/rustaceanvim/rust_analyzer.lua b/lua/rustaceanvim/rust_analyzer.lua index 19ec6300..fe0f8269 100644 --- a/lua/rustaceanvim/rust_analyzer.lua +++ b/lua/rustaceanvim/rust_analyzer.lua @@ -9,7 +9,8 @@ local M = {} M.load_os_rustc_target = function() vim.system({ 'rustc', '-Vv' }, { text = true }, function(result) if result.code == 0 then - for line in result.stdout:gmatch('[^\r\n]+') do + local stdout = result.stdout or '' + for line in stdout:gmatch('[^\r\n]+') do local host = line:match('^host:%s*(.+)$') if host then M.os_rustc_target = host @@ -22,8 +23,11 @@ end ---@class rustaceanvim.lsp.get_clients.Filter: vim.lsp.get_clients.Filter ---@field exclude_rustc_target? string Cargo target triple (e.g., 'x86_64-unknown-linux-gnu') to filter rust-analyzer clients +--- +--- Only return clients supporting the given method +--- @field method? string ----@param bufnr number | nil 0 for the current buffer, `nil` for no buffer filter +---@param bufnr integer | nil 0 for the current buffer, `nil` for no buffer filter ---@param filter? rustaceanvim.lsp.get_clients.Filter ---@return vim.lsp.Client[] M.get_active_rustaceanvim_clients = function(bufnr, filter) @@ -48,7 +52,7 @@ M.get_active_rustaceanvim_clients = function(bufnr, filter) return clients end ----@param bufnr number | nil 0 for the current buffer, `nil` for no buffer filter +---@param bufnr integer | nil 0 for the current buffer, `nil` for no buffer filter ---@param filter? rustaceanvim.lsp.get_clients.Filter ---@return vim.lsp.Client | nil M.find_active_rustaceanvim_client = function(bufnr, filter) @@ -66,7 +70,10 @@ M.any_buf_request = function(method, params, handler) return end -- No buffer found. Try any client. - for _, client in ipairs(M.get_active_rustaceanvim_clients(nil, { method = method })) do + ---@type rustaceanvim.lsp.get_clients.Filter + local filter = { method = method } + for _, client in ipairs(M.get_active_rustaceanvim_clients(nil, filter)) do + ---@diagnostic disable-next-line: param-type-not-match client:request(method, params, handler, 0) end end @@ -82,7 +89,10 @@ M.buf_request = function(bufnr, method, params, handler) bufnr = vim.api.nvim_get_current_buf() end local client_found = false - for _, client in ipairs(M.get_active_rustaceanvim_clients(bufnr, { method = method })) do + ---@type rustaceanvim.lsp.get_clients.Filter + local filter = { method = method } + for _, client in ipairs(M.get_active_rustaceanvim_clients(bufnr, filter)) do + ---@diagnostic disable-next-line: param-type-not-match client:request(method, params, handler, 0) client_found = true end @@ -93,7 +103,9 @@ end ---@param method string LSP method name ---@return vim.lsp.Client|nil M.get_client_for_file = function(file_path, method) - for _, client in ipairs(M.get_active_rustaceanvim_clients(nil, { method = method })) do + ---@type rustaceanvim.lsp.get_clients.Filter + local filter = { method = method } + for _, client in ipairs(M.get_active_rustaceanvim_clients(nil, filter)) do local root_dir = client.config.root_dir if root_dir and vim.startswith(os.normalize_path_on_windows(file_path), root_dir) then return client @@ -105,7 +117,10 @@ end ---@param params table|nil Parameters to send to the server M.notify = function(method, params) local client_found = false - for _, client in ipairs(M.get_active_rustaceanvim_clients(0, { method = method })) do + ---@type rustaceanvim.lsp.get_clients.Filter + local filter = { method = method } + for _, client in ipairs(M.get_active_rustaceanvim_clients(0, filter)) do + ---@diagnostic disable-next-line: param-type-not-match client:notify(method, params) client_found = true end diff --git a/lua/rustaceanvim/rustc.lua b/lua/rustaceanvim/rustc.lua index f9c43e3f..1d31df8d 100644 --- a/lua/rustaceanvim/rustc.lua +++ b/lua/rustaceanvim/rustc.lua @@ -5,13 +5,14 @@ local M = {} M.DEFAULT_RUSTC_TARGET = 'OS' ---Local rustc targets cache +---@type string[] | nil local rustc_targets_cache = nil ---Handles retrieving rustc target architectures and running the passed in callback ---to perform certain actions using the retrieved targets. ---@param callback fun(targets: string[]) M.with_rustc_target_architectures = function(callback) - if rustc_targets_cache then + if rustc_targets_cache ~= nil then return callback(rustc_targets_cache) end vim.system( @@ -22,7 +23,8 @@ M.with_rustc_target_architectures = function(callback) if result.code ~= 0 then error('Failed to retrieve rustc targets: ' .. result.stderr) end - rustc_targets_cache = vim.iter(result.stdout:gmatch('[^\r\n]+')):fold( + local stdout = result.stdout or '' + rustc_targets_cache = vim.iter(stdout:gmatch('[^\r\n]+')):fold( {}, ---@param acc table ---@param target string diff --git a/lua/rustaceanvim/test.lua b/lua/rustaceanvim/test.lua index 3c24e375..61290e64 100644 --- a/lua/rustaceanvim/test.lua +++ b/lua/rustaceanvim/test.lua @@ -31,39 +31,46 @@ function M.parse_cargo_test_diagnostics(output, bufnr) for failure_content, test_id, lnum, col in output:gmatch("(thread '([^']+)' panicked at [^:]+:(%d+):(%d+):%s-\n.-\n)\n") do - local diagnostic_lnum = tonumber(lnum) - 1 - local diagnostic_col = tonumber(col) or 0 - table.insert(diagnostics, { - bufnr = bufnr, - test_id = test_id, - lnum = diagnostic_lnum, - end_lnum = diagnostic_lnum, - col = diagnostic_col, - end_col = diagnostic_col, - message = remove_ansi_codes(unescape_html(failure_content)), - source = 'rustaceanvim', - severity = vim.diagnostic.severity.ERROR, - }) - end - - if #diagnostics == 0 then - --- Fall back to old format - for test_id, message, _, lnum, col in output:gmatch("thread '([^']+)' panicked at '([^']+)', ([^:]+):(%d+):(%d+)") do - local diagnostic_lnum = tonumber(lnum) - 1 + local panic_lnum = tonumber(lnum) ---@as integer? + if panic_lnum then + local diagnostic_lnum = panic_lnum - 1 local diagnostic_col = tonumber(col) or 0 - ---@type rustaceanvim.Diagnostic - local diagnostic = { + table.insert(diagnostics, { bufnr = bufnr, test_id = test_id, lnum = diagnostic_lnum, end_lnum = diagnostic_lnum, col = diagnostic_col, end_col = diagnostic_col, - message = remove_ansi_codes(unescape_html(message)), + message = remove_ansi_codes(unescape_html(failure_content)), source = 'rustaceanvim', severity = vim.diagnostic.severity.ERROR, - } - table.insert(diagnostics, diagnostic) + }) + end + end + + if #diagnostics == 0 then + --- Fall back to old format + for test_id, message, _, lnum, col in output:gmatch("thread '([^']+)' panicked at '([^']+)', ([^:]+):(%d+):(%d+)") do + local panic_lnum = tonumber(lnum) ---@as integer? + if panic_lnum then + local diagnostic_lnum = panic_lnum - 1 + local diagnostic_col = tonumber(col) or 0 + ---@cast diagnostic_col integer + ---@type rustaceanvim.Diagnostic + local diagnostic = { + bufnr = bufnr, + test_id = test_id, + lnum = diagnostic_lnum, + end_lnum = diagnostic_lnum, + col = diagnostic_col, + end_col = diagnostic_col, + message = remove_ansi_codes(unescape_html(message)), + source = 'rustaceanvim', + severity = vim.diagnostic.severity.ERROR, + } + table.insert(diagnostics, diagnostic) + end end end diff --git a/lux.lock b/lux.lock index b05e651c..bf39658e 100644 --- a/lux.lock +++ b/lux.lock @@ -117,6 +117,24 @@ "source": "sha256-8d2835/EcyDJX9yTn6MTfaZryjY1wkSP+IIIKGPDXMk=" } }, + "4beffe8f19bd92ebd061cfcf0f6fc22aff5a983169781cfc857b21365b619553": { + "name": "nvim-nio", + "version": "1.10.1-1", + "pinned": false, + "opt": false, + "dependencies": [], + "constraint": "==1.10.1", + "binaries": [], + "source": "luarocks_rockspec+https://luarocks.org/", + "source_url": { + "type": "url", + "url": "https://github.com/nvim-neotest/nvim-nio/archive/21f5324bfac14e22ba26553caf69ec76ae8a7662.zip" + }, + "hashes": { + "rockspec": "sha256-QL5OCZFBGixecdEoriGck4iG83tjM09ewYbWVSbcfa4=", + "source": "sha256-5x+iHduNdyVjS6+OrqfDh17g9o2tP2YFFqKEsK6Z5zw=" + } + }, "4e9592a499c9ced4f8ce366db9db7d9c0dd1424ea8d4c8c16c1550ea3a61a696": { "name": "say", "version": "1.4.1-3", @@ -352,6 +370,7 @@ }, "entrypoints": [ "36c63cd0c043eb0fbc7d5ecd75907926b7136414cef7703dcd5784a61e6a728e", + "4beffe8f19bd92ebd061cfcf0f6fc22aff5a983169781cfc857b21365b619553", "56b98be57b1a97b869fd8ded0d2c0b9ce0b6b052e2d5abf84d060748617b2c90", "b54df892b93931d9062f7bb8887a2ae7e6ce116e50d9aca0cf690256d7ac05b6", "d85464dc58c62460a1ecb14e6ac773ae615a66a8224b26ceb25d954c6b05ca74" diff --git a/lux.toml b/lux.toml index ef4b118e..8176db32 100644 --- a/lux.toml +++ b/lux.toml @@ -20,3 +20,4 @@ type = "busted-nlua" [test_dependencies] nvim-dap = "0.10.0-1" neotest = "5.13.1-1" +nvim-nio = "1.10.1-1"