From 642b76f17daf5d7391cf17687ca39bacec1b2489 Mon Sep 17 00:00:00 2001 From: Cristiano Carvalho Date: Mon, 14 Sep 2026 11:29:10 -0300 Subject: [PATCH] feat(gh): add action to edit your own inline review comments Adds gh_edit_comment, enabled when the cursor is on a comment you authored (viewerDidAuthor). Reuses the existing scratch-buffer edit flow, prefilled with the comment's current body, and PATCHes /repos/{repo}/pulls/comments/{id} on submit. comment_id (used by gh_reply_to_comment) always resolves to a thread's root comment, since GitHub's reply endpoint requires anchoring to the root. Editing needs the exact comment under the cursor instead, so this adds a separate edit_comment_id tag that covers only that comment's own lines, not lines belonging to nested replies rendered beneath it. viewerDidAuthor wasn't previously fetched for individual review comments (only at the review level), so the GraphQL query for inline comments now requests it too. --- lua/snacks/gh/actions.lua | 47 ++++++++++++++++++++++++++++++++++- lua/snacks/gh/api.lua | 1 + lua/snacks/gh/render/init.lua | 10 +++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lua/snacks/gh/actions.lua b/lua/snacks/gh/actions.lua index b0cfda147..a939bd96f 100644 --- a/lua/snacks/gh/actions.lua +++ b/lua/snacks/gh/actions.lua @@ -55,11 +55,24 @@ local function get_meta(item, ctx) return end local meta = Snacks.picker.highlight.meta(buf) - ---@type {comment_id?: number, diff?: snacks.diff.Meta}? + ---@type {comment_id?: number, edit_comment_id?: number, diff?: snacks.diff.Meta}? local m = meta and meta[vim.api.nvim_win_get_cursor(win)[1]] or nil return m, meta, buf, win end +---@param item snacks.picker.gh.Item +---@param id number +---@return snacks.gh.Comment? +local function find_comment(item, id) + for _, review in ipairs(item.reviews or {}) do + for _, comment in ipairs(review.comments or {}) do + if comment.databaseId == id then + return comment + end + end + end +end + ---@class snacks.gh.actions: {[string]:snacks.gh.Action} M.actions = setmetatable({}, { __index = function(_, key) @@ -295,6 +308,38 @@ M.actions.gh_reply_to_comment = { end, } +M.actions.gh_edit_comment = { + desc = "Edit comment", + title = "Edit comment on {type} #{number}", + priority = 150, + icon = " ", + enabled = function(item, ctx) + local m = get_meta(item, ctx) + return m and m.edit_comment_id ~= nil or false + end, + action = function(item, ctx) + local m = get_meta(item, ctx) + if not (m and m.edit_comment_id) then + Snacks.notify.error("No comment found to edit") + return + end + local comment = find_comment(item, m.edit_comment_id) + if not comment then + Snacks.notify.error("Could not find comment to edit") + return + end + local action = vim.deepcopy(M.cli_actions.gh_comment) + action.title = "Edit comment on {type} #{number}" + action.success = "Edited comment on {type} #{number}" + action.template = comment.body + action.api = { + endpoint = "/repos/{repo}/pulls/comments/" .. m.edit_comment_id, + method = "PATCH", + } + M.run(item, action, ctx) + end, +} + M.actions.gh_diff_comment = { desc = "Add diff comment", title = "Comment on diff in {type} #{number}", diff --git a/lua/snacks/gh/api.lua b/lua/snacks/gh/api.lua index 83bd24b1b..faa8a0ce6 100644 --- a/lua/snacks/gh/api.lua +++ b/lua/snacks/gh/api.lua @@ -490,6 +490,7 @@ function M.comments(item, cb) createdAt subjectType author { login } + viewerDidAuthor replyTo { id databaseId } reactionGroups { content diff --git a/lua/snacks/gh/render/init.lua b/lua/snacks/gh/render/init.lua index 98b0cf115..567436b6d 100644 --- a/lua/snacks/gh/render/init.lua +++ b/lua/snacks/gh/render/init.lua @@ -534,6 +534,11 @@ function M.comment(comment, ctx) end vim.list_extend(ret, M.comment_body(comment, ctx)) + -- lines belonging to this comment alone, before any replies get appended below. + -- comment_id (added further down) always resolves to the thread's root comment, + -- since replying requires anchoring to the root; edit_comment_id instead tags + -- only these lines, so editing targets the exact comment under the cursor. + local own_lines = #ret local replies = M.find_reply(comment.id, ctx) for _, reply in ipairs(replies) do ret[#ret + 1] = {} -- empty line between comment and reply @@ -541,11 +546,14 @@ function M.comment(comment, ctx) ctx.comment_skip[reply.id] = true end if ctx.is_review then - for _, line in ipairs(ret) do + for i, line in ipairs(ret) do local reply_id = comment.replyTo and comment.replyTo.databaseId or comment.databaseId if reply_id then line[#line + 1] = { "", meta = { comment_id = reply_id } } end + if i <= own_lines and comment.viewerDidAuthor and comment.databaseId then + line[#line + 1] = { "", meta = { edit_comment_id = comment.databaseId } } + end end end ret = M.indent(ret, ctx)