Skip to content

Commit 7277599

Browse files
committed
feat: open discussions immediately load data afterwards
Main features: - opens discussion tree immediately and loads data asynchronously - improves restoring cursor position after rebuilding trees Minor improvements: - moves view type switching from winbar to discussions - adds protection from invalid line number - replaces deprecated vim.api function
1 parent e3238ba commit 7277599

9 files changed

Lines changed: 93 additions & 92 deletions

File tree

doc/gitlab.nvim.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ you call this function with no values the defaults will be used:
264264
},
265265
spinner_chars = { "/", "|", "\\", "-" }, -- Characters for the refresh animation
266266
auto_open = true, -- Automatically open when the reviewer is opened
267+
enter_on_open = true, -- Automatically enter the discussion tree when it is opened
267268
default_view = "discussions", -- Show "discussions" or "notes" by default
268269
blacklist = {}, -- List of usernames to remove from tree (bots, CI, etc)
269270
sort_by = "latest_reply", -- Sort discussion tree by the "latest_reply", or by "original_comment", see `:h gitlab.nvim.toggle_sort_method`

lua/gitlab/actions/discussions/init.lua

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ M.initialize_discussions = function()
8989
M.refresh_diagnostics()
9090
end)
9191
reviewer.set_callback_for_buf_read(function(args)
92-
vim.api.nvim_buf_set_option(args.buf, "modifiable", false)
92+
vim.api.nvim_set_option_value("modifiable", false, { buf = args.buf })
9393
reviewer.set_keymaps(args.buf)
9494
reviewer.set_reviewer_autocommands(args.buf)
9595
end)
@@ -112,46 +112,67 @@ end
112112
---@param callback function?
113113
---@param view_type "discussions"|"notes" Defines the view type to select (useful for overriding the default view type when jumping to discussion tree when it's closed).
114114
M.open = function(callback, view_type)
115-
view_type = view_type and view_type or state.settings.discussion_tree.default_view
115+
local original_window = vim.api.nvim_get_current_win() -- The window from which ther user called M.open
116+
117+
M.current_view_type = view_type and view_type or state.settings.discussion_tree.default_view
118+
state.DISCUSSION_DATA = u.ensure_table(state.DISCUSSION_DATA)
116119
state.DISCUSSION_DATA.discussions = u.ensure_table(state.DISCUSSION_DATA.discussions)
117120
state.DISCUSSION_DATA.unlinked_discussions = u.ensure_table(state.DISCUSSION_DATA.unlinked_discussions)
118121
state.DRAFT_NOTES = u.ensure_table(state.DRAFT_NOTES)
119122

120-
-- Make buffers, get and set buffer numbers, set filetypes
123+
-- Make discussion split window and buffers, store buffer numbers
121124
local split, linked_bufnr, unlinked_bufnr = M.create_split_and_bufs()
122125
M.split = split
123126
M.linked_bufnr = linked_bufnr
124127
M.unlinked_bufnr = unlinked_bufnr
128+
M.split_visible = true
129+
split:mount()
125130

131+
-- Set window and buffer local options to discussion tree split after mounting the split
126132
for opt, val in pairs(state.settings.discussion_tree.winopts) do
127133
vim.api.nvim_set_option_value(opt, val, { win = M.split.winid })
128134
end
129135

130136
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.linked_bufnr })
131137
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.unlinked_bufnr })
132138

133-
M.split = split
134-
M.split_visible = true
135-
split:mount()
139+
-- Set autocmds to clean up state when discussions buffers are deleted manually
140+
vim.api.nvim_create_autocmd("BufWipeout", {
141+
buffer = M.linked_bufnr,
142+
callback = function()
143+
M.linked_bufnr = nil
144+
end,
145+
})
146+
vim.api.nvim_create_autocmd("BufWipeout", {
147+
buffer = M.unlinked_bufnr,
148+
callback = function()
149+
M.unlinked_bufnr = nil
150+
end,
151+
})
136152

137-
-- Initialize winbar module with data from buffers
138-
winbar.start_timer()
139-
winbar.set_buffers(M.linked_bufnr, M.unlinked_bufnr)
140-
winbar.switch_view_type(view_type)
153+
-- Set autocmd to clean up state when discussions split is closed manually
154+
vim.api.nvim_create_autocmd("WinClosed", {
155+
pattern = tostring(M.split.winid),
156+
callback = M.close,
157+
})
141158

142-
local current_window = vim.api.nvim_get_current_win() -- Save user's current window in case they switched while content was loading
143-
vim.api.nvim_set_current_win(M.split.winid)
159+
-- Initialize winbar
160+
winbar.start_timer()
144161

