Skip to content

Commit

Permalink
feat(lsp): RustLsp testables command + failed test diagnostics (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrcjkb authored Jan 30, 2024
1 parent dd3baa6 commit 7c63115
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 27 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ 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).

## [4.2.0] - 2024-01-30

- Config: Separate `tools.executor` and `tools.test_executor` options.
The `test_executor` is used for test runnables (e.g. `cargo test`).
- LSP: New test executor, `'background'` that runs tests in the background
and provides diagnostics for failed tests when complete.
Used by default in Neovim >= 0.10.
- LSP: `:RustLsp testables` command, which is equivalent
to `:RustLsp runnables`, but filters the runnables for tests only,

> [!IMPORTANT]
>
> In Neovim < 0.10, `'background'` executor blocks the UI while running tests.
## [4.1.0] - 2024-01-29

### Added
Expand Down
49 changes: 38 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ vim.keymap.set(
</summary>

```vimscript
:RustLsp[!] debuggables [args[]]
:RustLsp[!] debuggables [args[]]?
```
```lua
vim.cmd.RustLsp('debuggables')
Expand Down Expand Up @@ -198,7 +198,7 @@ vim.keymap.set(
</summary>

```vimscript
:RustLsp[!] runnables [args[]]
:RustLsp[!] runnables [args[]]?
```
```lua
vim.cmd.RustLsp('runnables')
Expand All @@ -215,7 +215,34 @@ vim.keymap.set(

<details>
<summary>
<b>Expand Macros Recursively</b>
<b>Testables and failed test diagnostics</b>
</summary>

If you are using Neovim >= 0.10, you can set the `vim.g.rustaceanvim.tools.test_executor`
option to `'background'`, and this plugin will run tests in the background,
parse the results, and - if possible - display failed tests as diagnostics.

This is also possible in Neovim 0.9, but tests won't be run in the background,
and will block the UI.

```vimscript
:RustLsp[!] testables [args[]]?
```
```lua
vim.cmd.RustLsp('testables')
-- or, to run the previous testables:
vim.cmd.RustLsp { 'testables', bang = true }
-- or, to override the executable's args:
vim.cmd.RustLsp {'testables', 'arg1', 'arg2' }
```

![](https://github.com/mrcjkb/rustaceanvim/assets/12857160/b3639b7a-105e-49de-9bdc-9c88e8e508a2)

</details>

<details>
<summary>
<b>Expand macros recursively</b>
</summary>

```vimscript
Expand Down Expand Up @@ -243,7 +270,7 @@ vim.keymap.set(

<details>
<summary>
<b>Move Item Up/Down</b>
<b>Move item up/down</b>
</summary>

```vimscript
Expand Down Expand Up @@ -282,7 +309,7 @@ vim.keymap.set(

<details>
<summary>
<b>Hover Actions</b>
<b>Hover actions</b>
</summary>

Note: To activate hover actions, run the command twice.
Expand All @@ -306,7 +333,7 @@ vim.keymap.set(

<details>
<summary>
<b>Hover Range</b>
<b>Hover range</b>
</summary>

```vimscript
Expand Down Expand Up @@ -386,7 +413,7 @@ vim.keymap.set(

<details>
<summary>
<b>Join Lines</b>
<b>Join lines</b>
</summary>

Join selected lines into one,
Expand All @@ -406,7 +433,7 @@ vim.keymap.set(

<details>
<summary>
<b>Structural Search Replace</b>
<b>Structural search replace</b>
</summary>

```vimscript
Expand All @@ -422,7 +449,7 @@ vim.keymap.set(

<details>
<summary>
<b>View Crate Graph</b>
<b>View crate graph</b>
</summary>

```vimscript
Expand All @@ -435,7 +462,7 @@ vim.keymap.set(

<details>
<summary>
<b>View Syntax Tree</b>
<b>View syntax tree</b>
</summary>

```vimscript
Expand Down Expand Up @@ -498,7 +525,7 @@ vim.keymap.set(

<details>
<summary>
<b>Rustc Unpretty</b>
<b>Rustc unpretty</b>
</summary>

Opens a buffer with a textual representation of the MIR or others things,
Expand Down
20 changes: 18 additions & 2 deletions doc/rustaceanvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ It accepts the following subcommands:
`debuggables [args[]]?` - Debug tests, executables, etc. (requires |nvim-dap|).
`:RustLsp!` means run the last debuggable (ignores any args).
`args[]` allows you to override the executable's arguments.
`testables [args[]]?` - Run tests
`:RustLsp!` means run the last testable (ignores any args).
`args[]` allows you to override the executable's arguments.
`expandMacro` - Expand macros recursively.
`moveItem [up|down]` - Move items up or down.
`hover [action|range]` - Hover actions, or hover over visually selected range.
Expand Down Expand Up @@ -118,7 +121,8 @@ RustaceanOpts *RustaceanOpts*
RustaceanToolsOpts *RustaceanToolsOpts*

Fields: ~
{executor?} (RustaceanExecutor|executor_alias)
{executor?} (RustaceanExecutor|executor_alias) The executor to use for runnables/debuggables
{test_executor?} (RustaceanExecutor|test_executor_alias) The executor to use for runnables that are tests / testables
{on_initialized?} (fun(health:RustAnalyzerInitializedStatus)) Function that is invoked when the LSP server has finished initializing
{reload_workspace_from_cargo_toml?} (boolean) Automatically call `RustReloadWorkspace` when writing to a Cargo.toml file
{hover_actions?} (RustaceanHoverActionsOpts) Options for hover actions
Expand All @@ -132,7 +136,13 @@ RustaceanToolsOpts *RustaceanToolsOpts*
RustaceanExecutor *RustaceanExecutor*

Fields: ~
{execute_command} (fun(cmd:string,args:string[],cwd:string|nil))
{execute_command} (fun(cmd:string,args:string[],cwd:string|nil,opts?:RustaceanExecutorOpts))


RustaceanExecutorOpts *RustaceanExecutorOpts*

Fields: ~
{bufnr?} (integer) The buffer from which the executor was invoked.


executor_alias *executor_alias*
Expand All @@ -141,6 +151,12 @@ executor_alias *executor_alias*
"termopen"|"quickfix"|"toggleterm"|"vimux"


test_executor_alias *test_executor_alias*

Type: ~
executor_alias|"background"


RustaceanHoverActionsOpts *RustaceanHoverActionsOpts*

Fields: ~
Expand Down
21 changes: 21 additions & 0 deletions lua/rustaceanvim/cached_commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ local cache = {
last_debuggable = nil,
---@type { choice: integer, runnables: RARunnable[] }
last_runnable = nil,
---@type { choice: integer, runnables: RARunnable[] }
last_testable = nil,
}

---@param choice integer
Expand All @@ -17,6 +19,15 @@ M.set_last_runnable = function(choice, runnables)
}
end

---@param choice integer
---@param runnables RARunnable[]
M.set_last_testable = function(choice, runnables)
cache.last_testable = {
choice = choice,
runnables = runnables,
}
end

---@param args RADebuggableArgs
M.set_last_debuggable = function(args)
cache.last_debuggable = args
Expand All @@ -33,6 +44,16 @@ M.execute_last_debuggable = function()
end
end

M.execute_last_testable = function()
local action = cache.last_testable
local runnables = require('rustaceanvim.runnables')
if action then
runnables.run_command(action.choice, action.runnables)
else
runnables.runnables()
end
end

M.execute_last_runnable = function()
local action = cache.last_runnable
local runnables = require('rustaceanvim.runnables')
Expand Down
10 changes: 10 additions & 0 deletions lua/rustaceanvim/commands/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ local rustlsp_command_tbl = {
end,
bang = true,
},
testables = {
impl = function(args, opts)
if opts.bang then
require('rustaceanvim.cached_commands').execute_last_testable()
else
require('rustaceanvim.runnables').runnables(args, { tests_only = true })
end
end,
bang = true,
},
joinLines = {
impl = function(_)
require('rustaceanvim.commands.join_lines')()
Expand Down
10 changes: 8 additions & 2 deletions lua/rustaceanvim/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ vim.g.rustaceanvim = vim.g.rustaceanvim
---@field dap? RustaceanDapOpts Debug adapter options

---@class RustaceanToolsOpts
---@field executor? RustaceanExecutor | executor_alias
---@field executor? RustaceanExecutor | executor_alias The executor to use for runnables/debuggables
---@field test_executor? RustaceanExecutor | test_executor_alias The executor to use for runnables that are tests / testables
---@field on_initialized? fun(health:RustAnalyzerInitializedStatus) Function that is invoked when the LSP server has finished initializing
---@field reload_workspace_from_cargo_toml? boolean Automatically call `RustReloadWorkspace` when writing to a Cargo.toml file
---@field hover_actions? RustaceanHoverActionsOpts Options for hover actions
Expand All @@ -66,10 +67,15 @@ vim.g.rustaceanvim = vim.g.rustaceanvim
---@field rustc? RustcOpts Options for `rustc`

---@class RustaceanExecutor
---@field execute_command fun(cmd:string, args:string[], cwd:string|nil)
---@field execute_command fun(cmd:string, args:string[], cwd:string|nil, opts?: RustaceanExecutorOpts)

---@class RustaceanExecutorOpts
---@field bufnr? integer The buffer from which the executor was invoked.

---@alias executor_alias 'termopen' | 'quickfix' | 'toggleterm' | 'vimux'

---@alias test_executor_alias executor_alias | 'background'

---@class RustaceanHoverActionsOpts
---@field replace_builtin_hover? boolean Whether to replace Neovim's built-in `vim.lsp.buf.hover` with hover actions. Default: `true`

Expand Down
8 changes: 6 additions & 2 deletions lua/rustaceanvim/config/internal.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local types = require('rustaceanvim.types.internal')
local compat = require('rustaceanvim.compat')
local config = require('rustaceanvim.config')
local executors = require('rustaceanvim.executors')

local RustaceanConfig

Expand Down Expand Up @@ -36,7 +37,10 @@ local RustaceanDefaultConfig = {
--- how to execute terminal commands
--- options right now: termopen / quickfix / toggleterm / vimux
---@type RustaceanExecutor
executor = require('rustaceanvim.executors').termopen,
executor = executors.termopen,

---@type RustaceanExecutor
test_executor = vim.fn.has('nvim-0.10.0') == 1 and executors.background or executors.termopen,

--- callback to execute once rust-analyzer is done initializing the workspace
--- The callback receives one parameter indicating the `health` of the server: "ok" | "warning" | "error"
Expand Down Expand Up @@ -348,7 +352,7 @@ local RustaceanDefaultConfig = {
local rustaceanvim = vim.g.rustaceanvim or {}
local opts = type(rustaceanvim) == 'function' and rustaceanvim() or rustaceanvim
if opts.tools and opts.tools.executor and type(opts.tools.executor) == 'string' then
opts.tools.executor = assert(require('rustaceanvim.executors')[opts.tools.executor], 'Unknown RustaceanExecutor')
opts.tools.executor = assert(executors[opts.tools.executor], 'Unknown RustaceanExecutor')
end

---@type RustaceanConfig
Expand Down
79 changes: 79 additions & 0 deletions lua/rustaceanvim/executors/background.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
local diag_namespace = vim.api.nvim_create_namespace('rustaceanvim')

---@param output string
---@return string | nil
local function get_test_summary(output)
return output:match('(test result:.*)')
end

---@type RustaceanExecutor
---@diagnostic disable-next-line: missing-fields
local M = {}

---@package
---@param output string
---@return vim.Diagnostic[]
---@diagnostic disable-next-line: inject-field
M.parse_diagnostics = function(output)
---@type vim.Diagnostic[]
local diagnostics = {}
for line, col, message in output:gmatch("thread '[^']+' panicked at [^:]+:(%d+):(%d+):\n([^\n]*)") do
diagnostics[#diagnostics + 1] = {
lnum = tonumber(line) - 1,
col = tonumber(col) or 0,
message = message,
source = 'rustaceanvim',
severity = vim.diagnostic.severity.ERROR,
}
end
if #diagnostics == 0 then
--- Fall back to old format
for message, line, col in output:gmatch("thread '[^']+' panicked at '([^']+)', [^:]+:(%d+):(%d+)") do
diagnostics[#diagnostics + 1] = {
lnum = tonumber(line) - 1,
col = tonumber(col) or 0,
message = message,
source = 'rustaceanvim',
severity = vim.diagnostic.severity.ERROR,
}
end
end
return diagnostics
end

M.execute_command = function(command, args, cwd, opts)
---@type RustaceanExecutorOpts
opts = vim.tbl_deep_extend('force', { bufnr = 0 }, opts or {})
if vim.fn.has('nvim-0.10.0') ~= 1 then
vim.schedule(function()
vim.notify_once("the 'background' executor is not recommended for Neovim < 0.10.", vim.log.levels.WARN)
end)
return
end

vim.diagnostic.reset(diag_namespace, opts.bufnr)
local is_single_test = args[1] == 'test'
local notify_prefix = (is_single_test and 'test ' or 'tests ')
local compat = require('rustaceanvim.compat')
local cmd = vim.list_extend({ command }, args)
compat.system(cmd, { cwd = cwd }, function(sc)
---@cast sc vim.SystemCompleted
if sc.code == 0 then
local summary = get_test_summary(sc.stdout or '')
vim.schedule(function()
vim.notify(summary and summary or (notify_prefix .. 'passed!'), vim.log.levels.INFO)
end)
return
end
local output = sc.stderr or ''
local diagnostics = M.parse_diagnostics(output)
local summary = get_test_summary(sc.stdout or '')
vim.schedule(function()
vim.diagnostic.set(diag_namespace, opts.bufnr, diagnostics)
vim.cmd.redraw()
vim.notify(summary and summary or (notify_prefix .. 'failed!'), vim.log.levels.ERROR)
end)
end)
end

return M
Loading

0 comments on commit 7c63115

Please sign in to comment.