Skip to content
Open
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
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ https://github.com/user-attachments/assets/64c41f01-dffe-4318-bce4-16eec8de356e
view_mode = "list", -- "list" or "tree" for files under commits
},

-- Pending comments editor
comments = {
ui = {
width = 72, -- Floating editor width (columns)
height = 6, -- Floating editor height (lines)
opacity = 0, -- Window blend (0 = opaque, 100 = fully transparent)
editor_mode = "insert", -- "insert" = start in insert mode, "normal" = start in normal mode
submit_keys = { "<CR>" }, -- Submit comment (normal mode)
cancel_keys = { "q" }, -- Close editor without saving (normal mode)
},
},

-- Keymaps in diff view
keymaps = {
view = {
Expand All @@ -157,6 +169,12 @@ https://github.com/user-attachments/assets/64c41f01-dffe-4318-bce4-16eec8de356e
show_help = "g?", -- Show floating window with available keymaps
align_move = "gm", -- Temporarily align moved code blocks across panes
toggle_layout = "t", -- Toggle between side-by-side and inline layout
comment_add = "<leader>ca", -- Open pending comment editor at cursor
comment_edit = "<leader>ce", -- Edit pending comment at cursor
comment_remove = "<leader>cd", -- Remove pending comment at cursor
comment_list = "<leader>cl", -- List all pending comments
comment_submit = "<leader>cs", -- Submit pending comments and clear UI
comment_clear = "<leader>cc", -- Clear pending comments without submitting
},
explorer = {
select = "<CR>", -- Open diff for selected file
Expand Down Expand Up @@ -328,6 +346,46 @@ Show only changes introduced since branching from a base branch—exactly like a

This uses `git merge-base` semantics (equivalent to `git diff main...HEAD`), showing only the changes introduced on your branch, not changes that happened on the base branch since you branched.

### Pending Comments

You can attach lightweight pending comments in active CodeDiff diff panes:

```vim
" Add a comment at cursor (opens multiline editor)
:CodeDiff comments add

" Add a comment directly
:CodeDiff comments add Rename this helper before merge

" Add a ranged comment (visual selection)
:'<,'>CodeDiff comments add
:'<,'>CodeDiff comments add Refactor this block

" Edit comment at cursor
:CodeDiff comments edit

" Edit comment by id
:CodeDiff comments edit 3

" Remove comment at cursor (for quick typo fixes)
:CodeDiff comments remove

" Remove comment by id (shown as [cN] in the diff view)
:CodeDiff comments remove 3

" Show all pending comments in quickfix (jump with <CR>)
:CodeDiff comments list

" Submit all comments (sends to hook if set, otherwise copies to clipboard)
" After submit, comments are cleared from the UI
:CodeDiff comments submit

" Clear pending comments without submitting
:CodeDiff comments clear
```

See `:help codediff-comments` for editor behavior, submit hooks, and transport details.

### Git Diff Mode

Compare the current buffer with a git revision:
Expand Down
15 changes: 15 additions & 0 deletions doc/codediff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,16 @@ Setup entry point:
conflicts = true,
},
},
comments = {
ui = {
width = 72,
height = 6,
opacity = 0, -- window blend (0 = opaque, 100 = fully transparent)
editor_mode = "insert", -- or "normal" to start in normal mode
submit_keys = { "<CR>" }, -- submit (normal mode)
cancel_keys = { "q" }, -- close editor (normal mode)
},
},
keymaps = {
view = {
quit = "q",
Expand All @@ -234,6 +244,11 @@ Setup entry point:
show_help = "g?",
align_move = "gm",
toggle_layout = "t",
comment_add = "<leader>ca",
comment_edit = "<leader>ce",
comment_remove = "<leader>cd",
comment_submit = "<leader>cs",
comment_clear = "<leader>cC",
},
explorer = {
select = "<CR>",
Expand Down
142 changes: 124 additions & 18 deletions lua/codediff/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
local M = {}

-- Subcommands available for :CodeDiff
M.SUBCOMMANDS = { "merge", "file", "dir", "history", "install" }
M.SUBCOMMANDS = { "merge", "file", "dir", "history", "comments", "install" }

local git = require("codediff.core.git")
local lifecycle = require("codediff.ui.lifecycle")
Expand Down Expand Up @@ -517,6 +517,107 @@ local function handle_git_diff_merge_base(base_rev, target_rev, global_opts)
end)
end

local function parse_comment_id(raw)
local id = tonumber(raw)
if not id then
return nil
end

if id < 1 or id ~= math.floor(id) then
return nil
end

return id
end

local function handle_comments(args, opts)
local comments = require("codediff.ui.comments")
local action = args[1]

-- exit early
if not action or action == "" then
return
end

if action == "add" then
local range_line1, range_line2
if opts and opts.range == 2 then
range_line1 = opts.line1
range_line2 = opts.line2
end

local text = table.concat(vim.list_slice(args, 2), " ")
if vim.trim(text) ~= "" then
comments.add_comment(text, range_line1, range_line2)
else
comments.open_add_editor({ range_line1 = range_line1, range_line2 = range_line2 })
end
return
end

if action == "edit" then
local rest = vim.list_slice(args, 2)
if #rest == 0 then
comments.open_edit_editor()
return
end

local id = parse_comment_id(rest[1])
if id then
if #rest == 1 then
comments.open_edit_editor(id)
else
comments.edit_comment(id, table.concat(rest, " ", 2))
end
return
end

comments.edit_comment(nil, table.concat(rest, " "))
return
end

if action == "remove" then
local rest = vim.list_slice(args, 2)
if #rest > 1 then
vim.notify("Usage: :CodeDiff comments remove [comment_id]", vim.log.levels.ERROR)
return
end

if #rest == 1 then
local id = parse_comment_id(rest[1])
if not id then
vim.notify("Usage: :CodeDiff comments remove [comment_id]", vim.log.levels.ERROR)
return
end
comments.remove_comment(id)
return
end

comments.remove_comment()
return
end

if action == "list" then
if #args > 1 then
vim.notify("Usage: :CodeDiff comments list", vim.log.levels.ERROR)
return
end

comments.list_comments()
return
end

if action == "submit" then
comments.submit_comments()
return
end

if action == "clear" then
comments.clear_comments()
return
end
end

function M.vscode_merge(opts)
local args = opts.fargs
if #args == 0 then
Expand Down Expand Up @@ -604,9 +705,27 @@ function M.vscode_merge(opts)
end

function M.vscode_diff(opts)
-- Check if current tab is a diff view and toggle (close) it if so
-- Pre-parse global flags -> strip them so subcommand
-- dispatch sees clean args
local global_opts = {}
local args = {}
for _, arg in ipairs(opts.fargs) do
if arg == "--inline" then
global_opts.layout = "inline"
elseif arg == "--side-by-side" then
global_opts.layout = "side-by-side"
else
table.insert(args, arg)
end
end

-- Check if current tab is a diff view and toggle (close) it if so.
-- Keep comments subcommands available while in an active diff session.
local current_tab = vim.api.nvim_get_current_tabpage()
if lifecycle.get_session(current_tab) then
local active_session = lifecycle.get_session(current_tab)
local subcommand = args[1]
local is_comments_command = subcommand == "comments" or subcommand == "comment"
if active_session and not is_comments_command then
-- Check for unsaved conflict files before closing
if not lifecycle.confirm_close_with_unsaved(current_tab) then
return -- User cancelled
Expand All @@ -620,19 +739,6 @@ function M.vscode_diff(opts)
return
end

-- Pre-parse global flags; strip them so subcommand dispatch sees clean args
local global_opts = {}
local args = {}
for _, arg in ipairs(opts.fargs) do
if arg == "--inline" then
global_opts.layout = "inline"
elseif arg == "--side-by-side" then
global_opts.layout = "side-by-side"
else
table.insert(args, arg)
end
end

if #args == 0 then
-- :CodeDiff without arguments opens explorer mode
handle_explorer(nil, nil, global_opts)
Expand All @@ -649,8 +755,6 @@ function M.vscode_diff(opts)
end
end

local subcommand = args[1]

if subcommand == "merge" then
-- :CodeDiff merge <filename> - Merge Tool Mode
if #args ~= 2 then
Expand Down Expand Up @@ -769,6 +873,8 @@ function M.vscode_diff(opts)
end

handle_history(range, file_path, flags, line_range, global_opts)
elseif subcommand == "comments" or subcommand == "comment" then
handle_comments(vim.list_slice(args, 2), opts)
elseif subcommand == "install" or subcommand == "install!" then
-- :CodeDiff install or :CodeDiff install!
-- Handle both :CodeDiff! install and :CodeDiff install!
Expand Down
Loading