Skip to content

Commit 95715b2

Browse files
authored
feat(lsp): target architecture switching command for RustAnalyzer (#541)
1 parent 4e78e8d commit 95715b2

File tree

3 files changed

+120
-29
lines changed

3 files changed

+120
-29
lines changed

doc/rustaceanvim.txt

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Commands:
2424
':RustAnalyzer stop' - Stop the LSP client.
2525
':RustAnalyzer restart' - Restart the LSP client.
2626
':RustAnalyzer reloadSettings' - Reload settings for the LSP client.
27+
':RustAnalyzer target <target_arch>' - Set the target architecture for the LSP client.
28+
29+
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.
2730

2831
The ':RustLsp[!]' command is available after the LSP client has initialized.
2932
It accepts the following subcommands:

lua/rustaceanvim/init.lua

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
--- ':RustAnalyzer stop' - Stop the LSP client.
1717
--- ':RustAnalyzer restart' - Restart the LSP client.
1818
--- ':RustAnalyzer reloadSettings' - Reload settings for the LSP client.
19+
--- ':RustAnalyzer target <target_arch>' - Set the target architecture for the LSP client.
20+
21+
--- The ':RustAnalyzer target' command can take a valid rustc target,
22+
--- such as 'wasm32-unknown-unknown', or it can be left empty to set the LSP client
23+
--- to use the default target architecture for the operating system.
1924
---
2025
---The ':RustLsp[!]' command is available after the LSP client has initialized.
2126
---It accepts the following subcommands:

lua/rustaceanvim/lsp/init.lua

+112-29
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ local server_status = require('rustaceanvim.server_status')
77
local cargo = require('rustaceanvim.cargo')
88
local os = require('rustaceanvim.os')
99

10+
---Local rustc targets cache
11+
local rustc_targets_cache = nil
12+
1013
local function override_apply_text_edits()
1114
local old_func = vim.lsp.util.apply_text_edits
1215
---@diagnostic disable-next-line
@@ -93,6 +96,71 @@ local function configure_file_watcher(server_cfg)
9396
end
9497
end
9598

99+
---Handles retrieving rustc target archs and running on_valid callback
100+
---to perform certain actions using the retrieved targets.
101+
---@param on_valid function(rustc_targets)
102+
local function validate_rustc_target(on_valid)
103+
local on_exit = function(result)
104+
-- use cache if available.
105+
if rustc_targets_cache then
106+
return on_valid(rustc_targets_cache)
107+
end
108+
109+
if result.code ~= 0 then
110+
error('Failed to retrieve rustc targets: ' .. result.stderr)
111+
end
112+
113+
rustc_targets_cache = {}
114+
for line in result.stdout:gmatch('[^\r\n]+') do
115+
rustc_targets_cache[line] = true
116+
end
117+
118+
return on_valid(rustc_targets_cache)
119+
end
120+
121+
-- Call vim.system with on_exit callback to avoid blocking Neovim's event loop.
122+
vim.system({ 'rustc', '--print', 'target-list' }, { text = true }, on_exit)
123+
end
124+
125+
---LSP restart internal implementations
126+
---@param bufnr? number
127+
---@param callback? function(client) Optional callback to run for each client before restarting.
128+
---@return number|nil client_id
129+
local function restart(bufnr, callback)
130+
bufnr = bufnr or vim.api.nvim_get_current_buf()
131+
local clients = M.stop(bufnr)
132+
local timer, _, _ = vim.uv.new_timer()
133+
if not timer then
134+
vim.notify('Failed to init timer for LSP client restart.', vim.log.levels.ERROR)
135+
return
136+
end
137+
local attempts_to_live = 50
138+
local stopped_client_count = 0
139+
timer:start(200, 100, function()
140+
for _, client in ipairs(clients) do
141+
if client:is_stopped() then
142+
stopped_client_count = stopped_client_count + 1
143+
vim.schedule(function()
144+
-- Execute the callback, if provided, for additional actions before restarting
145+
if callback then
146+
callback(client)
147+
end
148+
M.start(bufnr)
149+
end)
150+
end
151+
end
152+
if stopped_client_count >= #clients then
153+
timer:stop()
154+
attempts_to_live = 0
155+
elseif attempts_to_live <= 0 then
156+
vim.notify('rustaceanvim.lsp: Could not restart all LSP clients.', vim.log.levels.ERROR)
157+
timer:stop()
158+
attempts_to_live = 0
159+
end
160+
attempts_to_live = attempts_to_live - 1
161+
end)
162+
end
163+
96164
---@class rustaceanvim.lsp.StartConfig: rustaceanvim.lsp.ClientConfig
97165
---@field root_dir string | nil
98166
---@field init_options? table
@@ -249,39 +317,50 @@ M.reload_settings = function(bufnr)
249317
return clients
250318
end
251319

320+
---Updates the target architecture setting for the LSP client associated with the given buffer.
321+
---@param bufnr? number The buffer number, defaults to the current buffer
322+
---@param target? string The target architecture. Defaults to nil(the current buffer's target if not provided).
323+
M.set_target_arch = function(bufnr, target)
324+
local on_update_target = function(client)
325+
-- Get current user's rust-analyzer target
326+
local current_target = vim.tbl_get(client, 'config', 'settings', 'rust-analyzer', 'cargo', 'target')
327+
328+
if not target then
329+
if not current_target then
330+
vim.notify('Using default OS target architecture.', vim.log.levels.INFO)
331+
else
332+
vim.notify('Target architecture is already set to the default OS target.', vim.log.levels.INFO)
333+
end
334+
return
335+
end
336+
337+
---on_valid callback handles the main functionality in changing system's arch
338+
---by checking if rustc targets contains user's target or if user's provided target is nil.
339+
---Notifies user on unrecognized target arch request
340+
local on_valid = function(rustc_tgs)
341+
if target == nil or rustc_tgs[target] then
342+
client.settings['rust-analyzer'].cargo.target = target
343+
client.notify('workspace/didChangeConfiguration', { settings = client.config.settings })
344+
vim.notify('Target architecture updated successfully to: ' .. target, vim.log.levels.INFO)
345+
return
346+
else
347+
vim.notify('Invalid target architecture provided: ' .. tostring(target), vim.log.levels.ERROR)
348+
return
349+
end
350+
end
351+
352+
return validate_rustc_target(on_valid)
353+
end
354+
355+
restart(bufnr, on_update_target)
356+
end
357+
252358
---Restart the LSP client.
253359
---Fails silently if the buffer's filetype is not one of the filetypes specified in the config.
254360
---@param bufnr? number The buffer number (optional), defaults to the current buffer
255361
---@return number|nil client_id The LSP client ID after restart
256362
M.restart = function(bufnr)
257-
bufnr = bufnr or vim.api.nvim_get_current_buf()
258-
local clients = M.stop(bufnr)
259-
local timer, _, _ = vim.uv.new_timer()
260-
if not timer then
261-
-- TODO: Log error when logging is implemented
262-
return
263-
end
264-
local attempts_to_live = 50
265-
local stopped_client_count = 0
266-
timer:start(200, 100, function()
267-
for _, client in ipairs(clients) do
268-
if client:is_stopped() then
269-
stopped_client_count = stopped_client_count + 1
270-
vim.schedule(function()
271-
M.start(bufnr)
272-
end)
273-
end
274-
end
275-
if stopped_client_count >= #clients then
276-
timer:stop()
277-
attempts_to_live = 0
278-
elseif attempts_to_live <= 0 then
279-
vim.notify('rustaceanvim.lsp: Could not restart all LSP clients.', vim.log.levels.ERROR)
280-
timer:stop()
281-
attempts_to_live = 0
282-
end
283-
attempts_to_live = attempts_to_live - 1
284-
end)
363+
M.restart(bufnr)
285364
end
286365

287366
---@enum RustAnalyzerCmd
@@ -290,11 +369,13 @@ local RustAnalyzerCmd = {
290369
stop = 'stop',
291370
restart = 'restart',
292371
reload_settings = 'reloadSettings',
372+
target = 'target',
293373
}
294374

295375
local function rust_analyzer_cmd(opts)
296376
local fargs = opts.fargs
297377
local cmd = fargs[1]
378+
local arch = fargs[2]
298379
---@cast cmd RustAnalyzerCmd
299380
if cmd == RustAnalyzerCmd.start then
300381
M.start()
@@ -304,12 +385,14 @@ local function rust_analyzer_cmd(opts)
304385
M.restart()
305386
elseif cmd == RustAnalyzerCmd.reload_settings then
306387
M.reload_settings()
388+
elseif cmd == RustAnalyzerCmd.target then
389+
M.set_target_arch(nil, arch)
307390
end
308391
end
309392

310393
vim.api.nvim_create_user_command('RustAnalyzer', rust_analyzer_cmd, {
311394
nargs = '+',
312-
desc = 'Starts or stops the rust-analyzer LSP client',
395+
desc = 'Starts, stops the rust-analyzer LSP client or changes the target',
313396
complete = function(arg_lead, cmdline, _)
314397
local clients = rust_analyzer.get_active_rustaceanvim_clients()
315398
---@type RustAnalyzerCmd[]

0 commit comments

Comments
 (0)