From 2cfe0ef3722fb00e44dd2edbac28eca9b0c5a77c Mon Sep 17 00:00:00 2001 From: Julien Vincent Date: Mon, 17 Jun 2024 17:57:39 +0100 Subject: [PATCH] WIP: Split view layout --- lua/clojure-test/api/run.lua | 43 +++++---- lua/clojure-test/config.lua | 7 +- lua/clojure-test/ui/init.lua | 116 ++++++++++++++++++++++- lua/clojure-test/ui/layout.lua | 133 +++++++++------------------ lua/clojure-test/ui/layout/float.lua | 99 ++++++++++++++++++++ lua/clojure-test/ui/layout/split.lua | 88 ++++++++++++++++++ lua/clojure-test/ui/report-tree.lua | 58 ++++-------- 7 files changed, 388 insertions(+), 156 deletions(-) create mode 100644 lua/clojure-test/ui/layout/float.lua create mode 100644 lua/clojure-test/ui/layout/split.lua diff --git a/lua/clojure-test/api/run.lua b/lua/clojure-test/api/run.lua index b691c21..d39014b 100644 --- a/lua/clojure-test/api/run.lua +++ b/lua/clojure-test/api/run.lua @@ -1,7 +1,7 @@ local parser = require("clojure-test.api.report") local config = require("clojure-test.config") local eval = require("clojure-test.api.eval") -local ui = require("clojure-test.ui") +local interface_api = require("clojure-test.ui") local nio = require("nio") local function go_to_test(layout, test) @@ -10,14 +10,14 @@ local function go_to_test(layout, test) return end - layout:unmount() + layout:navigate_back() vim.cmd("edit " .. meta.file) vim.schedule(function() vim.api.nvim_win_set_cursor(0, { meta.line or 0, meta.column or 0 }) end) end -local function go_to_exception(layout, exception) +local function go_to_exception(ui, exception) local stack = exception["stack-trace"] if not stack or stack == vim.NIL then return @@ -37,7 +37,7 @@ local function go_to_exception(layout, exception) if symbol then local meta = eval.eval(eval.API.resolve_metadata_for_symbol, "'" .. symbol) if meta and meta ~= vim.NIL then - layout:unmount() + ui:navigate_back() vim.cmd("edit " .. meta.file) vim.schedule(function() vim.api.nvim_win_set_cursor(0, { line or meta.line or 0, meta.column or 0 }) @@ -53,40 +53,47 @@ end -- -- This function implements a kind of 'go-to-definition' for the various types -- of nodes -local function handle_on_enter(layout, node) +local function handle_go_to_event(ui, event) + local node = event.node nio.run(function() if node.test then - return go_to_test(layout, node.test) + return go_to_test(ui, node.test) end if node.assertion then if node.assertion.exception then - return go_to_exception(layout, node.assertion.exception[#node.assertion.exception]) + return go_to_exception(ui, node.assertion.exception[#node.assertion.exception]) end - return go_to_test(layout, node.test) + return go_to_test(ui, node.test) end if node.exception then - return go_to_exception(layout, node.exception) + return go_to_exception(ui, node.exception) end end) end local M = {} +local active_ui = nil + function M.run_tests(tests) if config.hooks.before_run then config.hooks.before_run(tests) end - local layout = ui.layout.create_test_layout() - - layout:mount() + local ui = active_ui + if not ui then + ui = interface_api.create(function(event) + if event.type == "go-to" then + return handle_go_to_event(ui, event) + end + end) + active_ui = ui + end - local tree = ui.report_tree.create_tree(layout, function(node) - handle_on_enter(layout, node) - end) + ui:mount() local reports = {} for _, test in ipairs(tests) do @@ -95,8 +102,7 @@ function M.run_tests(tests) local queue = nio.control.queue() - tree:set_reports(reports) - tree:render() + ui:render_reports(reports) local semaphore = nio.control.semaphore(1) for _, test in ipairs(tests) do @@ -120,8 +126,7 @@ function M.run_tests(tests) end reports[report.test] = parser.parse_test_report(report.test, report.data) - tree:set_reports(reports) - tree:render() + ui:render_reports(reports) end end diff --git a/lua/clojure-test/config.lua b/lua/clojure-test/config.lua index 4cf0a94..947a756 100644 --- a/lua/clojure-test/config.lua +++ b/lua/clojure-test/config.lua @@ -1,6 +1,10 @@ local backends = require("clojure-test.backends") local M = { + layout = { + style = "split", + }, + keys = { global = { run_all_tests = "ta", @@ -18,7 +22,8 @@ local M = { cycle_focus_forwards = "", cycle_focus_backwards = "", - quit = { "", "q" }, + quit = "q", + hide = "", }, }, diff --git a/lua/clojure-test/ui/init.lua b/lua/clojure-test/ui/init.lua index 85e1123..ba03d13 100644 --- a/lua/clojure-test/ui/init.lua +++ b/lua/clojure-test/ui/init.lua @@ -1,4 +1,112 @@ -return { - layout = require("clojure-test.ui.layout"), - report_tree = require("clojure-test.ui.report-tree"), -} +local report_tree_api = require("clojure-test.ui.report-tree") +local exceptions = require("clojure-test.ui.exceptions") +local layout_api = require("clojure-test.ui.layout") +local config = require("clojure-test.config") +local utils = require("clojure-test.utils") + +local M = {} + +local function write_clojure_to_buf(buf, contents) + vim.api.nvim_buf_set_option(buf, "filetype", "clojure") + + local lines = {} + if contents then + lines = vim.split(contents, "\n") + end + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) +end + +local function handle_on_move(UI, event) + local node = event.node + local layout = UI.layout + + if node.assertion then + if node.assertion.exception then + UI.layout:render_single() + exceptions.render_exception_to_buf(layout.windows.right.bufnr, node.assertion.exception) + return + end + + layout:render_double() + write_clojure_to_buf(layout.windows.left.bufnr, node.assertion.expected) + write_clojure_to_buf(layout.windows.right.bufnr, node.assertion.actual) + return + end + + if node.exception then + layout:render_single() + exceptions.render_exception_to_buf(layout.windows.right.bufnr, { node.exception }) + return + end + + layout:hide() +end + +function M.create(on_event) + local UI = { + mounted = false, + layout = nil, + tree = nil, + + last_active_window = vim.api.nvim_get_current_win(), + } + + function UI:mount() + if UI.mounted then + return + end + + UI.mounted = true + + UI.layout = layout_api.create(function(event) + if event.type == "on-focus-lost" then + return UI.layout:hide() + end + end) + UI.layout:mount() + + UI.tree = report_tree_api.create(UI.layout.windows.tree, function(event) + if event.type == "hover" then + return handle_on_move(UI, event) + end + + on_event(event) + end) + + for _, chord in ipairs(utils.into_table(config.keys.ui.quit)) do + UI.layout:map("n", chord, function() + UI:unmount() + end, { noremap = true }) + end + end + + function UI:unmount() + if not UI.mounted then + return + end + + UI.mounted = false + UI.layout:unmount() + UI.layout = nil + UI.tree = nil + + vim.api.nvim_set_current_win(UI.last_active_window) + end + + function UI:navigate_back() + -- UI.layout:hide() + vim.api.nvim_set_current_win(UI.last_active_window) + end + + function UI:render_reports(reports) + UI.tree:render_reports(reports) + end + + function UI:render_exceptions(exception_chain) end + + function UI:on() end + + return UI +end + +return M diff --git a/lua/clojure-test/ui/layout.lua b/lua/clojure-test/ui/layout.lua index 83168d1..50e2c45 100644 --- a/lua/clojure-test/ui/layout.lua +++ b/lua/clojure-test/ui/layout.lua @@ -1,7 +1,8 @@ +local FloatLayout = require("clojure-test.ui.layout.float") +local SplitLayout = require("clojure-test.ui.layout.split") + local config = require("clojure-test.config") local utils = require("clojure-test.utils") -local Layout = require("nui.layout") -local Popup = require("nui.popup") local M = {} @@ -41,10 +42,10 @@ local function cycle_focus(layout, direction) vim.api.nvim_set_current_win(window.winid) end -local function setup_quit_bindings(popup, layout) - for _, chord in ipairs(utils.into_table(config.keys.ui.quit)) do +local function setup_bindings(popup, layout, on_event) + for _, chord in ipairs(utils.into_table(config.keys.ui.hide)) do popup:map("n", chord, function() - layout:unmount() + layout:hide() end, { noremap = true }) end @@ -59,102 +60,52 @@ local function setup_quit_bindings(popup, layout) cycle_focus(layout, -1) end, { noremap = true }) end + + local event = require("nui.utils.autocmd").event + popup:on({ event.WinLeave }, function() + vim.schedule(function() + local currently_focused_window = vim.api.nvim_get_current_win() + local found = false + for _, win in pairs(layout.windows) do + if win.winid == currently_focused_window then + found = true + end + end + + if found then + return + end + + on_event({ + type = "on-focus-lost", + }) + end) + end, {}) end -function M.create_test_layout() - local top_left_popup = Popup({ - border = { - style = "rounded", - text = { - top = " Expected ", - top_align = "left", - }, - }, - }) - local top_right_popup = Popup({ - border = { - style = "rounded", - text = { - top = " Result ", - top_align = "left", - }, - }, - }) - - local report_popup = Popup({ - border = { - style = "rounded", - text = { - top = " Report ", - top_align = "left", - }, - }, - enter = true, - focusable = true, - }) - - local layout_side_by_side = Layout.Box({ - Layout.Box({ - Layout.Box(top_left_popup, { grow = 1 }), - Layout.Box(top_right_popup, { grow = 1 }), - }, { dir = "row", size = "70%" }), - - Layout.Box(report_popup, { size = "30%" }), - }, { dir = "col" }) - - local layout_single = Layout.Box({ - Layout.Box({ - Layout.Box(top_right_popup, { grow = 1 }), - }, { dir = "row", size = "70%" }), - - Layout.Box(report_popup, { size = "30%" }), - }, { dir = "col" }) - - local layout = Layout({ - position = "50%", - relative = "editor", - size = { - width = 150, - height = 60, - }, - }, layout_side_by_side) - - local TestLayout = { - layout = layout, - - windows = { - tree = report_popup, - left = top_left_popup, - right = top_right_popup, - }, - - last_active_window = vim.api.nvim_get_current_win(), - } - - function TestLayout:mount() - layout:mount() +function M.create(on_event) + local layout_fn = FloatLayout + if config.layout.style == "float" then + layout_fn = FloatLayout end - function TestLayout:hide_left() - top_left_popup:hide() - layout:update(layout_single) + if config.layout.style == "split" then + layout_fn = SplitLayout end - function TestLayout:show_left() - top_left_popup:show() - layout:update(layout_side_by_side) - end + local layout = layout_fn() - function TestLayout:unmount() - layout:unmount() - vim.api.nvim_set_current_win(TestLayout.last_active_window) + function layout:map(mode, chord, fn, opts) + layout.windows.tree:map(mode, chord, fn, opts) + layout.windows.left:map(mode, chord, fn, opts) + layout.windows.right:map(mode, chord, fn, opts) end - setup_quit_bindings(report_popup, TestLayout) - setup_quit_bindings(top_left_popup, TestLayout) - setup_quit_bindings(top_right_popup, TestLayout) + setup_bindings(layout.windows.tree, layout, on_event) + setup_bindings(layout.windows.left, layout, on_event) + setup_bindings(layout.windows.right, layout, on_event) - return TestLayout + return layout end return M diff --git a/lua/clojure-test/ui/layout/float.lua b/lua/clojure-test/ui/layout/float.lua new file mode 100644 index 0000000..1d73608 --- /dev/null +++ b/lua/clojure-test/ui/layout/float.lua @@ -0,0 +1,99 @@ +local NuiLayout = require("nui.layout") +local NuiPopup = require("nui.popup") + +return function() + local top_left_popup = NuiPopup({ + border = { + style = "rounded", + text = { + top = " Expected ", + top_align = "left", + }, + }, + }) + local top_right_popup = NuiPopup({ + border = { + style = "rounded", + text = { + top = " Result ", + top_align = "left", + }, + }, + }) + + local report_popup = NuiPopup({ + border = { + style = "rounded", + text = { + top = " Report ", + top_align = "left", + }, + }, + enter = true, + focusable = true, + }) + + local double = NuiLayout.Box({ + NuiLayout.Box({ + NuiLayout.Box(top_left_popup, { grow = 1 }), + NuiLayout.Box(top_right_popup, { grow = 1 }), + }, { dir = "row", size = "70%" }), + + NuiLayout.Box(report_popup, { size = "30%" }), + }, { dir = "col" }) + + local single = NuiLayout.Box({ + NuiLayout.Box({ + NuiLayout.Box(top_right_popup, { grow = 1 }), + }, { dir = "row", size = "70%" }), + + NuiLayout.Box(report_popup, { size = "30%" }), + }, { dir = "col" }) + + local root_layout = NuiLayout({ + position = "50%", + relative = "editor", + size = { + width = 150, + height = 60, + }, + }, double) + + local FloatLayout = { + layout = root_layout, + + windows = { + tree = report_popup, + left = top_left_popup, + right = top_right_popup, + }, + } + + function FloatLayout:mount() + root_layout:mount() + end + + function FloatLayout:render_single() + top_left_popup:hide() + root_layout:update(single) + end + + function FloatLayout:render_double() + top_left_popup:show() + root_layout:update(double) + end + + function FloatLayout:unmount() + root_layout:unmount() + end + + function FloatLayout:hide() + FloatLayout:unmount() + end + + function FloatLayout:show() + FloatLayout:mount() + end + + return FloatLayout +end diff --git a/lua/clojure-test/ui/layout/split.lua b/lua/clojure-test/ui/layout/split.lua new file mode 100644 index 0000000..233b4dd --- /dev/null +++ b/lua/clojure-test/ui/layout/split.lua @@ -0,0 +1,88 @@ +local NuiLayout = require("nui.layout") +local NuiPopup = require("nui.popup") +local NuiSplit = require("nui.split") + +return function() + local top_left_popup = NuiPopup({ + border = { + style = "rounded", + text = { + top = " Expected ", + top_align = "left", + }, + }, + }) + local top_right_popup = NuiPopup({ + border = { + style = "rounded", + text = { + top = " Result ", + top_align = "left", + }, + }, + }) + + local report_tree = NuiSplit({ + position = "right", + size = "25%", + enter = true, + focusable = true, + }) + + local double = NuiLayout.Box({ + NuiLayout.Box(top_left_popup, { grow = 1 }), + NuiLayout.Box(top_right_popup, { grow = 1 }), + }, { dir = "row", size = "70%" }) + + local single = NuiLayout.Box({ + NuiLayout.Box(top_right_popup, { grow = 1 }), + }, { dir = "row", size = "70%" }) + + local root_layout = NuiLayout({ + position = "50%", + relative = "editor", + size = { + width = 150, + height = 60, + }, + }, double) + + local SplitLayout = { + layout = root_layout, + + windows = { + tree = report_tree, + left = top_left_popup, + right = top_right_popup, + }, + } + + function SplitLayout:mount() + report_tree:mount() + end + + function SplitLayout:render_single() + root_layout:mount() + top_left_popup:hide() + root_layout:update(single) + end + + function SplitLayout:render_double() + root_layout:mount() + top_left_popup:show() + root_layout:update(double) + end + + function SplitLayout:unmount() + report_tree:unmount() + root_layout:unmount() + end + + function SplitLayout:hide() + root_layout:unmount() + end + + function SplitLayout:show() end + + return SplitLayout +end diff --git a/lua/clojure-test/ui/report-tree.lua b/lua/clojure-test/ui/report-tree.lua index a6da38e..ff33bfe 100644 --- a/lua/clojure-test/ui/report-tree.lua +++ b/lua/clojure-test/ui/report-tree.lua @@ -1,4 +1,3 @@ -local exceptions = require("clojure-test.ui.exceptions") local config = require("clojure-test.config") local utils = require("clojure-test.utils") @@ -102,23 +101,12 @@ local function reports_to_nodes(reports) return nodes end -local function write_clojure_to_buf(buf, contents) - vim.api.nvim_buf_set_option(buf, "filetype", "clojure") - - local lines = {} - if contents then - lines = vim.split(contents, "\n") - end - vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) -end - local M = {} -function M.create_tree(layout, on_enter_cb) - local window = layout.windows.tree - +function M.create(window, on_event) local tree = NuiTree({ winid = window.winid, + ns_id = "testns", nodes = {}, prepare_node = function(node) local line = NuiLine() @@ -143,6 +131,10 @@ function M.create_tree(layout, on_enter_cb) end, }) + local ReportTree = { + tree = tree, + } + local map_options = { noremap = true, nowait = true } for _, chord in ipairs(utils.into_table(config.keys.ui.collapse_node)) do @@ -178,44 +170,28 @@ function M.create_tree(layout, on_enter_cb) return end - on_enter_cb(node) + on_event({ + type = "go-to", + node = node, + }) end, map_options) end local event = require("nui.utils.autocmd").event - window:on({ event.CursorMoved }, function() local node = tree:get_node() - - if node.assertion then - write_clojure_to_buf(layout.windows.left.bufnr, node.assertion.expected) - - if node.assertion.exception then - layout:hide_left() - exceptions.render_exception_to_buf(layout.windows.right.bufnr, node.assertion.exception) - else - layout:show_left() - write_clojure_to_buf(layout.windows.right.bufnr, node.assertion.actual) - end + if not node then + return end - if node.exception then - write_clojure_to_buf(layout.windows.left.bufnr) - - layout:hide_left() - exceptions.render_exception_to_buf(layout.windows.right.bufnr, { node.exception }) - end + on_event({ + type = "hover", + node = node, + }) end, {}) - local ReportTree = { - tree = tree, - } - - function ReportTree:set_reports(reports) + function ReportTree:render_reports(reports) tree:set_nodes(reports_to_nodes(reports)) - end - - function ReportTree:render() tree:render() end