Winbar Support + Notes and Discussions; Help Popup + Auto-Open (#133)

- Adds support for toggling between discussions and notes views
- Deprecates the split view shared with both discussions and notes at the same time
- Adds winbar to discussion split, with metadata about resolved and unresolved discussions
- Adds help popups with information about keybindings for all views
- Modifies highlights in discussion tree and default symbol for unresolved discussions

This is a MINOR version bump as the default behavior of the discussion tree is changed slightly. Existing configurations should still function.
This commit is contained in:
Harrison (Harry) Cramer
2023-12-13 17:46:34 -05:00
committed by GitHub
parent d5038d63ca
commit d5510f9d9a
13 changed files with 588 additions and 428 deletions

View File

@@ -117,6 +117,7 @@ require("gitlab").setup({
config_path = nil, -- Custom path for `.gitlab.nvim` file, please read the "Connecting to Gitlab" section config_path = nil, -- Custom path for `.gitlab.nvim` file, please read the "Connecting to Gitlab" section
debug = { go_request = false, go_response = false }, -- Which values to log debug = { go_request = false, go_response = false }, -- Which values to log
attachment_dir = nil, -- The local directory for files (see the "summary" section) attachment_dir = nil, -- The local directory for files (see the "summary" section)
help = "?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
popup = { -- The popup for comment creation, editing, and replying popup = { -- The popup for comment creation, editing, and replying
exit = "<Esc>", exit = "<Esc>",
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc) perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
@@ -132,6 +133,9 @@ require("gitlab").setup({
reply = nil, reply = nil,
}, },
discussion_tree = { -- The discussion tree that holds all comments discussion_tree = { -- The discussion tree that holds all comments
auto_open = true, -- Automatically open when the reviewer is opened
switch_view = "T", -- Toggles between the notes and discussions views
default_view = "discussions" -- Show "discussions" or "notes" by default
blacklist = {}, -- List of usernames to remove from tree (bots, CI, etc) blacklist = {}, -- List of usernames to remove from tree (bots, CI, etc)
jump_to_file = "o", -- Jump to comment location in file jump_to_file = "o", -- Jump to comment location in file
jump_to_reviewer = "m", -- Jump to the location in the reviewer window jump_to_reviewer = "m", -- Jump to the location in the reviewer window
@@ -146,6 +150,8 @@ require("gitlab").setup({
resolved = '', -- Symbol to show next to resolved discussions resolved = '', -- Symbol to show next to resolved discussions
unresolved = '', -- Symbol to show next to unresolved discussions unresolved = '', -- Symbol to show next to unresolved discussions
tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file tree_type = "simple", -- Type of discussion tree - "simple" means just list of discussions, "by_file_name" means file tree with discussions under file
winbar = nil -- Custom function to return winbar title, should return a string. Provided with WinbarTable (defined in annotations.lua)
-- If using lualine, please add "gitlab" to disabled file types, otherwise you will not see the winbar.
}, },
info = { -- Show additional fields in the summary pane info = { -- Show additional fields in the summary pane
enabled = true, enabled = true,

View File

@@ -6,10 +6,14 @@ syntax match Username "@\S*"
syntax match Date "\v\d+\s+\w+\s+ago" syntax match Date "\v\d+\s+\w+\s+ago"
syntax match ChevronDown "" syntax match ChevronDown ""
syntax match ChevronRight "" syntax match ChevronRight ""
syntax match Resolved "✓$"
syntax match Unresolved "-$"
highlight link Username GitlabUsername highlight link Username GitlabUsername
highlight link Date GitlabDate highlight link Date GitlabDate
highlight link ChevronDown GitlabChevron highlight link ChevronDown GitlabChevron
highlight link ChevronRight GitlabChevron highlight link ChevronRight GitlabChevron
highlight link Resolved GitlabResolved
highlight link Unresolved GitlabUnresolved
let b:current_syntax = "gitlab" let b:current_syntax = "gitlab"

View File

@@ -129,6 +129,7 @@ M.confirm_create_comment = function(text, range, unlinked)
job.run_job("/comment", "POST", body, function(data) job.run_job("/comment", "POST", body, function(data)
u.notify("Note created!", vim.log.levels.INFO) u.notify("Note created!", vim.log.levels.INFO)
discussions.add_discussion({ data = data, unlinked = true }) discussions.add_discussion({ data = data, unlinked = true })
discussions.refresh_discussion_data()
end) end)
return return
end end

View File

@@ -64,3 +64,10 @@
---@class DiscussionData ---@class DiscussionData
---@field discussions Discussion[] ---@field discussions Discussion[]
---@field unlinked_discussions UnlinkedDiscussion[] ---@field unlinked_discussions UnlinkedDiscussion[]
---@class WinbarTable
---@field name string
---@field resolvable_discussions number
---@field resolved_discussions number
---@field resolvable_notes number
---@field resolved_notes number

View File

@@ -5,417 +5,146 @@ local Split = require("nui.split")
local Popup = require("nui.popup") local Popup = require("nui.popup")
local NuiTree = require("nui.tree") local NuiTree = require("nui.tree")
local NuiLine = require("nui.line") local NuiLine = require("nui.line")
local Layout = require("nui.layout")
local job = require("gitlab.job") local job = require("gitlab.job")
local u = require("gitlab.utils") local u = require("gitlab.utils")
local state = require("gitlab.state") local state = require("gitlab.state")
local reviewer = require("gitlab.reviewer") local reviewer = require("gitlab.reviewer")
local miscellaneous = require("gitlab.actions.miscellaneous") local miscellaneous = require("gitlab.actions.miscellaneous")
local discussions_tree = require("gitlab.actions.discussions.tree") local discussions_tree = require("gitlab.actions.discussions.tree")
local signs = require("gitlab.actions.discussions.signs")
local discussion_sign_name = "gitlab_discussion" local winbar = require("gitlab.actions.discussions.winbar")
local discussion_helper_sign_start = "gitlab_discussion_helper_start" local help = require("gitlab.actions.help")
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 = { local M = {
layout_visible = false, split_visible = false,
layout = nil, split = nil,
layout_buf = nil, ---@type number
split_bufnr = nil,
---@type Discussion[] ---@type Discussion[]
discussions = {}, discussions = {},
---@type UnlinkedDiscussion[] ---@type UnlinkedDiscussion[]
unlinked_discussions = {}, unlinked_discussions = {},
linked_section = nil, ---@type number
unlinked_section = nil, linked_bufnr = nil,
---@type number
unlinked_bufnr = nil,
---@type number
focused_bufnr = nil,
discussion_tree = nil, discussion_tree = nil,
} }
---Load the discussion data, storage them in M.discussions and M.unlinked_discussions and call ---Makes API call to get the discussion data, store it in M.discussions and M.unlinked_discussions and call
---callback with data ---callback with data
---@param callback (fun(data: DiscussionData): nil)? ---@param callback (fun(data: DiscussionData): nil)?
M.load_discussions = function(callback) M.load_discussions = function(callback)
job.run_job("/discussions/list", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data) job.run_job("/discussions/list", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
M.discussions = data.discussions M.discussions = data.discussions ~= vim.NIL and data.discussions or {}
M.unlinked_discussions = data.unlinked_discussions M.unlinked_discussions = data.unlinked_discussions ~= vim.NIL and data.unlinked_discussions or {}
if type(callback) == "function" then if type(callback) == "function" then
callback(data) callback(data)
end end
end) end)
end end
---Parse line code and return old and new line numbers ---Initialize everything for discussions like setup of signs, callbacks for reviewer, etc.
---@param line_code string gitlab line code -> 588440f66559714280628a4f9799f0c4eb880a4a_10_10 M.initialize_discussions = function()
---@return number? signs.setup_signs()
---@return number? -- Setup callback to refresh discussion data, discussion signs and diagnostics whenever the reviewed file changes.
local function _parse_line_code(line_code) reviewer.set_callback_for_file_changed(M.refresh_discussion_data)
local line_code_regex = "%w+_(%d+)_(%d+)" -- Setup callback to clear signs and diagnostics whenever reviewer is left.
local old_line, new_line = line_code:match(line_code_regex) reviewer.set_callback_for_reviewer_leave(signs.clear_signs_and_discussions)
return tonumber(old_line), tonumber(new_line)
end end
---Filter all discussions which are relevant for currently visible signs and diagnostscs. ---Refresh discussion data, signs, diagnostics, and winbar with new data from API
---@return Discussion[]?
M.filter_discussions_for_signs_and_diagnostics = function()
if type(M.discussions) ~= "table" then
return
end
local file = reviewer.get_current_file()
if not file then
return
end
local discussions = {}
for _, discussion in ipairs(M.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
---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)
M.refresh_signs = function()
local diagnostics = M.filter_discussions_for_signs_and_diagnostics()
if diagnostics == nil then
vim.diagnostic.reset(diagnostics_namespace)
return
end
local new_signs = {}
local old_signs = {}
for _, discussion in ipairs(diagnostics) do
local first_note = discussion.notes[1]
local base_sign = {
name = discussion_sign_name,
group = discussion_sign_name,
priority = state.settings.discussion_sign.priority,
}
local base_helper_sign = {
name = discussion_sign_name,
group = discussion_sign_name,
priority = state.settings.discussion_sign.priority - 1,
}
if first_note.position.line_range ~= nil then
local start_old_line, start_new_line = _parse_line_code(first_note.position.line_range.start.line_code)
local end_old_line, end_new_line = _parse_line_code(first_note.position.line_range["end"].line_code)
local discussion_line, start_line, end_line
if first_note.position.line_range.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 first_note.position.line_range.start.type == "old" 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
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 first_note.position.line_range.start.type == "new" then
vim.list_extend(new_signs, helper_signs)
elseif first_note.position.line_range.start.type == "old" then
vim.list_extend(old_signs, helper_signs)
end
end
else
local sign = vim.tbl_deep_extend("force", {
id = first_note.id,
}, base_sign)
if first_note.position.new_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
vim.fn.sign_unplace(discussion_sign_name)
reviewer.place_sign(old_signs, "old")
reviewer.place_sign(new_signs, "new")
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
---Refresh the diagnostics for the currently reviewed file
M.refresh_diagnostics = function()
-- Keep in mind that diagnostic line numbers use 0-based indexing while line numbers use
-- 1-based indexing
local diagnostics = M.filter_discussions_for_signs_and_diagnostics()
if diagnostics == nil then
vim.diagnostic.reset(diagnostics_namespace)
return
end
local new_diagnostics = {}
local old_diagnostics = {}
for _, discussion in ipairs(diagnostics) 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,
}
if first_note.position.line_range ~= nil then
-- Diagnostics for line range discussions are tricky - you need to set lnum to
-- line number equal to note.position.new_line or note.position.old_line because that is
-- only line where you can trigger the diagnostic show. This also need to be in sinc
-- with the sign placement.
local start_old_line, start_new_line = _parse_line_code(first_note.position.line_range.start.line_code)
local end_old_line, end_new_line = _parse_line_code(first_note.position.line_range["end"].line_code)
if first_note.position.line_range.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 first_note.position.line_range.start.type == "old" 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)
end
else
-- Diagnostics for single line discussions.
if first_note.position.new_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
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
---Refresh discussion data, discussion signs and diagnostics
M.refresh_discussion_data = function() M.refresh_discussion_data = function()
M.load_discussions(function() M.load_discussions(function()
if state.settings.discussion_sign.enabled then if state.settings.discussion_sign.enabled then
M.refresh_signs() signs.refresh_signs(M.discussions)
end end
if state.settings.discussion_diagnostic.enabled then if state.settings.discussion_diagnostic.enabled then
M.refresh_diagnostics() 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 end
end) end)
end 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
---Initialize everything for discussions like setup of signs, callbacks for reviewer, etc.
M.initialize_discussions = function()
M.setup_signs()
M.setup_refresh_discussion_data_callback()
M.setup_leave_reviewer_callback()
end
---Setup callback to refresh discussion data, discussion signs and diagnostics whenever the
---reviewed file changes.
M.setup_refresh_discussion_data_callback = function()
reviewer.set_callback_for_file_changed(M.refresh_discussion_data)
end
---Clear all signs and diagnostics
M.clear_signs_and_discussions = function()
vim.fn.sign_unplace(discussion_sign_name)
vim.diagnostic.reset(diagnostics_namespace)
end
---Setup callback to clear signs and diagnostics whenever reviewer is left.
M.setup_leave_reviewer_callback = function()
reviewer.set_callback_for_reviewer_leave(M.clear_signs_and_discussions)
end
M.refresh_discussion_tree = function()
if M.layout_visible == false then
return
end
if type(M.discussions) == "table" then
M.rebuild_discussion_tree()
end
if type(M.unlinked_discussions) == "table" then
M.rebuild_unlinked_discussion_tree()
end
M.switch_can_edit_bufs(true)
M.add_empty_titles({
{ M.linked_section.bufnr, M.discussions, "No Discussions for this MR" },
{ M.unlinked_section.bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
})
M.switch_can_edit_bufs(false)
end
---Opens the discussion tree, sets the keybindings. It also ---Opens the discussion tree, sets the keybindings. It also
---creates the tree for notes (which are not linked to specific lines of code) ---creates the tree for notes (which are not linked to specific lines of code)
---@param callback function? ---@param callback function?
M.toggle = function(callback) M.toggle = function(callback)
if M.layout_visible then if M.split_visible then
M.layout:unmount() M.close()
M.layout_visible = false
M.discussion_tree = nil
M.linked_section = nil
M.unlinked_section = nil
return return
end end
local linked_section, unlinked_section, layout = M.create_layout() local split, linked_bufnr, unlinked_bufnr = M.create_split_and_bufs()
M.linked_section = linked_section M.linked_bufnr = linked_bufnr
M.unlinked_section = unlinked_section M.unlinked_bufnr = unlinked_bufnr
M.split = split
M.split_visible = true
M.split_bufnr = split.bufnr
split:mount()
M.switch_can_edit_bufs(true)
vim.api.nvim_buf_set_lines(split.bufnr, 0, -1, false, { "Loading data..." })
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.split_bufnr })
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.unlinked_bufnr })
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.linked_bufnr })
local default_discussions = state.settings.discussion_tree.default_view == "discussions"
winbar.update_winbar({}, {}, default_discussions and "Discussions" or "Notes")
M.load_discussions(function() M.load_discussions(function()
if type(M.discussions) ~= "table" and type(M.unlinked_discussions) ~= "table" then if type(M.discussions) ~= "table" and type(M.unlinked_discussions) ~= "table" then
vim.notify("No discussions or notes for this MR", vim.log.levels.WARN) vim.notify("No discussions or notes for this MR", vim.log.levels.WARN)
vim.api.nvim_buf_set_lines(split.bufnr, 0, -1, false, { "" })
return return
end end
layout:mount() M.rebuild_discussion_tree()
layout:show() M.rebuild_unlinked_discussion_tree()
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.layout = layout local default_buffer = default_discussions and M.linked_bufnr or M.unlinked_bufnr
M.layout_visible = true vim.api.nvim_set_current_buf(default_buffer)
M.layout_buf = layout.bufnr M.focused_bufnr = default_buffer
state.discussion_buf = layout.bufnr
M.refresh_discussion_tree() M.switch_can_edit_bufs(false)
winbar.update_winbar(M.discussions, M.unlinked_discussions, default_discussions and "Discussions" or "Notes")
if type(callback) == "function" then if type(callback) == "function" then
callback() callback()
end end
end) end)
end end
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
vim.api.nvim_set_current_buf(new_bufnr)
winbar.update_winbar(M.discussions, M.unlinked_discussions, change_to_unlinked and "Notes" or "Discussions")
M.focused_bufnr = new_bufnr
end
-- Clears the discussion state and unmounts the split
M.close = function()
if M.split then
M.split:unmount()
end
M.split_visible = false
M.discussion_tree = nil
end
---Move to the discussion tree at the discussion from diagnostic on current line. ---Move to the discussion tree at the discussion from diagnostic on current line.
M.move_to_discussion_tree = function() M.move_to_discussion_tree = function()
local current_line = vim.api.nvim_win_get_cursor(0)[1] local current_line = vim.api.nvim_win_get_cursor(0)[1]
local diagnostics = vim.diagnostic.get(0, { namespace = diagnostics_namespace, lnum = current_line - 1 }) local diagnostics = vim.diagnostic.get(0, { namespace = signs.diagnostics_namespace, lnum = current_line - 1 })
---Function used to jump to the discussion tree after the menu selection. ---Function used to jump to the discussion tree after the menu selection.
local jump_after_menu_selection = function(diagnostic) local jump_after_menu_selection = function(diagnostic)
@@ -435,11 +164,11 @@ M.move_to_discussion_tree = function()
discussion_node:expand() discussion_node:expand()
end end
M.discussion_tree:render() M.discussion_tree:render()
vim.api.nvim_win_set_cursor(M.linked_section.winid, { line_number, 0 }) vim.api.nvim_win_set_cursor(M.split.winid, { line_number, 0 })
vim.api.nvim_set_current_win(M.linked_section.winid) vim.api.nvim_set_current_win(M.split.winid)
end end
if not M.layout_visible then if not M.split_visible then
M.toggle(jump_after_tree_opened) M.toggle(jump_after_tree_opened)
else else
jump_after_tree_opened() jump_after_tree_opened()
@@ -523,13 +252,14 @@ M.send_deletion = function(tree, unlinked)
M.discussions = u.remove_first_value(M.discussions) M.discussions = u.remove_first_value(M.discussions)
M.rebuild_discussion_tree() M.rebuild_discussion_tree()
end end
M.switch_can_edit_bufs(true)
M.add_empty_titles({ M.add_empty_titles({
{ M.linked_section.bufnr, M.discussions, "No Discussions for this MR" }, { M.linked_bufnr, M.discussions, "No Discussions for this MR" },
{ M.unlinked_section.bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" }, { M.unlinked_bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
}) })
M.switch_can_edit_bufs(false) M.switch_can_edit_bufs(false)
end end
M.refresh_discussion_data()
end) end)
end end
@@ -573,6 +303,7 @@ M.send_edits = function(discussion_id, note_id, unlinked)
} }
job.run_job("/comment", "PATCH", body, function(data) job.run_job("/comment", "PATCH", body, function(data)
u.notify(data.message, vim.log.levels.INFO) u.notify(data.message, vim.log.levels.INFO)
M.rebuild_discussion_tree()
if unlinked then if unlinked then
M.replace_text(M.unlinked_discussions, discussion_id, note_id, text) M.replace_text(M.unlinked_discussions, discussion_id, note_id, text)
M.rebuild_unlinked_discussion_tree() M.rebuild_unlinked_discussion_tree()
@@ -599,6 +330,7 @@ M.toggle_discussion_resolved = function(tree)
job.run_job("/discussions/resolve", "PUT", body, function(data) job.run_job("/discussions/resolve", "PUT", body, function(data)
u.notify(data.message, vim.log.levels.INFO) u.notify(data.message, vim.log.levels.INFO)
M.redraw_resolved_status(tree, note, not note.resolved) M.redraw_resolved_status(tree, note, not note.resolved)
M.refresh_discussion_data()
end) end)
end end
@@ -693,36 +425,37 @@ end
M.rebuild_discussion_tree = function() M.rebuild_discussion_tree = function()
M.switch_can_edit_bufs(true) M.switch_can_edit_bufs(true)
vim.api.nvim_buf_set_lines(M.linked_section.bufnr, 0, -1, false, {}) vim.api.nvim_buf_set_lines(M.linked_bufnr, 0, -1, false, {})
local discussion_tree_nodes = discussions_tree.add_discussions_to_table(M.discussions, false) local discussion_tree_nodes = discussions_tree.add_discussions_to_table(M.discussions, false)
local discussion_tree = local discussion_tree =
NuiTree({ nodes = discussion_tree_nodes, bufnr = M.linked_section.bufnr, prepare_node = nui_tree_prepare_node }) NuiTree({ nodes = discussion_tree_nodes, bufnr = M.linked_bufnr, prepare_node = nui_tree_prepare_node })
discussion_tree:render() discussion_tree:render()
M.set_tree_keymaps(discussion_tree, M.linked_section.bufnr, false) M.set_tree_keymaps(discussion_tree, M.linked_bufnr, false)
M.discussion_tree = discussion_tree M.discussion_tree = discussion_tree
M.switch_can_edit_bufs(false) M.switch_can_edit_bufs(false)
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.linked_section.bufnr }) vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.linked_bufnr })
end end
M.rebuild_unlinked_discussion_tree = function() M.rebuild_unlinked_discussion_tree = function()
M.switch_can_edit_bufs(true) M.switch_can_edit_bufs(true)
vim.api.nvim_buf_set_lines(M.unlinked_section.bufnr, 0, -1, false, {}) vim.api.nvim_buf_set_lines(M.unlinked_bufnr, 0, -1, false, {})
local unlinked_discussion_tree_nodes = discussions_tree.add_discussions_to_table(M.unlinked_discussions, true) local unlinked_discussion_tree_nodes = discussions_tree.add_discussions_to_table(M.unlinked_discussions, true)
local unlinked_discussion_tree = NuiTree({ local unlinked_discussion_tree = NuiTree({
nodes = unlinked_discussion_tree_nodes, nodes = unlinked_discussion_tree_nodes,
bufnr = M.unlinked_section.bufnr, bufnr = M.unlinked_bufnr,
prepare_node = nui_tree_prepare_node, prepare_node = nui_tree_prepare_node,
}) })
unlinked_discussion_tree:render() unlinked_discussion_tree:render()
M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_section.bufnr, true) M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_bufnr, true)
M.unlinked_discussion_tree = unlinked_discussion_tree M.unlinked_discussion_tree = unlinked_discussion_tree
M.switch_can_edit_bufs(false) M.switch_can_edit_bufs(false)
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.unlinked_section.bufnr })
end end
M.switch_can_edit_bufs = function(bool) M.switch_can_edit_bufs = function(bool)
u.switch_can_edit_buf(M.unlinked_section.bufnr, bool) u.switch_can_edit_buf(M.unlinked_bufnr, bool)
u.switch_can_edit_buf(M.linked_section.bufnr, bool) u.switch_can_edit_buf(M.linked_bufnr, bool)
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.unlinked_bufnr })
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.linked_bufnr })
end end
M.add_discussion = function(arg) M.add_discussion = function(arg)
@@ -732,44 +465,35 @@ M.add_discussion = function(arg)
M.unlinked_discussions = {} M.unlinked_discussions = {}
end end
table.insert(M.unlinked_discussions, 1, discussion) table.insert(M.unlinked_discussions, 1, discussion)
if M.unlinked_section ~= nil then
M.rebuild_unlinked_discussion_tree() M.rebuild_unlinked_discussion_tree()
end
return return
end end
if type(M.discussions) ~= "table" then if type(M.discussions) ~= "table" then
M.discussions = {} M.discussions = {}
end end
table.insert(M.discussions, 1, discussion) table.insert(M.discussions, 1, discussion)
if M.linked_section ~= nil then
M.rebuild_discussion_tree() M.rebuild_discussion_tree()
end
end end
M.create_layout = function() M.create_split_and_bufs = function()
local linked_section = Split({ enter = true })
local unlinked_section = Split({})
local position = state.settings.discussion_tree.position local position = state.settings.discussion_tree.position
local size = state.settings.discussion_tree.size local size = state.settings.discussion_tree.size
local relative = state.settings.discussion_tree.relative local relative = state.settings.discussion_tree.relative
local layout = Layout( local split = Split({
{ relative = relative,
position = position, position = position,
size = size, size = size,
relative = relative, })
},
Layout.Box({
Layout.Box(linked_section, { size = "50%" }),
Layout.Box(unlinked_section, { size = "50%" }),
}, { dir = (position == "left" and "col" or "row") })
)
return linked_section, unlinked_section, layout local linked_bufnr = vim.api.nvim_create_buf(true, false)
local unlinked_bufnr = vim.api.nvim_create_buf(true, false)
return split, linked_bufnr, unlinked_bufnr
end end
M.add_empty_titles = function(args) M.add_empty_titles = function(args)
M.switch_can_edit_bufs(true)
local ns_id = vim.api.nvim_create_namespace("GitlabNamespace") local ns_id = vim.api.nvim_create_namespace("GitlabNamespace")
vim.cmd("highlight default TitleHighlight guifg=#787878") vim.cmd("highlight default TitleHighlight guifg=#787878")
for _, section in ipairs(args) do for _, section in ipairs(args) do
@@ -830,7 +554,12 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
M.reply(tree) M.reply(tree)
end end
end, { buffer = bufnr, desc = "Reply" }) end, { buffer = bufnr, desc = "Reply" })
vim.keymap.set("n", state.settings.discussion_tree.switch_view, function()
switch_view_type()
end, { buffer = bufnr, desc = "Switch view type" })
vim.keymap.set("n", state.settings.help, function()
help.open()
end, { buffer = bufnr, desc = "Open help popup" })
if not unlinked then if not unlinked then
vim.keymap.set("n", state.settings.discussion_tree.jump_to_file, function() vim.keymap.set("n", state.settings.discussion_tree.jump_to_file, function()
if M.is_current_node_note(tree) then if M.is_current_node_note(tree) then

View File

@@ -0,0 +1,293 @@
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
---Parse line code and return old and new line numbers
---@param line_code string gitlab line code -> 588440f66559714280628a4f9799f0c4eb880a4a_10_10
---@return number?
---@return number?
local function _parse_line_code(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
---Filter all discussions which are relevant for currently visible signs and diagnostscs.
---@return Discussion[]?
local 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
---Build note header from note.
---@param note Note
---@return string
local build_note_header = function(note)
return "@" .. note.author.username .. " " .. u.time_since(note.created_at)
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
---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)
M.refresh_signs = function(discussions)
local diagnostics = filter_discussions_for_signs_and_diagnostics(discussions)
if diagnostics == nil then
vim.diagnostic.reset(diagnostics_namespace)
return
end
local new_signs = {}
local old_signs = {}
for _, discussion in ipairs(diagnostics) do
local first_note = discussion.notes[1]
local base_sign = {
name = discussion_sign_name,
group = discussion_sign_name,
priority = state.settings.discussion_sign.priority,
}
local base_helper_sign = {
name = discussion_sign_name,
group = discussion_sign_name,
priority = state.settings.discussion_sign.priority - 1,
}
if first_note.position.line_range ~= nil then
local start_old_line, start_new_line = _parse_line_code(first_note.position.line_range.start.line_code)
local end_old_line, end_new_line = _parse_line_code(first_note.position.line_range["end"].line_code)
local discussion_line, start_line, end_line
if first_note.position.line_range.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 first_note.position.line_range.start.type == "old" 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
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 first_note.position.line_range.start.type == "new" then
vim.list_extend(new_signs, helper_signs)
elseif first_note.position.line_range.start.type == "old" then
vim.list_extend(old_signs, helper_signs)
end
end
else
local sign = vim.tbl_deep_extend("force", {
id = first_note.id,
}, base_sign)
if first_note.position.new_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
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
M.refresh_diagnostics = function(discussions)
-- Keep in mind that diagnostic line numbers use 0-based indexing while line numbers use
-- 1-based indexing
local diagnostics = filter_discussions_for_signs_and_diagnostics(discussions)
if diagnostics == nil then
vim.diagnostic.reset(diagnostics_namespace)
return
end
local new_diagnostics = {}
local old_diagnostics = {}
for _, discussion in ipairs(diagnostics) do
local first_note = discussion.notes[1]
local message = ""
for _, note in ipairs(discussion.notes) do
message = message .. 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 = build_note_header(discussion.notes[1]) },
source = "gitlab",
code = state.settings.discussion_diagnostic.code,
}
if first_note.position.line_range ~= nil then
-- Diagnostics for line range discussions are tricky - you need to set lnum to
-- line number equal to note.position.new_line or note.position.old_line because that is
-- only line where you can trigger the diagnostic show. This also need to be in sinc
-- with the sign placement.
local start_old_line, start_new_line = _parse_line_code(first_note.position.line_range.start.line_code)
local end_old_line, end_new_line = _parse_line_code(first_note.position.line_range["end"].line_code)
if first_note.position.line_range.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 first_note.position.line_range.start.type == "old" 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)
end
else
-- Diagnostics for single line discussions.
if first_note.position.new_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
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
---Clear all signs and diagnostics
M.clear_signs_and_discussions = function()
vim.fn.sign_unplace(discussion_sign_name)
vim.diagnostic.reset(diagnostics_namespace)
end
return M

