Files
gitlab.nvim/lua/gitlab/actions/discussions/winbar.lua
Harrison (Harry) Cramer e4eabaf71d Release 1/24/26 (#519)
* Fix: Jumping to renamed files (#484)

* fix: prevent "cursor position outside buffer" error

* fix: swap file_name and old_file_name in reviewer data

`old_file_name` is not set to the empty string for un-renamed files anymore, because then we can
remove the empty-line check in `comment_helpers.go` which was used to replace the empty string with
the current file name anyway.

* fix: add old_file_name to discussion root node data

* fix: also consider old_file_name when jumping to the reviewer

This fixes jumping to renamed files, however, may not work for comments that
were created on renamed files with the previous version of `gitlab.nvim` as
that version assigned the `file_name` and `old_file_name` incorrectly.

* refactor: don't shadow variable

* fix: check file_name or old_file_name based on which SHA comment belongs to

* Fix: Store reviewer data before creating comment popup (#476)

* Fix: Make publishing drafts more robust (#483)

* Fix: Swap file_name and old_file_name in reviewer data (#485)

* Feat: Enable toggling date format between relative and absolute (#491)

* Fix: Add opts to help popup (#492)

* Fix: Force start_line for jumping to diagnostic to be inside buffer (#494)

* fix: redefine colors after reloading colorscheme (#500)

* Fix: Use path instead of oldpath as fallback for unrenamed files (#496)

* Fix: Use file_name when old_file_name is not set (#495)

* fix(ci): fix lua tests (#501)

* Proxy Support (#499)

* feat(ci): Cancel obsolete after a new commit is pushed in an open PR (#503)

* fix: start and clean up winbar timer properly (#513)

This is a PATCH.

* fix: put attach_file markdown on new line (#512)

This is a PATCH PR.

* docs: fix incorrect value for 'relative' option to Split (#511)

This is a PATCH.

* docs: add default keybinding maps available in the help (#506)

This is a PATCH change.

* feat: enable setting discussion tree options (#509)

* docs: add description of `refresh_data` function

* fix: only set gitlab filetype in one place

* feat: set some useful window options for the discussion tree split

This is a PATCH PR.

---------

Co-authored-by: Jakub F. Bortlík <jakub.bortlik@proton.me>
Co-authored-by: Jonathan Duck <Duckbrain30@gmail.com>
Co-authored-by: Kitsios Konstantinos <kitsios.konst@gmail.com>
Co-authored-by: Mohammad Akbari <makbari@users.noreply.github.com>
2026-01-24 10:04:47 -05:00

292 lines
8.3 KiB
Lua

local u = require("gitlab.utils")
local List = require("gitlab.utils.list")
local state = require("gitlab.state")
local M = {
bufnr_map = {
discussions = nil,
notes = nil,
},
current_view_type = state.settings.discussion_tree.default_view,
}
M.set_buffers = function(linked_bufnr, unlinked_bufnr)
M.bufnr_map = {
discussions = linked_bufnr,
notes = unlinked_bufnr,
}
end
---@param nodes Discussion[]|UnlinkedDiscussion[]|nil
---@return number, number, number
local get_data = function(nodes)
local total_resolvable = 0
local total_resolved = 0
local total_non_resolvable = 0
if nodes == nil or nodes == vim.NIL then
return total_resolvable, total_resolved, total_non_resolvable
end
total_resolvable = List.new(nodes):reduce(function(agg, d)
local first_child = d.notes[1]
if first_child and first_child.resolvable then
agg = agg + 1
end
return agg
end, 0)
total_non_resolvable = List.new(nodes):reduce(function(agg, d)
local first_child = d.notes[1]
if first_child and not first_child.resolvable then
agg = agg + 1
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, total_non_resolvable
end
local spinner_index = 0
state.discussion_tree.last_updated = nil
local function content()
local updated
if state.discussion_tree.last_updated then
local last_update = tostring(os.date("!%Y-%m-%dT%H:%M:%S", state.discussion_tree.last_updated))
updated = u.time_since(last_update) .. ""
else
spinner_index = (spinner_index % #state.settings.discussion_tree.spinner_chars) + 1
updated = state.settings.discussion_tree.spinner_chars[spinner_index]
end
local resolvable_discussions, resolved_discussions, non_resolvable_discussions =
get_data(state.DISCUSSION_DATA.discussions)
local resolvable_notes, resolved_notes, non_resolvable_notes = get_data(state.DISCUSSION_DATA.unlinked_discussions)
local draft_notes = require("gitlab.actions.draft_notes")
local inline_draft_notes, unlinked_draft_notes = List.new(state.DRAFT_NOTES):partition(function(note)
if note.discussion_id == "" then
return draft_notes.has_position(note)
end
for _, discussion in ipairs(state.DISCUSSION_DATA.unlinked_discussions) do
if discussion.id == note.discussion_id then
return false
end
end
return true
end)
local t = {
resolvable_discussions = resolvable_discussions,
resolved_discussions = resolved_discussions,
non_resolvable_discussions = non_resolvable_discussions,
inline_draft_notes = #inline_draft_notes,
unlinked_draft_notes = #unlinked_draft_notes,
resolvable_notes = resolvable_notes,
resolved_notes = resolved_notes,
non_resolvable_notes = non_resolvable_notes,
help_keymap = state.settings.keymaps.help,
updated = updated,
}
return state.settings.discussion_tree.winbar and state.settings.discussion_tree.winbar(t) or M.make_winbar(t)
end
---This function updates the winbar
M.update_winbar = function()
local d = require("gitlab.actions.discussions")
if d.split == nil then
return
end
local win_id = d.split.winid
if win_id == nil then
return
end
if not vim.api.nvim_win_is_valid(win_id) then
return
end
local c = content()
vim.api.nvim_set_option_value("winbar", c, { scope = "local", win = win_id })
end
local function get_connector(base_title)
return string.match(base_title, "%($") and "" or " "
end
---Builds the title string for both sections, using the count of resolvable and draft nodes
---@param base_title string
---@param resolvable_count integer
---@param resolved_count integer
---@param drafts_count integer
---@param focused boolean
---@return string
local add_drafts_and_resolvable = function(
base_title,
resolvable_count,
resolved_count,
drafts_count,
non_resolvable_count,
focused
)
if resolvable_count == 0 and drafts_count == 0 and non_resolvable_count == 0 then
return base_title
end
if resolvable_count ~= 0 then
base_title = base_title .. M.get_resolved_text(focused, resolved_count, resolvable_count)
end
if non_resolvable_count ~= 0 then
base_title = base_title .. M.get_nonresolveable_text(base_title, non_resolvable_count, focused)
end
if drafts_count ~= 0 then
base_title = base_title .. M.get_drafts_text(base_title, drafts_count, focused)
end
return base_title
end
---@param t WinbarTable
M.make_winbar = function(t)
local discussions_focused = M.current_view_type == "discussions"
local discussion_text = add_drafts_and_resolvable(
"Inline Comments:",
t.resolvable_discussions,
t.resolved_discussions,
t.inline_draft_notes,
t.non_resolvable_discussions,
discussions_focused
)
local notes_text = add_drafts_and_resolvable(
"Notes:",
t.resolvable_notes,
t.resolved_notes,
t.unlinked_draft_notes,
t.non_resolvable_notes,
not discussions_focused
)
-- Colorize the active tab
if discussions_focused then
discussion_text = "%#Text#" .. discussion_text
notes_text = "%#Comment#" .. notes_text
else
discussion_text = "%#Comment#" .. discussion_text
notes_text = "%#Text#" .. notes_text
end
local sort_method = M.get_sort_method()
local mode = M.get_mode()
-- Join everything together and return it
local separator = "%#Comment#|"
local end_section = "%="
local updated = "%#Text#" .. t.updated
local help = "%#Comment#Help: " .. (t.help_keymap and t.help_keymap:gsub(" ", "<space>") .. " " or "unmapped")
return string.format(
" %s %s %s %s %s %s %s %s %s %s %s",
discussion_text,
separator,
notes_text,
end_section,
updated,
separator,
sort_method,
separator,
mode,
separator,
help
)
end
---Returns a string for the winbar indicating the sort method
---@return string
M.get_sort_method = function()
local sort_method = state.settings.discussion_tree.sort_by == "original_comment" and "↓ by thread" or "↑ by reply"
return "%#GitlabSortMethod#" .. sort_method .. "%#Comment#"
end
M.get_resolved_text = function(focused, resolved_count, resolvable_count)
local text = focused and ("%#GitlabResolved#" .. state.settings.discussion_tree.resolved .. "%#Text#")
or state.settings.discussion_tree.resolved
return " " .. string.format("%d%s/%d", resolved_count, text, resolvable_count)
end
M.get_drafts_text = function(base_title, drafts_count, focused)
return get_connector(base_title)
.. string.format(
"%d%s",
drafts_count,
(
focused and ("%#GitlabDraft#" .. state.settings.discussion_tree.draft .. "%#Text#")
or state.settings.discussion_tree.draft
)
)
end
M.get_nonresolveable_text = function(base_title, non_resolvable_count, focused)
return get_connector(base_title)
.. string.format(
"%d%s",
non_resolvable_count,
(
focused and ("%#GitlabUnlinked#" .. state.settings.discussion_tree.unlinked .. "%#Text#")
or state.settings.discussion_tree.unlinked
)
)
end
---Returns a string for the winbar indicating the mode type, live or draft
---@return string
M.get_mode = function()
if state.settings.discussion_tree.draft_mode then
return "%#GitlabDraftMode#Draft"
else
return "%#GitlabLiveMode#Live"
end
end
---Toggles the current view type (or sets it to `override`) and then updates the view.
---@param override "discussions"|"notes" Defines the view type to select.
M.switch_view_type = function(override)
if override then
M.current_view_type = override
else
if M.current_view_type == "discussions" then
M.current_view_type = "notes"
elseif M.current_view_type == "notes" then
M.current_view_type = "discussions"
end
end
vim.api.nvim_set_current_buf(M.bufnr_map[M.current_view_type])
M.update_winbar()
end
---Set up a timer to update the winbar periodically
M.start_timer = function()
M.cleanup_timer()
---@type nil|uv_timer_t
M.timer = vim.uv.new_timer()
M.timer:start(0, 100, vim.schedule_wrap(M.update_winbar))
end
--Stop and close the timer
M.cleanup_timer = function()
if M.timer ~= nil then
M.timer:stop()
M.timer:close()
M.timer = nil
end
end
return M