145-
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
146-
M.rebuild_discussion_tree()
162+
-- Rebuild trees in order to set keymaps and make buffers protected
163+
M.switch_view_type(M.current_view_type)
147164
M.rebuild_unlinked_discussion_tree()
165+
M.rebuild_discussion_tree()
148166

149-
-- Set default buffer
150-
local default_buffer = winbar.bufnr_map[view_type]
151-
vim.api.nvim_set_current_buf(default_buffer)
152-
common.switch_can_edit_bufs(false, M.linked_bufnr, M.unlinked_bufnr)
167+
-- Focus the correct window
168+
local win_to_enter = not state.settings.discussion_tree.enter_on_open and original_window or M.split.winid
169+
if vim.api.nvim_win_is_valid(win_to_enter) then
170+
vim.api.nvim_set_current_win(win_to_enter)
171+
end
172+
173+
-- Relooad data
174+
draft_notes.rebuild_view(false, true)
153175

154-
vim.api.nvim_set_current_win(current_window)
155176
if type(callback) == "function" then
156177
callback()
157178
end
@@ -195,7 +216,7 @@ M.move_to_discussion_tree = function()
195216
end
196217
M.discussion_tree:render()
197218
vim.api.nvim_set_current_win(M.split.winid)
198-
winbar.switch_view_type("discussions")
219+
M.switch_view_type("discussions")
199220
vim.api.nvim_win_set_cursor(M.split.winid, { line_number, 0 })
200221
end
201222

@@ -441,6 +462,7 @@ M.rebuild_discussion_tree = function()
441462
end
442463

443464
local current_node = discussions_tree.get_node_at_cursor(M.discussion_tree, M.last_node_at_cursor)
465+
local current_cursor_column = vim.api.nvim_win_get_cursor(0)[2]
444466

445467
local expanded_node_ids = M.gather_expanded_node_ids(M.discussion_tree)
446468
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
@@ -463,7 +485,7 @@ M.rebuild_discussion_tree = function()
463485
tree_utils.open_node_by_id(discussion_tree, id)
464486
end
465487
discussion_tree:render()
466-
discussions_tree.restore_cursor_position(M.split.winid, discussion_tree, current_node)
488+
discussions_tree.restore_cursor_position(M.split.winid, discussion_tree, current_cursor_column, current_node, nil)
467489

468490
M.set_tree_keymaps(discussion_tree, M.linked_bufnr, false)
469491
M.discussion_tree = discussion_tree
@@ -479,6 +501,7 @@ M.rebuild_unlinked_discussion_tree = function()
479501
end
480502

481503
local current_node = discussions_tree.get_node_at_cursor(M.unlinked_discussion_tree, M.last_node_at_cursor)
504+
local current_cursor_column = vim.api.nvim_win_get_cursor(0)[2]
482505

483506
local expanded_node_ids = M.gather_expanded_node_ids(M.unlinked_discussion_tree)
484507
common.switch_can_edit_bufs(true, M.linked_bufnr, M.unlinked_bufnr)
@@ -501,7 +524,7 @@ M.rebuild_unlinked_discussion_tree = function()
501524
tree_utils.open_node_by_id(unlinked_discussion_tree, id)
502525
end
503526
unlinked_discussion_tree:render()
504-
discussions_tree.restore_cursor_position(M.split.winid, unlinked_discussion_tree, current_node)
527+
discussions_tree.restore_cursor_position(M.split.winid, unlinked_discussion_tree, current_cursor_column, current_node)
505528

506529
M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_bufnr, true)
507530
M.unlinked_discussion_tree = unlinked_discussion_tree
@@ -681,7 +704,7 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
681704

682705
if keymaps.discussion_tree.toggle_node then
683706
vim.keymap.set("n", keymaps.discussion_tree.toggle_node, function()
684-
tree_utils.toggle_node(tree)
707+
tree_utils.toggle_node(M.split.winid, tree)
685708
end, { buffer = bufnr, desc = "Toggle node", nowait = keymaps.discussion_tree.toggle_node_nowait })
686709
end
687710

@@ -737,7 +760,7 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
737760

