From b394709bb65d074ec9985c244d1eded19f6130f7 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Tue, 24 Dec 2024 16:49:04 +0100 Subject: [PATCH] feat(lsp): auto-connect to ra-multiplex if running --- README.md | 12 ++++++++ doc/rustaceanvim.txt | 18 +++++++++++ doc/tags | 1 + lua/rustaceanvim/config/init.lua | 19 ++++++++++++ lua/rustaceanvim/config/internal.lua | 29 +++++++++++++----- lua/rustaceanvim/lsp/init.lua | 45 ++++++++++++++++++++-------- 6 files changed, 105 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0d593360..98b4ab70 100644 --- a/README.md +++ b/README.md @@ -741,6 +741,18 @@ by setting the rust-analyzer +
+ + ra-multiplex + + + 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`. + +
## :gear: Advanced configuration diff --git a/doc/rustaceanvim.txt b/doc/rustaceanvim.txt index fe24a0d0..8e8eb93f 100644 --- a/doc/rustaceanvim.txt +++ b/doc/rustaceanvim.txt @@ -296,6 +296,8 @@ rustaceanvim.lsp.ClientOpts *rustaceanvim.lsp.ClientOpts* 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. @@ -314,6 +316,22 @@ rustaceanvim.lsp.ClientOpts *rustaceanvim.lsp.ClientOpts* 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* Type: ~ diff --git a/doc/tags b/doc/tags index e0c3bd1d..9ad3ad13 100644 --- a/doc/tags +++ b/doc/tags @@ -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* diff --git a/lua/rustaceanvim/config/init.lua b/lua/rustaceanvim/config/init.lua index d6e86b31..be672d63 100644 --- a/lua/rustaceanvim/config/init.lua +++ b/lua/rustaceanvim/config/init.lua @@ -177,6 +177,9 @@ vim.g.rustaceanvim = vim.g.rustaceanvim ---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. @@ -200,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 diff --git a/lua/rustaceanvim/config/internal.lua b/lua/rustaceanvim/config/internal.lua index 215f1df8..b44bd48c 100644 --- a/lua/rustaceanvim/config/internal.lua +++ b/lua/rustaceanvim/config/internal.lua @@ -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? @@ -278,6 +281,15 @@ local RustaceanDefaultConfig = { ---@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 @@ -393,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' diff --git a/lua/rustaceanvim/lsp/init.lua b/lua/rustaceanvim/lsp/init.lua index 7fd5b07a..85dfbcc1 100644 --- a/lua/rustaceanvim/lsp/init.lua +++ b/lua/rustaceanvim/lsp/init.lua @@ -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 @@ -215,6 +205,26 @@ 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') @@ -287,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. @@ -337,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 })