diff --git a/lua/rustaceanvim/config/init.lua b/lua/rustaceanvim/config/init.lua index 05e50ea5..e2acb9d8 100644 --- a/lua/rustaceanvim/config/init.lua +++ b/lua/rustaceanvim/config/init.lua @@ -88,7 +88,9 @@ vim.g.rustaceanvim = vim.g.rustaceanvim ---@class RustaceanDapOpts ---@field adapter? DapExecutableConfig | DapServerConfig | disable | fun():(DapExecutableConfig | DapServerConfig | disable) Defaults to a `DapServerConfig` if `codelldb` is detected, and to a `DapExecutableConfig` if `lldb` is detected. Set to `false` to disable. ----@field auto_generate_source_map fun():boolean | boolean Whether to auto-generate a source map for the standard library. +---@field add_dynamic_library_paths? boolean Accommodate dynamically-linked targets by passing library paths to lldb. Default: `true`. +---@field auto_generate_source_map? fun():boolean | boolean Whether to auto-generate a source map for the standard library. +---@field load_rust_types? fun():boolean | boolean Whether to get Rust types via initCommands (rustlib/etc/lldb_commands). Default: `true`. ---@alias disable false diff --git a/lua/rustaceanvim/config/internal.lua b/lua/rustaceanvim/config/internal.lua index bf7fda05..79e80150 100644 --- a/lua/rustaceanvim/config/internal.lua +++ b/lua/rustaceanvim/config/internal.lua @@ -6,6 +6,14 @@ local RustaceanConfig ---@field health lsp_server_health_status ---@field quiescent boolean inactive? +local function should_set_dap_config_value(dap_adapter) + local adapter = types.evaluate(dap_adapter) + if adapter == false then + return false + end + return vim.fn.executable('rustc') == 1 +end + ---@class RustaceanConfig local RustaceanDefaultConfig = { ---@class RustaceanToolsConfig @@ -194,12 +202,6 @@ local RustaceanDefaultConfig = { adapter = function() --- @type DapExecutableConfig | DapServerConfig | disable local result = false - ---@type DapExecutableConfig - local lldb_vscode = { - type = 'executable', - command = 'lldb-vscode', - name = 'lldb', - } if vim.fn.executable('codelldb') == 1 then ---@cast result DapServerConfig result = { @@ -211,23 +213,36 @@ local RustaceanDefaultConfig = { args = { '--port', '${port}' }, }, } - elseif vim.fn.executable('lldb-vscode') then - result = lldb_vscode - elseif vim.fn.executable('lldb-dap') then - -- On some distributions, it may still have the old name - result = lldb_vscode - result.command = 'lldb-dap' + else + local has_lldb_dap = vim.fn.executable('lldb-dap') ~= 0 + local has_lldb_vscode = vim.fn.executable('lldb-vscode') ~= 0 + if not has_lldb_dap and not has_lldb_vscode then + return result + end + local command = has_lldb_dap and 'lldb-dap' or 'lldb-vscode' + ---@cast result DapExecutableConfig + result = { + type = 'executable', + command = command, + name = 'lldb', + } end return result end, - --- Whether to auto-generate a source map for the standard library. + --- Accommodate dynamically-linked targets by passing library paths to lldb. + ---@type boolean + add_dynamic_library_paths = function() + return should_set_dap_config_value(RustaceanConfig.dap.adapter) + end, + --- Auto-generate a source map for the standard library. ---@type boolean | fun():boolean auto_generate_source_map = function() - local adapter = types.evaluate(RustaceanConfig.dap.adapter) - if adapter == false then - return false - end - return vim.fn.executable('rustc') == 1 + return should_set_dap_config_value(RustaceanConfig.dap.adapter) + end, + --- Get Rust types via initCommands (rustlib/etc/lldb_commands). + ---@type boolean + load_rust_types = function() + return should_set_dap_config_value(RustaceanConfig.dap.adapter) end, }, } diff --git a/lua/rustaceanvim/dap.lua b/lua/rustaceanvim/dap.lua index f8148467..681db20a 100644 --- a/lua/rustaceanvim/dap.lua +++ b/lua/rustaceanvim/dap.lua @@ -1,5 +1,6 @@ local config = require('rustaceanvim.config.internal') local compat = require('rustaceanvim.compat') +local shell = require('rustaceanvim.shell') local types = require('rustaceanvim.types.internal') local function scheduled_error(err) @@ -70,7 +71,7 @@ local function get_rustc_commit_hash(callback) if sc.code ~= 0 or result == nil then return end - local commit_hash = result:match('commit%-hash:%s+([^\n]+)$') + local commit_hash = result:match('commit%-hash:%s+([^\n]+)') if not commit_hash then return end @@ -85,7 +86,7 @@ local function get_rustc_sysroot(callback) if sc.code ~= 0 or result == nil then return end - callback(result) + callback((result:gsub('\n$', ''))) end) end @@ -102,21 +103,80 @@ local function generate_source_map() local new_map = { [compat.joinpath('/rustc/', commit_hash)] = compat.joinpath(rustc_sysroot, '/lib/rustlib/src/rust'), } - vim.tbl_extend('force', source_map, { new_map }) + source_map = vim.tbl_extend('force', source_map, { new_map }) end) end) end ----@param args RADebuggableArgs -function M.start(args) - vim.notify('Compiling a debug build for debugging. This might take some time...') +---@type string[] +local init_commands = {} + +local function get_lldb_commands() + get_rustc_sysroot(function(rustc_sysroot) + local script_import = 'command script import "' .. rustc_sysroot .. '/lib/rustlib/etc/lldb_lookup.py"' + local commands_file = rustc_sysroot .. '/lib/rustlib/etc/lldb_commands' + local file = io.open(commands_file, 'r') + if file then + for line in file:lines() do + table.insert(init_commands, line) + end + file:close() + end + table.insert(init_commands, 1, script_import) + end) +end + +---@type string[] +local environment = {} + +-- Most succinct description: https://github.com/bevyengine/bevy/issues/2589#issuecomment-1753413600 +---@param workspace_root string +local function add_dynamic_library_paths(workspace_root) + compat.system({ 'rustc', '--print', 'target-libdir' }, nil, function(sc) + ---@cast sc vim.SystemCompleted + local result = sc.stdout + if sc.code ~= 0 or result == nil then + return + end + local rustc_target_path = (result:gsub('\n$', '')) + local target_path = workspace_root .. '/target/debug/deps' + local sep = ':' + local win_sep = ';' + if shell.is_windows() then + table.insert(environment, 'PATH=' .. rustc_target_path .. win_sep .. target_path .. win_sep .. '$PATH') + elseif shell.is_macos() then + table.insert(environment, 'DKLD_LIBRARY_PATH=' .. rustc_target_path .. sep .. target_path) + else + table.insert(environment, 'LD_LIBRARY_PATH=' .. rustc_target_path .. sep .. target_path) + end + end) +end +local function handle_configured_options(args) local is_generate_source_map_enabled = types.evaluate(config.dap.auto_generate_source_map) ---@cast is_generate_source_map_enabled boolean if is_generate_source_map_enabled then generate_source_map() end + local is_load_rust_types_enabled = types.evaluate(config.dap.load_rust_types) + ---@cast is_load_rust_types_enabled boolean + if is_load_rust_types_enabled then + get_lldb_commands() + end + + local is_add_dynamic_library_paths_enabled = types.evaluate(config.dap.add_dynamic_library_paths) + ---@cast is_add_dynamic_library_paths_enabled boolean + if is_add_dynamic_library_paths_enabled then + add_dynamic_library_paths(args.workspaceRoot) + end +end + +---@param args RADebuggableArgs +function M.start(args) + vim.notify('Compiling a debug build for debugging. This might take some time...') + handle_configured_options(args) + local cargo_args = get_cargo_args_from_runnables_args(args) local cmd = vim.list_extend({ 'cargo' }, cargo_args) compat.system(cmd, { cwd = args.workspaceRoot }, function(sc) @@ -170,6 +230,9 @@ function M.start(args) -- create debug configuration local dap_config = { name = 'Rust tools debug', + + -- TODO: arguably, this type should just be 'lldb`. Is there an important reason to + -- use a custom type for the plugin? type = 'rt_lldb', request = 'launch', program = executables[1], @@ -189,10 +252,17 @@ function M.start(args) -- https://www.kernel.org/doc/html/latest/admin-guide/LSM/Yama.html runInTerminal = false, } - local final_config = is_generate_source_map_enabled - and next(source_map) ~= nil - and vim.tbl_deep_extend('force', dap_config, { sourceMap = source_map }) + + local final_config = next(init_commands) ~= nil + and vim.tbl_deep_extend('force', dap_config, { initCommands = init_commands }) or dap_config + + final_config = next(source_map) ~= nil and vim.tbl_deep_extend('force', final_config, { sourceMap = source_map }) + or final_config + + final_config = next(environment) ~= nil and vim.tbl_deep_extend('force', final_config, { env = environment }) + or final_config + -- start debugging dap.run(final_config) end) diff --git a/lua/rustaceanvim/shell.lua b/lua/rustaceanvim/shell.lua index da7f4a3f..cdb4550f 100644 --- a/lua/rustaceanvim/shell.lua +++ b/lua/rustaceanvim/shell.lua @@ -1,11 +1,16 @@ local M = {} ---@return boolean -local function is_windows() +function M.is_windows() local sysname = vim.loop.os_uname().sysname return sysname == 'Windows' or sysname == 'Windows_NT' end +---@return boolean +function M.is_macos() + return vim.loop.os_uname().sysname == 'Darwin' +end + ---@return boolean local function is_nushell() ---@diagnostic disable-next-line: missing-parameter