Skip to content

Commit b3e5677

Browse files
committed
feat(run): add get_command function
1 parent 2d90113 commit b3e5677

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,30 @@ Attach to the nearest test, see `:h neotest.run.attach()`
197197
require("neotest").run.attach()
198198
```
199199

200+
Get the command for the nearest test
201+
202+
```lua
203+
local cmd = require("neotest").run.get_command()
204+
```
205+
206+
Get the command for the current file
207+
208+
```lua
209+
local cmd = require("neotest").run.get_command(vim.fn.expand("%"))
210+
```
211+
212+
Use the command programmatically
213+
214+
```lua
215+
local cmd = require('neotest').run.get_command()
216+
217+
if cmd then
218+
vim.fn.setreg('*', table.concat(cmd, ' '))
219+
-- get_command may block for a bit, so send a notification when it is done
220+
vim.notify('Copied ' .. cmd[1] .. ' command!')
221+
end
222+
```
223+
200224
## Consumers
201225

202226
For extra features neotest provides consumers which interact with the state of the tests and their results.

doc/neotest.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,38 @@ Parameters~
546546
{args} `(string|neotest.run.StopArgs?)` Position ID to stop or args. If
547547
args then args[1] should be the position ID.
548548

549+
*neotest.run.get_command()*
550+
`get_command`({args})
551+
552+
Get the command that would be run for a test position as a string.
553+
554+
Get the command for the nearest test
555+
>vim
556+
local cmd = require("neotest").run.get_command()
557+
<
558+
559+
Get the command for the current file
560+
>vim
561+
local cmd = require("neotest").run.get_command(vim.fn.expand("%"))
562+
<
563+
564+
Use the command programmatically
565+
>vim
566+
local cmd = require('neotest').run.get_command()
567+
568+
if cmd then
569+
vim.fn.setreg('*', table.concat(cmd, ' '))
570+
-- get_command may block for a bit, so send a notification when it is done
571+
vim.notify('Copied ' .. cmd[1] .. ' command!')
572+
end
573+
<
574+
575+
Parameters~
576+
{args} `(string|neotest.run.RunArgs?)` Position ID to get command for or args.
577+
578+
Return~
579+
`(string|nil)` The command string that would be executed, or nil if no command is available
580+
549581
*neotest.run.AttachArgs*
550582
Inherits: `neotest.client.AttachArgs`
551583

lua/neotest/client/init.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ neotest.Client = {}
3838
---@field started fun()
3939
---@type neotest.Client
4040

41+
---Get whether the client has been started
42+
---@return boolean
43+
function neotest.Client:is_started()
44+
return self._started
45+
end
46+
4147
function neotest.Client:new(adapters)
4248
local events = NeotestEventProcessor()
4349

lua/neotest/consumers/run.lua

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,90 @@ function neotest.run.get_last_run()
210210
return unpack(last_run)
211211
end
212212

213+
--- Get the command that would be run for a test position
214+
---
215+
--- ```vim
216+
--- local cmd = require("neotest").run.get_command()
217+
--- ```
218+
---
219+
--- Get the command for a specific file
220+
--- ```vim
221+
--- local cmd = require("neotest").run.get_command(vim.fn.expand("%"))
222+
--- ```
223+
---@param args string|neotest.run.RunArgs? Position ID to get command for or args.
224+
---@return string[]|nil The command string or nil if not available
225+
function neotest.run.get_command(args)
226+
if not client:is_started() then
227+
lib.notify("Waiting for neotest client to start before getting command...")
228+
229+
local got_adapters = false
230+
nio.run(function()
231+
---@diagnostic disable-next-line: undefined-global
232+
await(client:get_adapters())
233+
got_adapters = true
234+
end)
235+
236+
vim.wait(1000, function()
237+
return got_adapters
238+
end)
239+
end
240+
241+
local tree = neotest.run.get_tree_from_args(args, false)
242+
if not tree then
243+
lib.notify("No tests found")
244+
return
245+
end
246+
247+
args = type(args) == "string" and { args } or args
248+
249+
---@type neotest.run.RunArgs
250+
---@diagnostic disable-next-line: assign-type-mismatch
251+
args = args or {}
252+
253+
---@diagnostic disable-next-line: invisible
254+
local adapter_id, adapter = client:_get_adapter(tree:data().id, args.adapter)
255+
if not adapter_id or not adapter then
256+
lib.notify("Adapter not found for position", "warn")
257+
return
258+
end
259+
260+
-- Ensure that the adapter has a build_spec function
261+
if not adapter.build_spec then
262+
lib.notify("Adapter doesn't support building test commands", "warn")
263+
return
264+
end
265+
266+
local success, spec = pcall(function()
267+
return adapter.build_spec(vim.tbl_extend("force", args, { tree = tree }))
268+
end)
269+
270+
if not success then
271+
lib.notify("Error building test command: " .. tostring(spec), "error")
272+
return
273+
end
274+
275+
if not spec then
276+
lib.notify("No command available for this position", "warn")
277+
return
278+
end
279+
280+
-- Handle case where spec is a table of specs
281+
if spec[1] then
282+
-- Multiple commands, let user know we're just getting the first one
283+
if #spec > 1 then
284+
lib.notify("Multiple commands found, getting the first one", "info")
285+
end
286+
spec = spec[1]
287+
end
288+
289+
if not spec.command or vim.tbl_isempty(spec.command) then
290+
lib.notify("No command found in test specification", "warn")
291+
return
292+
end
293+
294+
return spec.command
295+
end
296+
213297
neotest.run = setmetatable(neotest.run, {
214298
---@param client_ neotest.Client
215299
__call = function(_, client_)

0 commit comments

Comments
 (0)