Skip to content

Commit 3f1c5ef

Browse files
authored
Feat collapse and expand nodes (#176)
This MR adds the ability to expand and collapse nodes in the discussion tree in bulk. You can now toggle expansion of all nodes, toggle expansion of only resolved discussions, and toggle expansion of only unresolved discussions. The MR also adjusts keybindings in the discussion tree to support forward and backward searching, as well as the keybinding for the help popup. Thank you for the contribution @jakubbortlik! This is a #MINOR bump since it's changing keybindings, although core workflows are unchanged.
1 parent 6046669 commit 3f1c5ef

File tree

5 files changed

+154
-29
lines changed

5 files changed

+154
-29
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ require("gitlab").setup({
108108
imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen|
109109
},
110110
},
111-
help = "?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
111+
help = "g?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
112112
popup = { -- The popup for comment creation, editing, and replying
113113
exit = "<Esc>",
114114
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
@@ -127,7 +127,7 @@ require("gitlab").setup({
127127
},
128128
discussion_tree = { -- The discussion tree that holds all comments
129129
auto_open = true, -- Automatically open when the reviewer is opened
130-
switch_view = "T", -- Toggles between the notes and discussions views
130+
switch_view = "S", -- Toggles between the notes and discussions views
131131
default_view = "discussions" -- Show "discussions" or "notes" by default
132132
blacklist = {}, -- List of usernames to remove from tree (bots, CI, etc)
133133
jump_to_file = "o", -- Jump to comment location in file
@@ -136,6 +136,10 @@ require("gitlab").setup({
136136
delete_comment = "dd", -- Delete comment
137137
reply = "r", -- Reply to comment
138138
toggle_node = "t", -- Opens or closes the discussion
139+
toggle_all_discussions = "T", -- Open or close separately both resolved and unresolved discussions
140+
toggle_resolved_discussions = "R", -- Open or close all resolved discussions
141+
toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions
142+
keep_current_open = false, -- If true, current discussion stays open even if it should otherwise be closed when toggling
139143
toggle_resolved = "p" -- Toggles the resolved status of the whole discussion
140144
position = "left", -- "top", "right", "bottom" or "left"
141145
open_in_browser = "b" -- Jump to the URL of the current note/discussion

doc/gitlab.nvim.txt

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Table of Contents *gitlab.nvim.table-of-contents*
1212
- The Summary view |gitlab.nvim.the-summary-view|
1313
- Reviewing an MR |gitlab.nvim.reviewing-an-mr|
1414
- Discussions and Notes |gitlab.nvim.discussions-and-notes|
15-
- Labels |gitlab.nvim.labels|
15+
- Labels |gitlab.nvim.labels|
1616
- Signs and diagnostics |gitlab.nvim.signs-and-diagnostics|
1717
- Uploading Files |gitlab.nvim.uploading-files|
1818
- MR Approvals |gitlab.nvim.mr-approvals|
@@ -23,9 +23,9 @@ Table of Contents *gitlab.nvim.table-of-contents*
2323
- Restarting or Shutting Down |gitlab.nvim.restarting-or-shutting-down|
2424
- Keybindings |gitlab.nvim.keybindings|
2525
- Troubleshooting |gitlab.nvim.troubleshooting|
26-
- Api |gitlab.nvim.api|
26+
- Api |gitlab.nvim.api|
2727

28-
OVERVIEW *gitlab.nvim.overview*
28+
OVERVIEW *gitlab.nvim.overview*
2929

3030
This Neovim plugin is designed to make it easy to review Gitlab MRs from within
3131
the editor. This means you can do things like:
@@ -137,11 +137,11 @@ you call this function with no values the defaults will be used:
137137
debug = { go_request = false, go_response = false }, -- Which values to log
138138
attachment_dir = nil, -- The local directory for files (see the "summary" section)
139139
reviewer_settings = {
140-
diffview = {
141-
imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen|
142-
},
140+
diffview = {
141+
imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen|
142+
},
143143
},
144-
help = "?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
144+
help = "g?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc)
145145
popup = { -- The popup for comment creation, editing, and replying
146146
exit = "<Esc>",
147147
perform_action = "<leader>s", -- Once in normal mode, does action (like saving comment or editing description, etc)
@@ -156,11 +156,11 @@ you call this function with no values the defaults will be used:
156156
pipeline = nil,
157157
reply = nil,
158158
squash_message = nil,
159-
backup_register = nil,
159+
backup_register = nil,
160160
},
161161
discussion_tree = { -- The discussion tree that holds all comments
162162
auto_open = true, -- Automatically open when the reviewer is opened
163-
switch_view = "T", -- Toggles between the notes and discussions views
163+
switch_view = "S", -- Toggles between the notes and discussions views
164164
default_view = "discussions" -- Show "discussions" or "notes" by default
165165
blacklist = {}, -- List of usernames to remove from tree (bots, CI, etc)
166166
jump_to_file = "o", -- Jump to comment location in file
@@ -169,6 +169,10 @@ you call this function with no values the defaults will be used:
169169
delete_comment = "dd", -- Delete comment
170170
reply = "r", -- Reply to comment
171171
toggle_node = "t", -- Opens or closes the discussion
172+
toggle_all_discussions = "T", -- Open or close separately both resolved and unresolved discussions
173+
toggle_resolved_discussions = "R", -- Open or close all resolved discussions
174+
toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions
175+
keep_current_open = false, -- If true, current discussion stays open even if it should otherwise be closed when toggling
172176
toggle_resolved = "p" -- Toggles the resolved status of the whole discussion
173177
position = "left", -- "top", "right", "bottom" or "left"
174178
open_in_browser = "b" -- Jump to the URL of the current note/discussion
@@ -184,18 +188,18 @@ you call this function with no values the defaults will be used:
184188
enabled = true,
185189
horizontal = false, -- Display metadata to the left of the summary rather than underneath
186190
fields = { -- The fields listed here will be displayed, in whatever order you choose
187-
"author",
188-
"created_at",
189-
"updated_at",
190-
"merge_status",
191-
"draft",
192-
"conflicts",
193-
"assignees",
194-
"reviewers",
195-
"branch",
196-
"target_branch",
197-
"pipeline",
198-
"labels",
191+
"author",
192+
"created_at",
193+
"updated_at",
194+
"merge_status",
195+
"draft",
196+
"conflicts",
197+
"assignees",
198+
"reviewers",
199+
"branch",
200+
"target_branch",
201+
"pipeline",
202+
"labels",
199203
},
200204
},
201205
discussion_sign_and_diagnostic = {
@@ -368,7 +372,7 @@ $XDG_CONFIG_HOME/nvim/after/ftplugin/gitlab.lua with the following contents:
368372
vim.o.breakindent = true
369373
<
370374

371-
LABELS *gitlab.nvim.labels*
375+
LABELS *gitlab.nvim.labels*
372376

373377
You can add or remove labels from the current MR.
374378
>lua
@@ -418,7 +422,7 @@ diagnostics for discussions on outdated diff revisions.
418422

419423
When interacting with multiline comments, the cursor must be on the "main" line
420424
of diagnostic, where the `discussion_sign.text` is shown, otherwise
421-
`vim.diagnostic.show` and `jump_to_discussion_tree_from_diagnostic` will not
425+
`vim.diagnostic.show` and `move_to_discussion_tree_from_diagnostic` will not
422426
work.
423427

424428

lua/gitlab/actions/discussions/init.lua

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,86 @@ M.toggle_node = function(tree)
389389
tree:render()
390390
end
391391

392+
---@class ToggleNodesOptions
393+
---@field toggle_resolved boolean Whether to toggle resolved discussions.
394+
---@field toggle_unresolved boolean Whether to toggle unresolved discussions.
395+
---@field keep_current_open boolean Whether to keep the current discussion open even if it should otherwise be closed.
396+
397+
---This function (settings.discussion_tree.toggle_nodes) expands/collapses all nodes and their children according to the opts.
398+
---@param tree NuiTree
399+
---@param opts ToggleNodesOptions
400+
M.toggle_nodes = function(tree, opts)
401+
local current_node = tree:get_node()
402+
if current_node == nil then
403+
return
404+
end
405+
local root_node = M.get_root_node(tree, current_node)
406+
for _, node in ipairs(tree:get_nodes()) do
407+
if opts.toggle_resolved then
408+
if state.resolved_expanded then
409+
M.collapse_recursively(tree, node, root_node, opts.keep_current_open, true)
410+
else
411+
M.expand_recursively(tree, node, true)
412+
end
413+
end
414+
if opts.toggle_unresolved then
415+
if state.unresolved_expanded then
416+
M.collapse_recursively(tree, node, root_node, opts.keep_current_open, false)
417+
else
418+
M.expand_recursively(tree, node, false)
419+
end
420+
end
421+
end
422+
if opts.toggle_resolved then
423+
state.resolved_expanded = not state.resolved_expanded
424+
end
425+
if opts.toggle_unresolved then
426+
state.unresolved_expanded = not state.unresolved_expanded
427+
end
428+
tree:render()
429+
M.restore_cursor_position(tree, current_node, root_node)
430+
end
431+
432+
---This function (settings.discussion_tree.collapse_recursively) collapses a node and its children.
433+
---@param tree NuiTree
434+
---@param node NuiTree.Node
435+
---@param current_root_node NuiTree.Node The root node of the current node.
436+
---@param keep_current_open boolean If true, the current node stays open, even if it should otherwise be collapsed.
437+
---@param is_resolved boolean If true, collapse resolved discussions. If false, collapse unresolved discussions.
438+
M.collapse_recursively = function(tree, node, current_root_node, keep_current_open, is_resolved)
439+
if node == nil then
440+
return
441+
end
442+
local root_node = M.get_root_node(tree, node)
443+
if M.is_node_note(node) and root_node.resolved == is_resolved then
444+
if keep_current_open and root_node == current_root_node then
445+
return
446+
end
447+
node:collapse()
448+
end
449+
local children = node:get_child_ids()
450+
for _, child in ipairs(children) do
451+
M.collapse_recursively(tree, tree:get_node(child), current_root_node, keep_current_open, is_resolved)
452+
end
453+
end
454+
455+
---This function (settings.discussion_tree.expand_recursively) expands a node and its children.
456+
---@param tree NuiTree
457+
---@param node NuiTree.Node
458+
---@param is_resolved boolean If true, expand resolved discussions. If false, expand unresolved discussions.
459+
M.expand_recursively = function(tree, node, is_resolved)
460+
if node == nil then
461+
return
462+
end
463+
if M.is_node_note(node) and M.get_root_node(tree, node).resolved == is_resolved then
464+
node:expand()
465+
end
466+
local children = node:get_child_ids()
467+
for _, child in ipairs(children) do
468+
M.expand_recursively(tree, tree:get_node(child), is_resolved)
469+
end
470+
end
471+
392472
--
393473
-- 🌲 Helper Functions
394474
--
@@ -560,6 +640,27 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
560640
vim.keymap.set("n", state.settings.discussion_tree.toggle_node, function()
561641
M.toggle_node(tree)
562642
end, { buffer = bufnr, desc = "Toggle node" })
643+
vim.keymap.set("n", state.settings.discussion_tree.toggle_all_discussions, function()
644+
M.toggle_nodes(tree, {
645+
toggle_resolved = true,
646+
toggle_unresolved = true,
647+
keep_current_open = state.settings.discussion_tree.keep_current_open,
648+
})
649+
end, { buffer = bufnr, desc = "Toggle all nodes" })
650+
vim.keymap.set("n", state.settings.discussion_tree.toggle_resolved_discussions, function()
651+
M.toggle_nodes(tree, {
652+
toggle_resolved = true,
653+
toggle_unresolved = false,
654+
keep_current_open = state.settings.discussion_tree.keep_current_open,
655+
})
656+
end, { buffer = bufnr, desc = "Toggle resolved nodes" })
657+
vim.keymap.set("n", state.settings.discussion_tree.toggle_unresolved_discussions, function()
658+
M.toggle_nodes(tree, {
659+
toggle_resolved = false,
660+
toggle_unresolved = true,
661+
keep_current_open = state.settings.discussion_tree.keep_current_open,
662+
})
663+
end, { buffer = bufnr, desc = "Toggle unresolved nodes" })
563664
vim.keymap.set("n", state.settings.discussion_tree.reply, function()
564665
if M.is_current_node_note(tree) then
565666
M.reply(tree)
@@ -621,6 +722,18 @@ M.redraw_resolved_status = function(tree, note, mark_resolved)
621722
tree:render()
622723
end
623724

725+
---Restore cursor position to the original node if possible
726+
M.restore_cursor_position = function(tree, original_node, root_node)
727+
local _, line_number = tree:get_node("-" .. tostring(original_node.id))
728+
-- If current_node is has been collapsed, get line number of root node instead
729+
if line_number == nil and root_node then
730+
_, line_number = tree:get_node("-" .. tostring(root_node.id))
731+
end
732+
if line_number ~= nil then
733+
vim.api.nvim_win_set_cursor(M.split.winid, { line_number, 0 })
734+
end
735+
end
736+
624737
---Replace text in discussion after note update.
625738
---@param data Discussion[]|UnlinkedDiscussion[]
626739
---@param discussion_id string

lua/gitlab/actions/help.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ M.open = function()
1010
local help_content_lines = {}
1111
for _, keymap in ipairs(keymaps) do
1212
if keymap.desc ~= nil then
13-
local new_line = string.format("%s: %s", keymap.lhs, keymap.desc)
13+
local new_line = string.format("%s: %s", keymap.lhs:gsub(" ", "<space>"), keymap.desc)
1414
table.insert(help_content_lines, new_line)
1515
end
1616
end

lua/gitlab/state.lua

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ M.settings = {
1919
},
2020
},
2121
attachment_dir = "",
22-
help = "?",
22+
help = "g?",
2323
popup = {
2424
exit = "<Esc>",
2525
perform_action = "<leader>s",
@@ -39,6 +39,8 @@ M.settings = {
3939
},
4040
discussion_tree = {
4141
auto_open = true,
42+
switch_view = "S",
43+
default_view = "discussions",
4244
blacklist = {},
4345
jump_to_file = "o",
4446
jump_to_reviewer = "m",
@@ -47,15 +49,17 @@ M.settings = {
4749
open_in_browser = "b",
4850
reply = "r",
4951
toggle_node = "t",
52+
toggle_all_discussions = "T",
53+
toggle_resolved_discussions = "R",
54+
toggle_unresolved_discussions = "U",
55+
keep_current_open = false,
5056
toggle_resolved = "p",
5157
relative = "editor",
5258
position = "left",
5359
size = "20%",
5460
resolved = "",
5561
unresolved = "-",
5662
tree_type = "simple",
57-
switch_view = "T",
58-
default_view = "discussions",
5963
---@param t WinbarTable
6064
winbar = function(t)
6165
local discussions_content = t.resolvable_discussions ~= 0

0 commit comments

Comments
 (0)