diff --git a/doc/rustaceanvim.txt b/doc/rustaceanvim.txt index 2c9e45a1..aa81b141 100644 --- a/doc/rustaceanvim.txt +++ b/doc/rustaceanvim.txt @@ -24,6 +24,9 @@ Commands: ':RustAnalyzer stop' - Stop the LSP client. ':RustAnalyzer restart' - Restart the LSP client. ':RustAnalyzer reloadSettings' - Reload settings for the LSP client. + ':RustAnalyzer target ' - Set the target architecture for the LSP client. + +The ':RustAnalyzer target' command can take a valid rustc target, such as 'wasm32-unknown-unknown', or it can be left empty to set the LSP client to use the default target architecture for the operating system. The ':RustLsp[!]' command is available after the LSP client has initialized. It accepts the following subcommands: diff --git a/lua/rustaceanvim/init.lua b/lua/rustaceanvim/init.lua index 22050a46..bf7ee927 100644 --- a/lua/rustaceanvim/init.lua +++ b/lua/rustaceanvim/init.lua @@ -16,6 +16,11 @@ --- ':RustAnalyzer stop' - Stop the LSP client. --- ':RustAnalyzer restart' - Restart the LSP client. --- ':RustAnalyzer reloadSettings' - Reload settings for the LSP client. +--- ':RustAnalyzer target ' - Set the target architecture for the LSP client. + +--- The ':RustAnalyzer target' command can take a valid rustc target, +--- such as 'wasm32-unknown-unknown', or it can be left empty to set the LSP client +--- to use the default target architecture for the operating system. --- ---The ':RustLsp[!]' command is available after the LSP client has initialized. ---It accepts the following subcommands: diff --git a/lua/rustaceanvim/lsp/init.lua b/lua/rustaceanvim/lsp/init.lua index 4770d451..acba95aa 100644 --- a/lua/rustaceanvim/lsp/init.lua +++ b/lua/rustaceanvim/lsp/init.lua @@ -7,6 +7,9 @@ local server_status = require('rustaceanvim.server_status') local cargo = require('rustaceanvim.cargo') local os = require('rustaceanvim.os') +---Local rustc targets cache +local rustc_targets_cache = nil + local function override_apply_text_edits() local old_func = vim.lsp.util.apply_text_edits ---@diagnostic disable-next-line @@ -93,6 +96,71 @@ local function configure_file_watcher(server_cfg) end end +---Handles retrieving rustc target archs and running on_valid callback +---to perform certain actions using the retrieved targets. +---@param on_valid function(rustc_targets) +local function validate_rustc_target(on_valid) + local on_exit = function(result) + -- use cache if available. + if rustc_targets_cache then + return on_valid(rustc_targets_cache) + end + + if result.code ~= 0 then + error('Failed to retrieve rustc targets: ' .. result.stderr) + end + + rustc_targets_cache = {} + for line in result.stdout:gmatch('[^\r\n]+') do + rustc_targets_cache[line] = true + end + + return on_valid(rustc_targets_cache) + end + + -- Call vim.system with on_exit callback to avoid blocking Neovim's event loop. + vim.system({ 'rustc', '--print', 'target-list' }, { text = true }, on_exit) +end + +---LSP restart internal implementations +---@param bufnr? number +---@param callback? function(client) Optional callback to run for each client before restarting. +---@return number|nil client_id +local function restart(bufnr, callback) + bufnr = bufnr or vim.api.nvim_get_current_buf() + local clients = M.stop(bufnr) + local timer, _, _ = vim.uv.new_timer() + if not timer then + vim.notify('Failed to init timer for LSP client restart.', vim.log.levels.ERROR) + return + end + local attempts_to_live = 50 + local stopped_client_count = 0 + timer:start(200, 100, function() + for _, client in ipairs(clients) do + if client:is_stopped() then + stopped_client_count = stopped_client_count + 1 + vim.schedule(function() + -- Execute the callback, if provided, for additional actions before restarting + if callback then + callback(client) + end + M.start(bufnr) + end) + end + end + if stopped_client_count >= #clients then + timer:stop() + attempts_to_live = 0 + elseif attempts_to_live <= 0 then + vim.notify('rustaceanvim.lsp: Could not restart all LSP clients.', vim.log.levels.ERROR) + timer:stop() + attempts_to_live = 0 + end + attempts_to_live = attempts_to_live - 1 + end) +end + ---@class rustaceanvim.lsp.StartConfig: rustaceanvim.lsp.ClientConfig ---@field root_dir string | nil ---@field init_options? table @@ -249,39 +317,50 @@ M.reload_settings = function(bufnr) return clients end +---Updates the target architecture setting for the LSP client associated with the given buffer. +---@param bufnr? number The buffer number, defaults to the current buffer +---@param target? string The target architecture. Defaults to nil(the current buffer's target if not provided). +M.set_target_arch = function(bufnr, target) + local on_update_target = function(client) + -- Get current user's rust-analyzer target + local current_target = vim.tbl_get(client, 'config', 'settings', 'rust-analyzer', 'cargo', 'target') + + if not target then + if not current_target then + vim.notify('Using default OS target architecture.', vim.log.levels.INFO) + else + vim.notify('Target architecture is already set to the default OS target.', vim.log.levels.INFO) + end + return + end + + ---on_valid callback handles the main functionality in changing system's arch + ---by checking if rustc targets contains user's target or if user's provided target is nil. + ---Notifies user on unrecognized target arch request + local on_valid = function(rustc_tgs) + if target == nil or rustc_tgs[target] then + client.settings['rust-analyzer'].cargo.target = target + client.notify('workspace/didChangeConfiguration', { settings = client.config.settings }) + vim.notify('Target architecture updated successfully to: ' .. target, vim.log.levels.INFO) + return + else + vim.notify('Invalid target architecture provided: ' .. tostring(target), vim.log.levels.ERROR) + return + end + end + + return validate_rustc_target(on_valid) + end + + restart(bufnr, on_update_target) +end + ---Restart the LSP client. ---Fails silently if the buffer's filetype is not one of the filetypes specified in the config. ---@param bufnr? number The buffer number (optional), defaults to the current buffer ---@return number|nil client_id The LSP client ID after restart M.restart = function(bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() - local clients = M.stop(bufnr) - local timer, _, _ = vim.uv.new_timer() - if not timer then - -- TODO: Log error when logging is implemented - return - end - local attempts_to_live = 50 - local stopped_client_count = 0 - timer:start(200, 100, function() - for _, client in ipairs(clients) do - if client:is_stopped() then - stopped_client_count = stopped_client_count + 1 - vim.schedule(function() - M.start(bufnr) - end) - end - end - if stopped_client_count >= #clients then - timer:stop() - attempts_to_live = 0 - elseif attempts_to_live <= 0 then - vim.notify('rustaceanvim.lsp: Could not restart all LSP clients.', vim.log.levels.ERROR) - timer:stop() - attempts_to_live = 0 - end - attempts_to_live = attempts_to_live - 1 - end) + M.restart(bufnr) end ---@enum RustAnalyzerCmd @@ -290,11 +369,13 @@ local RustAnalyzerCmd = { stop = 'stop', restart = 'restart', reload_settings = 'reloadSettings', + target = 'target', } local function rust_analyzer_cmd(opts) local fargs = opts.fargs local cmd = fargs[1] + local arch = fargs[2] ---@cast cmd RustAnalyzerCmd if cmd == RustAnalyzerCmd.start then M.start() @@ -304,12 +385,14 @@ local function rust_analyzer_cmd(opts) M.restart() elseif cmd == RustAnalyzerCmd.reload_settings then M.reload_settings() + elseif cmd == RustAnalyzerCmd.target then + M.set_target_arch(nil, arch) end end vim.api.nvim_create_user_command('RustAnalyzer', rust_analyzer_cmd, { nargs = '+', - desc = 'Starts or stops the rust-analyzer LSP client', + desc = 'Starts, stops the rust-analyzer LSP client or changes the target', complete = function(arg_lead, cmdline, _) local clients = rust_analyzer.get_active_rustaceanvim_clients() ---@type RustAnalyzerCmd[]