Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lsp): auto-connect to ra-multiplex if running #632

Merged
merged 2 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,18 @@ by setting the rust-analyzer

</details>

<details>
<summary>
<b>ra-multiplex</b>
</summary>

On Linux and MacOS, rustaceanvim can auto-detect and connect to a
running [ra-multiplex](https://github.com/pr2502/ra-multiplex) server.
By default, it will try to do so automatically if the `vim.g.rustaceanvim.server.cmd`
option is unset.
See also `:h rustaceanvim.ra_multiplex`.

</details>
<!-- markdownlint-restore -->

## :gear: Advanced configuration
Expand Down
56 changes: 38 additions & 18 deletions doc/rustaceanvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,30 +286,50 @@ rustaceanvim.lsp.ClientOpts *rustaceanvim.lsp.ClientOpts*

Fields: ~
{auto_attach?} (boolean|fun(bufnr:integer):boolean)
Whether to automatically attach the LSP client.
Defaults to `true` if the `rust-analyzer` executable is found.
{cmd?} (string[]|fun():string[])
Command and arguments for starting rust-analyzer
Whether to automatically attach the LSP client.
Defaults to `true` if the `rust-analyzer` executable is found.
{cmd?} (string[]|fun():string[]|fun(dispatchers:vim.lsp.rpc.Dispatchers):vim.lsp.rpc.PublicClient)
Command and arguments for starting rust-analyzer
Can be a list of arguments, a function that returns a list of arguments,
or a function that returns an LSP RPC client factory (see |vim.lsp.rpc.connect|).
{root_dir?} (string|fun(filename:string,default:fun(filename:string):string|nil):string|nil)
The directory to use for the attached LSP.
Can be a function, which may return nil if no server should attach.
The second argument contains the default implementation, which can be used for fallback behavior.
The directory to use for the attached LSP.
Can be a function, which may return nil if no server should attach.
The second argument contains the default implementation, which can be used for fallback behavior.
{ra_multiplex?} (rustaceanvim.ra_multiplex.Opts)
Options for connecting to ra-multiplex.
{settings?} (table|fun(project_root:string|nil,default_settings:table):table)
Setting passed to rust-analyzer.
Defaults to a function that looks for a `rust-analyzer.json` file or returns an empty table.
See https://rust-analyzer.github.io/manual.html#configuration.
Setting passed to rust-analyzer.
Defaults to a function that looks for a `rust-analyzer.json` file or returns an empty table.
See https://rust-analyzer.github.io/manual.html#configuration.
{standalone?} (boolean)
Standalone file support (enabled by default).
Disabling it may improve rust-analyzer's startup time.
Standalone file support (enabled by default).
Disabling it may improve rust-analyzer's startup time.
{logfile?} (string)
The path to the rust-analyzer log file.
The path to the rust-analyzer log file.
{load_vscode_settings?} (boolean)
Whether to search (upward from the buffer) for rust-analyzer settings in .vscode/settings json.
If found, loaded settings will override configured options.
Default: `true`
Whether to search (upward from the buffer) for rust-analyzer settings in .vscode/settings json.
If found, loaded settings will override configured options.
Default: `true`
{status_notify_level?} (rustaceanvim.server.status_notify_level)
Server status warning level to notify at.
Default: 'error'
Server status warning level to notify at.
Default: 'error'


rustaceanvim.ra_multiplex.Opts *rustaceanvim.ra_multiplex.Opts*

Fields: ~
{enable?} (boolean)
Whether to enable ra-multiplex auto-discovery.
Default: `true` if `server.cmd` is not set, otherwise `false`.
If enabled, rustaceanvim will try to detect if an ra-multiplex server is running
and connect to it (Linux and MacOS only).
If auto-discovery does not work, you can set `server.cmd` to a function that
returns an LSP RPC client factory (see |vim.lsp.rpc.connect|).
{host?} (string)
The host to connect to. Default: '127.0.0.1'
{port?} (integer)
The port to connect to. Default: 27631


rustaceanvim.server.status_notify_level*rustaceanvim.server.status_notify_level*
Expand Down
1 change: 1 addition & 0 deletions doc/tags
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ rustaceanvim.lsp.ClientOpts rustaceanvim.txt /*rustaceanvim.lsp.ClientOpts*
rustaceanvim.lsp_server_health_status rustaceanvim.txt /*rustaceanvim.lsp_server_health_status*
rustaceanvim.mason mason.txt /*rustaceanvim.mason*
rustaceanvim.neotest rustaceanvim.txt /*rustaceanvim.neotest*
rustaceanvim.ra_multiplex.Opts rustaceanvim.txt /*rustaceanvim.ra_multiplex.Opts*
rustaceanvim.rustc.Opts rustaceanvim.txt /*rustaceanvim.rustc.Opts*
rustaceanvim.test_executor_alias rustaceanvim.txt /*rustaceanvim.test_executor_alias*
rustaceanvim.tools.Opts rustaceanvim.txt /*rustaceanvim.tools.Opts*
Expand Down
23 changes: 22 additions & 1 deletion lua/rustaceanvim/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,18 @@ vim.g.rustaceanvim = vim.g.rustaceanvim
---@field auto_attach? boolean | fun(bufnr: integer):boolean
---
---Command and arguments for starting rust-analyzer
---@field cmd? string[] | fun():string[]
---Can be a list of arguments, a function that returns a list of arguments,
---or a function that returns an LSP RPC client factory (see |vim.lsp.rpc.connect|).
---@field cmd? string[] | fun():(string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient)
---
---The directory to use for the attached LSP.
---Can be a function, which may return nil if no server should attach.
---The second argument contains the default implementation, which can be used for fallback behavior.
---@field root_dir? string | fun(filename: string, default: fun(filename: string):string|nil):string|nil
---
---Options for connecting to ra-multiplex.
---@field ra_multiplex? rustaceanvim.ra_multiplex.Opts
---
---Setting passed to rust-analyzer.
---Defaults to a function that looks for a `rust-analyzer.json` file or returns an empty table.
---See https://rust-analyzer.github.io/manual.html#configuration.
Expand All @@ -198,6 +203,22 @@ vim.g.rustaceanvim = vim.g.rustaceanvim
---
---@see vim.lsp.ClientConfig

---@class rustaceanvim.ra_multiplex.Opts
---
---Whether to enable ra-multiplex auto-discovery.
---Default: `true` if `server.cmd` is not set, otherwise `false`.
---If enabled, rustaceanvim will try to detect if an ra-multiplex server is running
---and connect to it (Linux and MacOS only).
---If auto-discovery does not work, you can set `server.cmd` to a function that
---returns an LSP RPC client factory (see |vim.lsp.rpc.connect|).
---@field enable? boolean
---
---The host to connect to. Default: '127.0.0.1'
---@field host? string
---
---The port to connect to. Default: 27631
---@field port? integer

---@alias rustaceanvim.server.status_notify_level 'error' | 'warning' | rustaceanvim.disable

---@alias rustaceanvim.disable false
Expand Down
35 changes: 27 additions & 8 deletions lua/rustaceanvim/config/internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ local server_config = require('rustaceanvim.config.server')

local RustaceanConfig

local rustaceanvim = vim.g.rustaceanvim or {}
local rustaceanvim_opts = type(rustaceanvim) == 'function' and rustaceanvim() or rustaceanvim

---@class rustaceanvim.internal.RAInitializedStatus : rustaceanvim.RAInitializedStatus
---@field health rustaceanvim.lsp_server_health_status
---@field quiescent boolean inactive?
Expand Down Expand Up @@ -262,18 +265,31 @@ local RustaceanDefaultConfig = {
return false
end
local cmd = types.evaluate(RustaceanConfig.server.cmd)
if type(cmd) == 'function' then
-- This could be a function that connects via a TCP socket, so we don't want to evaluate it.
return true
end
---@cast cmd string[]
local rs_bin = cmd[1]
return vim.fn.executable(rs_bin) == 1
end,
---@type string[] | fun():string[]
---@type string[] | fun():(string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient)
cmd = function()
return { 'rust-analyzer', '--log-file', RustaceanConfig.server.logfile }
end,

---@type string | fun(filename: string, default: fun(filename: string):string|nil):string|nil
root_dir = cargo.get_root_dir,

ra_multiplex = {
---@type boolean
enable = vim.tbl_get(rustaceanvim_opts, 'server', 'cmd') == nil,
---@type string
host = '127.0.0.1',
---@type integer
port = 27631,
},

--- standalone file support
--- setting it to false may improve startup time
---@type boolean
Expand Down Expand Up @@ -389,20 +405,23 @@ local RustaceanDefaultConfig = {
-- debug info
was_g_rustaceanvim_sourced = vim.g.rustaceanvim ~= nil,
}
local rustaceanvim = vim.g.rustaceanvim or {}
local opts = type(rustaceanvim) == 'function' and rustaceanvim() or rustaceanvim
for _, executor in pairs { 'executor', 'test_executor', 'crate_test_executor' } do
if opts.tools and opts.tools[executor] and type(opts.tools[executor]) == 'string' then
opts.tools[executor] = assert(executors[opts.tools[executor]], 'Unknown RustaceanExecutor')
if
rustaceanvim_opts.tools
and rustaceanvim_opts.tools[executor]
and type(rustaceanvim_opts.tools[executor]) == 'string'
then
rustaceanvim_opts.tools[executor] =
assert(executors[rustaceanvim_opts.tools[executor]], 'Unknown RustaceanExecutor')
end
end

---@type rustaceanvim.Config
RustaceanConfig = vim.tbl_deep_extend('force', {}, RustaceanDefaultConfig, opts)
RustaceanConfig = vim.tbl_deep_extend('force', {}, RustaceanDefaultConfig, rustaceanvim_opts)

-- Override user dap.adapter config in a backward compatible way
if opts.dap and opts.dap.adapter then
local user_adapter = opts.dap.adapter
if rustaceanvim_opts.dap and rustaceanvim_opts.dap.adapter then
local user_adapter = rustaceanvim_opts.dap.adapter
local default_adapter = types.evaluate(RustaceanConfig.dap.adapter)
if
type(user_adapter) == 'table'
Expand Down
79 changes: 53 additions & 26 deletions lua/rustaceanvim/lsp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,6 @@ Starting rust-analyzer client in detached/standalone mode (with reduced function
lsp_start_config.settings = get_start_settings(bufname, root_dir, client_config)
configure_file_watcher(lsp_start_config)

-- rust-analyzer treats settings in initializationOptions specially -- in particular, workspace_discoverConfig
-- so copy them to init_options (the vim name)
-- so they end up in initializationOptions (the LSP name)
-- ... and initialization_options (the rust name) in rust-analyzer's main.rs
lsp_start_config.init_options = vim.tbl_deep_extend(
'force',
lsp_start_config.init_options or {},
vim.tbl_get(lsp_start_config.settings, 'rust-analyzer')
)

-- Check if a client is already running and add the workspace folder if necessary.
for _, client in pairs(rust_analyzer.get_active_rustaceanvim_clients()) do
if root_dir and not is_in_workspace(client, root_dir) then
Expand All @@ -215,24 +205,50 @@ Starting rust-analyzer client in detached/standalone mode (with reduced function
end

local rust_analyzer_cmd = types.evaluate(client_config.cmd)

local ra_multiplex = lsp_start_config.ra_multiplex
if ra_multiplex.enable then
local ok, running_ra_multiplex = pcall(function()
local result = vim.system({ 'pgrep', 'ra-multiplex' }):wait().code
return result == 0
end)
if ok and running_ra_multiplex then
rust_analyzer_cmd = vim.lsp.rpc.connect(ra_multiplex.host, ra_multiplex.port)
local ra_settings = lsp_start_config.settings['rust-analyzer'] or {}
ra_settings.lspMux = ra_settings.lspMux
or {
version = '1',
method = 'connect',
server = 'rust-analyzer',
}
lsp_start_config.settings['rust-analyzer'] = ra_settings
end
end

-- special case: rust-analyzer has a `rust-analyzer.server.path` config option
-- that allows you to override the path via .vscode/settings.json
local server_path = vim.tbl_get(lsp_start_config.settings, 'rust-analyzer', 'server', 'path')
if type(server_path) == 'string' then
rust_analyzer_cmd[1] = server_path
if type(rust_analyzer_cmd) == 'table' then
rust_analyzer_cmd[1] = server_path
else
rust_analyzer_cmd = { server_path }
end
--
end
if #rust_analyzer_cmd == 0 then
vim.schedule(function()
vim.notify('rust-analyzer command is not set!', vim.log.levels.ERROR)
end)
return
end
if vim.fn.executable(rust_analyzer_cmd[1]) ~= 1 then
vim.schedule(function()
vim.notify(('%s is not executable'):format(rust_analyzer_cmd[1]), vim.log.levels.ERROR)
end)
return
if type(rust_analyzer_cmd) == 'table' then
if #rust_analyzer_cmd == 0 then
vim.schedule(function()
vim.notify('rust-analyzer command is not set!', vim.log.levels.ERROR)
end)
return
end
if vim.fn.executable(rust_analyzer_cmd[1]) ~= 1 then
vim.schedule(function()
vim.notify(('%s is not executable'):format(rust_analyzer_cmd[1]), vim.log.levels.ERROR)
end)
return
end
end
---@cast rust_analyzer_cmd string[]
lsp_start_config.cmd = rust_analyzer_cmd
Expand Down Expand Up @@ -281,7 +297,17 @@ Starting rust-analyzer client in detached/standalone mode (with reduced function
end
end

return vim.lsp.start(lsp_start_config)
-- rust-analyzer treats settings in initializationOptions specially -- in particular, workspace_discoverConfig
-- so copy them to init_options (the vim name)
-- so they end up in initializationOptions (the LSP name)
-- ... and initialization_options (the rust name) in rust-analyzer's main.rs
lsp_start_config.init_options = vim.tbl_deep_extend(
'force',
lsp_start_config.init_options or {},
vim.tbl_get(lsp_start_config.settings, 'rust-analyzer')
)

return vim.lsp.start(vim.print(lsp_start_config))
end

---Stop the LSP client.
Expand Down Expand Up @@ -331,7 +357,8 @@ 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']
local ra = client.config.settings['rust-analyzer'] or {}
---@diagnostic disable-next-line: inject-field
ra.cargo = ra.cargo or {}
ra.cargo.target = target
compat.client_notify(client, 'workspace/didChangeConfiguration', { settings = client.config.settings })
Expand Down Expand Up @@ -362,7 +389,7 @@ local RustAnalyzerCmd = {
target = 'target',
}

local function rust_analyzer_cmd(opts)
local function rust_analyzer_user_cmd(opts)
local fargs = opts.fargs
local cmd = fargs[1]
---@cast cmd RustAnalyzerCmd
Expand All @@ -380,7 +407,7 @@ local function rust_analyzer_cmd(opts)
end
end

vim.api.nvim_create_user_command('RustAnalyzer', rust_analyzer_cmd, {
vim.api.nvim_create_user_command('RustAnalyzer', rust_analyzer_user_cmd, {
nargs = '+',
desc = 'Starts, stops the rust-analyzer LSP client or changes the target',
complete = function(arg_lead, cmdline, _)
Expand Down
Loading