This MR is a #MAJOR breaking change to the plugin. While the plugin will continue to work for users with their existing settings, they will be informed of outdated configuration (diagnostics and signs have been simplified) the next time they open the reviewer.

Fix: Trim trailing slash from custom URLs
Update: .github/CONTRIBUTING.md, .github/ISSUE_TEMPLATE/bug_report.md
Feat: Improve discussion tree toggling (#192)
Fix: Toggle modified notes (#188)
Fix: Toggle discussion nodes correctly
Feat: Show Help keymap in discussion tree winbar
Fix: Enable toggling nodes from the note body
Fix: Enable toggling resolved status from child nodes
Fix: Only try to show emoji popup on note nodes
Feat: Add keymap for toggling tree type
Fix: Disable tree type toggling in Notes
Fix Multi Line Issues (Large Refactor) (#197)
Fix: Multi-line discussions. The calculation of a range for a multiline comment has been consolidated and moved into the location.lua file. This does not attempt to fix diagnostics.
Refactor: It refactors the discussions code to split hunk parsing and management into a separate module
Fix: Don't allow comments on modified buffers #194 by preventing comments on the reviewer when using --imply-local and when the working tree is dirty entirely.
Refactor: It introduces a new List class for data aggregation, filtering, etc.
Fix: It removes redundant API calls and refreshes from the discussion pane
Fix: Location provider (#198)
Fix: add nil check for Diffview performance issue (#199)
Fix: Switch Tabs During Comment Creation (#200)
Fix: Check if file is modified (#201)
Fix: Off-By-One Issue in Old SHA (#202)
Fix: Rebuild Diagnostics + Signs (#203)
Fix: Off-By-One Issue in New SHA (#205)
Fix: Reviewer Jumps to wrong location (#206)

BREAKING CHANGE: Changes configuration of diagnostics and signs in the setup call.
This commit is contained in:
Harrison (Harry) Cramer
2024-03-03 11:52:37 -05:00
committed by GitHub
parent f6a5238d4b
commit b5b475ce8b
31 changed files with 1529 additions and 1298 deletions

View File

@@ -101,3 +101,22 @@
---@field user_data table
---@field source string
---@field code string?
---@class LineRange
---@field start_line integer
---@field end_line integer
---@class DiffviewInfo
---@field modification_type string
---@field file_name string
---@field current_bufnr integer
---@field new_sha_win_id integer
---@field old_sha_win_id integer
---@field opposite_bufnr integer
---@field new_line_from_buf integer
---@field old_line_from_buf integer
---@class LocationData
---@field old_line integer | nil
---@field new_line integer | nil
---@field line_range ReviewerRangeInfo|nil

View File

@@ -9,9 +9,13 @@ local job = require("gitlab.job")
local u = require("gitlab.utils")
local state = require("gitlab.state")
local reviewer = require("gitlab.reviewer")
local List = require("gitlab.utils.list")
local miscellaneous = require("gitlab.actions.miscellaneous")
local discussions_tree = require("gitlab.actions.discussions.tree")
local signs = require("gitlab.actions.discussions.signs")
local diffview_lib = require("diffview.lib")
local common = require("gitlab.indicators.common")
local signs = require("gitlab.indicators.signs")
local diagnostics = require("gitlab.indicators.diagnostics")
local winbar = require("gitlab.actions.discussions.winbar")
local help = require("gitlab.actions.help")
local emoji = require("gitlab.emoji")
@@ -53,28 +57,68 @@ end
---Initialize everything for discussions like setup of signs, callbacks for reviewer, etc.
M.initialize_discussions = function()
signs.setup_signs()
-- Setup callback to refresh discussion data, discussion signs and diagnostics whenever the reviewed file changes.
reviewer.set_callback_for_file_changed(M.refresh_discussion_data)
-- Setup callback to clear signs and diagnostics whenever reviewer is left.
reviewer.set_callback_for_reviewer_leave(signs.clear_signs_and_diagnostics)
reviewer.set_callback_for_file_changed(function()
M.refresh_view()
M.modifiable(false)
end)
reviewer.set_callback_for_reviewer_enter(function()
M.modifiable(false)
end)
reviewer.set_callback_for_reviewer_leave(function()
signs.clear_signs()
diagnostics.clear_diagnostics()
M.modifiable(true)
end)
end
--- Ensures that the both buffers in the reviewer are/not modifiable. Relevant if the user is using
--- the --imply-local setting
M.modifiable = function(bool)
local view = diffview_lib.get_current_view()
local a = view.cur_layout.a.file.bufnr
local b = view.cur_layout.b.file.bufnr
if a ~= nil and vim.api.nvim_buf_is_loaded(a) then
vim.api.nvim_buf_set_option(a, "modifiable", bool)
end
if b ~= nil and vim.api.nvim_buf_is_loaded(b) then
vim.api.nvim_buf_set_option(b, "modifiable", bool)
end
end
---Refresh discussion data, signs, diagnostics, and winbar with new data from API
M.refresh_discussion_data = function()
--- and rebuild the entire view
M.refresh = function()
M.load_discussions(function()
if state.settings.discussion_sign.enabled then
signs.refresh_signs(M.discussions)
end
if state.settings.discussion_diagnostic.enabled then
signs.refresh_diagnostics(M.discussions)
end
if M.split_visible then
local linked_is_focused = M.linked_bufnr == M.focused_bufnr
winbar.update_winbar(M.discussions, M.unlinked_discussions, linked_is_focused and "Discussions" or "Notes")
end
M.refresh_view()
end)
end
--- Take existing data and refresh the diagnostics, the winbar, and the signs
M.refresh_view = function()
if state.settings.discussion_signs.enabled then
diagnostics.refresh_diagnostics(M.discussions)
end
if M.split_visible then
local linked_is_focused = M.linked_bufnr == M.focused_bufnr
winbar.update_winbar(M.discussions, M.unlinked_discussions, linked_is_focused and "Discussions" or "Notes")
end
end
---Toggle Discussions tree type between "simple" and "by_file_name"
---@param unlinked boolean True if selected view type is Notes (unlinked discussions)
M.toggle_tree_type = function(unlinked)
if unlinked then
u.notify("Toggling tree type is only possible in Discussions", vim.log.levels.INFO)
return
end
if state.settings.discussion_tree.tree_type == "simple" then
state.settings.discussion_tree.tree_type = "by_file_name"
else
state.settings.discussion_tree.tree_type = "simple"
end
M.rebuild_discussion_tree()
end
---Opens the discussion tree, sets the keybindings. It also
---creates the tree for notes (which are not linked to specific lines of code)
---@param callback function?
@@ -124,7 +168,7 @@ M.toggle = function(callback)
M.focused_bufnr = default_buffer
M.switch_can_edit_bufs(false)
winbar.update_winbar(M.discussions, M.unlinked_discussions, default_discussions and "Discussions" or "Notes")
M.refresh_view()
vim.api.nvim_set_current_win(current_window)
if type(callback) == "function" then
@@ -133,6 +177,7 @@ M.toggle = function(callback)
end)
end
-- Change between views in the discussion panel, either notes or discussions
local switch_view_type = function()
local change_to_unlinked = M.linked_bufnr == M.focused_bufnr
local new_bufnr = change_to_unlinked and M.unlinked_bufnr or M.linked_bufnr
@@ -153,7 +198,7 @@ end
---Move to the discussion tree at the discussion from diagnostic on current line.
M.move_to_discussion_tree = function()
local current_line = vim.api.nvim_win_get_cursor(0)[1]
local diagnostics = vim.diagnostic.get(0, { namespace = signs.diagnostics_namespace, lnum = current_line - 1 })
local d = vim.diagnostic.get(0, { namespace = diagnostics.diagnostics_namespace, lnum = current_line - 1 })
---Function used to jump to the discussion tree after the menu selection.
local jump_after_menu_selection = function(diagnostic)
@@ -184,11 +229,11 @@ M.move_to_discussion_tree = function()
end
end
if #diagnostics == 0 then
if #d == 0 then
u.notify("No diagnostics for this line", vim.log.levels.WARN)
return
elseif #diagnostics > 1 then
vim.ui.select(diagnostics, {
elseif #d > 1 then
vim.ui.select(d, {
prompt = "Choose discussion to jump to",
format_item = function(diagnostic)
return diagnostic.message
@@ -200,7 +245,7 @@ M.move_to_discussion_tree = function()
jump_after_menu_selection(diagnostic)
end)
else
jump_after_menu_selection(diagnostics[1])
jump_after_menu_selection(d[1])
end
end
@@ -239,36 +284,23 @@ 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, unlinked)
M.send_deletion = function(tree)
local current_node = tree:get_node()
local note_node = M.get_note_node(tree, current_node)
local root_node = M.get_root_node(tree, current_node)
local note_id = note_node.is_root and root_node.root_note_id or note_node.id
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 not note_node.is_root then
tree:remove_node("-" .. note_id) -- Note is not a discussion root, safe to remove
tree:render()
if note_node.is_root then
-- Replace root node w/ current node's contents...
tree:remove_node("-" .. root_node.id)
else
if unlinked then
M.unlinked_discussions = u.remove_first_value(M.unlinked_discussions)
M.rebuild_unlinked_discussion_tree()
else
M.discussions = u.remove_first_value(M.discussions)
M.rebuild_discussion_tree()
end
M.add_empty_titles({
{ M.linked_bufnr, M.discussions, "No Discussions for this MR" },
{ M.unlinked_bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
})
M.switch_can_edit_bufs(false)
tree:remove_node("-" .. note_id)
end
M.refresh_discussion_data()
tree:render()
M.refresh()
end)
end
@@ -278,18 +310,22 @@ M.edit_comment = function(tree, unlinked)
local current_node = tree:get_node()
local note_node = M.get_note_node(tree, current_node)
local root_node = M.get_root_node(tree, current_node)
if note_node == nil or root_node == nil then
u.notify("Could not get root or note node", vim.log.levels.ERROR)
return
end
edit_popup:mount()
local lines = {} -- Gather all lines from immediate children that aren't note nodes
local children_ids = note_node:get_child_ids()
for _, child_id in ipairs(children_ids) do
-- Gather all lines from immediate children that aren't note nodes
local lines = List.new(note_node:get_child_ids()):reduce(function(agg, child_id)
local child_node = tree:get_node(child_id)
if not child_node:has_children() then
local line = tree:get_node(child_id).text
table.insert(lines, line)
table.insert(agg, line)
end
end
return agg
end, {})
local currentBuffer = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
@@ -327,7 +363,15 @@ end
-- This function (settings.discussion_tree.toggle_discussion_resolved) will toggle the resolved status of the current discussion and send the change to the Go server
M.toggle_discussion_resolved = function(tree)
local note = tree:get_node()
if not note or not note.resolvable then
if note == nil then
return
end
-- Switch to the root node to enable toggling from child nodes and note bodies
if not note.resolvable and M.is_node_note(note) then
note = M.get_root_node(tree, note)
end
if note == nil then
return
end
@@ -339,29 +383,86 @@ 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_discussion_data()
M.refresh()
end)
end
---Takes a node and returns the line where the note is positioned in the new SHA. If
---the line is not in the new SHA, returns nil
---@param node any
---@return number|nil
local function get_new_line(node)
if node.new_line == nil then
return nil
end
---@type GitlabLineRange|nil
local range = node.range
if range == nil then
if node.new_line == nil then
return nil
end
return node.new_line
end
local start_new_line, _ = common.parse_line_code(range.start.line_code)
return start_new_line
end
---Takes a node and returns the line where the note is positioned in the old SHA. If
---the line is not in the old SHA, returns nil
---@param node any
---@return number|nil
local function get_old_line(node)
if node.old_line == nil then
return nil
end
---@type GitlabLineRange|nil
local range = node.range
if range == nil then
return node.old_line
end
local _, start_old_line = common.parse_line_code(range.start.line_code)
return start_old_line
end
-- This function (settings.discussion_tree.jump_to_reviewer) will jump the cursor to the reviewer's location associated with the note. The implementation depends on the reviewer
M.jump_to_reviewer = function(tree)
local file_name, new_line, old_line, is_undefined_type, error = M.get_note_location(tree)
if error ~= nil then
u.notify(error, vim.log.levels.ERROR)
local node = tree:get_node()
local root_node = M.get_root_node(tree, node)
if root_node == nil then
u.notify("Could not get discussion node", vim.log.levels.ERROR)
return
end
reviewer.jump(file_name, new_line, old_line, { is_undefined_type = is_undefined_type })
reviewer.jump(root_node.file_name, get_new_line(root_node), get_old_line(root_node))
M.refresh_view()
end
-- This function (settings.discussion_tree.jump_to_file) will jump to the file changed in a new tab
M.jump_to_file = function(tree)
local file_name, new_line, old_line, _, error = M.get_note_location(tree)
if error ~= nil then
u.notify(error, vim.log.levels.ERROR)
local node = tree:get_node()
local root_node = M.get_root_node(tree, node)
if root_node == nil then
u.notify("Could not get discussion node", vim.log.levels.ERROR)
return
end
vim.cmd.tabnew()
u.jump_to_file(file_name, (new_line or old_line))
local line_number = get_new_line(root_node) or get_old_line(root_node)
if line_number == nil then
line_number = 1
end
local bufnr = vim.fn.bufnr(root_node.filename)
if bufnr ~= -1 then
vim.cmd("buffer " .. bufnr)
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
return
end
-- If buffer is not already open, open it
vim.cmd("edit " .. root_node.filename)
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
end
-- This function (settings.discussion_tree.toggle_node) expands/collapses the current node and its children
@@ -370,6 +471,15 @@ M.toggle_node = function(tree)
if node == nil then
return
end
-- Switch to the "note" node from "note_body" nodes to enable toggling discussions inside comments
if node.type == "note_body" then
node = tree:get_node(node:get_parent_id())
end
if node == nil then
return
end
local children = node:get_child_ids()
if node == nil then
return
@@ -663,6 +773,9 @@ M.is_current_node_note = function(tree)
end
M.set_tree_keymaps = function(tree, bufnr, unlinked)
vim.keymap.set("n", state.settings.discussion_tree.toggle_tree_type, function()
M.toggle_tree_type(unlinked)
end, { buffer = bufnr, desc = "Toggle tree type between `simple` and `by_file_name`" })
vim.keymap.set("n", state.settings.discussion_tree.edit_comment, function()
if M.is_current_node_note(tree) then
M.edit_comment(tree, unlinked)
@@ -843,25 +956,6 @@ M.add_reply_to_tree = function(tree, note, discussion_id)
tree:render()
end
---Get note location
---@param tree NuiTree
---@return string, string, string, boolean, string?
M.get_note_location = function(tree)
local node = tree:get_node()
if node == nil then
return "", "", "", false, "Could not get node"
end
local discussion_node = M.get_root_node(tree, node)
if discussion_node == nil then
return "", "", "", false, "Could not get discussion node"
end
return discussion_node.file_name,
discussion_node.new_line,
discussion_node.old_line,
discussion_node.undefined_type or false,
nil
end
---@param tree NuiTree
M.open_in_browser = function(tree)
local current_node = tree:get_node()

View File

@@ -1,336 +0,0 @@
local state = require("gitlab.state")
local u = require("gitlab.utils")
local reviewer = require("gitlab.reviewer")
local discussion_sign_name = "gitlab_discussion"
local discussion_helper_sign_start = "gitlab_discussion_helper_start"
local discussion_helper_sign_mid = "gitlab_discussion_helper_mid"
local discussion_helper_sign_end = "gitlab_discussion_helper_end"
local diagnostics_namespace = vim.api.nvim_create_namespace(discussion_sign_name)
local M = {}
M.diagnostics_namespace = diagnostics_namespace
---Clear all signs and diagnostics
M.clear_signs_and_diagnostics = function()
vim.fn.sign_unplace(discussion_sign_name)
vim.diagnostic.reset(diagnostics_namespace)
end
---Refresh the discussion signs for currently loaded file in reviewer For convinience we use same
---string for sign name and sign group ( currently there is only one sign needed)
---@param discussions Discussion[]
M.refresh_signs = function(discussions)
local filtered_discussions = M.filter_discussions_for_signs_and_diagnostics(discussions)
if filtered_discussions == nil then
vim.diagnostic.reset(diagnostics_namespace)
return
end
local new_signs, old_signs, error = M.parse_signs_from_discussions(filtered_discussions)
if error ~= nil then
vim.notify(error, vim.log.levels.ERROR)
return
end
vim.fn.sign_unplace(discussion_sign_name)
reviewer.place_sign(old_signs, "old")
reviewer.place_sign(new_signs, "new")
end
---Refresh the diagnostics for the currently reviewed file
---@param discussions Discussion[]
M.refresh_diagnostics = function(discussions)
-- Keep in mind that diagnostic line numbers use 0-based indexing while line numbers use
-- 1-based indexing
local filtered_discussions = M.filter_discussions_for_signs_and_diagnostics(discussions)
if filtered_discussions == nil then
vim.diagnostic.reset(diagnostics_namespace)
return
end
local new_diagnostics, old_diagnostics = M.parse_diagnostics_from_discussions(filtered_discussions)
vim.diagnostic.reset(diagnostics_namespace)
reviewer.set_diagnostics(
diagnostics_namespace,
new_diagnostics,
"new",
state.settings.discussion_diagnostic.display_opts
)
reviewer.set_diagnostics(
diagnostics_namespace,
old_diagnostics,
"old",
state.settings.discussion_diagnostic.display_opts
)
end
---Filter all discussions which are relevant for currently visible signs and diagnostscs.
---@return Discussion[]?
M.filter_discussions_for_signs_and_diagnostics = function(all_discussions)
if type(all_discussions) ~= "table" then
return
end
local file = reviewer.get_current_file()
if not file then
return
end
local discussions = {}
for _, discussion in ipairs(all_discussions) do
local first_note = discussion.notes[1]
if
type(first_note.position) == "table"
and (first_note.position.new_path == file or first_note.position.old_path == file)
then
if
--Skip resolved discussions
not (
state.settings.discussion_sign_and_diagnostic.skip_resolved_discussion
and first_note.resolvable
and first_note.resolved
)
--Skip discussions from old revisions
and not (
state.settings.discussion_sign_and_diagnostic.skip_old_revision_discussion
and u.from_iso_format_date_to_timestamp(first_note.created_at)
<= u.from_iso_format_date_to_timestamp(state.MR_REVISIONS[1].created_at)
)
then
table.insert(discussions, discussion)
end
end
end
return discussions
end
---Define signs for discussions if not already defined
M.setup_signs = function()
local discussion_sign = state.settings.discussion_sign
local signs = {
[discussion_sign_name] = discussion_sign.text,
[discussion_helper_sign_start] = discussion_sign.helper_signs.start,
[discussion_helper_sign_mid] = discussion_sign.helper_signs.mid,
[discussion_helper_sign_end] = discussion_sign.helper_signs["end"],
}
for sign_name, sign_text in pairs(signs) do
if #vim.fn.sign_getdefined(sign_name) == 0 then
vim.fn.sign_define(sign_name, {
text = sign_text,
linehl = discussion_sign.linehl,
texthl = discussion_sign.texthl,
culhl = discussion_sign.culhl,
numhl = discussion_sign.numhl,
})
end
end
end
---Iterates over each discussion and returns a list of tables with sign
---data, for instance group, priority, line number etc.
---@param discussions Discussion[]
---@return DiagnosticTable[], DiagnosticTable[], string?
M.parse_diagnostics_from_discussions = function(discussions)
local new_diagnostics = {}
local old_diagnostics = {}
for _, discussion in ipairs(discussions) do
local first_note = discussion.notes[1]
local message = ""
for _, note in ipairs(discussion.notes) do
message = message .. M.build_note_header(note) .. "\n" .. note.body .. "\n"
end
local diagnostic = {
message = message,
col = 0,
severity = state.settings.discussion_diagnostic.severity,
user_data = { discussion_id = discussion.id, header = M.build_note_header(discussion.notes[1]) },
source = "gitlab",
code = state.settings.discussion_diagnostic.code,
}
-- Diagnostics for line range discussions are tricky - you need to set lnum to be the
-- line number equal to note.position.new_line or note.position.old_line because that is the
-- only line where you can trigger the diagnostic to show. This also needs to be in sync
-- with the sign placement.
local line_range = first_note.position.line_range
if line_range ~= nil then
local start_old_line, start_new_line = M.parse_line_code(line_range.start.line_code)
local end_old_line, end_new_line = M.parse_line_code(line_range["end"].line_code)
local start_type = line_range.start.type
if start_type == "new" then
local new_diagnostic
if first_note.position.new_line == start_new_line then
new_diagnostic = {
lnum = start_new_line - 1,
end_lnum = end_new_line - 1,
}
else
new_diagnostic = {
lnum = end_new_line - 1,
end_lnum = start_new_line - 1,
}
end
new_diagnostic = vim.tbl_deep_extend("force", new_diagnostic, diagnostic)
table.insert(new_diagnostics, new_diagnostic)
elseif start_type == "old" or start_type == "expanded" or start_type == "" then
local old_diagnostic
if first_note.position.old_line == start_old_line then
old_diagnostic = {
lnum = start_old_line - 1,
end_lnum = end_old_line - 1,
}
else
old_diagnostic = {
lnum = end_old_line - 1,
end_lnum = start_old_line - 1,
}
end
old_diagnostic = vim.tbl_deep_extend("force", old_diagnostic, diagnostic)
table.insert(old_diagnostics, old_diagnostic)
else -- Comments on expanded, non-changed lines
return {}, {}, string.format("Unsupported line range type found for discussion %s", discussion.id)
end
else -- Diagnostics for single line discussions.
if first_note.position.new_line ~= nil and first_note.position.old_line == nil then
local new_diagnostic = {
lnum = first_note.position.new_line - 1,
}
new_diagnostic = vim.tbl_deep_extend("force", new_diagnostic, diagnostic)
table.insert(new_diagnostics, new_diagnostic)
end
if first_note.position.old_line ~= nil then
local old_diagnostic = {
lnum = first_note.position.old_line - 1,
}
old_diagnostic = vim.tbl_deep_extend("force", old_diagnostic, diagnostic)
table.insert(old_diagnostics, old_diagnostic)
end
end
end
return new_diagnostics, old_diagnostics
end
local base_sign = {
name = discussion_sign_name,
group = discussion_sign_name,
priority = state.settings.discussion_sign.priority,
buffer = nil,
}
local base_helper_sign = {
name = discussion_sign_name,
group = discussion_sign_name,
priority = state.settings.discussion_sign.priority - 1,
buffer = nil,
}
---Iterates over each discussion and returns a list of tables with sign
---data, for instance group, priority, line number etc.
---@param discussions Discussion[]
---@return SignTable[], SignTable[], string?
M.parse_signs_from_discussions = function(discussions)
local new_signs = {}
local old_signs = {}
for _, discussion in ipairs(discussions) do
local first_note = discussion.notes[1]
local line_range = first_note.position.line_range
-- We have a line range which means we either have a multi-line comment or a comment
-- on a line in an "expanded" part of a file
if line_range ~= nil then
local start_old_line, start_new_line = M.parse_line_code(line_range.start.line_code)
local end_old_line, end_new_line = M.parse_line_code(line_range["end"].line_code)
local discussion_line, start_line, end_line
local start_type = line_range.start.type
if start_type == "new" then
table.insert(
new_signs,
vim.tbl_deep_extend("force", {
id = first_note.id,
lnum = first_note.position.new_line,
}, base_sign)
)
discussion_line = first_note.position.new_line
start_line = start_new_line
end_line = end_new_line
elseif start_type == "old" or start_type == "expanded" or start_type == "" then
table.insert(
old_signs,
vim.tbl_deep_extend("force", {
id = first_note.id,
lnum = first_note.position.old_line,
}, base_sign)
)
discussion_line = first_note.position.old_line
start_line = start_old_line
end_line = end_old_line
else
return {}, {}, string.format("Unsupported line range type found for discussion %s", discussion.id)
end
-- Helper signs does not have specific ids currently.
if state.settings.discussion_sign.helper_signs.enabled then
local helper_signs = {}
if start_line > end_line then
start_line, end_line = end_line, start_line
end
for i = start_line, end_line do
if i ~= discussion_line then
local sign_name
if i == start_line then
sign_name = discussion_helper_sign_start
elseif i == end_line then
sign_name = discussion_helper_sign_end
else
sign_name = discussion_helper_sign_mid
end
table.insert(
helper_signs,
vim.tbl_deep_extend("keep", {
name = sign_name,
lnum = i,
}, base_helper_sign)
)
end
end
if start_type == "new" then
vim.list_extend(new_signs, helper_signs)
elseif start_type == "old" or start_type == "expanded" or start_type == "" then
vim.list_extend(old_signs, helper_signs)
end
end
else -- The note is a normal comment, not a range comment
local sign = vim.tbl_deep_extend("force", {
id = first_note.id,
}, base_sign)
if first_note.position.new_line ~= nil and first_note.position.old_line == nil then
table.insert(new_signs, vim.tbl_deep_extend("force", { lnum = first_note.position.new_line }, sign))
end
if first_note.position.old_line ~= nil then
table.insert(old_signs, vim.tbl_deep_extend("force", { lnum = first_note.position.old_line }, sign))
end
end
end
return new_signs, old_signs, nil
end
---Parse line code and return old and new line numbers
---@param line_code string gitlab line code -> 588440f66559714280628a4f9799f0c4eb880a4a_10_10
---@return number?
M.parse_line_code = function(line_code)
local line_code_regex = "%w+_(%d+)_(%d+)"
local old_line, new_line = line_code:match(line_code_regex)
return tonumber(old_line), tonumber(new_line)
end
---Build note header from note.
---@param note Note
---@return string
M.build_note_header = function(note)
return "@" .. note.author.username .. " " .. u.time_since(note.created_at)
end
return M

View File

@@ -81,7 +81,7 @@ end
---Build note header from note.
---@param note Note
---@return string
local function build_note_header(note)
M.build_note_header = function(note)
return "@" .. note.author.username .. " " .. u.time_since(note.created_at)
end
@@ -112,7 +112,7 @@ local function build_note_body(note, resolve_info)
or state.settings.discussion_tree.unresolved
end
local noteHeader = build_note_header(note) .. " " .. resolve_symbol
local noteHeader = M.build_note_header(note) .. " " .. resolve_symbol
return noteHeader, text_nodes
end
@@ -158,8 +158,9 @@ M.add_discussions_to_table = function(items, unlinked)
local root_id
local root_text_nodes = {}
local resolvable = false
---@type GitlabLineRange|nil
local range = nil
local resolved = false
local undefined_type = false
local root_new_line = nil
local root_old_line = nil
local root_url
@@ -175,16 +176,7 @@ M.add_discussions_to_table = function(items, unlinked)
resolvable = note.resolvable
resolved = note.resolved
root_url = state.INFO.web_url .. "#note_" .. note.id
-- This appears to be a Gitlab 🐛 where the "type" is returned as an empty string in some cases
-- We link these comments to the old file by default
if
type(note.position) == "table"
and note.position.line_range ~= nil
and note.position.line_range.start.type == ""
then
undefined_type = true
end
range = (type(note.position) == "table" and note.position.line_range or nil)
else -- Otherwise insert it as a child node...
local note_node = M.build_note(note)
table.insert(discussion_children, note_node)
@@ -194,6 +186,7 @@ M.add_discussions_to_table = function(items, unlinked)
-- Creates the first node in the discussion, and attaches children
local body = u.spread(root_text_nodes, discussion_children)
local root_node = NuiTree.Node({
range = range,
text = root_text,
type = "note",
is_root = true,
@@ -204,7 +197,6 @@ M.add_discussions_to_table = function(items, unlinked)
old_line = root_old_line,
resolvable = resolvable,
resolved = resolved,
undefined_type = undefined_type,
url = root_url,
}, body)

View File

@@ -1,28 +1,31 @@
local M = {}
local state = require("gitlab.state")
local List = require("gitlab.utils.list")
---@param nodes Discussion[]|UnlinkedDiscussion[]|nil
---@return number, number
local get_data = function(nodes)
if nodes == nil then
return 0, 0
end
local total_resolvable = 0
local total_resolved = 0
if nodes == vim.NIL then
return ""
if nodes == nil or nodes == vim.NIL then
return total_resolvable, total_resolved
end
for _, d in ipairs(nodes) do
total_resolvable = List.new(nodes):reduce(function(agg, d)
local first_child = d.notes[1]
if first_child ~= nil then
if first_child.resolvable then
total_resolvable = total_resolvable + 1
end
if first_child.resolved then
total_resolved = total_resolved + 1
end
if first_child and first_child.resolvable then
agg = agg + 1
end
end
return agg
end, 0)
total_resolved = List.new(nodes):reduce(function(agg, d)
local first_child = d.notes[1]
if first_child and first_child.resolved then
agg = agg + 1
end
return agg
end, 0)
return total_resolvable, total_resolved
end