diff --git a/README.md b/README.md index 112f0f1..290da49 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ require("gitlab").setup({ imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen| }, }, - help = "?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc) + help = "g?", -- 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 exit = "", perform_action = "s", -- Once in normal mode, does action (like saving comment or editing description, etc) @@ -127,7 +127,7 @@ require("gitlab").setup({ }, 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 + switch_view = "S", -- 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) jump_to_file = "o", -- Jump to comment location in file @@ -136,6 +136,10 @@ require("gitlab").setup({ delete_comment = "dd", -- Delete comment reply = "r", -- Reply to comment toggle_node = "t", -- Opens or closes the discussion + toggle_all_discussions = "T", -- Open or close separately both resolved and unresolved discussions + toggle_resolved_discussions = "R", -- Open or close all resolved discussions + toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions + keep_current_open = false, -- If true, current discussion stays open even if it should otherwise be closed when toggling toggle_resolved = "p" -- Toggles the resolved status of the whole discussion position = "left", -- "top", "right", "bottom" or "left" open_in_browser = "b" -- Jump to the URL of the current note/discussion diff --git a/doc/gitlab.nvim.txt b/doc/gitlab.nvim.txt index be35a0f..54bcb87 100644 --- a/doc/gitlab.nvim.txt +++ b/doc/gitlab.nvim.txt @@ -12,7 +12,7 @@ Table of Contents *gitlab.nvim.table-of-contents* - The Summary view |gitlab.nvim.the-summary-view| - Reviewing an MR |gitlab.nvim.reviewing-an-mr| - Discussions and Notes |gitlab.nvim.discussions-and-notes| - - Labels |gitlab.nvim.labels| + - Labels |gitlab.nvim.labels| - Signs and diagnostics |gitlab.nvim.signs-and-diagnostics| - Uploading Files |gitlab.nvim.uploading-files| - MR Approvals |gitlab.nvim.mr-approvals| @@ -23,9 +23,9 @@ Table of Contents *gitlab.nvim.table-of-contents* - Restarting or Shutting Down |gitlab.nvim.restarting-or-shutting-down| - Keybindings |gitlab.nvim.keybindings| - Troubleshooting |gitlab.nvim.troubleshooting| - - Api |gitlab.nvim.api| + - Api |gitlab.nvim.api| -OVERVIEW *gitlab.nvim.overview* +OVERVIEW *gitlab.nvim.overview* This Neovim plugin is designed to make it easy to review Gitlab MRs from within 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: debug = { go_request = false, go_response = false }, -- Which values to log attachment_dir = nil, -- The local directory for files (see the "summary" section) reviewer_settings = { - diffview = { - imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen| - }, + diffview = { + imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen| + }, }, - help = "?", -- Opens a help popup for local keymaps when a relevant view is focused (popup, discussion panel, etc) + help = "g?", -- 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 exit = "", perform_action = "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: pipeline = nil, reply = nil, squash_message = nil, - backup_register = nil, + backup_register = nil, }, 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 + switch_view = "S", -- 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) 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: delete_comment = "dd", -- Delete comment reply = "r", -- Reply to comment toggle_node = "t", -- Opens or closes the discussion + toggle_all_discussions = "T", -- Open or close separately both resolved and unresolved discussions + toggle_resolved_discussions = "R", -- Open or close all resolved discussions + toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions + keep_current_open = false, -- If true, current discussion stays open even if it should otherwise be closed when toggling toggle_resolved = "p" -- Toggles the resolved status of the whole discussion position = "left", -- "top", "right", "bottom" or "left" 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: enabled = true, horizontal = false, -- Display metadata to the left of the summary rather than underneath fields = { -- The fields listed here will be displayed, in whatever order you choose - "author", - "created_at", - "updated_at", - "merge_status", - "draft", - "conflicts", - "assignees", - "reviewers", - "branch", - "target_branch", - "pipeline", - "labels", + "author", + "created_at", + "updated_at", + "merge_status", + "draft", + "conflicts", + "assignees", + "reviewers", + "branch", + "target_branch", + "pipeline", + "labels", }, }, discussion_sign_and_diagnostic = { @@ -368,7 +372,7 @@ $XDG_CONFIG_HOME/nvim/after/ftplugin/gitlab.lua with the following contents: vim.o.breakindent = true < -LABELS *gitlab.nvim.labels* +LABELS *gitlab.nvim.labels* You can add or remove labels from the current MR. >lua @@ -418,7 +422,7 @@ diagnostics for discussions on outdated diff revisions. When interacting with multiline comments, the cursor must be on the "main" line of diagnostic, where the `discussion_sign.text` is shown, otherwise -`vim.diagnostic.show` and `jump_to_discussion_tree_from_diagnostic` will not +`vim.diagnostic.show` and `move_to_discussion_tree_from_diagnostic` will not work. diff --git a/lua/gitlab/actions/discussions/init.lua b/lua/gitlab/actions/discussions/init.lua index cc6b1dd..ed3a0bc 100644 --- a/lua/gitlab/actions/discussions/init.lua +++ b/lua/gitlab/actions/discussions/init.lua @@ -389,6 +389,86 @@ M.toggle_node = function(tree) tree:render() end +---@class ToggleNodesOptions +---@field toggle_resolved boolean Whether to toggle resolved discussions. +---@field toggle_unresolved boolean Whether to toggle unresolved discussions. +---@field keep_current_open boolean Whether to keep the current discussion open even if it should otherwise be closed. + +---This function (settings.discussion_tree.toggle_nodes) expands/collapses all nodes and their children according to the opts. +---@param tree NuiTree +---@param opts ToggleNodesOptions +M.toggle_nodes = function(tree, opts) + local current_node = tree:get_node() + if current_node == nil then + return + end + local root_node = M.get_root_node(tree, current_node) + for _, node in ipairs(tree:get_nodes()) do + if opts.toggle_resolved then + if state.resolved_expanded then + M.collapse_recursively(tree, node, root_node, opts.keep_current_open, true) + else + M.expand_recursively(tree, node, true) + end + end + if opts.toggle_unresolved then + if state.unresolved_expanded then + M.collapse_recursively(tree, node, root_node, opts.keep_current_open, false) + else + M.expand_recursively(tree, node, false) + end + end + end + if opts.toggle_resolved then + state.resolved_expanded = not state.resolved_expanded + end + if opts.toggle_unresolved then + state.unresolved_expanded = not state.unresolved_expanded + end + tree:render() + M.restore_cursor_position(tree, current_node, root_node) +end + +---This function (settings.discussion_tree.collapse_recursively) collapses a node and its children. +---@param tree NuiTree +---@param node NuiTree.Node +---@param current_root_node NuiTree.Node The root node of the current node. +---@param keep_current_open boolean If true, the current node stays open, even if it should otherwise be collapsed. +---@param is_resolved boolean If true, collapse resolved discussions. If false, collapse unresolved discussions. +M.collapse_recursively = function(tree, node, current_root_node, keep_current_open, is_resolved) + if node == nil then + return + end + local root_node = M.get_root_node(tree, node) + if M.is_node_note(node) and root_node.resolved == is_resolved then + if keep_current_open and root_node == current_root_node then + return + end + node:collapse() + end + local children = node:get_child_ids() + for _, child in ipairs(children) do + M.collapse_recursively(tree, tree:get_node(child), current_root_node, keep_current_open, is_resolved) + end +end + +---This function (settings.discussion_tree.expand_recursively) expands a node and its children. +---@param tree NuiTree +---@param node NuiTree.Node +---@param is_resolved boolean If true, expand resolved discussions. If false, expand unresolved discussions. +M.expand_recursively = function(tree, node, is_resolved) + if node == nil then + return + end + if M.is_node_note(node) and M.get_root_node(tree, node).resolved == is_resolved then + node:expand() + end + local children = node:get_child_ids() + for _, child in ipairs(children) do + M.expand_recursively(tree, tree:get_node(child), is_resolved) + end +end + -- -- 🌲 Helper Functions -- @@ -560,6 +640,27 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked) vim.keymap.set("n", state.settings.discussion_tree.toggle_node, function() M.toggle_node(tree) end, { buffer = bufnr, desc = "Toggle node" }) + vim.keymap.set("n", state.settings.discussion_tree.toggle_all_discussions, function() + M.toggle_nodes(tree, { + toggle_resolved = true, + toggle_unresolved = true, + keep_current_open = state.settings.discussion_tree.keep_current_open, + }) + end, { buffer = bufnr, desc = "Toggle all nodes" }) + vim.keymap.set("n", state.settings.discussion_tree.toggle_resolved_discussions, function() + M.toggle_nodes(tree, { + toggle_resolved = true, + toggle_unresolved = false, + keep_current_open = state.settings.discussion_tree.keep_current_open, + }) + end, { buffer = bufnr, desc = "Toggle resolved nodes" }) + vim.keymap.set("n", state.settings.discussion_tree.toggle_unresolved_discussions, function() + M.toggle_nodes(tree, { + toggle_resolved = false, + toggle_unresolved = true, + keep_current_open = state.settings.discussion_tree.keep_current_open, + }) + end, { buffer = bufnr, desc = "Toggle unresolved nodes" }) vim.keymap.set("n", state.settings.discussion_tree.reply, function() if M.is_current_node_note(tree) then M.reply(tree) @@ -621,6 +722,18 @@ M.redraw_resolved_status = function(tree, note, mark_resolved) tree:render() end +---Restore cursor position to the original node if possible +M.restore_cursor_position = function(tree, original_node, root_node) + local _, line_number = tree:get_node("-" .. tostring(original_node.id)) + -- If current_node is has been collapsed, get line number of root node instead + if line_number == nil and root_node then + _, line_number = tree:get_node("-" .. tostring(root_node.id)) + end + if line_number ~= nil then + vim.api.nvim_win_set_cursor(M.split.winid, { line_number, 0 }) + end +end + ---Replace text in discussion after note update. ---@param data Discussion[]|UnlinkedDiscussion[] ---@param discussion_id string diff --git a/lua/gitlab/actions/help.lua b/lua/gitlab/actions/help.lua index 4ba1f71..d588ba2 100644 --- a/lua/gitlab/actions/help.lua +++ b/lua/gitlab/actions/help.lua @@ -10,7 +10,7 @@ M.open = function() 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) + local new_line = string.format("%s: %s", keymap.lhs:gsub(" ", ""), keymap.desc) table.insert(help_content_lines, new_line) end end diff --git a/lua/gitlab/state.lua b/lua/gitlab/state.lua index 5a8086f..3c59c17 100644 --- a/lua/gitlab/state.lua +++ b/lua/gitlab/state.lua @@ -19,7 +19,7 @@ M.settings = { }, }, attachment_dir = "", - help = "?", + help = "g?", popup = { exit = "", perform_action = "s", @@ -39,6 +39,8 @@ M.settings = { }, discussion_tree = { auto_open = true, + switch_view = "S", + default_view = "discussions", blacklist = {}, jump_to_file = "o", jump_to_reviewer = "m", @@ -47,6 +49,10 @@ M.settings = { open_in_browser = "b", reply = "r", toggle_node = "t", + toggle_all_discussions = "T", + toggle_resolved_discussions = "R", + toggle_unresolved_discussions = "U", + keep_current_open = false, toggle_resolved = "p", relative = "editor", position = "left", @@ -54,8 +60,6 @@ M.settings = { resolved = "✓", unresolved = "-", tree_type = "simple", - switch_view = "T", - default_view = "discussions", ---@param t WinbarTable winbar = function(t) local discussions_content = t.resolvable_discussions ~= 0