From 07f5c2de99ca8a4ec25203d504fc17f3d741bf9d Mon Sep 17 00:00:00 2001 From: saying Date: Mon, 29 Jan 2024 19:02:09 +0800 Subject: [PATCH 1/7] feat(rustc_unpretty): use rustc inspect mir (#192) Co-authored-by: saying121 --- CHANGELOG.md | 9 ++ README.md | 19 +++ lua/rustaceanvim/commands/init.lua | 24 ++++ lua/rustaceanvim/commands/rustc_unpretty.lua | 143 +++++++++++++++++++ lua/rustaceanvim/config/check.lua | 7 + lua/rustaceanvim/config/init.lua | 1 + lua/rustaceanvim/config/internal.lua | 6 + 7 files changed, 209 insertions(+) create mode 100644 lua/rustaceanvim/commands/rustc_unpretty.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index b0993d99..ce34e1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- `:RustLsp rustcUnpretty` command: + Use `rustc -Z unpretty=mir` to inspect mir and other things, + and achieve an experience similar to Rust Playground. +- Config: `tools.rustc_unpretty` arguments for `rustc`. + ## [4.0.3] - 2024-01-28 ### Fixed diff --git a/README.md b/README.md index ff4058ef..d05c4ca3 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,25 @@ vim.keymap.set( vim.cmd.RustLsp { 'view', 'hir' } vim.cmd.RustLsp { 'view', 'mir' } ``` +
+ + Rustc Unpretty + + + Opens a buffer with a textual representation of the MIR or others things + of the function containing the cursor. + Achieve an experience similar to Rust Playground. + + NOTE: the feature need [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) + install rust parser and need nightly rustc. + + ```vimscript + :RustLsp rustcUnpretty [hir|mir|...] + ``` + ```lua + vim.cmd.RustLsp { 'rustcUnpretty', 'hir' } + vim.cmd.RustLsp { 'rustcUnpretty', 'mir' } + ```
diff --git a/lua/rustaceanvim/commands/init.lua b/lua/rustaceanvim/commands/init.lua index dd3ad3c0..f2d1c6f0 100644 --- a/lua/rustaceanvim/commands/init.lua +++ b/lua/rustaceanvim/commands/init.lua @@ -97,6 +97,27 @@ local command_tbl = { local cmd = args[1] or 'run' require('rustaceanvim.commands.fly_check')(cmd) end, + rustcUnpretty = function(args) + local err_msg = table.concat(require('rustaceanvim.commands.rustc_unpretty').available_unpretty, ' | ') + if not args or #args == 0 then + vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) + return + end + local arg = args[1]:lower() + local available = false + for _, value in ipairs(require('rustaceanvim.commands.rustc_unpretty').available_unpretty) do + if value == arg then + available = true + break + end + end + if not available then + vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) + return + end + + require('rustaceanvim.commands.rustc_unpretty').rustc_unpretty(arg) + end, view = function(args) if not args or #args == 0 then vim.notify("Expected argument: 'mir' or 'hir'", vim.log.levels.ERROR) @@ -166,6 +187,9 @@ function M.create_rust_lsp_command() if cmdline:match(match_start .. '%sview' .. subcmd_match) then return { 'mir', 'hir' } end + if cmdline:match(match_start .. '%srustcUnpretty' .. subcmd_match) then + return require('rustaceanvim.commands.rustc_unpretty').available_unpretty + end if cmdline:match(match_start .. '%s+%w*$') then return vim.tbl_filter(function(command) return command:find(arg_lead) ~= nil diff --git a/lua/rustaceanvim/commands/rustc_unpretty.lua b/lua/rustaceanvim/commands/rustc_unpretty.lua new file mode 100644 index 00000000..8a6429b3 --- /dev/null +++ b/lua/rustaceanvim/commands/rustc_unpretty.lua @@ -0,0 +1,143 @@ +local M = {} + +local config = require('rustaceanvim.config.internal') +local compat = require('rustaceanvim.compat') +local ui = require('rustaceanvim.ui') +local api = vim.api +local ts = vim.treesitter + +local rustc = 'rustc' + +M.available_unpretty = { + 'normal', + 'identified', + 'expanded', + 'expanded,identified', + 'expanded,hygiene', + 'ast-tree', + 'ast-tree,expanded', + 'hir', + 'hir,identified', + 'hir,typed', + 'hir-tree', + 'thir-tree', + 'thir-flat', + 'mir', + 'stable-mir', + 'mir-cfg', +} +---@alias rustcir_level 'normal'| 'identified'| 'expanded'| 'expanded,identified'| 'expanded,hygiene'| 'ast-tree'| 'ast-tree,expanded'| 'hir'| 'hir,identified'| 'hir,typed'| 'hir-tree'| 'thir-tree'| 'thir-flat'| 'mir'| 'stable-mir'| 'mir-cfg' + +---@type integer | nil +local latest_buf_id = nil + +---Get a compatible vim range (1 index based) from a TS node range. +--- +---TS nodes start with 0 and the end col is ending exclusive. +---They also treat a EOF/EOL char as a char ending in the first +---col of the next row. +---comment +---@param range integer[] +---@param buf integer|nil +---@return integer, integer, integer, integer +local function get_vim_range(range, buf) + ---@type integer, integer, integer, integer + local srow, scol, erow, ecol = unpack(range) + srow = srow + 1 + scol = scol + 1 + erow = erow + 1 + + if ecol == 0 then + -- Use the value of the last col of the previous row instead. + erow = erow - 1 + if not buf or buf == 0 then + ecol = vim.fn.col { erow, '$' } - 1 + else + ecol = #vim.api.nvim_buf_get_lines(buf, erow - 1, erow, false)[1] + end + ecol = math.max(ecol, 1) + end + + return srow, scol, erow, ecol +end + +---@param node TSNode +local function get_rows(node) + local start_row, _, end_row, _ = get_vim_range({ ts.get_node_range(node) }, 0) + return vim.api.nvim_buf_get_lines(0, start_row - 1, end_row, true) +end + +---@param sc vim.SystemCompleted +local function handler(sc) + if sc.code ~= 0 then + vim.notify('rustc unpretty failed' .. sc.stderr, vim.log.levels.ERROR) + return + end + + -- check if a buffer with the latest id is already open, if it is then + -- delete it and continue + ui.delete_buf(latest_buf_id) + + -- create a new buffer + latest_buf_id = vim.api.nvim_create_buf(false, true) -- not listed and scratch + + -- split the window to create a new buffer and set it to our window + ui.split(true, latest_buf_id) + + local lines = vim.split(sc.stdout, '\n') + + -- set filetype to rust for syntax highlighting + vim.bo[latest_buf_id].filetype = 'rust' + -- write the expansion content to the buffer + vim.api.nvim_buf_set_lines(latest_buf_id, 0, 0, false, lines) +end + +---@param level rustcir_level +function M.rustc_unpretty(level) + if #api.nvim_get_runtime_file('parser/rust.so', true) == 0 then + vim.notify("a treesitter parser for Rust is required for 'rustc unpretty'", vim.log.levels.ERROR) + return + end + if vim.fn.executable(rustc) ~= 1 then + vim.notify('rustc is needed to rustc unpretty.', vim.log.levels.ERROR) + return + end + + local text + + local cursor = api.nvim_win_get_cursor(0) + local pos = { math.max(cursor[1] - 1, 0), cursor[2] } + + local cline = api.nvim_get_current_line() + if not string.find(cline, 'fn%s*') then + local temp = vim.fn.searchpos('fn ', 'bcn', vim.fn.line('w0')) + pos = { math.max(temp[1] - 1, 0), temp[2] } + end + + local node = ts.get_node { pos = pos } + + if node == nil or node:type() ~= 'function_item' then + vim.notify('no function found or function is incomplete', vim.log.levels.ERROR) + return + end + + local b = get_rows(node) + if b == nil then + vim.notify('get code text failed', vim.log.levels.ERROR) + return + end + text = table.concat(b, '\n') + + -- rustc need a main function for `-Z unpretty` + if not string.find(text, 'fn%s*main') then + text = text .. 'fn main() {}' + end + + compat.system( + { rustc, '--edition', config.tools.rustc_unpretty.edition, '-Z', 'unpretty=' .. level, '-' }, + { stdin = text }, + vim.schedule_wrap(handler) + ) +end + +return M diff --git a/lua/rustaceanvim/config/check.lua b/lua/rustaceanvim/config/check.lua index 1a9290aa..625f0516 100644 --- a/lua/rustaceanvim/config/check.lua +++ b/lua/rustaceanvim/config/check.lua @@ -65,6 +65,13 @@ function M.validate(cfg) if not ok then return false, err end + local rustc_unpretty = tools.rustc_unpretty + ok, err = validate('tools.rustc_ir', { + edition = { rustc_unpretty.edition, 'string' }, + }) + if not ok then + return false, err + end ok, err = validate('tools', { executor = { tools.executor, { 'table', 'string' } }, on_initialized = { tools.on_initialized, 'function', true }, diff --git a/lua/rustaceanvim/config/init.lua b/lua/rustaceanvim/config/init.lua index 91e36cde..28731522 100644 --- a/lua/rustaceanvim/config/init.lua +++ b/lua/rustaceanvim/config/init.lua @@ -63,6 +63,7 @@ vim.g.rustaceanvim = vim.g.rustaceanvim ---@field float_win_config? table Options applied to floating windows. See |api-win_config|. ---@field create_graph? RustaceanCrateGraphConfig Options for showing the crate graph based on graphviz and the dot ---@field open_url? fun(url:string):nil If set, overrides how to open URLs +---@field rustc_unpretty? table Options for `rustcUnpretty` ---@class RustaceanHoverActionsOpts ---@field replace_builtin_hover? boolean Whether to replace Neovim's built-in `vim.lsp.buf.hover` with hover actions. Default: `true` diff --git a/lua/rustaceanvim/config/internal.lua b/lua/rustaceanvim/config/internal.lua index 883f9aa1..aa54ccb8 100644 --- a/lua/rustaceanvim/config/internal.lua +++ b/lua/rustaceanvim/config/internal.lua @@ -184,6 +184,12 @@ local RustaceanDefaultConfig = { open_url = function(url) require('rustaceanvim.os').open_url(url) end, + ---settings for rustc -Z unpretty + ---@type table + rustc_unpretty = { + ---@type string + edition = '2021', + }, }, --- all the opts to send to the LSP client From b14cc987e8865e515129f8a1236cee5f03cc7eff Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 29 Jan 2024 13:01:24 +0100 Subject: [PATCH 2/7] feat(commands): `RustLsp rustcUnpretty` -> `Rustc unpretty` --- CHANGELOG.md | 5 +- README.md | 17 +- ftplugin/rust.lua | 7 +- lua/rustaceanvim/commands/init.lua | 431 ++++++++++++++++++----------- lua/rustaceanvim/neotest/init.lua | 0 5 files changed, 287 insertions(+), 173 deletions(-) create mode 100644 lua/rustaceanvim/neotest/init.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index ce34e1be..b5150f17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `:RustLsp rustcUnpretty` command: - Use `rustc -Z unpretty=mir` to inspect mir and other things, +- `:Rustc unpretty` command: + Use `rustc -Z unpretty=[mir|hir|...]` to inspect mir and other things, and achieve an experience similar to Rust Playground. + (currently requires a nightly compiler). - Config: `tools.rustc_unpretty` arguments for `rustc`. ## [4.0.3] - 2024-01-28 diff --git a/README.md b/README.md index d05c4ca3..5b7eec1d 100644 --- a/README.md +++ b/README.md @@ -496,19 +496,20 @@ vim.keymap.set( Rustc Unpretty - Opens a buffer with a textual representation of the MIR or others things - of the function containing the cursor. - Achieve an experience similar to Rust Playground. + Opens a buffer with a textual representation of the MIR or others things, + of the function closest to the cursor. + Achieves an experience similar to Rust Playground. - NOTE: the feature need [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) - install rust parser and need nightly rustc. + NOTE: This currently requires a tree-sitter parser for Rust, + and a nightly compiler toolchain. ```vimscript - :RustLsp rustcUnpretty [hir|mir|...] + :Rustc unpretty [hir|mir|...] ``` ```lua - vim.cmd.RustLsp { 'rustcUnpretty', 'hir' } - vim.cmd.RustLsp { 'rustcUnpretty', 'mir' } + vim.cmd.Rustc { 'unpretty', 'hir' } + vim.cmd.Rustc { 'unpretty', 'mir' } + -- ... ``` diff --git a/ftplugin/rust.lua b/ftplugin/rust.lua index c34c5306..29e7030c 100644 --- a/ftplugin/rust.lua +++ b/ftplugin/rust.lua @@ -8,7 +8,7 @@ local config = require('rustaceanvim.config.internal') local types = require('rustaceanvim.types.internal') local lsp = require('rustaceanvim.lsp') -if not vim.g.did_rustaceanvim_setup_commands then +if not vim.g.did_rustaceanvim_initialize then vim.lsp.commands['rust-analyzer.runSingle'] = function(command) local runnables = require('rustaceanvim.runnables') local cached_commands = require('rustaceanvim.cached_commands') @@ -36,9 +36,12 @@ if not vim.g.did_rustaceanvim_setup_commands then local rt_dap = require('rustaceanvim.dap') rt_dap.start(args) end + + local commands = require('rustaceanvim.commands') + commands.create_rustc_command() end -vim.g.did_rustaceanvim_setup_commands = true +vim.g.did_rustaceanvim_initialize = true local auto_attach = types.evaluate(config.server.auto_attach) diff --git a/lua/rustaceanvim/commands/init.lua b/lua/rustaceanvim/commands/init.lua index f2d1c6f0..13fb2332 100644 --- a/lua/rustaceanvim/commands/init.lua +++ b/lua/rustaceanvim/commands/init.lua @@ -6,152 +6,258 @@ local config = require('rustaceanvim.config.internal') local M = {} local rust_lsp_cmd_name = 'RustLsp' +local rustc_cmd_name = 'Rustc' ----@type { string: fun(args: string[], opts: vim.api.keyset.user_command) } -local command_tbl = { - codeAction = function(_) - require('rustaceanvim.commands.code_action_group')() - end, - crateGraph = function(args) - require('rustaceanvim.commands.crate_graph')(unpack(args)) - end, - debuggables = function(args, opts) - if opts.bang then - require('rustaceanvim.cached_commands').execute_last_debuggable() - else - require('rustaceanvim.commands.debuggables').debuggables(args) - end - end, - expandMacro = function(_) - require('rustaceanvim.commands.expand_macro')() - end, - explainError = function(_) - require('rustaceanvim.commands.diagnostic').explain_error() - end, - renderDiagnostic = function(_) - require('rustaceanvim.commands.diagnostic').render_diagnostic() - end, - rebuildProcMacros = function() - require('rustaceanvim.commands.rebuild_proc_macros')() - end, - externalDocs = function(_) - require('rustaceanvim.commands.external_docs')() - end, - hover = function(args) - if #args == 0 then - vim.notify("hover: called without 'actions' or 'range'", vim.log.levels.ERROR) - return - end - local subcmd = args[1] - if subcmd == 'actions' then - require('rustaceanvim.hover_actions').hover_actions() - elseif subcmd == 'range' then - require('rustaceanvim.commands.hover_range')() - else - vim.notify('hover: unknown subcommand: ' .. subcmd .. " expected 'actions' or 'range'", vim.log.levels.ERROR) - end - end, - runnables = function(args, opts) - if opts.bang then - require('rustaceanvim.cached_commands').execute_last_runnable() - else - require('rustaceanvim.runnables').runnables(args) - end - end, - joinLines = function(_) - require('rustaceanvim.commands.join_lines')() - end, - moveItem = function(args) - if #args == 0 then - vim.notify("moveItem: called without 'up' or 'down'", vim.log.levels.ERROR) - return - end - if args[1] == 'down' then - require('rustaceanvim.commands.move_item')() - elseif args[1] == 'up' then - require('rustaceanvim.commands.move_item')(true) - else - vim.notify( - 'moveItem: unexpected argument: ' .. vim.inspect(args) .. " expected 'up' or 'down'", - vim.log.levels.ERROR - ) - end - end, - openCargo = function(_) - require('rustaceanvim.commands.open_cargo_toml')() - end, - parentModule = function(_) - require('rustaceanvim.commands.parent_module')() - end, - ssr = function(args) - local query = args and #args > 0 and table.concat(args, ' ') or nil - require('rustaceanvim.commands.ssr')(query) - end, - reloadWorkspace = function() - require('rustaceanvim.commands.workspace_refresh')() - end, - syntaxTree = function() - require('rustaceanvim.commands.syntax_tree')() - end, - flyCheck = function(args) - local cmd = args[1] or 'run' - require('rustaceanvim.commands.fly_check')(cmd) - end, - rustcUnpretty = function(args) - local err_msg = table.concat(require('rustaceanvim.commands.rustc_unpretty').available_unpretty, ' | ') - if not args or #args == 0 then - vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) - return - end - local arg = args[1]:lower() - local available = false - for _, value in ipairs(require('rustaceanvim.commands.rustc_unpretty').available_unpretty) do - if value == arg then - available = true - break +---@class command_tbl +---@field impl fun(args: string[], opts: vim.api.keyset.user_command) The command implementation +---@field complete? fun(subcmd_arg_lead: string): string[] Command completions callback, taking the lead of the subcommand's arguments +---@field bang? boolean Whether this command supports a bang! + +---@type command_tbl[] +local rustlsp_command_tbl = { + codeAction = { + impl = function(_) + require('rustaceanvim.commands.code_action_group')() + end, + }, + crateGraph = { + impl = function(args) + require('rustaceanvim.commands.crate_graph')(unpack(args)) + end, + complete = function(subcmd_arg_lead) + return vim.tbl_filter(function(backend) + return backend:find(subcmd_arg_lead) ~= nil + end, config.tools.crate_graph.enabled_graphviz_backends or {}) + end, + }, + debuggables = { + impl = function(args, opts) + if opts.bang then + require('rustaceanvim.cached_commands').execute_last_debuggable() + else + require('rustaceanvim.commands.debuggables').debuggables(args) end - end - if not available then - vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) - return - end + end, + bang = true, + }, + expandMacro = { + impl = function(_) + require('rustaceanvim.commands.expand_macro')() + end, + }, + explainError = { + impl = function(_) + require('rustaceanvim.commands.diagnostic').explain_error() + end, + }, + renderDiagnostic = { + impl = function(_) + require('rustaceanvim.commands.diagnostic').render_diagnostic() + end, + }, + rebuildProcMacros = { + impl = function() + require('rustaceanvim.commands.rebuild_proc_macros')() + end, + }, + externalDocs = { + impl = function(_) + require('rustaceanvim.commands.external_docs')() + end, + }, + hover = { + impl = function(args) + if #args == 0 then + vim.notify("hover: called without 'actions' or 'range'", vim.log.levels.ERROR) + return + end + local subcmd = args[1] + if subcmd == 'actions' then + require('rustaceanvim.hover_actions').hover_actions() + elseif subcmd == 'range' then + require('rustaceanvim.commands.hover_range')() + else + vim.notify('hover: unknown subcommand: ' .. subcmd .. " expected 'actions' or 'range'", vim.log.levels.ERROR) + end + end, + complete = function() + return { 'actions', 'range' } + end, + }, + runnables = { + impl = function(args, opts) + if opts.bang then + require('rustaceanvim.cached_commands').execute_last_runnable() + else + require('rustaceanvim.runnables').runnables(args) + end + end, + bang = true, + }, + joinLines = { + impl = function(_) + require('rustaceanvim.commands.join_lines')() + end, + }, + moveItem = { + impl = function(args) + if #args == 0 then + vim.notify("moveItem: called without 'up' or 'down'", vim.log.levels.ERROR) + return + end + if args[1] == 'down' then + require('rustaceanvim.commands.move_item')() + elseif args[1] == 'up' then + require('rustaceanvim.commands.move_item')(true) + else + vim.notify( + 'moveItem: unexpected argument: ' .. vim.inspect(args) .. " expected 'up' or 'down'", + vim.log.levels.ERROR + ) + end + end, + complete = function() + return { 'up', 'down' } + end, + }, + openCargo = { + impl = function(_) + require('rustaceanvim.commands.open_cargo_toml')() + end, + }, + parentModule = { + impl = function(_) + require('rustaceanvim.commands.parent_module')() + end, + }, + ssr = { + impl = function(args) + local query = args and #args > 0 and table.concat(args, ' ') or nil + require('rustaceanvim.commands.ssr')(query) + end, + }, + reloadWorkspace = { + impl = function() + require('rustaceanvim.commands.workspace_refresh')() + end, + }, + syntaxTree = { + impl = function() + require('rustaceanvim.commands.syntax_tree')() + end, + }, + flyCheck = { + impl = function(args) + local cmd = args[1] or 'run' + require('rustaceanvim.commands.fly_check')(cmd) + end, + complete = function(subcmd_arg_lead) + return vim.tbl_filter(function(arg) + return arg:find(subcmd_arg_lead) ~= nil + end, { 'run', 'clear', 'cancel' }) + end, + }, + view = { + impl = function(args) + if not args or #args == 0 then + vim.notify("Expected argument: 'mir' or 'hir'", vim.log.levels.ERROR) + return + end + local level + local arg = args[1]:lower() + if arg == 'mir' then + level = 'Mir' + elseif arg == 'hir' then + level = 'Hir' + else + vim.notify('Unexpected argument: ' .. arg .. " Expected: 'mir' or 'hir'", vim.log.levels.ERROR) + return + end + require('rustaceanvim.commands.view_ir')(level) + end, + complete = function(subcmd_arg_lead) + return vim.tbl_filter(function(arg) + return arg:find(subcmd_arg_lead) ~= nil + end, { 'mir', 'hir' }) + end, + }, + logFile = { + impl = function() + vim.cmd.e(config.server.logfile) + end, + }, +} - require('rustaceanvim.commands.rustc_unpretty').rustc_unpretty(arg) - end, - view = function(args) - if not args or #args == 0 then - vim.notify("Expected argument: 'mir' or 'hir'", vim.log.levels.ERROR) - return - end - local level - local arg = args[1]:lower() - if arg == 'mir' then - level = 'Mir' - elseif arg == 'hir' then - level = 'Hir' - else - vim.notify('Unexpected argument: ' .. arg .. " Expected: 'mir' or 'hir'", vim.log.levels.ERROR) - return - end - require('rustaceanvim.commands.view_ir')(level) - end, - logFile = function() - vim.cmd.e(config.server.logfile) - end, +---@type command_tbl[] +local rustc_command_tbl = { + unpretty = { + impl = function(args) + local err_msg = table.concat(require('rustaceanvim.commands.rustc_unpretty').available_unpretty, ' | ') + if not args or #args == 0 then + vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) + return + end + local arg = args[1]:lower() + local available = false + for _, value in ipairs(require('rustaceanvim.commands.rustc_unpretty').available_unpretty) do + if value == arg then + available = true + break + end + end + if not available then + vim.notify('Expected argument list: ' .. err_msg, vim.log.levels.ERROR) + return + end + require('rustaceanvim.commands.rustc_unpretty').rustc_unpretty(arg) + end, + complete = function(subcmd_arg_lead) + return vim.tbl_filter(function(arg) + return arg:find(subcmd_arg_lead) ~= nil + end, require('rustaceanvim.commands.rustc_unpretty').available_unpretty) + end, + }, } +---@param command_tbl command_tbl ---@param opts table ---@see vim.api.nvim_create_user_command -local function rust_lsp(opts) +local function run_command(command_tbl, cmd_name, opts) local fargs = opts.fargs local cmd = fargs[1] local args = #fargs > 1 and vim.list_slice(fargs, 2, #fargs) or {} local command = command_tbl[cmd] - if not command then - vim.notify(rust_lsp_cmd_name .. ': Unknown subcommand: ' .. cmd, vim.log.levels.ERROR) + if type(command) ~= 'table' or type(command.impl) ~= 'function' then + vim.notify(cmd_name .. ': Unknown subcommand: ' .. cmd, vim.log.levels.ERROR) return end - command(args, opts) + command.impl(args, opts) +end + +---@param opts table +---@see vim.api.nvim_create_user_command +local function rust_lsp(opts) + run_command(rustlsp_command_tbl, rust_lsp_cmd_name, opts) +end + +---@param opts table +---@see vim.api.nvim_create_user_command +local function rustc(opts) + run_command(rustc_command_tbl, rustc_cmd_name, opts) +end + +---@generic K, V +---@param predicate fun(V):boolean +---@param tbl table +---@return K[] +local function tbl_keys_by_value_filter(predicate, tbl) + local ret = {} + for k, v in pairs(tbl) do + if predicate(v) then + ret[k] = v + end + end + return vim.tbl_keys(ret) end ---Create the `:RustLsp` command @@ -162,35 +268,17 @@ function M.create_rust_lsp_command() bang = true, desc = 'Interacts with the rust-analyzer LSP client', complete = function(arg_lead, cmdline, _) - local commands = vim.tbl_keys(command_tbl) - local match_start = '^' .. rust_lsp_cmd_name .. '[!]*' - local subcmd_match = '%s+%w*$' - -- special case: crateGraph comes with graphviz backend completions - if - cmdline:match(match_start .. '%sdebuggables' .. subcmd_match) - or cmdline:match(match_start .. '%srunnables%s+%w*$') - then - return { 'last' } - end - if cmdline:match(match_start .. '%shover' .. subcmd_match) then - return { 'actions', 'range' } - end - if cmdline:match(match_start .. '%smoveItem' .. subcmd_match) then - return { 'up', 'down' } - end - if cmdline:match(match_start .. '%scrateGraph' .. subcmd_match) then - return config.tools.crate_graph.enabled_graphviz_backends or {} + local commands = cmdline:match('^' .. rust_lsp_cmd_name .. '!') ~= nil + -- bang! + and tbl_keys_by_value_filter(function(command) + return command.bang == true + end, rustlsp_command_tbl) + or vim.tbl_keys(rustlsp_command_tbl) + local subcmd, subcmd_arg_lead = cmdline:match('^' .. rust_lsp_cmd_name .. '[!]*%s(%S+)%s(.*)$') + if subcmd and subcmd_arg_lead and rustlsp_command_tbl[subcmd] and rustlsp_command_tbl[subcmd].complete then + return rustlsp_command_tbl[subcmd].complete(subcmd_arg_lead) end - if cmdline:match(match_start .. '%sflyCheck' .. subcmd_match) then - return { 'run', 'clear', 'cancel' } - end - if cmdline:match(match_start .. '%sview' .. subcmd_match) then - return { 'mir', 'hir' } - end - if cmdline:match(match_start .. '%srustcUnpretty' .. subcmd_match) then - return require('rustaceanvim.commands.rustc_unpretty').available_unpretty - end - if cmdline:match(match_start .. '%s+%w*$') then + if cmdline:match('^' .. rust_lsp_cmd_name .. '[!]*%s+%w*$') then return vim.tbl_filter(function(command) return command:find(arg_lead) ~= nil end, commands) @@ -204,4 +292,25 @@ function M.delete_rust_lsp_command() vim.api.nvim_del_user_command(rust_lsp_cmd_name) end +---Create the `:Rustc` command +function M.create_rustc_command() + vim.api.nvim_create_user_command(rustc_cmd_name, rustc, { + nargs = '+', + range = true, + desc = 'Interacts with rustc', + complete = function(arg_lead, cmdline, _) + local commands = vim.tbl_keys(rustc_command_tbl) + local subcmd, subcmd_arg_lead = cmdline:match('^' .. rustc_cmd_name .. '[!]*%s(%S+)%s(.*)$') + if subcmd and subcmd_arg_lead and rustc_command_tbl[subcmd] and rustc_command_tbl[subcmd].complete then + return rustc_command_tbl[subcmd].complete(subcmd_arg_lead) + end + if cmdline:match('^' .. rustc_cmd_name .. '[!]*%s+%w*$') then + return vim.tbl_filter(function(command) + return command:find(arg_lead) ~= nil + end, commands) + end + end, + }) +end + return M diff --git a/lua/rustaceanvim/neotest/init.lua b/lua/rustaceanvim/neotest/init.lua new file mode 100644 index 00000000..e69de29b From 1201b4a70af0bbc31be688c51adbe62e4dd249f8 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 29 Jan 2024 13:05:52 +0100 Subject: [PATCH 3/7] docs(changelog): update --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5150f17..f2d5ce4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Use `rustc -Z unpretty=[mir|hir|...]` to inspect mir and other things, and achieve an experience similar to Rust Playground. (currently requires a nightly compiler). + Thanks [saying121](https://github.com/saying121)! - Config: `tools.rustc_unpretty` arguments for `rustc`. +### Changed + +- Improved command completions. + - Filter suggested subcommand arguments based on existing user input. + - When calling, `:RustLsp!`, show only subcommands that change + behaviour with a bang. + +### Fixed + +- Command completions: Removed completions + for `runnables/debuggables last`. + ## [4.0.3] - 2024-01-28 ### Fixed From 7c62e8c70b6b514ebc8591a0c0b232c4c1d8dfc6 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 29 Jan 2024 13:14:34 +0100 Subject: [PATCH 4/7] docs(vimdoc): update --- doc/rustaceanvim.txt | 16 ++++++++++++++++ lua/rustaceanvim/commands/rustc_unpretty.lua | 3 ++- lua/rustaceanvim/config/init.lua | 5 ++++- lua/rustaceanvim/config/internal.lua | 4 ++-- lua/rustaceanvim/init.lua | 9 +++++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/doc/rustaceanvim.txt b/doc/rustaceanvim.txt index 7f2923c4..58e82d23 100644 --- a/doc/rustaceanvim.txt +++ b/doc/rustaceanvim.txt @@ -53,6 +53,15 @@ It accepts the following subcommands: Defaults to `flyCheck run` if called without an argument. `logFile` - Open the rust-analyzer log file. +The `:Rustc` command can be used to interact with rustc. +It accepts the following subcommands: + + `unpretty args[]` - Opens a buffer with a textual representation of the MIR or others things, + of the function closest to the cursor. + Achieves an experience similar to Rust Playground. + NOTE: This currently requires a tree-sitter parser for Rust, + and a nightly compiler toolchain. + ============================================================================== plugin configuration *rustaceanvim.config* @@ -117,6 +126,7 @@ RustaceanToolsOpts *RustaceanToolsOpts* {float_win_config?} (table) Options applied to floating windows. See |api-win_config|. {create_graph?} (RustaceanCrateGraphConfig) Options for showing the crate graph based on graphviz and the dot {open_url?} (fun(url:string):nil) If set, overrides how to open URLs + {rustc?} (RustcOpts) Options for `rustc` RustaceanHoverActionsOpts *RustaceanHoverActionsOpts* @@ -152,6 +162,12 @@ RustaceanCrateGraphConfig *RustaceanCrateGraphConfig* {pipe?} (string) Overide the pipe symbol in the shell command. Useful if using a shell that is not supported by this plugin. +RustcOpts *RustcOpts* + + Fields: ~ + {edition} (string) The edition to use. See https://rustc-dev-guide.rust-lang.org/guides/editions.html. Default '2021'. + + RustaceanLspClientOpts *RustaceanLspClientOpts* Fields: ~ diff --git a/lua/rustaceanvim/commands/rustc_unpretty.lua b/lua/rustaceanvim/commands/rustc_unpretty.lua index 8a6429b3..c42d403e 100644 --- a/lua/rustaceanvim/commands/rustc_unpretty.lua +++ b/lua/rustaceanvim/commands/rustc_unpretty.lua @@ -8,6 +8,7 @@ local ts = vim.treesitter local rustc = 'rustc' +-- TODO: See if these can be queried from rustc? M.available_unpretty = { 'normal', 'identified', @@ -134,7 +135,7 @@ function M.rustc_unpretty(level) end compat.system( - { rustc, '--edition', config.tools.rustc_unpretty.edition, '-Z', 'unpretty=' .. level, '-' }, + { rustc, '--edition', config.tools.rustc.edition, '-Z', 'unpretty=' .. level, '-' }, { stdin = text }, vim.schedule_wrap(handler) ) diff --git a/lua/rustaceanvim/config/init.lua b/lua/rustaceanvim/config/init.lua index 28731522..2d2933f8 100644 --- a/lua/rustaceanvim/config/init.lua +++ b/lua/rustaceanvim/config/init.lua @@ -63,7 +63,7 @@ vim.g.rustaceanvim = vim.g.rustaceanvim ---@field float_win_config? table Options applied to floating windows. See |api-win_config|. ---@field create_graph? RustaceanCrateGraphConfig Options for showing the crate graph based on graphviz and the dot ---@field open_url? fun(url:string):nil If set, overrides how to open URLs ----@field rustc_unpretty? table Options for `rustcUnpretty` +---@field rustc? RustcOpts Options for `rustc` ---@class RustaceanHoverActionsOpts ---@field replace_builtin_hover? boolean Whether to replace Neovim's built-in `vim.lsp.buf.hover` with hover actions. Default: `true` @@ -82,6 +82,9 @@ vim.g.rustaceanvim = vim.g.rustaceanvim ---@field enabled_graphviz_backends? string[] Override the enabled graphviz backends list, used for input validation and autocompletion. ---@field pipe? string Overide the pipe symbol in the shell command. Useful if using a shell that is not supported by this plugin. +---@class RustcOpts +---@field edition string The edition to use. See https://rustc-dev-guide.rust-lang.org/guides/editions.html. Default '2021'. + ---@class RustaceanLspClientOpts ---@field auto_attach? boolean | fun():boolean Whether to automatically attach the LSP client. Defaults to `true` if the `rust-analyzer` executable is found. ---@field cmd? string[] | fun():string[] Command and arguments for starting rust-analyzer diff --git a/lua/rustaceanvim/config/internal.lua b/lua/rustaceanvim/config/internal.lua index aa54ccb8..00d6aaf1 100644 --- a/lua/rustaceanvim/config/internal.lua +++ b/lua/rustaceanvim/config/internal.lua @@ -184,9 +184,9 @@ local RustaceanDefaultConfig = { open_url = function(url) require('rustaceanvim.os').open_url(url) end, - ---settings for rustc -Z unpretty + ---settings for rustc ---@type table - rustc_unpretty = { + rustc = { ---@type string edition = '2021', }, diff --git a/lua/rustaceanvim/init.lua b/lua/rustaceanvim/init.lua index b1a38fa8..f3cd4fd8 100644 --- a/lua/rustaceanvim/init.lua +++ b/lua/rustaceanvim/init.lua @@ -46,6 +46,15 @@ --- can be costly. --- Defaults to `flyCheck run` if called without an argument. --- `logFile` - Open the rust-analyzer log file. +--- +---The `:Rustc` command can be used to interact with rustc. +---It accepts the following subcommands: +--- +--- `unpretty args[]` - Opens a buffer with a textual representation of the MIR or others things, +--- of the function closest to the cursor. +--- Achieves an experience similar to Rust Playground. +--- NOTE: This currently requires a tree-sitter parser for Rust, +--- and a nightly compiler toolchain. ---@brief ]] local M = {} From 45f9b9920ce4ae5bea34087227dd803d5c38fe7d Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 29 Jan 2024 13:20:31 +0100 Subject: [PATCH 5/7] feat(health): check for tree-sitter parser --- lua/rustaceanvim/config/check.lua | 6 +++--- lua/rustaceanvim/health.lua | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lua/rustaceanvim/config/check.lua b/lua/rustaceanvim/config/check.lua index 625f0516..c6502387 100644 --- a/lua/rustaceanvim/config/check.lua +++ b/lua/rustaceanvim/config/check.lua @@ -65,9 +65,9 @@ function M.validate(cfg) if not ok then return false, err end - local rustc_unpretty = tools.rustc_unpretty - ok, err = validate('tools.rustc_ir', { - edition = { rustc_unpretty.edition, 'string' }, + local rustc = tools.rustc + ok, err = validate('tools.rustc', { + edition = { rustc.edition, 'string' }, }) if not ok then return false, err diff --git a/lua/rustaceanvim/health.lua b/lua/rustaceanvim/health.lua index d3a10184..45eb6d68 100644 --- a/lua/rustaceanvim/health.lua +++ b/lua/rustaceanvim/health.lua @@ -133,6 +133,16 @@ local function check_for_conflicts() ok('No conflicting plugins detected.') end +local function check_tree_sitter() + start('Checking for tree-sitter parser') + local has_tree_sitter_rust_parser = #vim.api.nvim_get_runtime_file('parser/rust.so', true) > 0 + if has_tree_sitter_rust_parser then + ok('tree-sitter parser for Rust detected.') + else + warn("No tree-sitter parser for Rust detected. Required by 'Rustc unpretty' command.") + end +end + function health.check() local types = require('rustaceanvim.types.internal') local config = require('rustaceanvim.config.internal') @@ -226,6 +236,7 @@ function health.check() end check_config(config) check_for_conflicts() + check_tree_sitter() end return health From 3f8e5f1744291d50e0044bc7af1b725cccc469a7 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 29 Jan 2024 13:25:55 +0100 Subject: [PATCH 6/7] docs(readme): add tree-sitter recommendation --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5b7eec1d..8d672be9 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,9 @@ or [`codelldb`](https://github.com/vadimcn/codelldb)) and [`nvim-dap`](https://github.com/mfussenegger/nvim-dap), required for debugging. +- A tree-sitter parser for Rust (required for the `:Rustc unpretty` command). + Can be installed using [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter), + which also provides highlights, etc. ## Installation From 1b62e43eecf90f07eabf337de5cd39eb6daa61a9 Mon Sep 17 00:00:00 2001 From: Marc Jakobi Date: Mon, 29 Jan 2024 13:27:04 +0100 Subject: [PATCH 7/7] docs(changelog): update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d5ce4f..259c2c7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [4.1.0] - 2024-01-29 ### Added