local u = require("gitlab.utils") local NuiTree = require("nui.tree") local job = require("gitlab.job") local state = require("gitlab.state") local Job = require("plenary.job") local Popup = require("nui.popup") local keymaps = require("gitlab.keymaps") local M = {} local replyPopup = Popup(u.create_popup_state("Reply", "80%", "80%")) M.reply = function() if u.base_invalid() then return end replyPopup:mount() keymaps.set_popup_keymaps(replyPopup, M.send_reply) end M.send_reply = function(text) local escapedText = string.gsub(text, "\n", "\\n") local json = string.format('{"discussion_id": "%s", "reply": "%s"}', state.ACTIVE_DISCUSSION, escapedText) job.run_job("reply", "POST", json, function(data) local note_node = M.build_note(data.note) note_node:expand() state.tree:add_node(note_node, "-" .. state.ACTIVE_DISCUSSION) vim.schedule(function() state.tree:render() local buf = vim.api.nvim_get_current_buf() u.darken_metadata(buf, '') vim.notify("Sent reply!", vim.log.levels.INFO) end) end) end -- Places all of the discussions into a readable list M.list_discussions = function() if u.base_invalid() then return end Job:new({ command = "curl", args = { "-s", string.format("localhost:%s/discussions", state.PORT) }, on_stdout = function(_, output) local data_ok, data = pcall(vim.json.decode, output) if data_ok and data ~= nil then local status = (data.status >= 200 and data.status < 300) and "success" or "error" if status == "error" then vim.notify("Could not fetch discussions!", vim.log.levels.ERROR) return end M.discussions = data.discussions vim.schedule(function() if type(data.discussions) ~= "table" then vim.notify("No discussions for this MR") return end vim.cmd.tabnew() local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_command("aboveleft vsplit") vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown') vim.api.nvim_set_current_buf(buf) local allDiscussions = {} for i, discussion in ipairs(data.discussions) do local discussionChildren = {} for _, note in ipairs(discussion.notes) do local note_node = M.build_note(note) if i == 1 then note_node:expand() end table.insert(discussionChildren, note_node) end local discussionNode = NuiTree.Node({ text = discussion.id, id = discussion.id, is_discussion = true }, discussionChildren) if i == 1 then discussionNode:expand() end table.insert(allDiscussions, discussionNode) end state.tree = NuiTree({ nodes = allDiscussions, bufnr = buf }) M.set_tree_keymaps(buf) state.tree:render() vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown') u.darken_metadata(buf, '') M.jump_to_file() end) end end, on_stderr = function(_, output) vim.notify("Could not run approve command!", vim.log.levels.ERROR) error(output) end, }):start() end M.jump_to_file = function() local node = state.tree:get_node() if node == nil then return end local childrenIds = node:get_child_ids() -- We have selected a note node if node.file_name ~= nil then u.jump_to_file(node.file_name, node.line_number) elseif node.is_body then local parentId = node:get_parent_id() local parent = state.tree:get_node(parentId) if parent == nil then return end u.jump_to_file(parent.file_name, parent.line_number) else local firstChild = state.tree:get_node(childrenIds[1]) if firstChild == nil then return end u.jump_to_file(firstChild.file_name, firstChild.line_number) end end M.set_tree_keymaps = function(buf) -- Jump to file location where comment was left vim.keymap.set('n', state.keymaps.discussion_tree.jump_to_location, function() M.jump_to_file() end, { buffer = true }) vim.keymap.set('n', state.keymaps.discussion_tree.edit_comment, function() require("gitlab.comment").edit_comment() end, { buffer = true }) vim.keymap.set('n', state.keymaps.discussion_tree.delete_comment, function() require("gitlab.comment").delete_comment() end) -- Expand/collapse the current node vim.keymap.set('n', state.keymaps.discussion_tree.toggle_node, function() local node = state.tree:get_node() if node == nil then return end local children = node:get_child_ids() if node == nil then return end if node:is_expanded() then node:collapse() for _, child in ipairs(children) do state.tree:get_node(child):collapse() end else for _, child in ipairs(children) do state.tree:get_node(child):expand() end node:expand() end state.tree:render() u.darken_metadata(buf, '') end, { buffer = true }) vim.keymap.set('n', 'r', function() local node = state.tree:get_node() if node == nil then return end -- Get closest discussion parent if node.is_body then local parentId = node:get_parent_id() local parent = state.tree:get_node(parentId) if parent == nil then return end parentId = parent:get_parent_id() parent = state.tree:get_node(parentId) if parent == nil then return end node = parent elseif node.is_note then local parentId = node:get_parent_id() local parent = state.tree:get_node(parentId) if parent == nil then return end node = parent end state.ACTIVE_DISCUSSION = node.id M.reply() end, { buffer = true }) end M.build_note = function(note) local noteTextNodes = {} for bodyLine in note.body:gmatch("[^\n]+") do table.insert(noteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {})) end local noteHeader = "@" .. note.author.username .. " on " .. u.format_date(note.created_at) local line_number = note.position.new_line or note.position.old_line local note_node = NuiTree.Node( { text = noteHeader, id = note.id, file_name = note.position.new_path, line_number = line_number, is_note = true }, noteTextNodes) return note_node end return M