Tree Refresh; Draft Note Replies (#289)
* fix: always refresh discussion tree data after choosing a new branch * fix: rebuild discussion tree without collapsing nodes after all edit/delete/create actions * feat: add command to refresh discussion tree * feat: Add support for draft note replies, e.g. replies to existing notes and comments in draft form * fix: allow backticks in comment suggestions This is a #MINOR release
This commit is contained in:
committed by
GitHub
parent
cf6ccddce3
commit
0d0ed1639a
@@ -17,31 +17,53 @@ local M = {
|
||||
current_win = nil,
|
||||
start_line = nil,
|
||||
end_line = nil,
|
||||
draft_popup = nil,
|
||||
comment_popup = nil,
|
||||
}
|
||||
|
||||
---Fires the API that sends the comment data to the Go server, called when you "confirm" creation
|
||||
---via the M.settings.popup.perform_action keybinding
|
||||
---@param text string comment text
|
||||
---@param visual_range LineRange | nil range of visual selection or nil
|
||||
---@param unlinked boolean | nil if true, the comment is not linked to a line
|
||||
local confirm_create_comment = function(text, visual_range, unlinked)
|
||||
---@param unlinked boolean if true, the comment is not linked to a line
|
||||
---@param discussion_id string | nil The ID of the discussion to which the reply is responding, nil if not a reply
|
||||
local confirm_create_comment = function(text, visual_range, unlinked, discussion_id)
|
||||
if text == nil then
|
||||
u.notify("Reviewer did not provide text of change", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local is_draft = M.draft_popup and u.string_to_bool(u.get_buffer_text(M.draft_popup.bufnr))
|
||||
if unlinked then
|
||||
|
||||
-- Creating a normal reply to a discussion
|
||||
if discussion_id ~= nil and not is_draft then
|
||||
local body = { discussion_id = discussion_id, reply = text, draft = is_draft }
|
||||
job.run_job("/mr/reply", "POST", body, function()
|
||||
u.notify("Sent reply!", vim.log.levels.INFO)
|
||||
if is_draft then
|
||||
draft_notes.load_draft_notes(function()
|
||||
discussions.rebuild_view(unlinked)
|
||||
end)
|
||||
else
|
||||
discussions.rebuild_view(unlinked)
|
||||
end
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
-- Creating a note (unlinked comment)
|
||||
if unlinked and discussion_id == nil then
|
||||
local body = { comment = text }
|
||||
local endpoint = is_draft and "/mr/draft_notes/" or "/mr/comment"
|
||||
job.run_job(endpoint, "POST", body, function(data)
|
||||
job.run_job(endpoint, "POST", body, function()
|
||||
u.notify(is_draft and "Draft note created!" or "Note created!", vim.log.levels.INFO)
|
||||
if is_draft then
|
||||
draft_notes.add_draft_note({ draft_note = data.draft_note, unlinked = true })
|
||||
draft_notes.load_draft_notes(function()
|
||||
discussions.rebuild_view(unlinked)
|
||||
end)
|
||||
else
|
||||
discussions.add_discussion({ data = data, unlinked = true })
|
||||
discussions.rebuild_view(unlinked)
|
||||
end
|
||||
discussions.refresh()
|
||||
end)
|
||||
return
|
||||
end
|
||||
@@ -61,9 +83,7 @@ local confirm_create_comment = function(text, visual_range, unlinked)
|
||||
end
|
||||
|
||||
local revision = state.MR_REVISIONS[1]
|
||||
local body = {
|
||||
type = "text",
|
||||
comment = text,
|
||||
local position_data = {
|
||||
file_name = reviewer_data.file_name,
|
||||
base_commit_sha = revision.base_commit_sha,
|
||||
start_commit_sha = revision.start_commit_sha,
|
||||
@@ -73,20 +93,67 @@ local confirm_create_comment = function(text, visual_range, unlinked)
|
||||
line_range = location_data.line_range,
|
||||
}
|
||||
|
||||
-- Creating a draft reply, in response to a discussion ID
|
||||
if discussion_id ~= nil and is_draft then
|
||||
local body = { comment = text, discussion_id = discussion_id, position = position_data }
|
||||
job.run_job("/mr/draft_notes/", "POST", body, function()
|
||||
u.notify("Draft reply created!", vim.log.levels.INFO)
|
||||
draft_notes.load_draft_notes(function()
|
||||
discussions.rebuild_view(false, true)
|
||||
end)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
-- Creating a new comment (linked to specific changes)
|
||||
local body = u.merge({ type = "text", comment = text }, position_data)
|
||||
local endpoint = is_draft and "/mr/draft_notes/" or "/mr/comment"
|
||||
job.run_job(endpoint, "POST", body, function(data)
|
||||
job.run_job(endpoint, "POST", body, function()
|
||||
u.notify(is_draft and "Draft comment created!" or "Comment created!", vim.log.levels.INFO)
|
||||
if is_draft then
|
||||
draft_notes.add_draft_note({ draft_note = data.draft_note, unlinked = false })
|
||||
draft_notes.load_draft_notes(function()
|
||||
discussions.rebuild_view(unlinked)
|
||||
end)
|
||||
else
|
||||
discussions.add_discussion({ data = data, has_position = true })
|
||||
discussions.rebuild_view(unlinked)
|
||||
end
|
||||
discussions.refresh()
|
||||
end)
|
||||
end
|
||||
|
||||
-- This function will actually send the deletion to Gitlab when you make a selection,
|
||||
-- and re-render the tree
|
||||
---@param note_id integer
|
||||
---@param discussion_id string
|
||||
---@param unlinked boolean
|
||||
M.confirm_delete_comment = function(note_id, discussion_id, unlinked)
|
||||
local body = { discussion_id = discussion_id, note_id = tonumber(note_id) }
|
||||
job.run_job("/mr/comment", "DELETE", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
discussions.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
|
||||
---This function sends the edited comment to the Go server
|
||||
---@param discussion_id string
|
||||
---@param note_id integer
|
||||
---@param unlinked boolean
|
||||
M.confirm_edit_comment = function(discussion_id, note_id, unlinked)
|
||||
return function(text)
|
||||
local body = {
|
||||
discussion_id = discussion_id,
|
||||
note_id = note_id,
|
||||
comment = text,
|
||||
}
|
||||
job.run_job("/mr/comment", "PATCH", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
discussions.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
---@class LayoutOpts
|
||||
---@field ranged boolean
|
||||
---@field discussion_id string|nil
|
||||
---@field unlinked boolean
|
||||
|
||||
---This function sets up the layout and popups needed to create a comment, note and
|
||||
@@ -94,13 +161,16 @@ end
|
||||
---window panes, and for the non-primary sections.
|
||||
---@param opts LayoutOpts|nil
|
||||
---@return NuiLayout
|
||||
local function create_comment_layout(opts)
|
||||
M.create_comment_layout = function(opts)
|
||||
if opts == nil then
|
||||
opts = {}
|
||||
end
|
||||
|
||||
local title = opts.discussion_id and "Reply" or "Comment"
|
||||
local settings = opts.discussion_id ~= nil and state.settings.popup.reply or state.settings.popup.comment
|
||||
|
||||
M.current_win = vim.api.nvim_get_current_win()
|
||||
M.comment_popup = Popup(u.create_popup_state("Comment", state.settings.popup.comment))
|
||||
M.comment_popup = Popup(u.create_popup_state(title, settings))
|
||||
M.draft_popup = Popup(u.create_box_popup_state("Draft", false))
|
||||
M.start_line, M.end_line = u.get_visual_selection_boundaries()
|
||||
|
||||
@@ -128,14 +198,16 @@ local function create_comment_layout(opts)
|
||||
local range = opts.ranged and { start_line = M.start_line, end_line = M.end_line } or nil
|
||||
local unlinked = opts.unlinked or false
|
||||
|
||||
---Keybinding for focus on text section
|
||||
state.set_popup_keymaps(M.draft_popup, function()
|
||||
local text = u.get_buffer_text(M.comment_popup.bufnr)
|
||||
confirm_create_comment(text, range, unlinked)
|
||||
confirm_create_comment(text, range, unlinked, opts.discussion_id)
|
||||
vim.api.nvim_set_current_win(M.current_win)
|
||||
end, miscellaneous.toggle_bool, popup_opts)
|
||||
|
||||
---Keybinding for focus on draft section
|
||||
state.set_popup_keymaps(M.comment_popup, function(text)
|
||||
confirm_create_comment(text, range, unlinked)
|
||||
confirm_create_comment(text, range, unlinked, opts.discussion_id)
|
||||
vim.api.nvim_set_current_win(M.current_win)
|
||||
end, miscellaneous.attach_file, popup_opts)
|
||||
|
||||
@@ -144,6 +216,14 @@ local function create_comment_layout(opts)
|
||||
vim.api.nvim_buf_set_lines(M.draft_popup.bufnr, 0, -1, false, { u.bool_to_string(draft_mode) })
|
||||
end)
|
||||
|
||||
--Send back to previous window on close
|
||||
vim.api.nvim_create_autocmd("BufHidden", {
|
||||
buffer = M.draft_popup.bufnr,
|
||||
callback = function()
|
||||
vim.api.nvim_set_current_win(M.current_win)
|
||||
end,
|
||||
})
|
||||
|
||||
return layout
|
||||
end
|
||||
|
||||
@@ -167,7 +247,7 @@ M.create_comment = function()
|
||||
return
|
||||
end
|
||||
|
||||
local layout = create_comment_layout()
|
||||
local layout = M.create_comment_layout({ ranged = false, unlinked = false })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
@@ -181,14 +261,14 @@ M.create_multiline_comment = function()
|
||||
return
|
||||
end
|
||||
|
||||
local layout = create_comment_layout({ ranged = true, unlinked = false })
|
||||
local layout = M.create_comment_layout({ ranged = true, unlinked = false })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
--- This function will open a a popup to create a "note" (e.g. unlinked comment)
|
||||
--- on the changed/updated line in the current MR
|
||||
M.create_note = function()
|
||||
local layout = create_comment_layout({ ranged = false, unlinked = true })
|
||||
local layout = M.create_comment_layout({ ranged = false, unlinked = true })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
@@ -204,8 +284,8 @@ local build_suggestion = function()
|
||||
local backticks = "```"
|
||||
local selected_lines = u.get_lines(M.start_line, M.end_line)
|
||||
|
||||
for line in ipairs(selected_lines) do
|
||||
if string.match(line, "^```$") then
|
||||
for _, line in ipairs(selected_lines) do
|
||||
if string.match(line, "^```%S*$") then
|
||||
backticks = "````"
|
||||
break
|
||||
end
|
||||
@@ -243,7 +323,7 @@ M.create_comment_suggestion = function()
|
||||
|
||||
local suggestion_lines, range_length = build_suggestion()
|
||||
|
||||
local layout = create_comment_layout({ ranged = range_length > 0, unlinked = false })
|
||||
local layout = M.create_comment_layout({ ranged = range_length > 0, unlinked = false })
|
||||
layout:mount()
|
||||
vim.schedule(function()
|
||||
if suggestion_lines then
|
||||
|
||||
@@ -207,7 +207,7 @@ end
|
||||
M.get_line_number = function(id)
|
||||
---@type Discussion|DraftNote|nil
|
||||
local d_or_n
|
||||
d_or_n = List.new(state.DISCUSSION_DATA.discussions or {}):find(function(d)
|
||||
d_or_n = List.new(state.DISCUSSION_DATA and state.DISCUSSION_DATA.discussions or {}):find(function(d)
|
||||
return d.id == id
|
||||
end) or List.new(state.DRAFT_NOTES or {}):find(function(d)
|
||||
return d.id == id
|
||||
|
||||
@@ -29,18 +29,37 @@ local M = {
|
||||
linked_bufnr = nil,
|
||||
---@type number
|
||||
unlinked_bufnr = nil,
|
||||
---@type number
|
||||
---@type NuiTree|nil
|
||||
discussion_tree = nil,
|
||||
---@type NuiTree|nil
|
||||
unlinked_discussion_tree = nil,
|
||||
}
|
||||
|
||||
---Re-fetches all discussions and re-renders the relevant view
|
||||
---@param unlinked boolean
|
||||
---@param all boolean|nil
|
||||
M.rebuild_view = function(unlinked, all)
|
||||
M.load_discussions(function()
|
||||
if all then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
M.rebuild_discussion_tree()
|
||||
elseif unlinked then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
M.refresh_diagnostics_and_winbar()
|
||||
end)
|
||||
end
|
||||
|
||||
---Makes API call to get the discussion data, stores it in the state, and calls the callback
|
||||
---@param callback function|nil
|
||||
M.load_discussions = function(callback)
|
||||
job.run_job("/mr/discussions/list", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
|
||||
state.load_new_state("discussion_data", function(data)
|
||||
state.DISCUSSION_DATA.discussions = u.ensure_table(data.discussions)
|
||||
state.DISCUSSION_DATA.unlinked_discussions = u.ensure_table(data.unlinked_discussions)
|
||||
state.DISCUSSION_DATA.emojis = u.ensure_table(data.emojis)
|
||||
if type(callback) == "function" then
|
||||
if callback ~= nil then
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
@@ -50,7 +69,7 @@ end
|
||||
M.initialize_discussions = function()
|
||||
signs.setup_signs()
|
||||
reviewer.set_callback_for_file_changed(function()
|
||||
M.refresh_view()
|
||||
M.refresh_diagnostics_and_winbar()
|
||||
M.modifiable(false)
|
||||
end)
|
||||
reviewer.set_callback_for_reviewer_enter(function()
|
||||
@@ -77,19 +96,8 @@ M.modifiable = function(bool)
|
||||
end
|
||||
end
|
||||
|
||||
---Refresh discussion data, signs, diagnostics, and winbar with new data from API
|
||||
--- and rebuild the entire view
|
||||
M.refresh = function(cb)
|
||||
M.load_discussions(function()
|
||||
M.refresh_view()
|
||||
if cb ~= nil then
|
||||
cb()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Take existing data and refresh the diagnostics, the winbar, and the signs
|
||||
M.refresh_view = function()
|
||||
M.refresh_diagnostics_and_winbar = function()
|
||||
if state.settings.discussion_signs.enabled then
|
||||
diagnostics.refresh_diagnostics()
|
||||
end
|
||||
@@ -146,7 +154,7 @@ M.toggle = function(callback)
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
M.refresh_view()
|
||||
M.refresh_diagnostics_and_winbar()
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -219,76 +227,48 @@ M.reply = function(tree)
|
||||
u.notify("Gitlab does not support replying to draft notes", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
local reply_popup = Popup(u.create_popup_state("Reply", state.settings.popup.reply))
|
||||
|
||||
local node = tree:get_node()
|
||||
local discussion_node = common.get_root_node(tree, node)
|
||||
local id = tostring(discussion_node.id)
|
||||
reply_popup:mount()
|
||||
state.set_popup_keymaps(
|
||||
reply_popup,
|
||||
M.send_reply(tree, id),
|
||||
miscellaneous.attach_file,
|
||||
miscellaneous.editable_popup_opts
|
||||
)
|
||||
end
|
||||
|
||||
-- This function will send the reply to the Go API
|
||||
M.send_reply = function(tree, discussion_id)
|
||||
return function(text)
|
||||
local body = { discussion_id = discussion_id, reply = text }
|
||||
|
||||
job.run_job("/mr/reply", "POST", body, function(data)
|
||||
u.notify("Sent reply!", vim.log.levels.INFO)
|
||||
M.add_reply_to_tree(tree, data.note, discussion_id)
|
||||
M.load_discussions()
|
||||
end)
|
||||
if discussion_node == nil then
|
||||
u.notify("Could not get discussion root", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local discussion_id = tostring(discussion_node.id)
|
||||
local comment = require("gitlab.actions.comment")
|
||||
local unlinked = tree.bufnr == M.unlinked_bufnr
|
||||
local layout = comment.create_comment_layout({ ranged = false, discussion_id = discussion_id, unlinked = unlinked })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
-- This function (settings.discussion_tree.delete_comment) will trigger a popup prompting you to delete the current comment
|
||||
M.delete_comment = function(tree)
|
||||
M.delete_comment = function(tree, unlinked)
|
||||
vim.ui.select({ "Confirm", "Cancel" }, {
|
||||
prompt = "Delete comment?",
|
||||
}, function(choice)
|
||||
if choice == "Confirm" then
|
||||
M.send_deletion(tree)
|
||||
local current_node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
if note_node == nil or root_node == nil then
|
||||
u.notify("Could not get note or root node", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
---@type integer
|
||||
if M.is_draft_note(tree) then
|
||||
draft_notes.confirm_delete_draft_note(note_node.id, unlinked)
|
||||
else
|
||||
local note_id = note_node.is_root and root_node.root_note_id or note_node.id
|
||||
local comment = require("gitlab.actions.comment")
|
||||
comment.confirm_delete_comment(note_id, root_node.id, unlinked)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- This function will actually send the deletion to Gitlab
|
||||
-- when you make a selection, and re-render the tree
|
||||
M.send_deletion = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
if note_node == nil or root_node == nil then
|
||||
u.notify("Could not get note or root node", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
---@type integer
|
||||
local note_id = note_node.is_root and root_node.root_note_id or note_node.id
|
||||
|
||||
if root_node.is_draft then
|
||||
draft_notes.send_deletion(tree)
|
||||
else
|
||||
local body = { discussion_id = root_node.id, note_id = tonumber(note_id) }
|
||||
job.run_job("/mr/comment", "DELETE", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
if note_node.is_root then
|
||||
-- Replace root node w/ current node's contents...
|
||||
tree:remove_node("-" .. root_node.id)
|
||||
else
|
||||
tree:remove_node("-" .. note_id)
|
||||
end
|
||||
tree:render()
|
||||
M.refresh()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- This function (settings.discussion_tree.edit_comment) will open the edit popup for the current comment in the discussion tree
|
||||
M.edit_comment = function(tree, unlinked)
|
||||
local edit_popup = Popup(u.create_popup_state("Edit Comment", state.settings.popup.edit))
|
||||
@@ -316,39 +296,21 @@ M.edit_comment = function(tree, unlinked)
|
||||
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
|
||||
|
||||
-- Draft notes module handles edits for draft notes
|
||||
if root_node.is_draft then
|
||||
state.set_popup_keymaps(edit_popup, draft_notes.send_edits(root_node.id), nil, miscellaneous.editable_popup_opts)
|
||||
else
|
||||
if M.is_draft_note(tree) then
|
||||
state.set_popup_keymaps(
|
||||
edit_popup,
|
||||
M.send_edits(tostring(root_node.id), tonumber(note_node.root_note_id or note_node.id), unlinked),
|
||||
draft_notes.confirm_edit_draft_note(note_node.id, unlinked),
|
||||
nil,
|
||||
miscellaneous.editable_popup_opts
|
||||
)
|
||||
else
|
||||
local comment = require("gitlab.actions.comment")
|
||||
state.set_popup_keymaps(
|
||||
edit_popup,
|
||||
comment.confirm_edit_comment(tostring(root_node.id), tonumber(note_node.root_note_id or note_node.id), unlinked),
|
||||
nil,
|
||||
miscellaneous.editable_popup_opts
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---This function sends the edited comment to the Go server
|
||||
---@param discussion_id string
|
||||
---@param note_id integer
|
||||
---@param unlinked boolean
|
||||
M.send_edits = function(discussion_id, note_id, unlinked)
|
||||
return function(text)
|
||||
local body = {
|
||||
discussion_id = discussion_id,
|
||||
note_id = note_id,
|
||||
comment = text,
|
||||
}
|
||||
job.run_job("/mr/comment", "PATCH", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
if unlinked then
|
||||
M.replace_text(state.DISCUSSION_DATA.unlinked_discussions, discussion_id, note_id, text)
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.replace_text(state.DISCUSSION_DATA.discussions, discussion_id, note_id, text)
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -374,8 +336,61 @@ M.toggle_discussion_resolved = function(tree)
|
||||
|
||||
job.run_job("/mr/discussions/resolve", "PUT", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
M.redraw_resolved_status(tree, note, not note.resolved)
|
||||
M.refresh()
|
||||
local unlinked = tree.bufnr == M.unlinked_bufnr
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to attach to the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.add_emoji_to_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local emojis = require("gitlab.emoji").emoji_list
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local body = { emoji = name, note_id = note_id }
|
||||
job.run_job("/mr/awardable/note/", "POST", body, function()
|
||||
u.notify("Emoji added", vim.log.levels.INFO)
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to remove from the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.delete_emoji_from_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local note_id_str = tostring(note_id)
|
||||
|
||||
local e = require("gitlab.emoji")
|
||||
|
||||
local emojis = {}
|
||||
local current_emojis = state.DISCUSSION_DATA.emojis[note_id_str]
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if state.USER.id == current_emoji.user.id then
|
||||
table.insert(emojis, e.emoji_map[current_emoji.name])
|
||||
end
|
||||
end
|
||||
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local awardable_id
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if current_emoji.name == name and current_emoji.user.id == state.USER.id then
|
||||
awardable_id = current_emoji.id
|
||||
break
|
||||
end
|
||||
end
|
||||
job.run_job(string.format("/mr/awardable/note/%d/%d", note_id, awardable_id), "DELETE", nil, function()
|
||||
u.notify("Emoji removed", vim.log.levels.INFO)
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -383,31 +398,46 @@ end
|
||||
-- 🌲 Helper Functions
|
||||
--
|
||||
|
||||
---Used to collect all nodes in a tree prior to rebuilding it, so that they
|
||||
---can be re-expanded before render
|
||||
---@param tree any
|
||||
---@return table
|
||||
M.gather_expanded_node_ids = function(tree)
|
||||
-- Gather all nodes for later expansion, after rebuild
|
||||
local ids = {}
|
||||
for id, node in pairs(tree and tree.nodes.by_id or {}) do
|
||||
if node._is_expanded then
|
||||
table.insert(ids, id)
|
||||
end
|
||||
end
|
||||
return ids
|
||||
end
|
||||
|
||||
---Rebuilds the discussion tree, which contains all comments and draft comments
|
||||
---linked to specific places in the code.
|
||||
M.rebuild_discussion_tree = function()
|
||||
if M.linked_bufnr == nil then
|
||||
return
|
||||
end
|
||||
local expanded_node_ids = M.gather_expanded_node_ids(M.discussion_tree)
|
||||
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
|
||||
|
||||
vim.api.nvim_buf_set_lines(M.linked_bufnr, 0, -1, false, {})
|
||||
local existing_comment_nodes = discussions_tree.add_discussions_to_table(state.DISCUSSION_DATA.discussions, false)
|
||||
local draft_comment_nodes = draft_notes.add_draft_notes_to_table(false)
|
||||
|
||||
-- Combine inline draft notes with regular comments
|
||||
local all_nodes = {}
|
||||
for _, draft_node in ipairs(draft_comment_nodes) do
|
||||
table.insert(all_nodes, draft_node)
|
||||
end
|
||||
for _, node in ipairs(existing_comment_nodes) do
|
||||
table.insert(all_nodes, node)
|
||||
end
|
||||
local all_nodes = u.join(draft_comment_nodes, existing_comment_nodes)
|
||||
|
||||
local discussion_tree = NuiTree({
|
||||
nodes = all_nodes,
|
||||
bufnr = M.linked_bufnr,
|
||||
prepare_node = tree_utils.nui_tree_prepare_node,
|
||||
})
|
||||
-- Re-expand already expanded nodes
|
||||
for _, id in ipairs(expanded_node_ids) do
|
||||
tree_utils.open_node_by_id(discussion_tree, id)
|
||||
end
|
||||
|
||||
discussion_tree:render()
|
||||
M.set_tree_keymaps(discussion_tree, M.linked_bufnr, false)
|
||||
@@ -423,6 +453,7 @@ M.rebuild_unlinked_discussion_tree = function()
|
||||
if M.unlinked_bufnr == nil then
|
||||
return
|
||||
end
|
||||
local expanded_node_ids = M.gather_expanded_node_ids(M.unlinked_discussion_tree)
|
||||
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
|
||||
vim.api.nvim_buf_set_lines(M.unlinked_bufnr, 0, -1, false, {})
|
||||
local existing_note_nodes =
|
||||
@@ -430,20 +461,20 @@ M.rebuild_unlinked_discussion_tree = function()
|
||||
local draft_comment_nodes = draft_notes.add_draft_notes_to_table(true)
|
||||
|
||||
-- Combine draft notes with regular notes
|
||||
local all_nodes = {}
|
||||
for _, draft_node in ipairs(draft_comment_nodes) do
|
||||
table.insert(all_nodes, draft_node)
|
||||
end
|
||||
for _, node in ipairs(existing_note_nodes) do
|
||||
table.insert(all_nodes, node)
|
||||
end
|
||||
local all_nodes = u.join(draft_comment_nodes, existing_note_nodes)
|
||||
|
||||
local unlinked_discussion_tree = NuiTree({
|
||||
nodes = all_nodes,
|
||||
bufnr = M.unlinked_bufnr,
|
||||
prepare_node = tree_utils.nui_tree_prepare_node,
|
||||
})
|
||||
|
||||
-- Re-expand already expanded nodes
|
||||
for _, id in ipairs(expanded_node_ids) do
|
||||
tree_utils.open_node_by_id(unlinked_discussion_tree, id)
|
||||
end
|
||||
unlinked_discussion_tree:render()
|
||||
|
||||
M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_bufnr, true)
|
||||
M.unlinked_discussion_tree = unlinked_discussion_tree
|
||||
common.switch_can_edit_bufs(false, M.linked_bufnr, M.unlinked_bufnr)
|
||||
@@ -498,6 +529,7 @@ M.is_current_node_note = function(tree)
|
||||
end
|
||||
|
||||
M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
---Keybindings only relevant for linked (comment) view
|
||||
if not unlinked then
|
||||
vim.keymap.set("n", state.settings.discussion_tree.jump_to_file, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
@@ -506,13 +538,19 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
end, { buffer = bufnr, desc = "Jump to file" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.jump_to_reviewer, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
common.jump_to_reviewer(tree, M.refresh_view)
|
||||
common.jump_to_reviewer(tree, M.refresh_diagnostics_and_winbar)
|
||||
end
|
||||
end, { buffer = bufnr, desc = "Jump to reviewer" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.toggle_tree_type, function()
|
||||
M.toggle_tree_type()
|
||||
end, { buffer = bufnr, desc = "Toggle tree type between `simple` and `by_file_name`" })
|
||||
end
|
||||
|
||||
vim.keymap.set("n", state.settings.discussion_tree.refresh_data, function()
|
||||
u.notify("Refreshing data...", vim.log.levels.INFO)
|
||||
draft_notes.rebuild_view(unlinked, false)
|
||||
end, { buffer = bufnr, desc = "Refreshes the view with Gitlab's APIs" })
|
||||
|
||||
vim.keymap.set("n", state.settings.discussion_tree.edit_comment, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
M.edit_comment(tree, unlinked)
|
||||
@@ -525,12 +563,11 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
end, { buffer = bufnr, desc = "Publish draft" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.delete_comment, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
M.delete_comment(tree)
|
||||
M.delete_comment(tree, unlinked)
|
||||
end
|
||||
end, { buffer = bufnr, desc = "Delete comment" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.toggle_draft_mode, function()
|
||||
state.settings.discussion_tree.draft_mode = not state.settings.discussion_tree.draft_mode
|
||||
winbar.update_winbar()
|
||||
M.toggle_draft_mode()
|
||||
end, { buffer = bufnr, desc = "Toggle between draft mode and live mode" })
|
||||
vim.keymap.set("n", state.settings.discussion_tree.toggle_resolved, function()
|
||||
if M.is_current_node_note(tree) and not M.is_draft_note(tree) then
|
||||
@@ -591,68 +628,6 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
emoji.init_popup(tree, bufnr)
|
||||
end
|
||||
|
||||
---Redraws the header of a node in a tree when it's been toggled to resolved/unresolved
|
||||
---@param tree NuiTree
|
||||
---@param note NuiTree.Node
|
||||
---@param mark_resolved boolean
|
||||
M.redraw_resolved_status = function(tree, note, mark_resolved)
|
||||
local current_text = tree.nodes.by_id["-" .. note.id].text
|
||||
local target = mark_resolved and "resolved" or "unresolved"
|
||||
local current = mark_resolved and "unresolved" or "resolved"
|
||||
|
||||
local function set_property(key, val)
|
||||
tree.nodes.by_id["-" .. note.id][key] = val
|
||||
end
|
||||
|
||||
local has_symbol = function(s)
|
||||
return state.settings.discussion_tree[s] ~= nil and state.settings.discussion_tree[s] ~= ""
|
||||
end
|
||||
|
||||
set_property("resolved", mark_resolved)
|
||||
|
||||
if not has_symbol(current) and not has_symbol(target) then
|
||||
return
|
||||
end
|
||||
|
||||
if not has_symbol(current) and has_symbol(target) then
|
||||
set_property("text", (current_text .. " " .. state.settings.discussion_tree[target]))
|
||||
elseif has_symbol(current) and not has_symbol(target) then
|
||||
set_property("text", u.remove_last_chunk(current_text))
|
||||
else
|
||||
set_property("text", (u.remove_last_chunk(current_text) .. " " .. state.settings.discussion_tree[target]))
|
||||
end
|
||||
|
||||
tree:render()
|
||||
end
|
||||
|
||||
---Replace text in discussion after note update.
|
||||
---@param data Discussion[]|UnlinkedDiscussion[]
|
||||
---@param discussion_id string
|
||||
---@param note_id integer
|
||||
---@param text string
|
||||
M.replace_text = function(data, discussion_id, note_id, text)
|
||||
for i, discussion in ipairs(data) do
|
||||
if discussion.id == discussion_id then
|
||||
for j, note in ipairs(discussion.notes) do
|
||||
if note.id == note_id then
|
||||
data[i].notes[j].body = text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Given some note data, adds it to the tree and re-renders the tree
|
||||
---@param tree any
|
||||
---@param note any
|
||||
---@param discussion_id any
|
||||
M.add_reply_to_tree = function(tree, note, discussion_id)
|
||||
local note_node = tree_utils.build_note(note)
|
||||
note_node:expand()
|
||||
tree:add_node(note_node, discussion_id and ("-" .. discussion_id) or nil)
|
||||
tree:render()
|
||||
end
|
||||
|
||||
---Toggle comments tree type between "simple" and "by_file_name"
|
||||
M.toggle_tree_type = function()
|
||||
if state.settings.discussion_tree.tree_type == "simple" then
|
||||
@@ -663,89 +638,23 @@ M.toggle_tree_type = function()
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
|
||||
---Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately)
|
||||
M.toggle_draft_mode = function()
|
||||
state.settings.discussion_tree.draft_mode = not state.settings.discussion_tree.draft_mode
|
||||
winbar.update_winbar()
|
||||
end
|
||||
|
||||
---Indicates whether the node under the cursor is a draft note or not
|
||||
---@param tree NuiTree
|
||||
---@return boolean
|
||||
M.is_draft_note = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
if note_node and note_node.is_draft then
|
||||
return true
|
||||
end
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
return root_node ~= nil and root_node.is_draft
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to attach to the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.add_emoji_to_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local note_id_str = tostring(note_id)
|
||||
local emojis = require("gitlab.emoji").emoji_list
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local body = { emoji = name, note_id = note_id }
|
||||
job.run_job("/mr/awardable/note/", "POST", body, function(data)
|
||||
if state.DISCUSSION_DATA.emojis[note_id_str] == nil then
|
||||
state.DISCUSSION_DATA.emojis[note_id_str] = {}
|
||||
table.insert(state.DISCUSSION_DATA.emojis[note_id_str], data.Emoji)
|
||||
else
|
||||
table.insert(state.DISCUSSION_DATA.emojis[note_id_str], data.Emoji)
|
||||
end
|
||||
if unlinked then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
u.notify("Emoji added", vim.log.levels.INFO)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
---Opens a popup prompting the user to choose an emoji to remove from the current node
|
||||
---@param tree any
|
||||
---@param unlinked boolean
|
||||
M.delete_emoji_from_note = function(tree, unlinked)
|
||||
local node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, node)
|
||||
local root_node = common.get_root_node(tree, node)
|
||||
local note_id = tonumber(note_node.is_root and root_node.root_note_id or note_node.id)
|
||||
local note_id_str = tostring(note_id)
|
||||
|
||||
local e = require("gitlab.emoji")
|
||||
|
||||
local emojis = {}
|
||||
local current_emojis = state.DISCUSSION_DATA.emojis[note_id_str]
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if state.USER.id == current_emoji.user.id then
|
||||
table.insert(emojis, e.emoji_map[current_emoji.name])
|
||||
end
|
||||
end
|
||||
|
||||
emoji.pick_emoji(emojis, function(name)
|
||||
local awardable_id
|
||||
for _, current_emoji in ipairs(current_emojis) do
|
||||
if current_emoji.name == name and current_emoji.user.id == state.USER.id then
|
||||
awardable_id = current_emoji.id
|
||||
break
|
||||
end
|
||||
end
|
||||
job.run_job(string.format("/mr/awardable/note/%d/%d", note_id, awardable_id), "DELETE", nil, function(_)
|
||||
local keep = {} -- Emojis to keep after deletion in the UI
|
||||
for _, saved in ipairs(state.DISCUSSION_DATA.emojis[note_id_str]) do
|
||||
if saved.name ~= name or saved.user.id ~= state.USER.id then
|
||||
table.insert(keep, saved)
|
||||
end
|
||||
end
|
||||
state.DISCUSSION_DATA.emojis[note_id_str] = keep
|
||||
if unlinked then
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
M.rebuild_discussion_tree()
|
||||
end
|
||||
e.init_popup(tree, unlinked and M.unlinked_bufnr or M.linked_bufnr)
|
||||
u.notify("Emoji removed", vim.log.levels.INFO)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
-- is not used in the draft notes tree
|
||||
local u = require("gitlab.utils")
|
||||
local common = require("gitlab.actions.common")
|
||||
local List = require("gitlab.utils.list")
|
||||
local state = require("gitlab.state")
|
||||
local NuiTree = require("nui.tree")
|
||||
local NuiLine = require("nui.line")
|
||||
@@ -56,8 +57,20 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
end
|
||||
end
|
||||
|
||||
-- Attaches draft notes that are replies to their parent discussions
|
||||
local draft_replies = List.new(state.DRAFT_NOTES or {})
|
||||
:filter(function(note)
|
||||
return note.discussion_id == discussion.id
|
||||
end)
|
||||
:map(function(note)
|
||||
local result = M.build_note(note)
|
||||
return result
|
||||
end)
|
||||
|
||||
local all_children = u.join(discussion_children, draft_replies)
|
||||
|
||||
-- Creates the first node in the discussion, and attaches children
|
||||
local body = u.spread(root_text_nodes, discussion_children)
|
||||
local body = u.spread(root_text_nodes, all_children)
|
||||
local root_node = NuiTree.Node({
|
||||
range = range,
|
||||
text = root_text,
|
||||
@@ -460,6 +473,16 @@ M.collapse_recursively = function(tree, node, current_root_node, keep_current_op
|
||||
end
|
||||
end
|
||||
|
||||
---Expands a given node in a given tree by it's ID
|
||||
---@param tree NuiTree
|
||||
---@param id string
|
||||
M.open_node_by_id = function(tree, id)
|
||||
local node = tree:get_node(id)
|
||||
if node then
|
||||
node:expand()
|
||||
end
|
||||
end
|
||||
|
||||
-- This function (settings.discussion_tree.toggle_node) expands/collapses the current node and its children
|
||||
M.toggle_node = function(tree)
|
||||
local node = tree:get_node()
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
-- That includes things like editing existing draft notes in the tree, and
|
||||
-- and deleting them. Normal notes and comments are managed separately,
|
||||
-- under lua/gitlab/actions/discussions/init.lua
|
||||
local winbar = require("gitlab.actions.discussions.winbar")
|
||||
local diagnostics = require("gitlab.indicators.diagnostics")
|
||||
local common = require("gitlab.actions.common")
|
||||
local discussion_tree = require("gitlab.actions.discussions.tree")
|
||||
local job = require("gitlab.job")
|
||||
@@ -14,76 +12,31 @@ local state = require("gitlab.state")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class AddDraftNoteOpts table
|
||||
---@field draft_note DraftNote
|
||||
---@field unlinked boolean
|
||||
|
||||
---Adds a draft note to the draft notes state, then rebuilds the view
|
||||
---@param opts AddDraftNoteOpts
|
||||
M.add_draft_note = function(opts)
|
||||
local new_draft_notes = u.ensure_table(state.DRAFT_NOTES)
|
||||
table.insert(new_draft_notes, opts.draft_note)
|
||||
state.DRAFT_NOTES = new_draft_notes
|
||||
local discussions = require("gitlab.actions.discussions")
|
||||
if opts.unlinked then
|
||||
discussions.rebuild_unlinked_discussion_tree()
|
||||
else
|
||||
discussions.rebuild_discussion_tree()
|
||||
end
|
||||
winbar.update_winbar()
|
||||
end
|
||||
|
||||
---Tells whether a draft note was left on a particular diff or is an unlinked note
|
||||
---@param note DraftNote
|
||||
M.has_position = function(note)
|
||||
return note.position.new_path ~= nil or note.position.old_path ~= nil
|
||||
end
|
||||
|
||||
---Returns a list of nodes to add to the discussion tree. Can filter and return only unlinked (note) nodes.
|
||||
---Re-fetches all draft notes (and non-draft notes) and re-renders the relevant views
|
||||
---@param unlinked boolean
|
||||
---@return NuiTree.Node[]
|
||||
M.add_draft_notes_to_table = function(unlinked)
|
||||
local draft_notes = List.new(state.DRAFT_NOTES)
|
||||
|
||||
local draft_note_nodes = draft_notes
|
||||
---@param note DraftNote
|
||||
:filter(function(note)
|
||||
if unlinked then
|
||||
return not M.has_position(note)
|
||||
end
|
||||
return M.has_position(note)
|
||||
end)
|
||||
---@param note DraftNote
|
||||
:map(function(note)
|
||||
local _, root_text, root_text_nodes = discussion_tree.build_note(note)
|
||||
return NuiTree.Node({
|
||||
range = (type(note.position) == "table" and note.position.line_range or nil),
|
||||
text = root_text,
|
||||
type = "note",
|
||||
is_root = true,
|
||||
is_draft = true,
|
||||
id = note.id,
|
||||
root_note_id = note.id,
|
||||
file_name = (type(note.position) == "table" and note.position.new_path or nil),
|
||||
new_line = (type(note.position) == "table" and note.position.new_line or nil),
|
||||
old_line = (type(note.position) == "table" and note.position.old_line or nil),
|
||||
resolvable = false,
|
||||
resolved = false,
|
||||
url = state.INFO.web_url .. "#note_" .. note.id,
|
||||
}, root_text_nodes)
|
||||
end)
|
||||
|
||||
return draft_note_nodes
|
||||
|
||||
-- TODO: Combine draft_notes and normal discussion nodes in the complex discussion
|
||||
-- tree. The code for that feature is a clusterfuck so this is difficult
|
||||
-- if state.settings.discussion_tree.tree_type == "simple" then
|
||||
-- return draft_note_nodes
|
||||
-- end
|
||||
---@param all boolean|nil
|
||||
M.rebuild_view = function(unlinked, all)
|
||||
M.load_draft_notes(function()
|
||||
local discussions = require("gitlab.actions.discussions")
|
||||
discussions.rebuild_view(unlinked, all)
|
||||
end)
|
||||
end
|
||||
|
||||
---Send edits will actually send the edits to Gitlab and refresh the draft_notes tree
|
||||
M.send_edits = function(note_id)
|
||||
---Makes API call to get the discussion data, stores it in the state, and calls the callback
|
||||
---@param callback function|nil
|
||||
M.load_draft_notes = function(callback)
|
||||
state.load_new_state("draft_notes", function()
|
||||
if callback ~= nil then
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---Will actually send the edits to Gitlab and refresh the draft_notes tree
|
||||
---@param note_id integer
|
||||
---@param unlinked boolean
|
||||
---@return function
|
||||
M.confirm_edit_draft_note = function(note_id, unlinked)
|
||||
return function(text)
|
||||
local all_notes = List.new(state.DRAFT_NOTES)
|
||||
local the_note = all_notes:find(function(note)
|
||||
@@ -92,67 +45,18 @@ M.send_edits = function(note_id)
|
||||
local body = { note = text, position = the_note.position }
|
||||
job.run_job(string.format("/mr/draft_notes/%d", note_id), "PATCH", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
local has_position = false
|
||||
local new_draft_notes = all_notes:map(function(note)
|
||||
if note.id == note_id then
|
||||
has_position = M.has_position(note)
|
||||
note.note = text
|
||||
end
|
||||
return note
|
||||
end)
|
||||
state.DRAFT_NOTES = new_draft_notes
|
||||
local discussions = require("gitlab.actions.discussions")
|
||||
if has_position then
|
||||
discussions.rebuild_discussion_tree()
|
||||
else
|
||||
discussions.rebuild_unlinked_discussion_tree()
|
||||
end
|
||||
winbar.update_winbar()
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- This function will actually send the deletion to Gitlab when you make a selection, and re-render the tree
|
||||
M.send_deletion = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
|
||||
if note_node == nil or root_node == nil then
|
||||
u.notify("Could not get note or root node", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
---@type integer
|
||||
local note_id = note_node.is_root and root_node.id or note_node.id
|
||||
|
||||
---This function will actually send the deletion to Gitlab when you make a selection, and re-render the tree
|
||||
---@param note_id integer
|
||||
---@param unlinked boolean
|
||||
M.confirm_delete_draft_note = function(note_id, unlinked)
|
||||
job.run_job(string.format("/mr/draft_notes/%d", note_id), "DELETE", nil, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
|
||||
local has_position = false
|
||||
local new_draft_notes = List.new(state.DRAFT_NOTES):filter(function(note)
|
||||
if note.id ~= note_id then
|
||||
return true
|
||||
else
|
||||
has_position = M.has_position(note)
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
state.DRAFT_NOTES = new_draft_notes
|
||||
local discussions = require("gitlab.actions.discussions")
|
||||
if has_position then
|
||||
discussions.rebuild_discussion_tree()
|
||||
else
|
||||
discussions.rebuild_unlinked_discussion_tree()
|
||||
end
|
||||
|
||||
if state.settings.discussion_signs.enabled and state.DISCUSSION_DATA then
|
||||
diagnostics.refresh_diagnostics()
|
||||
end
|
||||
|
||||
winbar.update_winbar()
|
||||
common.add_empty_titles()
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -185,11 +89,7 @@ M.confirm_publish_all_drafts = function()
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
state.DRAFT_NOTES = {}
|
||||
local discussions = require("gitlab.actions.discussions")
|
||||
discussions.refresh(function()
|
||||
discussions.rebuild_discussion_tree()
|
||||
discussions.rebuild_unlinked_discussion_tree()
|
||||
winbar.update_winbar()
|
||||
end)
|
||||
discussions.rebuild_view(false, true)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -213,27 +113,61 @@ M.confirm_publish_draft = function(tree)
|
||||
job.run_job("/mr/draft_notes/publish", "POST", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
|
||||
local has_position = false
|
||||
local new_draft_notes = List.new(state.DRAFT_NOTES):filter(function(note)
|
||||
if note.id ~= note_id then
|
||||
return true
|
||||
else
|
||||
has_position = M.has_position(note)
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
state.DRAFT_NOTES = new_draft_notes
|
||||
local discussions = require("gitlab.actions.discussions")
|
||||
discussions.refresh(function()
|
||||
if has_position then
|
||||
discussions.rebuild_discussion_tree()
|
||||
else
|
||||
discussions.rebuild_unlinked_discussion_tree()
|
||||
end
|
||||
winbar.update_winbar()
|
||||
end)
|
||||
local unlinked = tree.bufnr == discussions.unlinked_bufnr
|
||||
M.rebuild_view(unlinked)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Helper functions
|
||||
---Tells whether a draft note was left on a particular diff or is an unlinked note
|
||||
---@param note DraftNote
|
||||
M.has_position = function(note)
|
||||
return note.position.new_path ~= nil or note.position.old_path ~= nil
|
||||
end
|
||||
|
||||
---Builds a note for the discussion tree for draft notes that are roots
|
||||
---of their own discussions, e.g. not replies
|
||||
---@param note DraftNote
|
||||
---@return NuiTree.Node
|
||||
M.build_root_draft_note = function(note)
|
||||
local _, root_text, root_text_nodes = discussion_tree.build_note(note)
|
||||
return NuiTree.Node({
|
||||
range = (type(note.position) == "table" and note.position.line_range or nil),
|
||||
text = root_text,
|
||||
type = "note",
|
||||
is_root = true,
|
||||
is_draft = true,
|
||||
id = note.id,
|
||||
root_note_id = note.id,
|
||||
file_name = (type(note.position) == "table" and note.position.new_path or nil),
|
||||
new_line = (type(note.position) == "table" and note.position.new_line or nil),
|
||||
old_line = (type(note.position) == "table" and note.position.old_line or nil),
|
||||
resolvable = false,
|
||||
resolved = false,
|
||||
url = state.INFO.web_url .. "#note_" .. note.id,
|
||||
}, root_text_nodes)
|
||||
end
|
||||
|
||||
---Returns a list of nodes to add to the discussion tree. Can filter and return only unlinked (note) nodes.
|
||||
---@param unlinked boolean
|
||||
---@return NuiTree.Node[]
|
||||
M.add_draft_notes_to_table = function(unlinked)
|
||||
local draft_notes = List.new(state.DRAFT_NOTES)
|
||||
local draft_note_nodes = draft_notes
|
||||
---@param note DraftNote
|
||||
:filter(function(note)
|
||||
if unlinked then
|
||||
return not M.has_position(note)
|
||||
end
|
||||
return M.has_position(note)
|
||||
end)
|
||||
:filter(function(note)
|
||||
return note.discussion_id == "" -- Do not include draft replies
|
||||
end)
|
||||
:map(M.build_root_draft_note)
|
||||
|
||||
return draft_note_nodes
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -91,7 +91,10 @@ M.build_info_lines = function()
|
||||
branch = { title = "Branch", content = info.source_branch },
|
||||
labels = { title = "Labels", content = table.concat(info.labels, ", ") },
|
||||
target_branch = { title = "Target Branch", content = info.target_branch },
|
||||
delete_branch = { title = "Delete Source Branch", content = (info.force_remove_source_branch and "Yes" or "No") },
|
||||
delete_branch = {
|
||||
title = "Delete Source Branch",
|
||||
content = (info.force_remove_source_branch and "Yes" or "No"),
|
||||
},
|
||||
squash = { title = "Squash Commits", content = (info.squash and "Yes" or "No") },
|
||||
pipeline = {
|
||||
title = "Pipeline Status",
|
||||
|
||||
Reference in New Issue
Block a user