diff --git a/lua/codediff/config.lua b/lua/codediff/config.lua index 0b52d3d..3bc4c1b 100644 --- a/lua/codediff/config.lua +++ b/lua/codediff/config.lua @@ -111,6 +111,7 @@ M.defaults = { restore = "X", -- Discard changes to file (restore to index/HEAD) toggle_changes = "gu", -- Toggle Changes (unstaged) group visibility toggle_staged = "gs", -- Toggle Staged Changes group visibility + toggle_layout = "gl", -- Toggle diff layout between 'side-by-side' and 'inline' }, history = { select = "", -- Select commit/file or toggle expand diff --git a/lua/codediff/ui/explorer/actions.lua b/lua/codediff/ui/explorer/actions.lua index 6c9b3fa..07111f2 100644 --- a/lua/codediff/ui/explorer/actions.lua +++ b/lua/codediff/ui/explorer/actions.lua @@ -297,6 +297,122 @@ function M.toggle_stage_entry(explorer, tree) end end +-- Toggle diff layout between 'side-by-side' and 'inline' +function M.toggle_layout(explorer) + local lifecycle = require("codediff.ui.lifecycle") + local layout_manager = require("codediff.ui.layout") + + local tabpage = explorer and explorer.tabpage or vim.api.nvim_get_current_tabpage() + local session = lifecycle.get_session(tabpage) + + if not session then + vim.notify("No active diff session", vim.log.levels.WARN) + return + end + + -- Don't toggle in conflict mode + if session.result_win and vim.api.nvim_win_is_valid(session.result_win) then + vim.notify("Cannot toggle layout in conflict mode", vim.log.levels.WARN) + return + end + + local current_layout = session.layout or "side-by-side" + local new_layout = current_layout == "inline" and "side-by-side" or "inline" + + -- Update global config so subsequent file selections use the new layout + config.options.diff.layout = new_layout + + -- Build session_config from current session state for re-rendering + local session_config = { + mode = session.mode, + git_root = session.git_root, + original_path = session.original_path, + modified_path = session.modified_path, + original_revision = session.original_revision, + modified_revision = session.modified_revision, + } + + local is_placeholder = (session.original_path == "" and session.modified_path == "") + + if new_layout == "side-by-side" then + -- inline → side-by-side: create new window for the original side + local modified_win = session.modified_win + if not modified_win or not vim.api.nvim_win_is_valid(modified_win) then + return + end + + -- Clear inline decorations from the modified buffer + local inline_mod = require("codediff.ui.inline") + if session.modified_bufnr and vim.api.nvim_buf_is_valid(session.modified_bufnr) then + inline_mod.clear(session.modified_bufnr) + end + lifecycle.clear_highlights(session.modified_bufnr) + + -- Create original window (split on the appropriate side) + local split_cmd = config.options.diff.original_position == "right" and "rightbelow vsplit" or "leftabove vsplit" + vim.api.nvim_set_current_win(modified_win) + vim.cmd(split_cmd) + local original_win = vim.api.nvim_get_current_win() + vim.w[original_win].codediff_restore = 1 + + -- Update session window state + session.original_win = original_win + session.layout = nil -- nil means side-by-side (default) + + if is_placeholder then + -- Load scratch buffer into the new original window + local orig_scratch = vim.api.nvim_create_buf(false, true) + vim.bo[orig_scratch].buftype = "nofile" + vim.api.nvim_win_set_buf(original_win, orig_scratch) + session.original_bufnr = orig_scratch + layout_manager.arrange(tabpage) + else + vim.schedule(function() + require("codediff.ui.view.side_by_side").update(tabpage, session_config, false) + layout_manager.arrange(tabpage) + end) + end + else + -- side-by-side → inline: close original_win, keep modified_win + local original_win = session.original_win + local modified_win = session.modified_win + + if not modified_win or not vim.api.nvim_win_is_valid(modified_win) then + return + end + + -- Clear diff highlights from both buffers + lifecycle.clear_highlights(session.original_bufnr) + lifecycle.clear_highlights(session.modified_bufnr) + + -- Disable scrollbind on modified window before closing original + if vim.api.nvim_win_is_valid(modified_win) then + vim.wo[modified_win].scrollbind = false + end + + -- Close original window if it is distinct from modified + if original_win and vim.api.nvim_win_is_valid(original_win) and original_win ~= modified_win then + vim.api.nvim_set_current_win(modified_win) + pcall(vim.api.nvim_win_close, original_win, false) + end + + -- Collapse session to single window + session.original_win = modified_win + session.layout = "inline" + + if is_placeholder then + layout_manager.arrange(tabpage) + else + vim.schedule(function() + require("codediff.ui.view.inline_view").update(tabpage, session_config, false) + layout_manager.arrange(tabpage) + end) + end + end + + vim.notify("Layout: " .. new_layout, vim.log.levels.INFO) +end + -- Stage all files function M.stage_all(explorer) if not explorer or not explorer.git_root then diff --git a/lua/codediff/ui/explorer/init.lua b/lua/codediff/ui/explorer/init.lua index 63e50b1..77455fb 100644 --- a/lua/codediff/ui/explorer/init.lua +++ b/lua/codediff/ui/explorer/init.lua @@ -19,6 +19,7 @@ M.navigate_next = actions.navigate_next M.navigate_prev = actions.navigate_prev M.toggle_visibility = actions.toggle_visibility M.toggle_view_mode = actions.toggle_view_mode +M.toggle_layout = actions.toggle_layout M.toggle_stage_entry = actions.toggle_stage_entry M.toggle_stage_file = actions.toggle_stage_file M.stage_all = actions.stage_all diff --git a/lua/codediff/ui/explorer/keymaps.lua b/lua/codediff/ui/explorer/keymaps.lua index 2ed79a2..11a32f8 100644 --- a/lua/codediff/ui/explorer/keymaps.lua +++ b/lua/codediff/ui/explorer/keymaps.lua @@ -171,6 +171,13 @@ function M.setup(explorer) end, vim.tbl_extend("force", map_options, { buffer = split.bufnr, desc = "Toggle Staged Changes visibility" })) end + -- Toggle layout between 'side-by-side' and 'inline' (gl) + if explorer_keymaps.toggle_layout then + vim.keymap.set("n", explorer_keymaps.toggle_layout, function() + actions_module.toggle_layout(explorer) + end, vim.tbl_extend("force", map_options, { buffer = split.bufnr, desc = "Toggle side-by-side/inline layout" })) + end + -- Note: next_file/prev_file keymaps are set via view/keymaps.lua:setup_all_keymaps() -- which uses set_tab_keymap to set them on all buffers including explorer end diff --git a/lua/codediff/ui/view/keymaps.lua b/lua/codediff/ui/view/keymaps.lua index d72e46a..44be8cf 100644 --- a/lua/codediff/ui/view/keymaps.lua +++ b/lua/codediff/ui/view/keymaps.lua @@ -901,6 +901,15 @@ function M.setup_all_keymaps(tabpage, original_bufnr, modified_bufnr, is_explore if keymaps.align_move and not is_inline and config.options.diff.compute_moves then lifecycle.set_tab_keymap(tabpage, "n", keymaps.align_move, align_move, { desc = "Align moved code block" }) end + + -- Toggle layout (side-by-side ↔ inline) - tab-wide, available from any diff buffer + local explorer_keymaps = config.options.keymaps.explorer or {} + if explorer_keymaps.toggle_layout then + lifecycle.set_tab_keymap(tabpage, "n", explorer_keymaps.toggle_layout, function() + local explorer_actions = require("codediff.ui.explorer.actions") + explorer_actions.toggle_layout(nil) + end, { desc = "Toggle side-by-side/inline layout" }) + end end return M