738761
if keymaps.discussion_tree.switch_view then
739762
vim.keymap.set("n", keymaps.discussion_tree.switch_view, function()
740-
winbar.switch_view_type()
763+
M.switch_view_type()
741764
end, {
742765
buffer = bufnr,
743766
desc = "Change view type between discussions and notes",
@@ -804,6 +827,21 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
804827
emoji.init_popup(tree, bufnr)
805828
end
806829

830+
---Toggles the current view type (or sets it to `override`) and then updates the view.
831+
---@param override? "discussions"|"notes" The view type to select.
832+
M.switch_view_type = function(override)
833+
vim.api.nvim_set_option_value("winfixbuf", false, { win = M.split.winid })
834+
if override == "discussions" or M.current_view_type == "notes" then
835+
M.current_view_type = "discussions"
836+
vim.api.nvim_set_current_buf(M.linked_bufnr)
837+
elseif override == "notes" or M.current_view_type == "discussions" then
838+
M.current_view_type = "notes"
839+
vim.api.nvim_set_current_buf(M.unlinked_bufnr)
840+
end
841+
vim.api.nvim_set_option_value("winfixbuf", true, { win = M.split.winid })
842+
winbar.update_winbar()
843+
end
844+
807845
---Toggle comments tree type between "simple" and "by_file_name"
808846
M.toggle_tree_type = function()
809847
if state.settings.discussion_tree.tree_type == "simple" then

lua/gitlab/actions/discussions/tree.lua

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -256,29 +256,26 @@ M.create_node_list_by_file_name = function(node_list)
256256
return discussion_by_file_name
257257
end
258258

259-
local attach_uuid = function(str)
260-
return { text = str, id = u.uuid() }
261-
end
262-
263259
---Build note node body
264260
---@param note Note|DraftNote
265261
---@param resolve_info table?
266262
---@return string
267263
---@return NuiTree.Node[]
268264
local function build_note_body(note, resolve_info)
269265
local text_nodes = {}
270-
for bodyLine in u.split_by_new_lines(note.body or note.note) do
271-
local line = attach_uuid(bodyLine)
266+
local i = 0
267+
for body_line in u.split_by_new_lines(note.body or note.note) do
272268
table.insert(
273269
text_nodes,
274270
NuiTree.Node({
275271
new_line = (type(note.position) == "table" and note.position.new_line),
276272
old_line = (type(note.position) == "table" and note.position.old_line),
277-
text = line.text,
278-
id = line.id,
273+
text = body_line,
274+
id = string.format("%d:%d", note.id, i),
279275
type = "note_body",
280276
}, {})
281277
)
278+
i = i + 1
282279
end
283280

284281
local symbol = ""
@@ -377,8 +374,8 @@ end
377374
---@field keep_current_open boolean Whether to keep the current discussion open even if it should otherwise be closed.
378375

379376
---This function expands/collapses all nodes and their children according to the opts.
380-
---@param tree NuiTree
381377
---@param winid integer
378+
---@param tree NuiTree
382379
---@param unlinked boolean
383380
---@param opts ToggleNodesOptions
384381
M.toggle_nodes = function(winid, tree, unlinked, opts)
@@ -387,6 +384,7 @@ M.toggle_nodes = function(winid, tree, unlinked, opts)
387384
return
388385
end
389386
local root_node = common.get_root_node(tree, current_node)
387+
local current_cursor_column = vim.api.nvim_win_get_cursor(winid)[2]
390388
for _, node in ipairs(tree:get_nodes()) do
391389
if opts.toggle_resolved then
392390
if
@@ -426,7 +424,7 @@ M.toggle_nodes = function(winid, tree, unlinked, opts)
426424
end
427425
end
428426
tree:render()
429-
M.restore_cursor_position(winid, tree, current_node, root_node)
427+
M.restore_cursor_position(winid, tree, current_cursor_column, current_node, root_node)
430428
end
431429

432430
-- Get current node for restoring cursor position
@@ -446,9 +444,10 @@ end
446444
---Restore cursor position to the original node if possible
447445
---@param winid integer Window number of the discussions split
448446
---@param tree NuiTree The inline discussion tree or the unlinked discussion tree
447+
---@param cursor_column integer The original column of the cursor
449448
---@param original_node NuiTree.Node|nil The last node with the cursor
450449
---@param root_node NuiTree.Node|nil The root node of the last node with the cursor
451-
M.restore_cursor_position = function(winid, tree, original_node, root_node)
450+
M.restore_cursor_position = function(winid, tree, cursor_column, original_node, root_node)
452451
if original_node == nil or tree == nil then
453452
return
454453
end
@@ -460,10 +459,9 @@ M.restore_cursor_position = function(winid, tree, original_node, root_node)
460459
_, line_number = tree:get_node("-" .. tostring(root_node.id))
461460
end
462461
end
463-
if line_number ~= nil then
464-
if vim.api.nvim_win_is_valid(winid) then
465-
vim.api.nvim_win_set_cursor(winid, { line_number, 0 })
466-
end
462+
if line_number ~= nil and winid and vim.api.nvim_win_is_valid(winid) then
463+
local last_line = vim.fn.line("$")
464+
vim.api.nvim_win_set_cursor(winid, { math.min(line_number, last_line), cursor_column or 0 })
467465
end
468466
end
469467

@@ -518,24 +516,22 @@ M.open_node_by_id = function(tree, id)
518516
end
519517

520518
-- This function (settings.keymaps.discussion_tree.toggle_node) expands/collapses the current node and its children
521-
M.toggle_node = function(tree)
519+
---@param winid integer The id if the tree split.
520+
---@param tree NuiTree The current discussion tree.
521+
M.toggle_node = function(winid, tree)
522522
local node = tree:get_node()
523-
if node == nil then
524-
return
525-
end
523+
local current_cursor_column = vim.api.nvim_win_get_cursor(winid)[2]
526524

527525
-- Switch to the "note" node from "note_body" nodes to enable toggling discussions inside comments
528-
if node.type == "note_body" then
526+
if node ~= nil and node.type == "note_body" then
529527
node = tree:get_node(node:get_parent_id())
530528
end
531529
if node == nil then
532530
return
533531
end
534532

535533
local children = node:get_child_ids()
536-
if node == nil then
537-
return
538-
end
534+
539535
if node:is_expanded() then
540536
node:collapse()
541537
if common.is_node_note(node) then
@@ -553,6 +549,7 @@ M.toggle_node = function(tree)
553549
end
554550

555551
tree:render()
552+
M.restore_cursor_position(winid, tree, current_cursor_column, node, common.get_root_node(tree, node))
556553
end
557554

558555
return M

lua/gitlab/actions/discussions/winbar.lua

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,7 @@ local u = require("gitlab.utils")
22
local List = require("gitlab.utils.list")
33
local state = require("gitlab.state")
44

5-
local M = {
6-
bufnr_map = {
7-
discussions = nil,
8-
notes = nil,
9-
},
10-
current_view_type = state.settings.discussion_tree.default_view,
11-
}
12-
13-
M.set_buffers = function(linked_bufnr, unlinked_bufnr)
14-
M.bufnr_map = {
15-
discussions = linked_bufnr,
16-
notes = unlinked_bufnr,
17-
}
18-
end
5+
local M = {}
196

207
---@param nodes Discussion[]|UnlinkedDiscussion[]|nil
218
---@return number, number, number
@@ -158,7 +145,7 @@ end
158145

159146
---@param t WinbarTable
160147
M.make_winbar = function(t)
161-
local discussions_focused = M.current_view_type == "discussions"
148+
local discussions_focused = require("gitlab.actions.discussions").current_view_type == "discussions"
162149
local discussion_text = add_drafts_and_resolvable(
163150
"Comments:",
164151
t.resolvable_discussions,
@@ -269,23 +256,6 @@ M.get_ahead_behind = function(ahead, behind)
269256
return a .. "" .. b .. ""
270257
end
271258

272-
---Toggles the current view type (or sets it to `override`) and then updates the view.
273-
---@param override "discussions"|"notes" Defines the view type to select.
274-
M.switch_view_type = function(override)
275-
if override then
276-
M.current_view_type = override
277-
else
278-
if M.current_view_type == "discussions" then
279-
M.current_view_type = "notes"
280-
elseif M.current_view_type == "notes" then
281-
M.current_view_type = "discussions"
282-
end
283-
end
284-
285-
vim.api.nvim_set_current_buf(M.bufnr_map[M.current_view_type])
286-
M.update_winbar()
287-
end
288-
289259
---Set up a timer to update the winbar periodically
290260
M.start_timer = function()
291261
M.cleanup_timer()

lua/gitlab/annotations.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@
300300
---@class DiscussionSettings: table
301301
---@field expanders? ExpanderOpts -- Customize the expander icons in the discussion tree
302302
---@field auto_open? boolean -- Automatically open when the reviewer is opened
303+
---@field enter_on_open? boolean -- Automatically enter the discussion tree when it is opened
303304
---@field default_view? string - Show "discussions" or "notes" by default
304305
---@field blacklist? table<string> -- List of usernames to remove from tree (bots, CI, etc)
305306
---@field keep_current_open? boolean -- If true, current discussion stays open even if it should otherwise be closed when toggling

lua/gitlab/indicators/diagnostics.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ end
113113
---Filter and place the diagnostics for the given buffer.
114114
---@param bufnr number The number of the buffer for placing diagnostics.
115115
M.place_diagnostics = function(bufnr)
116+
if not vim.api.nvim_buf_is_valid(bufnr) then
117+
return
118+
end
116119
if not state.settings.discussion_signs.enabled then
117120
return
118121
end

0 commit comments

Comments
 (0)