Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(output_panel): recreate window and term channel if panel closed #475

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions lua/neotest/consumers/output_panel/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ local init = function(client)
if partial then
return
end
if not chan then

local channel_is_valid = function(chan_id)
return chan_id and pcall(vim.api.nvim_chan_send, chan_id, "\n")
end

if not channel_is_valid(chan) then
chan = lib.ui.open_term(panel.win:buffer())
-- neovim sometimes adds random blank lines when creating a terminal buffer
nio.api.nvim_buf_set_option(panel.win:buffer(), "modifiable", true)
Expand All @@ -50,7 +55,11 @@ local init = function(client)
for file, _ in pairs(files_to_read) do
local output = lib.files.read(file)
local dos_newlines = string.find(output, "\r\n") ~= nil
nio.api.nvim_chan_send(chan, dos_newlines and output or output:gsub("\n", "\r\n"))
if not pcall(nio.api.nvim_chan_send, chan, dos_newlines and output or output:gsub("\n", "\r\n")) then
lib.notify(("Error sending output to term channel: %s"):format(chan), vim.log.levels.ERROR)
chan = nil
break
end
end
end
end
Expand Down
13 changes: 12 additions & 1 deletion lua/neotest/consumers/watch/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ local neotest = {}
---@class neotest.consumers.watch
neotest.watch = {}

local init = function(client)
client.listeners.discover_positions = function(_, tree)
for _, watcher in pairs(watchers) do
if watcher.tree:data().path == tree:data().path then
watcher.discover_positions_event.set()
end
end
end
end

local function get_valid_client(bufnr)
local clients = nio.lsp.get_clients({ bufnr = bufnr })
for _, client in ipairs(clients) do
Expand Down Expand Up @@ -200,7 +210,8 @@ function neotest.watch.is_watching(position_id)
end

neotest.watch = setmetatable(neotest.watch, {
__call = function()
__call = function(_, client)
init(client)
return neotest.watch
end,
})
Expand Down
8 changes: 8 additions & 0 deletions lua/neotest/consumers/watch/watcher.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ local config = require("neotest.config")
---@class neotest.consumers.watch.Watcher
---@field lsp_client nio.lsp.Client
---@field autocmd_id? string
---@field tree neotest.Tree
---@field discover_positions_event nio.control.Future
local Watcher = {}

function Watcher:new(lsp_client)
Expand Down Expand Up @@ -159,6 +161,9 @@ function Watcher:watch(tree, args)
logger.debug("Built dependencies in", elapsed, "ms for", tree:data().id, ":", dependencies)
local dependants = self:_build_dependants(dependencies)

self.tree = tree
self.discover_positions_event = nio.control.future()

