diff --git a/lua/snacks/gh/actions.lua b/lua/snacks/gh/actions.lua index 73e74814..5b9a0c91 100644 --- a/lua/snacks/gh/actions.lua +++ b/lua/snacks/gh/actions.lua @@ -6,6 +6,7 @@ local M = {} ---@class snacks.gh.action.ctx ---@field items snacks.picker.gh.Item[] ---@field picker? snacks.Picker +---@field main? number ---@field action? snacks.picker.Action ---@class snacks.gh.cli.Action.ctx @@ -79,6 +80,7 @@ M.actions.gh_actions = { if ctx.action and ctx.action.cmd then return Snacks.picker.actions.jump(ctx.picker, item, ctx.action) end + ctx.main = ctx.main or ctx.picker and ctx.picker.main or nil local actions = M.get_actions(item) actions.gh_actions = nil -- remove this action actions.gh_perform_action = nil -- remove this action @@ -103,6 +105,7 @@ M.actions.gh_actions = { if ctx.picker then ctx.picker:focus() end + ctx.main = ctx.main or picker and picker.main or nil it.action.action(item, ctx) picker:close() end, @@ -233,6 +236,35 @@ M.actions.gh_yank = { end, } +M.actions.gh_comment = { + desc = "Comment on {type} #{number}", + icon = " ", + action = function(item, ctx) + local win = ctx.main or vim.api.nvim_get_current_win() + local buf = vim.api.nvim_win_get_buf(win) + + local action = vim.deepcopy(M.cli_actions.gh_comment) + if item.uri == vim.api.nvim_buf_get_name(buf) then + local lino = vim.api.nvim_win_get_cursor(win)[1] + ---@type {line:number, id:number}[] + local comments = vim.b[buf].snacks_gh_comments or {} + for _, c in ipairs(comments) do + if c.line == lino then + action.title = "Reply to comment on {type} #{number}" + action.api = { + endpoint = "/repos/{repo}/pulls/{number}/comments", + input = { + in_reply_to = c.id, + }, + } + break + end + end + end + M.run(item, action, ctx) + end, +} + ---@type table M.cli_actions = { gh_comment = { @@ -570,7 +602,6 @@ function M._run(ctx, force) Api.request( cb, Snacks.config.merge(ctx.opts.api or {}, { - input = ctx.input, args = ctx.args, on_error = function() spinner:stop() @@ -655,10 +686,20 @@ function M.submit(ctx) if body:find("%S") then if edit == "body-file" then - vim.list_extend(ctx.args, { "--body-file", "-" }) - ctx.input = body + if ctx.opts.api then + ctx.opts.api.input = ctx.opts.api.input or {} + ctx.opts.api.input.body = body + else + ctx.input = body + vim.list_extend(ctx.args, { "--body-file", "-" }) + end else - vim.list_extend(ctx.args, { "--" .. edit, body }) + if ctx.opts.api then + ctx.opts.api.fields = ctx.opts.api.fields or {} + ctx.opts.api.fields[edit] = body + else + vim.list_extend(ctx.args, { "--" .. edit, body }) + end end end diff --git a/lua/snacks/gh/api.lua b/lua/snacks/gh/api.lua index 113a95be..6d16326a 100644 --- a/lua/snacks/gh/api.lua +++ b/lua/snacks/gh/api.lua @@ -228,7 +228,7 @@ function M.request(cb, opts) cb(proc, data and data:find("%S") and proc:json() or nil) end, { args = args, - input = opts.input, + input = opts.input and vim.json.encode(opts.input) or nil, on_error = opts.on_error, }) end @@ -417,6 +417,7 @@ function M.comments(item, cb) comments(first: 50) { nodes { id + databaseId body path diffHunk @@ -427,7 +428,7 @@ function M.comments(item, cb) createdAt subjectType author { login } - replyTo { id } + replyTo { id databaseId } reactionGroups { content users { totalCount } @@ -439,6 +440,7 @@ function M.comments(item, cb) comments(first: 100) { nodes { id + databaseId body author { login } authorAssociation diff --git a/lua/snacks/gh/render/init.lua b/lua/snacks/gh/render/init.lua index 702c8785..56a71902 100644 --- a/lua/snacks/gh/render/init.lua +++ b/lua/snacks/gh/render/init.lua @@ -11,6 +11,7 @@ local extend = Snacks.picker.highlight.extend ---@field item snacks.picker.gh.Item ---@field opts snacks.gh.Config ---@field comment_skip table +---@field is_review? boolean ---@param field string local function time_prop(field) @@ -296,17 +297,24 @@ function M.render(buf, item, opts) if #threads > 0 then lines[#lines + 1] = { { "" } } -- empty line lines[#lines + 1] = { { "---", "@punctuation.special.markdown" } } + lines[#lines + 1] = {} -- empty line for _, thread in ipairs(threads) do - lines[#lines + 1] = {} -- empty line + local c = #lines if thread.submitted then ---@cast thread snacks.gh.Review + ctx.is_review = true vim.list_extend(lines, M.review(thread, 1, ctx)) else ---@cast thread snacks.gh.Comment + ctx.is_review = false vim.list_extend(lines, M.comment(thread, 1, ctx)) end + + if #lines > c then -- only add separator if there were comments added + lines[#lines + 1] = {} -- empty line + end end end @@ -316,7 +324,13 @@ function M.render(buf, item, opts) vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) local changed = #lines ~= #buf_lines + local comments = {} ---@type {line:number, id:number}[] for l, line in ipairs(lines) do + for _, segment in ipairs(line) do + if segment.meta and segment.meta.reply then + comments[#comments + 1] = { line = l, id = segment.meta.reply } + end + end local line_text, extmarks = Snacks.picker.highlight.to_text(line) if line_text ~= buf_lines[l] then vim.api.nvim_buf_set_lines(buf, l - 1, l, false, { line_text }) @@ -339,6 +353,7 @@ function M.render(buf, item, opts) end end end + vim.b[buf].snacks_gh_comments = comments if #lines < #buf_lines then vim.api.nvim_buf_set_lines(buf, #lines, -1, false, {}) end @@ -490,6 +505,14 @@ function M.comment(comment, level, ctx) vim.list_extend(ret, M.comment(reply, level, ctx)) ctx.comment_skip[reply.id] = true end + if ctx.is_review then + for _, 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 = { reply = reply_id } } + end + end + end return ret end diff --git a/lua/snacks/gh/types.lua b/lua/snacks/gh/types.lua index 261f591a..abce3770 100644 --- a/lua/snacks/gh/types.lua +++ b/lua/snacks/gh/types.lua @@ -30,7 +30,7 @@ ---@field params? table typed fields (--field) ---@field header? table ---@field jq? string ----@field input? string +---@field input? any ---@field method? "GET" | "POST" | "PATCH" | "PUT" | "DELETE" ---@field paginate? boolean ---@field silent? boolean @@ -138,6 +138,7 @@ ---@class snacks.gh.Comment ---@field id string +---@field databaseId number ---@field url string ---@field author { login: string } ---@field authorAssociation? string @@ -149,7 +150,7 @@ ---@field createdAt string ---@field reactionGroups? snacks.gh.Reaction[] ---@field created? number ----@field replyTo? {id: string} +---@field replyTo? {id: string, databaseId: number} ---@field path? string ---@field diffHunk? string ---@field line? number diff --git a/lua/snacks/picker/config/defaults.lua b/lua/snacks/picker/config/defaults.lua index 596c0e7d..ed9c10d7 100644 --- a/lua/snacks/picker/config/defaults.lua +++ b/lua/snacks/picker/config/defaults.lua @@ -2,7 +2,7 @@ local M = {} ---@alias snacks.picker.format.resolve fun(max_width:number):snacks.picker.Highlight[] ---@alias snacks.picker.Extmark vim.api.keyset.set_extmark|{col:number, row?:number, field?:string} ----@alias snacks.picker.Text {[1]:string, [2]:(string|string[])?, virtual?:boolean, field?:string, resolve?:snacks.picker.format.resolve} +---@alias snacks.picker.Text {[1]:string, [2]:(string|string[])?, virtual?:boolean, field?:string, resolve?:snacks.picker.format.resolve, meta?:table} ---@alias snacks.picker.Highlight snacks.picker.Text|snacks.picker.Extmark ---@alias snacks.picker.format fun(item:snacks.picker.Item, picker:snacks.Picker):snacks.picker.Highlight[] ---@alias snacks.picker.preview fun(ctx: snacks.picker.preview.ctx):boolean?