Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/rustaceanvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <target_arch>' - 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:
Expand Down
5 changes: 5 additions & 0 deletions lua/rustaceanvim/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 <target_arch>' - 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:
Expand Down
106 changes: 77 additions & 29 deletions lua/rustaceanvim/lsp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local rust_analyzer = require('rustaceanvim.rust_analyzer')
local server_status = require('rustaceanvim.server_status')
local cargo = require('rustaceanvim.cargo')
local os = require('rustaceanvim.os')
local targets = require('rustaceanvim.lsp.targets')

local function override_apply_text_edits()
local old_func = vim.lsp.util.apply_text_edits
Expand Down Expand Up @@ -93,6 +94,45 @@ local function configure_file_watcher(server_cfg)
end
end

---LSP restart ininer implementations
---@param bufnr? number
---@param set_target_callback? function(client) Optional callback to run for each client before restarting.
---@return number|nil client_id
local function restart(bufnr, set_target_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 set_target_callback then
set_target_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
Expand Down Expand Up @@ -249,39 +289,43 @@ 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 the current buffer's target if not provided.
M.set_target_arch = function(bufnr, target)
local function update_target(client)
-- Get the current target from the client's settings
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

if targets.target_is_valid_rustc_target(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

restart(bufnr, 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
Expand All @@ -290,11 +334,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()
Expand All @@ -304,12 +350,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[]
Expand Down
43 changes: 43 additions & 0 deletions lua/rustaceanvim/lsp/targets.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
local T = {}

local rustc_targets_cache = nil

--- Get rustc targets, use cache if available
---@return table<string, boolean>
local function get_rustc_targets()
if rustc_targets_cache then
return rustc_targets_cache
end

local result = vim.system({ 'rustc', '--print', 'target-list' }):wait()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: vim.system():wait() blocks Neovim's event loop.
It's generally better to use vim.system and pass in a callback, which is a function(result) where result is a vim.SystemCompleted.

This means that this function has to be changed, e.g. to with_rustc_targets(callback: fun(targets)).
It's a bit of a refactor, but I think it's doable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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 rustc_targets_cache
end

--- Validates if the provided target is a valid Rust compiler (rustc) target.
--- If no target is provided, it defaults to the system's architecture.
---@param target? string
---@return boolean
function T.target_is_valid_rustc_target(target)
if target == nil then
return true
end

local success, targets = pcall(get_rustc_targets)
if not success then
vim.notify('Error retrieving rustc targets: ' .. tostring(targets), vim.log.levels.ERROR)
return false
end

return targets[target] or false
end

return T
Loading