Feat: Discussion Changes + Code Cleanup (#24)
* Changed to relative time * Added days * Updated display of discussion tree * Updated a lot of functionality + behaviors for discussions + comments * Modified state on deletion/creation * Fixed relative times
This commit is contained in:
committed by
GitHub
parent
8349bccb29
commit
b4077ba8c2
@@ -44,6 +44,11 @@ type EditCommentRequest struct {
|
|||||||
DiscussionId string `json:"discussion_id"`
|
DiscussionId string `json:"discussion_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentResponse struct {
|
||||||
|
SuccessResponse
|
||||||
|
Comment *gitlab.Note `json:"note"`
|
||||||
|
}
|
||||||
|
|
||||||
func CommentHandler(w http.ResponseWriter, r *http.Request) {
|
func CommentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
@@ -157,20 +162,25 @@ func EditComment(w http.ResponseWriter, r *http.Request) {
|
|||||||
Body: gitlab.String(editCommentRequest.Comment),
|
Body: gitlab.String(editCommentRequest.Comment),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, res, err := c.git.Discussions.UpdateMergeRequestDiscussionNote(c.projectId, c.mergeId, editCommentRequest.DiscussionId, editCommentRequest.NoteId, &options)
|
note, res, err := c.git.Discussions.UpdateMergeRequestDiscussionNote(c.projectId, c.mergeId, editCommentRequest.DiscussionId, editCommentRequest.NoteId, &options)
|
||||||
|
|
||||||
for k, v := range res.Header {
|
|
||||||
w.Header().Set(k, v[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.handleError(w, err, "Could not edit comment", res.StatusCode)
|
c.handleError(w, err, "Could not edit comment", res.StatusCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := SuccessResponse{
|
w.WriteHeader(res.StatusCode)
|
||||||
Message: "Comment edited succesfully",
|
|
||||||
Status: http.StatusOK,
|
if res.StatusCode != http.StatusOK {
|
||||||
|
c.handleError(w, errors.New("Non-200 status code recieved"), "Could not edit comment", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := CommentResponse{
|
||||||
|
SuccessResponse: SuccessResponse{
|
||||||
|
Message: "Comment edited succesfully",
|
||||||
|
Status: http.StatusOK,
|
||||||
|
},
|
||||||
|
Comment: note,
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
|
|||||||
@@ -1,42 +1,24 @@
|
|||||||
local Menu = require("nui.menu")
|
local Menu = require("nui.menu")
|
||||||
local NuiTree = require("nui.tree")
|
local NuiTree = require("nui.tree")
|
||||||
|
local Popup = require("nui.popup")
|
||||||
local job = require("gitlab.job")
|
local job = require("gitlab.job")
|
||||||
local state = require("gitlab.state")
|
local state = require("gitlab.state")
|
||||||
local u = require("gitlab.utils")
|
local u = require("gitlab.utils")
|
||||||
|
local discussions = require("gitlab.discussions")
|
||||||
local keymaps = require("gitlab.keymaps")
|
local keymaps = require("gitlab.keymaps")
|
||||||
local Popup = require("nui.popup")
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local commentPopup = Popup(u.create_popup_state("Comment", "40%", "60%"))
|
local comment_popup = Popup(u.create_popup_state("Comment", "40%", "60%"))
|
||||||
local editPopup = Popup(u.create_popup_state("Edit Comment", "80%", "80%"))
|
local edit_popup = Popup(u.create_popup_state("Edit Comment", "80%", "80%"))
|
||||||
|
|
||||||
M.line_status = nil
|
|
||||||
|
|
||||||
|
-- Function that fires to open the comment popup
|
||||||
M.create_comment = function()
|
M.create_comment = function()
|
||||||
if u.base_invalid() then return end
|
if u.base_invalid() then return end
|
||||||
commentPopup:mount()
|
comment_popup:mount()
|
||||||
keymaps.set_popup_keymaps(commentPopup, M.confirm_create_comment)
|
keymaps.set_popup_keymaps(comment_popup, M.confirm_create_comment)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.find_deletion_commit = function(file)
|
-- Actually sends the comment to Gitlab
|
||||||
local current_line = vim.api.nvim_get_current_line()
|
|
||||||
local command = string.format("git log -S '%s' %s", current_line, file)
|
|
||||||
local handle = io.popen(command)
|
|
||||||
local output = handle:read("*line")
|
|
||||||
if output == nil then
|
|
||||||
vim.notify("Error reading SHA of deletion commit", vim.log.levels.ERROR)
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
handle:close()
|
|
||||||
local words = {}
|
|
||||||
for word in output:gmatch("%S+") do
|
|
||||||
table.insert(words, word)
|
|
||||||
end
|
|
||||||
|
|
||||||
return words[2]
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Sends the comment to Gitlab
|
|
||||||
M.confirm_create_comment = function(text)
|
M.confirm_create_comment = function(text)
|
||||||
if u.base_invalid() then return end
|
if u.base_invalid() then return end
|
||||||
local relative_file_path = u.get_relative_file_path()
|
local relative_file_path = u.get_relative_file_path()
|
||||||
@@ -58,9 +40,13 @@ M.confirm_create_comment = function(text)
|
|||||||
local jsonTable = { line_number = current_line_number, file_name = relative_file_path, comment = text }
|
local jsonTable = { line_number = current_line_number, file_name = relative_file_path, comment = text }
|
||||||
local json = vim.json.encode(jsonTable)
|
local json = vim.json.encode(jsonTable)
|
||||||
|
|
||||||
job.run_job("comment", "POST", json)
|
job.run_job("comment", "POST", json, function()
|
||||||
|
vim.notify("Comment created")
|
||||||
|
discussions.refresh_tree()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Function to open the deletion popup
|
||||||
M.delete_comment = function()
|
M.delete_comment = function()
|
||||||
local menu = Menu({
|
local menu = Menu({
|
||||||
position = "50%",
|
position = "50%",
|
||||||
@@ -89,101 +75,107 @@ M.delete_comment = function()
|
|||||||
close = state.keymaps.dialogue.close,
|
close = state.keymaps.dialogue.close,
|
||||||
submit = state.keymaps.dialogue.submit,
|
submit = state.keymaps.dialogue.submit,
|
||||||
},
|
},
|
||||||
on_submit = function(item)
|
on_submit = M.send_deletion
|
||||||
if item.text == "Confirm" then
|
|
||||||
local note_id
|
|
||||||
local node = state.tree:get_node()
|
|
||||||
if node.is_note then
|
|
||||||
note_id = node:get_id()
|
|
||||||
end
|
|
||||||
local parentId = node:get_parent_id()
|
|
||||||
while (parentId ~= nil) do
|
|
||||||
node = state.tree:get_node(parentId)
|
|
||||||
parentId = node:get_parent_id()
|
|
||||||
if node.is_note then
|
|
||||||
note_id = node:get_id()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local discussion_id = node:get_id()
|
|
||||||
discussion_id = string.sub(discussion_id, 2) -- Remove the "-" at the start
|
|
||||||
note_id = tonumber(string.sub(note_id, 2)) -- Remove the "-" at the start
|
|
||||||
|
|
||||||
local jsonTable = { discussion_id = discussion_id, note_id = note_id }
|
|
||||||
local json = vim.json.encode(jsonTable)
|
|
||||||
|
|
||||||
job.run_job("comment", "DELETE", json, function(data)
|
|
||||||
vim.notify(data.message, vim.log.levels.INFO)
|
|
||||||
state.tree:remove_node("-" .. note_id)
|
|
||||||
local discussion_node = state.tree:get_node("-" .. discussion_id)
|
|
||||||
if not discussion_node:has_children() then
|
|
||||||
state.tree:remove_node("-" .. discussion_id)
|
|
||||||
end
|
|
||||||
state.tree:render()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
})
|
||||||
menu:mount()
|
menu:mount()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Function to actually send the deletion to Gitlab
|
||||||
|
M.send_deletion = function(item)
|
||||||
|
if item.text == "Confirm" then
|
||||||
|
local current_node = state.tree:get_node()
|
||||||
|
|
||||||
M.edit_comment = function()
|
local note_node = discussions.get_note_node(current_node)
|
||||||
if u.base_invalid() then return end
|
local root_node = discussions.get_root_node(current_node)
|
||||||
local node = state.tree:get_node()
|
|
||||||
if node.is_discussion then return end
|
local jsonTable = { discussion_id = root_node.id, note_id = root_node.root_note_id or note_node.id }
|
||||||
if node.is_body then
|
local json = vim.json.encode(jsonTable)
|
||||||
local parentId = node:get_parent_id()
|
|
||||||
node = state.tree:get_node(parentId) -- Get the node for the comment
|
job.run_job("comment", "DELETE", json, function(data)
|
||||||
|
M.delete_node()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
editPopup:mount()
|
-- Function that opens the edit popup from the discussion tree
|
||||||
|
M.edit_comment = function()
|
||||||
|
if u.base_invalid() then return end
|
||||||
|
local current_node = state.tree:get_node()
|
||||||
|
local note_node = discussions.get_note_node(current_node)
|
||||||
|
local root_node = discussions.get_root_node(current_node)
|
||||||
|
|
||||||
local note_id = tonumber(string.sub(node:get_id(), 2)) -- Remove the "-" at the start
|
edit_popup:mount()
|
||||||
local discussion_id = node:get_parent_id()
|
|
||||||
discussion_id = string.sub(discussion_id, 2) -- Remove the "-" at the start
|
|
||||||
|
|
||||||
state.ACTIVE_DISCUSSION = discussion_id
|
local lines = {} -- Gather all lines from immediate children that aren't note nodes
|
||||||
state.ACTIVE_NOTE = note_id
|
local children_ids = note_node:get_child_ids()
|
||||||
|
for _, child_id in ipairs(children_ids) do
|
||||||
local lines = {}
|
local child_node = state.tree:get_node(child_id)
|
||||||
local childrenIds = node:get_child_ids()
|
if (not child_node:has_children()) then
|
||||||
for _, value in ipairs(childrenIds) do
|
local line = state.tree:get_node(child_id).text
|
||||||
local line = state.tree:get_node(value).text
|
table.insert(lines, line)
|
||||||
table.insert(lines, line)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local currentBuffer = vim.api.nvim_get_current_buf()
|
local currentBuffer = vim.api.nvim_get_current_buf()
|
||||||
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
|
||||||
keymaps.set_popup_keymaps(editPopup, M.send_edits)
|
keymaps.set_popup_keymaps(edit_popup, M.send_edits(tostring(root_node.id), note_node.root_note_id or note_node.id))
|
||||||
end
|
end
|
||||||
|
|
||||||
M.send_edits = function(text)
|
-- Function that actually makes the API call
|
||||||
local escapedText = string.gsub(text, "\n", "\\n")
|
M.send_edits = function(discussion_id, note_id)
|
||||||
|
return function(text)
|
||||||
local jsonTable = { discussion_id = state.ACTIVE_DISCUSSION, note_id = state.ACTIVE_NOTE, comment = escapedText }
|
local json_table = {
|
||||||
local json = vim.json.encode(jsonTable)
|
discussion_id = discussion_id,
|
||||||
|
note_id = note_id,
|
||||||
job.run_job("comment", "PATCH", json, function()
|
comment = text
|
||||||
vim.schedule(function()
|
}
|
||||||
local node = state.tree:get_node("-" .. state.ACTIVE_NOTE)
|
local json = vim.json.encode(json_table)
|
||||||
local childrenIds = node:get_child_ids()
|
job.run_job("comment", "PATCH", json, function(data)
|
||||||
for _, value in ipairs(childrenIds) do
|
vim.notify(data.message, vim.log.levels.INFO)
|
||||||
state.tree:remove_node(value)
|
M.redraw_node(text)
|
||||||
end
|
|
||||||
|
|
||||||
local newNoteTextNodes = {}
|
|
||||||
for bodyLine in text:gmatch("[^\n]+") do
|
|
||||||
table.insert(newNoteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
|
|
||||||
end
|
|
||||||
|
|
||||||
state.tree:set_nodes(newNoteTextNodes, "-" .. state.ACTIVE_NOTE)
|
|
||||||
|
|
||||||
state.tree:render()
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
u.darken_metadata(buf, '')
|
|
||||||
vim.notify("Edited comment!", vim.log.levels.INFO)
|
|
||||||
end)
|
end)
|
||||||
end)
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helpers
|
||||||
|
M.find_deletion_commit = function(file)
|
||||||
|
local current_line = vim.api.nvim_get_current_line()
|
||||||
|
local command = string.format("git log -S '%s' %s", current_line, file)
|
||||||
|
local handle = io.popen(command)
|
||||||
|
local output = handle:read("*line")
|
||||||
|
if output == nil then
|
||||||
|
vim.notify("Error reading SHA of deletion commit", vim.log.levels.ERROR)
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
handle:close()
|
||||||
|
local words = {}
|
||||||
|
for word in output:gmatch("%S+") do
|
||||||
|
table.insert(words, word)
|
||||||
|
end
|
||||||
|
|
||||||
|
return words[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
M.redraw_node = function(text)
|
||||||
|
local current_node = state.tree:get_node()
|
||||||
|
local note_node = discussions.get_note_node(current_node)
|
||||||
|
|
||||||
|
local childrenIds = note_node:get_child_ids()
|
||||||
|
for _, value in ipairs(childrenIds) do
|
||||||
|
state.tree:remove_node(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local newNoteTextNodes = {}
|
||||||
|
for bodyLine in text:gmatch("[^\n]+") do
|
||||||
|
table.insert(newNoteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
|
||||||
|
end
|
||||||
|
|
||||||
|
state.tree:set_nodes(newNoteTextNodes, "-" .. note_node.id)
|
||||||
|
|
||||||
|
state.tree:render()
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
u.darken_metadata(buf, '')
|
||||||
|
vim.notify("Edited comment!", vim.log.levels.INFO)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,108 +1,60 @@
|
|||||||
local u = require("gitlab.utils")
|
local u = require("gitlab.utils")
|
||||||
local NuiTree = require("nui.tree")
|
local NuiTree = require("nui.tree")
|
||||||
local NuiSplit = require("nui.split")
|
local NuiSplit = require("nui.split")
|
||||||
local job = require("gitlab.job")
|
local job = require("gitlab.job")
|
||||||
local state = require("gitlab.state")
|
local state = require("gitlab.state")
|
||||||
local Job = require("plenary.job")
|
local Popup = require("nui.popup")
|
||||||
local Popup = require("nui.popup")
|
local keymaps = require("gitlab.keymaps")
|
||||||
local keymaps = require("gitlab.keymaps")
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local replyPopup = Popup(u.create_popup_state("Reply", "80%", "80%"))
|
local replyPopup = Popup(u.create_popup_state("Reply", "80%", "80%"))
|
||||||
|
|
||||||
M.reply = function()
|
M.reply = function(discussion_id)
|
||||||
if u.base_invalid() then return end
|
if u.base_invalid() then return end
|
||||||
replyPopup:mount()
|
replyPopup:mount()
|
||||||
keymaps.set_popup_keymaps(replyPopup, M.send_reply)
|
keymaps.set_popup_keymaps(replyPopup, M.send_reply(discussion_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
M.send_reply = function(text)
|
M.send_reply = function(discussion_id)
|
||||||
local escapedText = string.gsub(text, "\n", "\\n")
|
return function(text)
|
||||||
|
local jsonTable = { discussion_id = discussion_id, reply = text }
|
||||||
local jsonTable = { discussion_id = state.ACTIVE_DISCUSSION, reply = escapedText }
|
local json = vim.json.encode(jsonTable)
|
||||||
local json = vim.json.encode(jsonTable)
|
job.run_job("reply", "POST", json, function(data)
|
||||||
|
M.add_note_to_tree(data.note, discussion_id)
|
||||||
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)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Places all of the discussions into a readable list
|
-- Places all of the discussions into a readable list
|
||||||
M.list_discussions = function()
|
M.list_discussions = function()
|
||||||
if u.base_invalid() then return end
|
if u.base_invalid() then return end
|
||||||
Job:new({
|
job.run_job("discussions", "GET", nil, function(data)
|
||||||
command = "curl",
|
if type(data.discussions) ~= "table" then
|
||||||
args = { "-s", string.format("localhost:%s/discussions", state.PORT) },
|
vim.notify("No discussions for this MR")
|
||||||
on_stdout = function(_, output)
|
return
|
||||||
local data_ok, data = pcall(vim.json.decode, output)
|
end
|
||||||
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
|
|
||||||
|
|
||||||
local splitState = state.DISCUSSION_SPLIT
|
local splitState = state.DISCUSSION_SPLIT
|
||||||
splitState.buf_options = { modifiable = false }
|
splitState.buf_options = { modifiable = false }
|
||||||
local split = NuiSplit(splitState)
|
local split = NuiSplit(splitState)
|
||||||
split:mount()
|
split:mount()
|
||||||
|
|
||||||
local buf = split.bufnr
|
local buf = split.bufnr
|
||||||
local allDiscussions = {}
|
state.SPLIT_BUF = buf
|
||||||
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)
|
local tree_nodes = M.add_discussions_to_table(data.discussions)
|
||||||
|
|
||||||
state.tree:render()
|
state.tree = NuiTree({ nodes = tree_nodes, bufnr = buf })
|
||||||
vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown')
|
M.set_tree_keymaps(buf)
|
||||||
u.darken_metadata(buf, '')
|
|
||||||
end)
|
state.tree:render()
|
||||||
end
|
vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown')
|
||||||
end,
|
u.darken_metadata(buf, '')
|
||||||
on_stderr = function(_, output)
|
end)
|
||||||
vim.notify("Could not run approve command!", vim.log.levels.ERROR)
|
|
||||||
error(output)
|
|
||||||
end,
|
|
||||||
}):start()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
M.jump_to_file = function()
|
M.jump_to_file = function()
|
||||||
local node = state.tree:get_node()
|
local node = state.tree:get_node()
|
||||||
if node == nil then return end
|
if node == nil then return end
|
||||||
|
|
||||||
@@ -114,23 +66,11 @@ M.jump_to_file = function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local childrenIds = node:get_child_ids()
|
local discussion_node = M.get_root_node(node)
|
||||||
-- We have selected a note node
|
u.jump_to_file(discussion_node.file_name, discussion_node.line_number)
|
||||||
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
|
end
|
||||||
|
|
||||||
M.set_tree_keymaps = function(buf)
|
M.set_tree_keymaps = function(buf)
|
||||||
-- Jump to file location where comment was left
|
-- Jump to file location where comment was left
|
||||||
vim.keymap.set('n', state.keymaps.discussion_tree.jump_to_location, function()
|
vim.keymap.set('n', state.keymaps.discussion_tree.jump_to_location, function()
|
||||||
M.jump_to_file()
|
M.jump_to_file()
|
||||||
@@ -142,7 +82,7 @@ M.set_tree_keymaps = function(buf)
|
|||||||
|
|
||||||
vim.keymap.set('n', state.keymaps.discussion_tree.delete_comment, function()
|
vim.keymap.set('n', state.keymaps.discussion_tree.delete_comment, function()
|
||||||
require("gitlab.comment").delete_comment()
|
require("gitlab.comment").delete_comment()
|
||||||
end)
|
end, { buffer = true })
|
||||||
|
|
||||||
-- Expand/collapse the current node
|
-- Expand/collapse the current node
|
||||||
vim.keymap.set('n', state.keymaps.discussion_tree.toggle_node, function()
|
vim.keymap.set('n', state.keymaps.discussion_tree.toggle_node, function()
|
||||||
@@ -162,7 +102,6 @@ M.set_tree_keymaps = function(buf)
|
|||||||
node:expand()
|
node:expand()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
state.tree:render()
|
state.tree:render()
|
||||||
u.darken_metadata(buf, '')
|
u.darken_metadata(buf, '')
|
||||||
end,
|
end,
|
||||||
@@ -171,47 +110,132 @@ M.set_tree_keymaps = function(buf)
|
|||||||
vim.keymap.set('n', 'r', function()
|
vim.keymap.set('n', 'r', function()
|
||||||
local node = state.tree:get_node()
|
local node = state.tree:get_node()
|
||||||
if node == nil then return end
|
if node == nil then return end
|
||||||
|
local discussion_node = M.get_root_node(node)
|
||||||
-- Get closest discussion parent
|
M.reply(tostring(discussion_node.id))
|
||||||
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, { buffer = true })
|
||||||
end
|
end
|
||||||
|
|
||||||
M.build_note = function(note)
|
M.get_root_node = function(node)
|
||||||
local noteTextNodes = {}
|
if (not node.is_root) then
|
||||||
|
local parent_id = node:get_parent_id()
|
||||||
|
return M.get_root_node(state.tree:get_node(parent_id))
|
||||||
|
else
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.get_note_node = function(node)
|
||||||
|
if (not node.is_note) then
|
||||||
|
local parent_id = node:get_parent_id()
|
||||||
|
if parent_id == nil then return node end
|
||||||
|
return M.get_note_node(state.tree:get_node(parent_id))
|
||||||
|
else
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.build_note_body = function(note)
|
||||||
|
local text_nodes = {}
|
||||||
for bodyLine in note.body:gmatch("[^\n]+") do
|
for bodyLine in note.body:gmatch("[^\n]+") do
|
||||||
table.insert(noteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
|
table.insert(text_nodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
|
||||||
end
|
end
|
||||||
local noteHeader = "@" ..
|
local noteHeader = "@" ..
|
||||||
note.author.username .. " on " .. u.format_date(note.created_at)
|
note.author.username .. " " .. u.format_date(note.created_at)
|
||||||
|
|
||||||
|
return noteHeader, text_nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
M.build_note = function(note)
|
||||||
|
local text, text_nodes = M.build_note_body(note)
|
||||||
local line_number = note.position.new_line or note.position.old_line
|
local line_number = note.position.new_line or note.position.old_line
|
||||||
local note_node = NuiTree.Node(
|
local note_node = NuiTree.Node(
|
||||||
{
|
{
|
||||||
text = noteHeader,
|
text = text,
|
||||||
id = note.id,
|
id = note.id,
|
||||||
file_name = note.position.new_path,
|
file_name = note.position.new_path,
|
||||||
line_number = line_number,
|
line_number = line_number,
|
||||||
is_note = true
|
is_note = true
|
||||||
}, noteTextNodes)
|
}, text_nodes)
|
||||||
|
|
||||||
return note_node
|
return note_node, text, text_nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
M.add_note_to_tree = function(note, discussion_id)
|
||||||
|
local note_node = M.build_note(note)
|
||||||
|
note_node:expand()
|
||||||
|
state.tree:add_node(note_node, discussion_id and ("-" .. discussion_id) or nil)
|
||||||
|
state.tree:render()
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
u.darken_metadata(buf, '')
|
||||||
|
vim.notify("Sent reply!", vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.refresh_tree = function()
|
||||||
|
job.run_job("discussions", "GET", nil, function(data)
|
||||||
|
if type(data.discussions) ~= "table" then
|
||||||
|
vim.notify("No discussions for this MR")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not state.SPLIT_BUF then return end
|
||||||
|
|
||||||
|
vim.api.nvim_buf_set_option(state.SPLIT_BUF, 'modifiable', true)
|
||||||
|
vim.api.nvim_buf_set_option(state.SPLIT_BUF, 'readonly', false)
|
||||||
|
vim.api.nvim_buf_set_lines(state.SPLIT_BUF, 0, -1, false, {})
|
||||||
|
vim.api.nvim_buf_set_option(state.SPLIT_BUF, 'readonly', true)
|
||||||
|
vim.api.nvim_buf_set_option(state.SPLIT_BUF, 'modifiable', false)
|
||||||
|
|
||||||
|
local tree_nodes = M.add_discussions_to_table(data.discussions)
|
||||||
|
state.tree = NuiTree({ nodes = tree_nodes, bufnr = state.SPLIT_BUF })
|
||||||
|
M.set_tree_keymaps(state.SPLIT_BUF)
|
||||||
|
state.tree:render()
|
||||||
|
vim.api.nvim_buf_set_option(state.SPLIT_BUF, 'filetype', 'markdown')
|
||||||
|
u.darken_metadata(state.SPLIT_BUF, '')
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.add_discussions_to_table = function(discussions)
|
||||||
|
local t = {}
|
||||||
|
for _, discussion in ipairs(discussions) do
|
||||||
|
local discussion_children = {}
|
||||||
|
|
||||||
|
-- These properties are filled in by the first note
|
||||||
|
local root_text = ''
|
||||||
|
local root_note_id = ''
|
||||||
|
local root_line_number = 0
|
||||||
|
local root_file_name = ''
|
||||||
|
local root_id = 0
|
||||||
|
local root_text_nodes = {}
|
||||||
|
|
||||||
|
for j, note in ipairs(discussion.notes) do
|
||||||
|
if j == 1 then
|
||||||
|
__, root_text, root_text_nodes = M.build_note(note)
|
||||||
|
root_file_name = note.position.new_path
|
||||||
|
root_line_number = note.position.new_line or note.position.old_line
|
||||||
|
root_id = discussion.id
|
||||||
|
root_note_id = note.id
|
||||||
|
else -- Otherwise insert it as a child node...
|
||||||
|
local note_node = M.build_note(note)
|
||||||
|
table.insert(discussion_children, note_node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Creates the first node in the discussion, and attaches children
|
||||||
|
local body = u.join_tables(root_text_nodes, discussion_children)
|
||||||
|
local root_node = NuiTree.Node({
|
||||||
|
text = root_text,
|
||||||
|
is_note = true,
|
||||||
|
is_root = true,
|
||||||
|
id = root_id,
|
||||||
|
root_note_id = root_note_id,
|
||||||
|
file_name = root_file_name,
|
||||||
|
line_number = root_line_number,
|
||||||
|
}, body)
|
||||||
|
|
||||||
|
table.insert(t, root_node)
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -95,12 +95,33 @@ local base_invalid = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local format_date = function(date_string)
|
local format_date = function(date_string)
|
||||||
|
local date_table = os.date("!*t")
|
||||||
local year, month, day, hour, min, sec = date_string:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)")
|
local year, month, day, hour, min, sec = date_string:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)")
|
||||||
local date = os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec })
|
local date = os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec })
|
||||||
|
|
||||||
-- Format date into human-readable string without leading zeros
|
local current_date = os.time({
|
||||||
local formatted_date = os.date("%A, %B %e at %l:%M %p", date)
|
year = date_table.year,
|
||||||
return formatted_date
|
month = date_table.month,
|
||||||
|
day = date_table.day,
|
||||||
|
hour = date_table.hour,
|
||||||
|
min = date_table.min,
|
||||||
|
sec = date_table.sec
|
||||||
|
})
|
||||||
|
|
||||||
|
local time_diff = current_date - date
|
||||||
|
|
||||||
|
if time_diff < 60 then
|
||||||
|
return time_diff .. " seconds ago"
|
||||||
|
elseif time_diff < 3600 then
|
||||||
|
return math.floor(time_diff / 60) .. " minutes ago"
|
||||||
|
elseif time_diff < 86400 then
|
||||||
|
return math.floor(time_diff / 3600) .. " hours ago"
|
||||||
|
elseif time_diff < 2592000 then
|
||||||
|
return math.floor(time_diff / 86400) .. " days ago"
|
||||||
|
else
|
||||||
|
local formatted_date = os.date("%A, %B %e", date)
|
||||||
|
return formatted_date
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local add_comment_sign = function(line_number)
|
local add_comment_sign = function(line_number)
|
||||||
@@ -222,7 +243,16 @@ local current_file_path = function()
|
|||||||
return vim.fn.fnamemodify(path, ':p')
|
return vim.fn.fnamemodify(path, ':p')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Function to join two tables
|
||||||
|
function join_tables(table1, table2)
|
||||||
|
for _, value in ipairs(table2) do
|
||||||
|
table.insert(table1, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
return table1
|
||||||
|
end
|
||||||
|
|
||||||
|
M.join_tables = join_tables
|
||||||
M.get_relative_file_path = get_relative_file_path
|
M.get_relative_file_path = get_relative_file_path
|
||||||
M.get_current_line_number = get_current_line_number
|
M.get_current_line_number = get_current_line_number
|
||||||
M.get_buffer_text = get_buffer_text
|
M.get_buffer_text = get_buffer_text
|
||||||
|
|||||||
Reference in New Issue
Block a user