diff --git a/lua/codetyper/adapters/nvim/ui/diff_review.lua b/lua/codetyper/adapters/nvim/ui/diff_review.lua index 8fcd872..050325b 100644 --- a/lua/codetyper/adapters/nvim/ui/diff_review.lua +++ b/lua/codetyper/adapters/nvim/ui/diff_review.lua @@ -5,394 +5,20 @@ local M = {} -local utils = require("codetyper.support.utils") -local prompts = require("codetyper.prompts.agents.diff") +local get_config_utils = require("codetyper.utils.get_config") ----@class DiffEntry ----@field path string File path ----@field operation string "create"|"edit"|"delete" ----@field original string|nil Original content (nil for new files) ----@field modified string New/modified content ----@field approved boolean Whether change was approved ----@field applied boolean Whether change was applied - ----@class DiffReviewState ----@field entries DiffEntry[] List of changes ----@field current_index number Currently selected entry ----@field list_buf number|nil File list buffer ----@field list_win number|nil File list window ----@field diff_buf number|nil Diff view buffer ----@field diff_win number|nil Diff view window ----@field is_open boolean Whether review UI is open - -local state = { - entries = {}, - current_index = 1, - list_buf = nil, - list_win = nil, - diff_buf = nil, - diff_win = nil, - is_open = false, -} - ---- Clear all collected diffs -function M.clear() - state.entries = {} - state.current_index = 1 -end - ---- Add a diff entry ----@param entry DiffEntry -function M.add(entry) - table.insert(state.entries, entry) -end - ---- Get all entries ----@return DiffEntry[] -function M.get_entries() - return state.entries -end - ---- Get entry count ----@return number -function M.count() - return #state.entries -end - ---- Generate unified diff between two strings ----@param original string|nil ----@param modified string ----@param filepath string ----@return string[] -local function generate_diff_lines(original, modified, filepath) - local lines = {} - local filename = vim.fn.fnamemodify(filepath, ":t") - - if not original then - -- New file - table.insert(lines, "--- /dev/null") - table.insert(lines, "+++ b/" .. filename) - table.insert(lines, "@@ -0,0 +1," .. #vim.split(modified, "\n") .. " @@") - for _, line in ipairs(vim.split(modified, "\n")) do - table.insert(lines, "+" .. line) - end - else - -- Modified file - use vim's diff - table.insert(lines, "--- a/" .. filename) - table.insert(lines, "+++ b/" .. filename) - - local orig_lines = vim.split(original, "\n") - local mod_lines = vim.split(modified, "\n") - - -- Simple diff: show removed and added lines - local max_lines = math.max(#orig_lines, #mod_lines) - local context_start = 1 - local in_change = false - - for i = 1, max_lines do - local orig = orig_lines[i] or "" - local mod = mod_lines[i] or "" - - if orig ~= mod then - if not in_change then - table.insert( - lines, - string.format( - "@@ -%d,%d +%d,%d @@", - math.max(1, i - 2), - math.min(5, #orig_lines - i + 3), - math.max(1, i - 2), - math.min(5, #mod_lines - i + 3) - ) - ) - in_change = true - end - if orig ~= "" then - table.insert(lines, "-" .. orig) - end - if mod ~= "" then - table.insert(lines, "+" .. mod) - end - else - if in_change then - table.insert(lines, " " .. orig) - in_change = false - end - end - end - end - - return lines -end - ---- Update the diff view for current entry -local function update_diff_view() - if not state.diff_buf or not vim.api.nvim_buf_is_valid(state.diff_buf) then - return - end - - local entry = state.entries[state.current_index] - local ui_prompts = prompts.review - if not entry then - vim.bo[state.diff_buf].modifiable = true - vim.api.nvim_buf_set_lines(state.diff_buf, 0, -1, false, { ui_prompts.messages.no_changes_short }) - vim.bo[state.diff_buf].modifiable = false - return - end - - local lines = {} - - -- Header - local status_icon = entry.applied and " " or (entry.approved and " " or " ") - local op_icon = entry.operation == "create" and "+" or (entry.operation == "delete" and "-" or "~") - local current_status = entry.applied and ui_prompts.status.applied - or (entry.approved and ui_prompts.status.approved or ui_prompts.status.pending) - - table.insert( - lines, - string.format(ui_prompts.diff_header.top, status_icon, op_icon, vim.fn.fnamemodify(entry.path, ":t")) - ) - table.insert(lines, string.format(ui_prompts.diff_header.path, entry.path)) - table.insert(lines, string.format(ui_prompts.diff_header.op, entry.operation)) - table.insert(lines, string.format(ui_prompts.diff_header.status, current_status)) - table.insert(lines, ui_prompts.diff_header.bottom) - table.insert(lines, "") - - -- Diff content - local diff_lines = generate_diff_lines(entry.original, entry.modified, entry.path) - for _, line in ipairs(diff_lines) do - table.insert(lines, line) - end - - vim.bo[state.diff_buf].modifiable = true - vim.api.nvim_buf_set_lines(state.diff_buf, 0, -1, false, lines) - vim.bo[state.diff_buf].modifiable = false - vim.bo[state.diff_buf].filetype = "diff" -end - ---- Update the file list -local function update_file_list() - if not state.list_buf or not vim.api.nvim_buf_is_valid(state.list_buf) then - return - end - - local ui_prompts = prompts.review - local lines = {} - table.insert(lines, string.format(ui_prompts.list_menu.top, #state.entries)) - for _, item in ipairs(ui_prompts.list_menu.items) do - table.insert(lines, item) - end - table.insert(lines, ui_prompts.list_menu.bottom) - table.insert(lines, "") - - for i, entry in ipairs(state.entries) do - local prefix = (i == state.current_index) and "▶ " or " " - local status = entry.applied and "" or (entry.approved and "" or "○") - local op = entry.operation == "create" and "[+]" or (entry.operation == "delete" and "[-]" or "[~]") - local filename = vim.fn.fnamemodify(entry.path, ":t") - - table.insert(lines, string.format("%s%s %s %s", prefix, status, op, filename)) - end - - if #state.entries == 0 then - table.insert(lines, ui_prompts.messages.no_changes) - end - - vim.bo[state.list_buf].modifiable = true - vim.api.nvim_buf_set_lines(state.list_buf, 0, -1, false, lines) - vim.bo[state.list_buf].modifiable = false - - -- Highlight current line - if state.list_win and vim.api.nvim_win_is_valid(state.list_win) then - local target_line = 9 + state.current_index - 1 - if target_line <= vim.api.nvim_buf_line_count(state.list_buf) then - vim.api.nvim_win_set_cursor(state.list_win, { target_line, 0 }) - end - end -end - ---- Navigate to next entry -function M.next() - if state.current_index < #state.entries then - state.current_index = state.current_index + 1 - update_file_list() - update_diff_view() - end -end - ---- Navigate to previous entry -function M.prev() - if state.current_index > 1 then - state.current_index = state.current_index - 1 - update_file_list() - update_diff_view() - end -end - ---- Approve current entry -function M.approve_current() - local entry = state.entries[state.current_index] - if entry and not entry.applied then - entry.approved = true - update_file_list() - update_diff_view() - end -end - ---- Reject current entry -function M.reject_current() - local entry = state.entries[state.current_index] - if entry and not entry.applied then - entry.approved = false - update_file_list() - update_diff_view() - end -end - ---- Approve all entries -function M.approve_all() - for _, entry in ipairs(state.entries) do - if not entry.applied then - entry.approved = true - end - end - update_file_list() - update_diff_view() -end - ---- Apply approved changes -function M.apply_approved() - local applied_count = 0 - - for _, entry in ipairs(state.entries) do - if entry.approved and not entry.applied then - if entry.operation == "create" or entry.operation == "edit" then - local ok = utils.write_file(entry.path, entry.modified) - if ok then - entry.applied = true - applied_count = applied_count + 1 - end - elseif entry.operation == "delete" then - local ok = os.remove(entry.path) - if ok then - entry.applied = true - applied_count = applied_count + 1 - end - end - end - end - - update_file_list() - update_diff_view() - - if applied_count > 0 then - utils.notify(string.format(prompts.review.messages.applied_count, applied_count)) - end - - return applied_count -end - ---- Open the diff review UI -function M.open() - if state.is_open then - return - end - - if #state.entries == 0 then - utils.notify(prompts.review.messages.no_changes_short, vim.log.levels.INFO) - return - end - - -- Create list buffer - state.list_buf = vim.api.nvim_create_buf(false, true) - vim.bo[state.list_buf].buftype = "nofile" - vim.bo[state.list_buf].bufhidden = "wipe" - vim.bo[state.list_buf].swapfile = false - - -- Create diff buffer - state.diff_buf = vim.api.nvim_create_buf(false, true) - vim.bo[state.diff_buf].buftype = "nofile" - vim.bo[state.diff_buf].bufhidden = "wipe" - vim.bo[state.diff_buf].swapfile = false - - -- Create layout: list on left (30 cols), diff on right - vim.cmd("tabnew") - state.diff_win = vim.api.nvim_get_current_win() - vim.api.nvim_win_set_buf(state.diff_win, state.diff_buf) - - vim.cmd("topleft vsplit") - state.list_win = vim.api.nvim_get_current_win() - vim.api.nvim_win_set_buf(state.list_win, state.list_buf) - vim.api.nvim_win_set_width(state.list_win, 35) - - -- Window options - for _, win in ipairs({ state.list_win, state.diff_win }) do - vim.wo[win].number = false - vim.wo[win].relativenumber = false - vim.wo[win].signcolumn = "no" - vim.wo[win].wrap = false - vim.wo[win].cursorline = true - end - - -- Set up keymaps for list buffer - local list_opts = { buffer = state.list_buf, noremap = true, silent = true } - vim.keymap.set("n", "j", M.next, list_opts) - vim.keymap.set("n", "k", M.prev, list_opts) - vim.keymap.set("n", "", M.next, list_opts) - vim.keymap.set("n", "", M.prev, list_opts) - vim.keymap.set("n", "", function() - vim.api.nvim_set_current_win(state.diff_win) - end, list_opts) - vim.keymap.set("n", "a", M.approve_current, list_opts) - vim.keymap.set("n", "r", M.reject_current, list_opts) - vim.keymap.set("n", "A", M.approve_all, list_opts) - vim.keymap.set("n", "q", M.close, list_opts) - vim.keymap.set("n", "", M.close, list_opts) - - -- Set up keymaps for diff buffer - local diff_opts = { buffer = state.diff_buf, noremap = true, silent = true } - vim.keymap.set("n", "j", M.next, diff_opts) - vim.keymap.set("n", "k", M.prev, diff_opts) - vim.keymap.set("n", "", function() - vim.api.nvim_set_current_win(state.list_win) - end, diff_opts) - vim.keymap.set("n", "a", M.approve_current, diff_opts) - vim.keymap.set("n", "r", M.reject_current, diff_opts) - vim.keymap.set("n", "A", M.approve_all, diff_opts) - vim.keymap.set("n", "q", M.close, diff_opts) - vim.keymap.set("n", "", M.close, diff_opts) - - state.is_open = true - state.current_index = 1 - - -- Initial render - update_file_list() - update_diff_view() - - -- Focus list window - vim.api.nvim_set_current_win(state.list_win) -end - ---- Close the diff review UI -function M.close() - if not state.is_open then - return - end - - -- Close the tab (which closes both windows) - pcall(vim.cmd, "tabclose") - - state.list_buf = nil - state.list_win = nil - state.diff_buf = nil - state.diff_win = nil - state.is_open = false -end - ---- Check if review UI is open ----@return boolean -function M.is_open() - return state.is_open -end +M.clear = get_config_utils.clear_diff_entries +M.add = get_config_utils.add_diff_entry +M.get_entries = get_config_utils.get_diff_entries +M.count = get_config_utils.count_diff_entries +M.next = require("codetyper.adapters.nvim.ui.diff_review.navigate_next") +M.prev = require("codetyper.adapters.nvim.ui.diff_review.navigate_prev") +M.approve_current = require("codetyper.adapters.nvim.ui.diff_review.approve_current") +M.reject_current = require("codetyper.adapters.nvim.ui.diff_review.reject_current") +M.approve_all = require("codetyper.adapters.nvim.ui.diff_review.approve_all") +M.apply_approved = require("codetyper.adapters.nvim.ui.diff_review.apply_approved") +M.close = require("codetyper.adapters.nvim.ui.diff_review.close") +M.is_open = require("codetyper.adapters.nvim.ui.diff_review.is_open") +M.open = require("codetyper.adapters.nvim.ui.diff_review.open") return M diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/apply_approved.lua b/lua/codetyper/adapters/nvim/ui/diff_review/apply_approved.lua new file mode 100644 index 0000000..6c85563 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/apply_approved.lua @@ -0,0 +1,40 @@ +local state = require("codetyper.state.state") +local utils = require("codetyper.support.utils") +local prompts = require("codetyper.prompts.agents.diff") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") + +--- Apply all approved changes to disk +---@return number applied_count Number of successfully applied changes +local function apply_approved() + local applied_count = 0 + + for _, entry in ipairs(state.entries) do + if entry.approved and not entry.applied then + if entry.operation == "create" or entry.operation == "edit" then + local write_success = utils.write_file(entry.path, entry.modified) + if write_success then + entry.applied = true + applied_count = applied_count + 1 + end + elseif entry.operation == "delete" then + local delete_success = os.remove(entry.path) + if delete_success then + entry.applied = true + applied_count = applied_count + 1 + end + end + end + end + + update_file_list() + update_diff_view() + + if applied_count > 0 then + utils.notify(string.format(prompts.review.messages.applied_count, applied_count)) + end + + return applied_count +end + +return apply_approved diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/approve_all.lua b/lua/codetyper/adapters/nvim/ui/diff_review/approve_all.lua new file mode 100644 index 0000000..281dd73 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/approve_all.lua @@ -0,0 +1,16 @@ +local state = require("codetyper.state.state") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") + +--- Approve all unapplied diff entries +local function approve_all() + for _, entry in ipairs(state.entries) do + if not entry.applied then + entry.approved = true + end + end + update_file_list() + update_diff_view() +end + +return approve_all diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/approve_current.lua b/lua/codetyper/adapters/nvim/ui/diff_review/approve_current.lua new file mode 100644 index 0000000..1762d41 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/approve_current.lua @@ -0,0 +1,15 @@ +local state = require("codetyper.state.state") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") + +--- Approve the currently selected diff entry +local function approve_current() + local entry = state.entries[state.current_index] + if entry and not entry.applied then + entry.approved = true + update_file_list() + update_diff_view() + end +end + +return approve_current diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/close.lua b/lua/codetyper/adapters/nvim/ui/diff_review/close.lua new file mode 100644 index 0000000..345a1a0 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/close.lua @@ -0,0 +1,18 @@ +local state = require("codetyper.state.state") + +--- Close the diff review UI +local function close() + if not state.is_open then + return + end + + pcall(vim.cmd, "tabclose") + + state.list_buf = nil + state.list_win = nil + state.diff_buf = nil + state.diff_win = nil + state.is_open = false +end + +return close diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/generate_diff_lines.lua b/lua/codetyper/adapters/nvim/ui/diff_review/generate_diff_lines.lua new file mode 100644 index 0000000..d0890b9 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/generate_diff_lines.lua @@ -0,0 +1,67 @@ +--- Generate unified diff between two strings +---@param original string|nil +---@param modified string +---@param filepath string +---@return string[] +local function generate_diff_lines(original, modified, filepath) + local lines = {} + local filename = vim.fn.fnamemodify(filepath, ":t") + + if not original then + -- New file + table.insert(lines, "--- /dev/null") + table.insert(lines, "+++ b/" .. filename) + table.insert(lines, "@@ -0,0 +1," .. #vim.split(modified, "\n") .. " @@") + for _, line in ipairs(vim.split(modified, "\n")) do + table.insert(lines, "+" .. line) + end + else + -- Modified file - use vim's diff + table.insert(lines, "--- a/" .. filename) + table.insert(lines, "+++ b/" .. filename) + + local orig_lines = vim.split(original, "\n") + local mod_lines = vim.split(modified, "\n") + + -- Simple diff: show removed and added lines + local max_lines = math.max(#orig_lines, #mod_lines) + local context_start = 1 + local in_change = false + + for i = 1, max_lines do + local orig = orig_lines[i] or "" + local mod = mod_lines[i] or "" + + if orig ~= mod then + if not in_change then + table.insert( + lines, + string.format( + "@@ -%d,%d +%d,%d @@", + math.max(1, i - 2), + math.min(5, #orig_lines - i + 3), + math.max(1, i - 2), + math.min(5, #mod_lines - i + 3) + ) + ) + in_change = true + end + if orig ~= "" then + table.insert(lines, "-" .. orig) + end + if mod ~= "" then + table.insert(lines, "+" .. mod) + end + else + if in_change then + table.insert(lines, " " .. orig) + in_change = false + end + end + end + end + + return lines +end + +return generate_diff_lines diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/is_open.lua b/lua/codetyper/adapters/nvim/ui/diff_review/is_open.lua new file mode 100644 index 0000000..cc1c7a1 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/is_open.lua @@ -0,0 +1,9 @@ +local state = require("codetyper.state.state") + +--- Check if the diff review UI is open +---@return boolean +local function is_open() + return state.is_open +end + +return is_open diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/navigate_next.lua b/lua/codetyper/adapters/nvim/ui/diff_review/navigate_next.lua new file mode 100644 index 0000000..7ba7ab5 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/navigate_next.lua @@ -0,0 +1,14 @@ +local state = require("codetyper.state.state") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") + +--- Navigate to next diff entry +local function navigate_next() + if state.current_index < #state.entries then + state.current_index = state.current_index + 1 + update_file_list() + update_diff_view() + end +end + +return navigate_next diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/navigate_prev.lua b/lua/codetyper/adapters/nvim/ui/diff_review/navigate_prev.lua new file mode 100644 index 0000000..30b4a1a --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/navigate_prev.lua @@ -0,0 +1,14 @@ +local state = require("codetyper.state.state") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") + +--- Navigate to previous diff entry +local function navigate_prev() + if state.current_index > 1 then + state.current_index = state.current_index - 1 + update_file_list() + update_diff_view() + end +end + +return navigate_prev diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/open.lua b/lua/codetyper/adapters/nvim/ui/diff_review/open.lua new file mode 100644 index 0000000..5246942 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/open.lua @@ -0,0 +1,86 @@ +local state = require("codetyper.state.state") +local utils = require("codetyper.support.utils") +local prompts = require("codetyper.prompts.agents.diff") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") +local navigate_next = require("codetyper.adapters.nvim.ui.diff_review.navigate_next") +local navigate_prev = require("codetyper.adapters.nvim.ui.diff_review.navigate_prev") +local approve_current = require("codetyper.adapters.nvim.ui.diff_review.approve_current") +local reject_current = require("codetyper.adapters.nvim.ui.diff_review.reject_current") +local approve_all = require("codetyper.adapters.nvim.ui.diff_review.approve_all") +local close = require("codetyper.adapters.nvim.ui.diff_review.close") + +--- Open the diff review UI +local function open() + if state.is_open then + return + end + + if #state.entries == 0 then + utils.notify(prompts.review.messages.no_changes_short, vim.log.levels.INFO) + return + end + + state.list_buf = vim.api.nvim_create_buf(false, true) + vim.bo[state.list_buf].buftype = "nofile" + vim.bo[state.list_buf].bufhidden = "wipe" + vim.bo[state.list_buf].swapfile = false + + state.diff_buf = vim.api.nvim_create_buf(false, true) + vim.bo[state.diff_buf].buftype = "nofile" + vim.bo[state.diff_buf].bufhidden = "wipe" + vim.bo[state.diff_buf].swapfile = false + + vim.cmd("tabnew") + state.diff_win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(state.diff_win, state.diff_buf) + + vim.cmd("topleft vsplit") + state.list_win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(state.list_win, state.list_buf) + vim.api.nvim_win_set_width(state.list_win, 35) + + for _, win in ipairs({ state.list_win, state.diff_win }) do + vim.wo[win].number = false + vim.wo[win].relativenumber = false + vim.wo[win].signcolumn = "no" + vim.wo[win].wrap = false + vim.wo[win].cursorline = true + end + + local list_keymap_opts = { buffer = state.list_buf, noremap = true, silent = true } + vim.keymap.set("n", "j", navigate_next, list_keymap_opts) + vim.keymap.set("n", "k", navigate_prev, list_keymap_opts) + vim.keymap.set("n", "", navigate_next, list_keymap_opts) + vim.keymap.set("n", "", navigate_prev, list_keymap_opts) + vim.keymap.set("n", "", function() + vim.api.nvim_set_current_win(state.diff_win) + end, list_keymap_opts) + vim.keymap.set("n", "a", approve_current, list_keymap_opts) + vim.keymap.set("n", "r", reject_current, list_keymap_opts) + vim.keymap.set("n", "A", approve_all, list_keymap_opts) + vim.keymap.set("n", "q", close, list_keymap_opts) + vim.keymap.set("n", "", close, list_keymap_opts) + + local diff_keymap_opts = { buffer = state.diff_buf, noremap = true, silent = true } + vim.keymap.set("n", "j", navigate_next, diff_keymap_opts) + vim.keymap.set("n", "k", navigate_prev, diff_keymap_opts) + vim.keymap.set("n", "", function() + vim.api.nvim_set_current_win(state.list_win) + end, diff_keymap_opts) + vim.keymap.set("n", "a", approve_current, diff_keymap_opts) + vim.keymap.set("n", "r", reject_current, diff_keymap_opts) + vim.keymap.set("n", "A", approve_all, diff_keymap_opts) + vim.keymap.set("n", "q", close, diff_keymap_opts) + vim.keymap.set("n", "", close, diff_keymap_opts) + + state.is_open = true + state.current_index = 1 + + update_file_list() + update_diff_view() + + vim.api.nvim_set_current_win(state.list_win) +end + +return open diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/reject_current.lua b/lua/codetyper/adapters/nvim/ui/diff_review/reject_current.lua new file mode 100644 index 0000000..026b514 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/reject_current.lua @@ -0,0 +1,15 @@ +local state = require("codetyper.state.state") +local update_file_list = require("codetyper.adapters.nvim.ui.diff_review.update_file_list") +local update_diff_view = require("codetyper.adapters.nvim.ui.diff_review.update_diff_view") + +--- Reject the currently selected diff entry +local function reject_current() + local entry = state.entries[state.current_index] + if entry and not entry.applied then + entry.approved = false + update_file_list() + update_diff_view() + end +end + +return reject_current diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/update_diff_view.lua b/lua/codetyper/adapters/nvim/ui/diff_review/update_diff_view.lua new file mode 100644 index 0000000..59ab2bc --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/update_diff_view.lua @@ -0,0 +1,48 @@ +local state = require("codetyper.state.state") +local prompts = require("codetyper.prompts.agents.diff") +local generate_diff_lines = require("codetyper.adapters.nvim.ui.diff_review.generate_diff_lines") + +--- Update the diff view for current entry +local function update_diff_view() + if not state.diff_buf or not vim.api.nvim_buf_is_valid(state.diff_buf) then + return + end + + local entry = state.entries[state.current_index] + local ui_prompts = prompts.review + if not entry then + vim.bo[state.diff_buf].modifiable = true + vim.api.nvim_buf_set_lines(state.diff_buf, 0, -1, false, { ui_prompts.messages.no_changes_short }) + vim.bo[state.diff_buf].modifiable = false + return + end + + local lines = {} + + local status_icon = entry.applied and " " or (entry.approved and " " or " ") + local op_icon = entry.operation == "create" and "+" or (entry.operation == "delete" and "-" or "~") + local current_status = entry.applied and ui_prompts.status.applied + or (entry.approved and ui_prompts.status.approved or ui_prompts.status.pending) + + table.insert( + lines, + string.format(ui_prompts.diff_header.top, status_icon, op_icon, vim.fn.fnamemodify(entry.path, ":t")) + ) + table.insert(lines, string.format(ui_prompts.diff_header.path, entry.path)) + table.insert(lines, string.format(ui_prompts.diff_header.op, entry.operation)) + table.insert(lines, string.format(ui_prompts.diff_header.status, current_status)) + table.insert(lines, ui_prompts.diff_header.bottom) + table.insert(lines, "") + + local diff_lines = generate_diff_lines(entry.original, entry.modified, entry.path) + for _, line in ipairs(diff_lines) do + table.insert(lines, line) + end + + vim.bo[state.diff_buf].modifiable = true + vim.api.nvim_buf_set_lines(state.diff_buf, 0, -1, false, lines) + vim.bo[state.diff_buf].modifiable = false + vim.bo[state.diff_buf].filetype = "diff" +end + +return update_diff_view diff --git a/lua/codetyper/adapters/nvim/ui/diff_review/update_file_list.lua b/lua/codetyper/adapters/nvim/ui/diff_review/update_file_list.lua new file mode 100644 index 0000000..1c056f8 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/diff_review/update_file_list.lua @@ -0,0 +1,44 @@ +local state = require("codetyper.state.state") +local prompts = require("codetyper.prompts.agents.diff") + +--- Update the file list sidebar +local function update_file_list() + if not state.list_buf or not vim.api.nvim_buf_is_valid(state.list_buf) then + return + end + + local ui_prompts = prompts.review + local lines = {} + table.insert(lines, string.format(ui_prompts.list_menu.top, #state.entries)) + for _, item in ipairs(ui_prompts.list_menu.items) do + table.insert(lines, item) + end + table.insert(lines, ui_prompts.list_menu.bottom) + table.insert(lines, "") + + for entry_index, entry in ipairs(state.entries) do + local prefix = (entry_index == state.current_index) and "▶ " or " " + local status = entry.applied and "" or (entry.approved and "" or "○") + local operation_icon = entry.operation == "create" and "[+]" or (entry.operation == "delete" and "[-]" or "[~]") + local filename = vim.fn.fnamemodify(entry.path, ":t") + + table.insert(lines, string.format("%s%s %s %s", prefix, status, operation_icon, filename)) + end + + if #state.entries == 0 then + table.insert(lines, ui_prompts.messages.no_changes) + end + + vim.bo[state.list_buf].modifiable = true + vim.api.nvim_buf_set_lines(state.list_buf, 0, -1, false, lines) + vim.bo[state.list_buf].modifiable = false + + if state.list_win and vim.api.nvim_win_is_valid(state.list_win) then + local target_line = 9 + state.current_index - 1 + if target_line <= vim.api.nvim_buf_line_count(state.list_buf) then + vim.api.nvim_win_set_cursor(state.list_win, { target_line, 0 }) + end + end +end + +return update_file_list diff --git a/lua/codetyper/state/state.lua b/lua/codetyper/state/state.lua index 419abc3..8964dd8 100644 --- a/lua/codetyper/state/state.lua +++ b/lua/codetyper/state/state.lua @@ -5,6 +5,13 @@ local state = { callback = nil, llm_response = nil, attached_files = nil, + entries = {}, + current_index = 1, + list_buf = nil, + list_win = nil, + diff_buf = nil, + diff_win = nil, + is_open = false, } return state diff --git a/lua/codetyper/utils/get_config.lua b/lua/codetyper/utils/get_config.lua index fd80f32..73d1c53 100644 --- a/lua/codetyper/utils/get_config.lua +++ b/lua/codetyper/utils/get_config.lua @@ -1,14 +1,39 @@ +local state = require("codetyper.state.state") + local M = {} -- Get current codetyper configuration at call time function M.get_config() - local ok, codetyper = pcall(require, "codetyper") - if ok and codetyper.get_config then + local codetyper_loaded, codetyper = pcall(require, "codetyper") + if codetyper_loaded and codetyper.get_config then return codetyper.get_config() or {} end - -- Fall back to defaults if codetyper isn't available local defaults = require("codetyper.config.defaults") return defaults.get_defaults() end +--- Clear all collected diff entries and reset index +function M.clear_diff_entries() + state.entries = {} + state.current_index = 1 +end + +--- Add a diff entry +---@param entry DiffEntry +function M.add_diff_entry(entry) + table.insert(state.entries, entry) +end + +--- Get all diff entries +---@return DiffEntry[] +function M.get_diff_entries() + return state.entries +end + +--- Get the number of diff entries +---@return number +function M.count_diff_entries() + return #state.entries +end + return M