View File

@@ -0,0 +1,58 @@
local M = {}
local state = require("gitlab.state")
---@param nodes Discussion[]|UnlinkedDiscussion[]|nil
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 ""
end
for _, d in ipairs(nodes) do
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
end
end
return total_resolvable, total_resolved
end
---@param discussions Discussion[]|nil
---@param unlinked_discussions UnlinkedDiscussion[]|nil
---@param file_name string
local function content(discussions, unlinked_discussions, file_name)
local resolvable_discussions, resolved_discussions = get_data(discussions)
local resolvable_notes, resolved_notes = get_data(unlinked_discussions)
local t = {
name = file_name,
resolvable_discussions = resolvable_discussions,
resolved_discussions = resolved_discussions,
resolvable_notes = resolvable_notes,
resolved_notes = resolved_notes,
}
return state.settings.discussion_tree.winbar(t)
end
---This function sends the edited comment to the Go server
---@param discussions Discussion[]
---@param unlinked_discussions UnlinkedDiscussion[]
---@param base_title string
M.update_winbar = function(discussions, unlinked_discussions, base_title)
local d = require("gitlab.actions.discussions")
local winId = d.split.winid
vim.wo[winId].winbar = content(discussions, unlinked_discussions, base_title)
end
return M

View File

@@ -0,0 +1,27 @@
local M = {}
local u = require("gitlab.utils")
local state = require("gitlab.state")
local Popup = require("nui.popup")
M.open = function()
local bufnr = vim.api.nvim_get_current_buf()
local keymaps = vim.api.nvim_buf_get_keymap(bufnr, "n")
local help_content_lines = {}
for _, keymap in ipairs(keymaps) do
if keymap.desc ~= nil then
local new_line = string.format("%s: %s", keymap.lhs, keymap.desc)
table.insert(help_content_lines, new_line)
end
end
local longest_line = u.get_longest_string(help_content_lines)
local help_popup =
Popup(u.create_popup_state("Help", state.settings.popup.help, longest_line + 3, #help_content_lines + 3, 60))
help_popup:mount()
state.set_popup_keymaps(help_popup, "Help", nil)
local currentBuffer = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(currentBuffer, 0, #help_content_lines, false, help_content_lines)
end
return M

View File

@@ -10,3 +10,5 @@ vim.api.nvim_set_hl(0, "GitlabChevron", u.get_colors_for_group(discussion.chevro
vim.api.nvim_set_hl(0, "GitlabDirectory", u.get_colors_for_group(discussion.directory)) vim.api.nvim_set_hl(0, "GitlabDirectory", u.get_colors_for_group(discussion.directory))
vim.api.nvim_set_hl(0, "GitlabDirectoryIcon", u.get_colors_for_group(discussion.directory_icon)) vim.api.nvim_set_hl(0, "GitlabDirectoryIcon", u.get_colors_for_group(discussion.directory_icon))
vim.api.nvim_set_hl(0, "GitlabFileName", u.get_colors_for_group(discussion.file_name)) vim.api.nvim_set_hl(0, "GitlabFileName", u.get_colors_for_group(discussion.file_name))
vim.api.nvim_set_hl(0, "GitlabResolved", u.get_colors_for_group(discussion.resolved))
vim.api.nvim_set_hl(0, "GitlabUnresolved", u.get_colors_for_group(discussion.unresolved))

View File

@@ -39,6 +39,12 @@ M.open = function()
end end
end, end,
}) })
if state.settings.discussion_tree.auto_open then
local discussions = require("gitlab.actions.discussions")
discussions.close()
discussions.toggle()
end
end end
M.jump = function(file_name, new_line, old_line) M.jump = function(file_name, new_line, old_line)

View File

@@ -14,6 +14,7 @@ M.settings = {
config_path = nil, config_path = nil,
reviewer = "diffview", reviewer = "diffview",
attachment_dir = "", attachment_dir = "",
help = "?",
popup = { popup = {
exit = "<Esc>", exit = "<Esc>",
perform_action = "<leader>s", perform_action = "<leader>s",
@@ -26,9 +27,11 @@ M.settings = {
reply = nil, reply = nil,
comment = nil, comment = nil,
note = nil, note = nil,
help = nil,
pipeline = nil, pipeline = nil,
}, },
discussion_tree = { discussion_tree = {
auto_open = true,
blacklist = {}, blacklist = {},
jump_to_file = "o", jump_to_file = "o",
jump_to_reviewer = "m", jump_to_reviewer = "m",
@@ -41,8 +44,27 @@ M.settings = {
position = "left", position = "left",
size = "20%", size = "20%",
resolved = "", resolved = "",
unresolved = "", unresolved = "-",
tree_type = "simple", tree_type = "simple",
switch_view = "T",
default_view = "discussions",
---@param t WinbarTable
winbar = function(t)
local discussions_content = t.resolvable_discussions ~= 0
and string.format("Discussions (%d/%d)", t.resolved_discussions, t.resolvable_discussions)
or "Discussions"
local notes_content = t.resolvable_notes ~= 0
and string.format("Notes (%d/%d)", t.resolved_notes, t.resolvable_notes)
or "Notes"
if t.name == "Discussions" then
notes_content = "%#Comment#" .. notes_content
discussions_content = "%#Text#" .. discussions_content
else
discussions_content = "%#Comment#" .. discussions_content
notes_content = "%#Text#" .. notes_content
end
return " " .. discussions_content .. " %#Comment#| " .. notes_content
end,
}, },
info = { info = {
enabled = true, enabled = true,
@@ -91,13 +113,13 @@ M.settings = {
display_opts = {}, -- this is dirrectly used as opts in vim.diagnostic.set, see :h vim.diagnostic.config. display_opts = {}, -- this is dirrectly used as opts in vim.diagnostic.set, see :h vim.diagnostic.config.
}, },
pipeline = { pipeline = {
created = "", created = "",
pending = "", pending = "",
preparing = "", preparing = "",
scheduled = "", scheduled = "",
running = "", running = "",
canceled = "", canceled = "",
skipped = "", skipped = "",
success = "", success = "",
failed = "", failed = "",
}, },
@@ -111,6 +133,8 @@ M.settings = {
directory = "Directory", directory = "Directory",
directory_icon = "DiffviewFolderSign", directory_icon = "DiffviewFolderSign",
file_name = "Normal", file_name = "Normal",
resolved = "DiagnosticSignOk",
unresolved = "DiagnosticSignWarn",
}, },
}, },
} }
@@ -217,6 +241,13 @@ M.set_popup_keymaps = function(popup, action, linewise_action, opts)
vim.keymap.set("n", M.settings.popup.exit, function() vim.keymap.set("n", M.settings.popup.exit, function()
exit(popup, opts.cb) exit(popup, opts.cb)
end, { buffer = popup.bufnr, desc = "Exit popup" }) end, { buffer = popup.bufnr, desc = "Exit popup" })
if action ~= "Help" then -- Don't show help on the help popup
vim.keymap.set("n", M.settings.help, function()
local help = require("gitlab.actions.help")
help.open()
end, { buffer = popup.bufnr, desc = "Open help" })
end
if action ~= nil then if action ~= nil then
vim.keymap.set("n", M.settings.popup.perform_action, function() vim.keymap.set("n", M.settings.popup.perform_action, function()
local text = u.get_buffer_text(popup.bufnr) local text = u.get_buffer_text(popup.bufnr)

View File

@@ -262,17 +262,6 @@ M.split_path = function(path)
return path_parts return path_parts
end end
M.P = function(...)
local objects = {}
for i = 1, select("#", ...) do
local v = select(i, ...)
table.insert(objects, vim.inspect(v))
end
print(table.concat(objects, "\n"))
return ...
end
M.get_buffer_text = function(bufnr) M.get_buffer_text = function(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local text = table.concat(lines, "\n") local text = table.concat(lines, "\n")
@@ -347,10 +336,10 @@ end
---Get the popup view_opts ---Get the popup view_opts
---@param title string The string to appear on top of the popup ---@param title string The string to appear on top of the popup
---@param settings table User defined popup settings ---@param settings table User defined popup settings
---@param width string Override default width ---@param width number? Override default width
---@param height string Override default height ---@param height number? Override default height
---@return table ---@return table
M.create_popup_state = function(title, settings, width, height) M.create_popup_state = function(title, settings, width, height, zindex)
local default_settings = require("gitlab.state").settings.popup local default_settings = require("gitlab.state").settings.popup
local user_settings = settings or {} local user_settings = settings or {}
local view_opts = { local view_opts = {
@@ -360,6 +349,7 @@ M.create_popup_state = function(title, settings, width, height)
relative = "editor", relative = "editor",
enter = true, enter = true,
focusable = true, focusable = true,
zindex = zindex or 50,
border = { border = {
style = user_settings.border or default_settings.border, style = user_settings.border or default_settings.border,
text = { text = {
@@ -373,6 +363,7 @@ M.create_popup_state = function(title, settings, width, height)
}, },
opacity = user_settings.opacity or default_settings.opacity, opacity = user_settings.opacity or default_settings.opacity,
} }
return view_opts return view_opts
end end
@@ -627,4 +618,9 @@ M.get_icon = function(filename)
end end
end end
M.basename = function(str)
local name = string.gsub(str, "(.*/)(.*)", "%2")
return name
end
return M return M

View File

@@ -256,7 +256,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
local nodes = tree.add_discussions_to_table(discussions) local nodes = tree.add_discussions_to_table(discussions)
assert.are.same(tree_nodes_to_table(nodes, { note = true, note_body = true }), { assert.are.same(tree_nodes_to_table(nodes, { note = true, note_body = true }), {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -267,7 +267,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
}, },
}, },
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -293,7 +293,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -312,7 +312,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -339,7 +339,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -377,7 +377,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -396,7 +396,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -415,7 +415,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -464,7 +464,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -491,7 +491,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -514,7 +514,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -550,7 +550,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -561,7 +561,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
}, },
}, },
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -591,7 +591,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
type = "file_name", type = "file_name",
children = { children = {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -602,7 +602,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
}, },
}, },
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -626,7 +626,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
local nodes = tree.add_discussions_to_table(unlinked_discussions, true) local nodes = tree.add_discussions_to_table(unlinked_discussions, true)
assert.are.same(tree_nodes_to_table(nodes, { note = true, note_body = true }), { assert.are.same(tree_nodes_to_table(nodes, { note = true, note_body = true }), {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -637,7 +637,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
}, },
}, },
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -669,7 +669,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
local nodes = tree.add_discussions_to_table(unlinked_discussions, true) local nodes = tree.add_discussions_to_table(unlinked_discussions, true)
assert.are.same(tree_nodes_to_table(nodes, { note = true, note_body = true }), { assert.are.same(tree_nodes_to_table(nodes, { note = true, note_body = true }), {
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {
@@ -680,7 +680,7 @@ describe("gitlab/actions/discussions/tree.lua", function()
}, },
}, },
{ {
text = "@gitlab.username 5 days ago ", text = "@gitlab.username 5 days ago -",
type = "note", type = "note",
children = { children = {
{ {