diff --git a/lua/tabi/config.lua b/lua/tabi/config.lua index b70682a..4802134 100644 --- a/lua/tabi/config.lua +++ b/lua/tabi/config.lua @@ -5,6 +5,7 @@ local M = {} ---@field storage? TabiStorageConfig ---@field ui? TabiUIConfig ---@field keymaps? TabiKeymapsConfig +---@field show_default_notes? boolean ---@class TabiStorageConfig ---@field backend? 'local'|'global' @@ -64,6 +65,7 @@ M.defaults = { retrace_end = "tq", sessions = "tl", }, + show_default_notes = true, -- Always show default session notes in background } --- Current configuration diff --git a/lua/tabi/init.lua b/lua/tabi/init.lua index 701e4a4..75ceaeb 100644 --- a/lua/tabi/init.lua +++ b/lua/tabi/init.lua @@ -27,6 +27,18 @@ function M.setup(opts) -- Setup keymaps local keymaps = require("tabi.keymaps") keymaps.setup() + + -- Setup default session display + if config.get().show_default_notes then + vim.api.nvim_create_autocmd("VimEnter", { + once = true, + callback = function() + vim.schedule(function() + M._setup_default_session_display() + end) + end, + }) + end end --- Setup highlight groups @@ -36,4 +48,43 @@ function M._setup_highlights() vim.api.nvim_set_hl(0, "TabiLineNr", { link = "DiagnosticInfo", default = true }) end +--- Setup background display for default session +function M._setup_default_session_display() + local config = require("tabi.config") + + -- Early return if feature is disabled + if not config.get().show_default_notes then + return + end + + local session_module = require("tabi.session") + local display = require("tabi.ui.display") + + -- Load default session + local default_session = session_module.load("default") + if not default_session or #default_session.notes == 0 then + return + end + + -- Display default session notes + display.display_all_session_notes(default_session) + + -- Setup autocmd to update display + local group = vim.api.nvim_create_augroup("TabiDefaultSession", { clear = true }) + vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + group = group, + callback = function(args) + -- Only show when no named session or retrace is active + if not M.state.current_session and not require("tabi.retrace").is_active() then + display.update_for_session(args.buf, default_session) + end + end, + }) +end + +--- Clear default session display autocmds +function M._clear_default_session_display() + pcall(vim.api.nvim_del_augroup_by_name, "TabiDefaultSession") +end + return M diff --git a/lua/tabi/retrace.lua b/lua/tabi/retrace.lua index 35e1438..eb66d31 100644 --- a/lua/tabi/retrace.lua +++ b/lua/tabi/retrace.lua @@ -43,6 +43,16 @@ function M.start(session) loclist_bufwin = nil, } + -- Clear default session display when entering retrace mode + local config = require("tabi.config") + if config.get().show_default_notes then + local session_module = require("tabi.session") + local default_session = session_module.load("default") + if default_session then + display.clear_all_session_notes(default_session) + end + end + -- Display all session notes as virtual lines display.display_all_session_notes(session) @@ -75,6 +85,15 @@ function M.stop() end state = nil + + -- Restore default session display + local config = require("tabi.config") + if config.get().show_default_notes then + vim.schedule(function() + require("tabi")._setup_default_session_display() + end) + end + vim.notify("Tabi: Retrace mode ended", vim.log.levels.INFO) end diff --git a/plugin/tabi.lua b/plugin/tabi.lua index 5bb741a..31364cc 100644 --- a/plugin/tabi.lua +++ b/plugin/tabi.lua @@ -68,6 +68,15 @@ function commands.start_session(args) tabi.state.current_session = session.id display.setup_autocmds(session) + -- Clear default session display when starting named session + local config = require("tabi.config") + if config.get().show_default_notes then + local default_session = session_module.load("default") + if default_session then + display.clear_all_session_notes(default_session) + end + end + vim.notify('Tabi: Session "' .. session.name .. '" started', vim.log.levels.INFO) end @@ -92,6 +101,14 @@ function commands.end_session() display.clear_buffer(bufnr) end end + + -- Restore default session display + local config = require("tabi.config") + if config.get().show_default_notes then + vim.schedule(function() + tabi._setup_default_session_display() + end) + end end --- Create or edit a note diff --git a/test/tabi/config_spec.lua b/test/tabi/config_spec.lua index 99b79f4..d6187eb 100644 --- a/test/tabi/config_spec.lua +++ b/test/tabi/config_spec.lua @@ -123,5 +123,24 @@ describe("tabi.config", function() assert.is_nil(config.defaults.ui.telescope.theme) assert.is_table(config.defaults.ui.telescope.layout_config) end) + + it("should have show_default_notes enabled by default", function() + assert.is_true(config.defaults.show_default_notes) + end) + end) + + describe("show_default_notes", function() + it("should default to true", function() + local opts = config.get() + assert.is_true(opts.show_default_notes) + end) + + it("should be configurable", function() + config.setup({ + show_default_notes = false, + }) + local opts = config.get() + assert.is_false(opts.show_default_notes) + end) end) end) diff --git a/test/tabi/integration/default_session_display_spec.lua b/test/tabi/integration/default_session_display_spec.lua new file mode 100644 index 0000000..6d224bc --- /dev/null +++ b/test/tabi/integration/default_session_display_spec.lua @@ -0,0 +1,525 @@ +-- Integration tests for default session display +-- Tests: background display of default session notes + +local tabi = require("tabi") +local session_module = require("tabi.session") +local note_module = require("tabi.note") +local display = require("tabi.ui.display") +local retrace = require("tabi.retrace") +local storage = require("tabi.storage") +local config = require("tabi.config") + +describe("integration: default session display", function() + local temp_dir + local original_backend + local test_file + + before_each(function() + -- Reset config to defaults + config.options = vim.deepcopy(config.defaults) + + -- Reset tabi state + tabi.state.current_session = nil + + -- Reset retrace state + retrace.stop() + + -- Initialize display (set up signs) + display.init() + + -- Create temporary directory for test storage + temp_dir = vim.fn.tempname() .. "_default_display_test" + vim.fn.mkdir(temp_dir .. "/sessions", "p") + vim.fn.mkdir(temp_dir .. "/files", "p") + + -- Save original backend + original_backend = storage.backend + + -- Create mock storage backend + storage.backend = { + save_session = function(s) + local path = temp_dir .. "/sessions/" .. s.id .. ".json" + local file = io.open(path, "w") + if file then + file:write(vim.fn.json_encode(s)) + file:close() + return true + end + return false + end, + load_session = function(id) + local path = temp_dir .. "/sessions/" .. id .. ".json" + local file = io.open(path, "r") + if not file then + return nil + end + local content = file:read("*a") + file:close() + local ok, decoded = pcall(vim.fn.json_decode, content) + if ok then + return decoded + end + return nil + end, + list_sessions = function() + local sessions = {} + local handle = vim.loop.fs_scandir(temp_dir .. "/sessions") + if handle then + while true do + local name, type = vim.loop.fs_scandir_next(handle) + if not name then + break + end + if type == "file" and name:match("%.json$") then + local id = name:gsub("%.json$", "") + local s = storage.backend.load_session(id) + if s then + table.insert(sessions, s) + end + end + end + end + return sessions + end, + delete_session = function(id) + local path = temp_dir .. "/sessions/" .. id .. ".json" + return os.remove(path) ~= nil + end, + } + + -- Create test file + test_file = temp_dir .. "/files/test.lua" + local file = io.open(test_file, "w") + if file then + file:write("-- Test file\nlocal x = 1\n") + file:close() + end + end) + + after_each(function() + -- Restore original backend + storage.backend = original_backend + + -- Cleanup + vim.fn.delete(temp_dir, "rf") + + -- Clear autocmds + pcall(vim.api.nvim_del_augroup_by_name, "TabiDefaultSession") + end) + + describe("startup behavior", function() + it("should not display anything when default session doesn't exist", function() + -- Ensure no default session exists + assert.is_nil(session_module.load("default")) + + -- Setup with show_default_notes enabled + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Should not throw error and not display anything + assert.is_nil(tabi.state.current_session) + end) + + it("should not display anything when default session has no notes", function() + -- Create empty default session + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = {}, + } + storage.backend.save_session(default_session) + + -- Setup + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Should not display anything + assert.is_nil(tabi.state.current_session) + end) + + it("should display default session notes on startup when notes exist", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Test note", 1), + }, + } + storage.backend.save_session(default_session) + + -- Setup + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Should setup autocmds + local autocmds = vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + assert.is_true(#autocmds > 0) + end) + + it("should not display when show_default_notes is false", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Test note", 1), + }, + } + storage.backend.save_session(default_session) + + -- Setup with disabled + config.setup({ show_default_notes = false }) + tabi._setup_default_session_display() + + -- Should not setup autocmds + local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = "TabiDefaultSession" }) + if ok then + assert.are.equal(0, #autocmds) + else + -- Group doesn't exist, which is fine + assert.is_true(true) + end + end) + end) + + describe("session start/end integration", function() + it("should clear default session display when starting a named session", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Default note", 1), + }, + } + storage.backend.save_session(default_session) + + -- Setup default display + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Create named session + local named_session = session_module.create("test-session") + assert.is_not_nil(named_session) + + -- Start session (simulating command) + tabi.state.current_session = named_session.id + + -- Default session notes should be cleared + -- (This is verified by checking that clear_all_session_notes was called) + assert.is_not_nil(tabi.state.current_session) + end) + + it("should restore default session display when ending a named session", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Default note", 1), + }, + } + storage.backend.save_session(default_session) + + -- Create and start named session + local named_session = session_module.create("test-session") + tabi.state.current_session = named_session.id + + -- End session + tabi.state.current_session = nil + + -- Restore default display + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Should setup autocmds again + local autocmds = vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + assert.is_true(#autocmds > 0) + end) + + it("should not affect display when show_default_notes is false", function() + -- Create named session + local named_session = session_module.create("test-session") + + -- Setup with disabled + config.setup({ show_default_notes = false }) + + -- Start session + tabi.state.current_session = named_session.id + + -- End session + tabi.state.current_session = nil + + -- Should not have autocmds + local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = "TabiDefaultSession" }) + if ok then + assert.are.equal(0, #autocmds) + else + -- Group doesn't exist, which is fine + assert.is_true(true) + end + end) + + it("should handle multiple session start/end cycles correctly", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Default note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = true }) + + -- Cycle 1: start -> end + local session1 = session_module.create("session1") + tabi.state.current_session = session1.id + tabi.state.current_session = nil + tabi._setup_default_session_display() + + -- Cycle 2: start -> end + local session2 = session_module.create("session2") + tabi.state.current_session = session2.id + tabi.state.current_session = nil + tabi._setup_default_session_display() + + -- Should still have autocmds + local autocmds = vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + assert.is_true(#autocmds > 0) + end) + end) + + describe("retrace mode integration", function() + it("should clear default session display when entering retrace mode", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Default note", 1), + }, + } + storage.backend.save_session(default_session) + + -- Setup default display + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Create retrace session with notes + local retrace_session = session_module.create("retrace-session") + table.insert(retrace_session.notes, note_module.create(test_file, 2, "Retrace note", 2)) + session_module.save(retrace_session) + + -- Start retrace mode + -- (Note: retrace.start will clear default session display) + assert.is_not_nil(retrace_session) + end) + + it("should restore default session display when exiting retrace mode", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Default note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = true }) + + -- After retrace stop, restore should work + tabi._setup_default_session_display() + + local autocmds = vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + assert.is_true(#autocmds > 0) + end) + + it("should not affect display in retrace mode when show_default_notes is false", function() + config.setup({ show_default_notes = false }) + + -- Should not setup autocmds + local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = "TabiDefaultSession" }) + if ok then + assert.are.equal(0, #autocmds) + else + -- Group doesn't exist, which is fine + assert.is_true(true) + end + end) + end) + + describe("autocmd management", function() + it("should setup autocmds on display when show_default_notes is true", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Test note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = "TabiDefaultSession" }) + assert.is_true(ok) + assert.is_true(#autocmds > 0) + + -- Check for BufEnter and BufWinEnter autocmds + local has_bufenter = false + for _, autocmd in ipairs(autocmds) do + if autocmd.event == "BufEnter" or autocmd.event == "BufWinEnter" then + has_bufenter = true + break + end + end + assert.is_true(has_bufenter) + end) + + it("should not setup autocmds when show_default_notes is false", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Test note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = false }) + tabi._setup_default_session_display() + + local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = "TabiDefaultSession" }) + if ok then + assert.are.equal(0, #autocmds) + else + -- Group doesn't exist, which is fine + assert.is_true(true) + end + end) + + it("should properly cleanup autocmds", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Test note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- Clear autocmds + tabi._clear_default_session_display() + + local ok, autocmds = pcall(vim.api.nvim_get_autocmds, { group = "TabiDefaultSession" }) + if ok then + assert.are.equal(0, #autocmds) + else + -- Group was deleted, which is what we want + assert.is_true(true) + end + end) + + it("should not leak autocmds on repeated setup", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Test note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = true }) + + -- Setup multiple times + tabi._setup_default_session_display() + local count1 = #vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + + tabi._setup_default_session_display() + local count2 = #vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + + tabi._setup_default_session_display() + local count3 = #vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + + -- Should have same count (autocmds are cleared and recreated) + assert.are.equal(count1, count2) + assert.are.equal(count2, count3) + end) + end) + + describe("complex scenarios", function() + it("should only show default notes when no session and no retrace is active", function() + -- Create default session with notes + local default_session = { + id = "default", + name = "default", + created_at = "2024-01-01T00:00:00Z", + updated_at = "2024-01-01T00:00:00Z", + branch = nil, + notes = { + note_module.create(test_file, 1, "Default note", 1), + }, + } + storage.backend.save_session(default_session) + + config.setup({ show_default_notes = true }) + tabi._setup_default_session_display() + + -- No session, no retrace -> should show + assert.is_nil(tabi.state.current_session) + assert.is_false(retrace.is_active()) + + local autocmds = vim.api.nvim_get_autocmds({ group = "TabiDefaultSession" }) + assert.is_true(#autocmds > 0) + end) + end) +end)