self.autocmd_id = nio.api.nvim_create_autocmd("BufWritePost", {
callback = function(autocmd_args)
if type(args.run_predicate) == "function" and not args.run_predicate(autocmd_args.buf) then
Expand All @@ -172,6 +177,9 @@ function Watcher:watch(tree, args)
return
end

self.discover_positions_event.wait()
self.discover_positions_event = nio.control.future()

if tree:data().type ~= "dir" then
run.run(vim.tbl_extend("keep", { tree:data().id }, args))
else
Expand Down
12 changes: 11 additions & 1 deletion lua/neotest/lib/window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,20 @@ function PersistentWindow:open()
end

function PersistentWindow:buffer()
if self._bufnr and vim.fn.bufexists(self._bufnr) == 1 then
if
self._bufnr
and nio.api.nvim_buf_is_valid(self._bufnr)
and nio.api.nvim_buf_is_loaded(self._bufnr)
then
return self._bufnr
end

for _, bufnr in ipairs(nio.api.nvim_list_bufs()) do
if nio.api.nvim_buf_get_name(bufnr):find(self.name, 1, true) then
nio.api.nvim_buf_delete(bufnr, { force = true })
end
end

self._bufnr = nio.api.nvim_create_buf(false, true)
nio.api.nvim_buf_set_name(self._bufnr, self.name)
for k, v in pairs(self._bufopts) do
Expand Down
182 changes: 182 additions & 0 deletions tests/unit/consumers/output_panel_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
local neotest = require("neotest")

local nio = require("nio")
local a = nio.tests

local stub = require("luassert.stub")
local Tree = require("neotest.types").Tree
local lib = require("neotest.lib")

local NeotestClient = require("neotest.client")
local AdapterGroup = require("neotest.adapters")

describe("neotest consumer - output_panel", function()
---@type neotest.Client
local client

---@type neotest.Adapter
local mock_adapter
local mock_strategy
local exit_future_1, exit_future_2

local dir = vim.loop.cwd()
local files
local dirs = { dir }

---@return neotest.Tree
local get_pos = function(...)
---@diagnostic disable-next-line
return client:get_position(...)
end

before_each(function()
dirs = { dir }
files = { dir .. "/test_file_1", dir .. "/test_file_2" }

stub(lib.files, "find", files)
stub(lib.files, "read", "Test results - passed and failed\r\n")
stub(lib.files, "is_dir", function(path)
return vim.tbl_contains(dirs, path)
end)
stub(lib.files, "exists", function(path)
return path ~= ""
end)

exit_future_1, exit_future_2 = nio.control.future(), nio.control.future()

mock_adapter = {
name = "adapter",

is_test_file = function(file_path)
return file_path ~= "" and not vim.endswith(file_path, lib.files.sep)
end,

root = function()
return dir
end,

discover_positions = function(file_path)
return Tree.from_list({
{ id = file_path, type = "file", path = file_path, name = file_path },
{
{
id = file_path .. "::namespace",
type = "namespace",
path = file_path,
name = "namespace",
range = { 5, 0, 50, 0 },
},
{
id = file_path .. "::test_a",
type = "test",
path = file_path,
name = "test_a",
range = { 10, 0, 20, 50 },
},
},
}, function(pos)
return pos.id
end)
end,

build_spec = function()
return { strategy = { output = "not_a_file" } }
end,

results = function(_, _, tree)
return {}
end,
}

mock_strategy = function(spec)
return {
is_complete = function()
return true
end,

output = function()
return type(spec.strategy) == "table" and spec.strategy.output or "not_a_file"
end,

stop = function()
exit_future_1.set()
exit_future_2.set()
end,

result = function()
if not exit_future_1.is_set() then
exit_future_1.wait()
else
exit_future_2.wait()
end
return type(spec.strategy) == "table" and spec.strategy.exit_code or 0
end,
}
end

client = NeotestClient(AdapterGroup())
---@diagnostic disable-next-line
neotest.setup({ adapters = { mock_adapter }, output_panel = { enabled = true } })

require("neotest.consumers.output_panel")(client)
---@diagnostic disable-next-line
client.listeners.results = { output_panel = client.listeners.results }
end)

after_each(function()
lib.files.find:revert()
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
vim.api.nvim_buf_delete(buf, { force = true })
end
end)

describe("user forcefully closes the panel", function()
local panel_bufnr = function()
return vim.tbl_filter(function(bufnr)
return nio.api.nvim_buf_get_name(bufnr):find("Neotest Output Panel")
end, nio.api.nvim_list_bufs())[1]
end

before_each(function()
neotest.output_panel.open()
end)

a.it("recreates terminal session if term channel is invalid", function()
local tree = get_pos(dir .. "/test_file_1")

nio.run(function()
client:run_tree(tree, { strategy = mock_strategy })
end)
exit_future_1.set()

nio.api.nvim_buf_delete(panel_bufnr(), { force = true })
neotest.output_panel.open()

nio.run(function()
assert.has_no_error(function()
client:run_tree(tree, { strategy = mock_strategy })
end)
end)
exit_future_2.set()
end)

it("recreates panel buffer if it was closed", function()
vim.api.nvim_buf_delete(panel_bufnr(), { force = true })

assert.has_no_error(function()
neotest.output_panel.open()
end)
end)

it("deletes panel buffer if it already exists with the same name", function()
vim.api.nvim_buf_delete(panel_bufnr(), { force = true })

local buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_buf_set_name(buf, "Neotest Output Panel")

assert.has_no_error(function()
neotest.output_panel.open()
end)
end)
end)
end)