diff --git a/README.md b/README.md index 8ff7326..320c979 100644 --- a/README.md +++ b/README.md @@ -122,37 +122,7 @@ For a list of all these settings please run `:h gitlab.nvim.configuring-the-plug ## Keybindings -The plugin does not set up any keybindings outside of the special buffers it creates, -you need to set them up yourself. Here's what I'm using: - -```lua -local gitlab = require("gitlab") -local gitlab_server = require("gitlab.server") -vim.keymap.set("n", "glb", gitlab.choose_merge_request) -vim.keymap.set("n", "glr", gitlab.review) -vim.keymap.set("n", "gls", gitlab.summary) -vim.keymap.set("n", "glA", gitlab.approve) -vim.keymap.set("n", "glR", gitlab.revoke) -vim.keymap.set("n", "glc", gitlab.create_comment) -vim.keymap.set("v", "glc", gitlab.create_multiline_comment) -vim.keymap.set("v", "glC", gitlab.create_comment_suggestion) -vim.keymap.set("n", "glO", gitlab.create_mr) -vim.keymap.set("n", "glm", gitlab.move_to_discussion_tree_from_diagnostic) -vim.keymap.set("n", "gln", gitlab.create_note) -vim.keymap.set("n", "gld", gitlab.toggle_discussions) -vim.keymap.set("n", "glaa", gitlab.add_assignee) -vim.keymap.set("n", "glad", gitlab.delete_assignee) -vim.keymap.set("n", "glla", gitlab.add_label) -vim.keymap.set("n", "glld", gitlab.delete_label) -vim.keymap.set("n", "glra", gitlab.add_reviewer) -vim.keymap.set("n", "glrd", gitlab.delete_reviewer) -vim.keymap.set("n", "glp", gitlab.pipeline) -vim.keymap.set("n", "glo", gitlab.open_in_browser) -vim.keymap.set("n", "glM", gitlab.merge) -vim.keymap.set("n", "glu", gitlab.copy_mr_url) -vim.keymap.set("n", "glP", gitlab.publish_all_drafts) -vim.keymap.set("n", "glD", gitlab.toggle_draft_mode) -``` +The plugin sets up a number of useful keybindings in the special buffers it creates, and some global keybindings as well. Refer to the relevant section of the manual `:h gitlab.nvim.keybindings` for more details. For more information about each of these commands, and about the APIs in general, run `:h gitlab.nvim.api` diff --git a/after/syntax/gitlab.vim b/after/syntax/gitlab.vim index ef24147..3acf898 100644 --- a/after/syntax/gitlab.vim +++ b/after/syntax/gitlab.vim @@ -2,22 +2,29 @@ if filereadable($VIMRUNTIME . '/syntax/markdown.vim') source $VIMRUNTIME/syntax/markdown.vim endif -syntax match Username "\%([]\)\@<= @\S*" -syntax match Mention "\%([] \)\@", -- Cycle to the next field. Accepts |count|. prev_field = "", -- Cycle to the previous field. Accepts |count|. + perform_action = "ZZ", -- Once in normal mode, does action (like saving comment or applying description edit, etc) + perform_linewise_action = "ZA", -- Once in normal mode, does the linewise action (see logs for this job, etc) + discard_changes = "ZQ", -- Quit the popup discarding changes, the popup content is not saved to the `temp_registers` (see `:h gitlab.nvim.temp-registers`) }, - perform_action = "s", -- Once in normal mode, does action (like saving comment or editing description, etc) - perform_linewise_action = "l", -- Once in normal mode, does the linewise action (see logs for this job, etc) + discussion_tree = { + disable_all = false, -- Disable all default mappings for the discussion tree window + add_emoji = "Ea", -- Add an emoji to the note/comment + delete_emoji = "Ed", -- Remove an emoji from a note/comment + delete_comment = "dd", -- Delete comment + edit_comment = "e", -- Edit comment + reply = "r", -- Reply to comment + toggle_resolved = "-", -- Toggle the resolved status of the whole discussion + jump_to_file = "o", -- Jump to comment location in file + jump_to_reviewer = "a", -- Jump to the comment location in the reviewer window + open_in_browser = "b", -- Jump to the URL of the current note/discussion + copy_node_url = "u", -- Copy the URL of the current node to clipboard + switch_view = "c", -- Toggle between the notes and discussions views + toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name" + publish_draft = "P", -- Publish the currently focused note/comment + toggle_draft_mode = "D", -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately) + toggle_node = "t", -- Open or close 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 + refresh_data = "", -- Refresh the data in the view by hitting Gitlab's APIs again + print_node = "p", -- Print the current node (for debugging) + }, + reviewer = { + disable_all = false, -- Disable all default mappings for the reviewer windows + create_comment = "c", -- Create a comment for the lines that the following {motion} moves over. Repeat the key(s) for creating comment for the current line + create_suggestion = "s", -- Create a suggestion for the lines that the following {motion} moves over. Repeat the key(s) for creating comment for the current line + move_to_discussion_tree = "a", -- Jump to the comment in the discussion tree + }, + }, + popup = { -- The popup for comment creation, editing, and replying width = "40%", height = "60%", border = "rounded", -- One of "rounded", "single", "double", "solid" @@ -187,36 +244,22 @@ you call this function with no values the defaults will be used: temp_registers = {}, -- List of registers for backing up popup content (see `:h gitlab.nvim.temp-registers`) }, discussion_tree = { -- The discussion tree that holds all comments + expanders = { -- Discussion tree icons + expanded = " ", -- Icon for expanded discussion thread + collapsed = " ", -- Icon for collapsed discussion thread + indentation = " ", -- Indentation Icon + }, auto_open = true, -- Automatically open when the reviewer is opened - 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 - jump_to_reviewer = "m", -- Jump to the location in the reviewer window - edit_comment = "e", -- Edit comment - delete_comment = "dd", -- Delete comment - refresh_data = "a", -- Refreshes the data in the view by hitting Gitlab's APIs again - reply = "r", -- Reply to comment - toggle_node = "t", -- Opens or closes the discussion - add_emoji = "Ea" -- Add an emoji to the note/comment - delete_emoji = "Ed" -- Remove an emoji from a note/comment - 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 - publish_draft = "P", -- Publishes the currently focused note/comment - 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 - copy_node_url = "u", -- Copy the URL of the current node to clipboard size = "20%", -- Size of split relative = "editor", -- Position of tree split relative to "editor" or "window" resolved = '✓', -- Symbol to show next to resolved 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 - toggle_tree_type = "i", -- Toggle type of discussion tree - "simple", or "by_file_name" draft_mode = false, -- Whether comments are posted as drafts as part of a review - toggle_draft_mode = "D" -- Toggle between draft mode (comments posted as drafts) and live mode (comments are posted immediately) 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. }, @@ -285,7 +328,7 @@ you call this function with no values the defaults will be used: username = "Keyword", mention = "WarningMsg", date = "Comment", - chevron = "DiffviewNonText", + expander = "DiffviewNonText", directory = "Directory", directory_icon = "DiffviewFolderSign", file_name = "Normal", @@ -314,7 +357,7 @@ The `summary` action will open the MR title and description: require("gitlab").summary() < After editing the description or title, you may save your changes via the -`settings.popup.perform_action` keybinding. +`keymaps.popup.perform_action` keybinding. By default this plugin will also show additional metadata about the MR in a separate pane underneath the description. This can be disabled, and these @@ -338,20 +381,20 @@ For suggesting changes you can use `create_comment_suggestion` in visual mode which works similar to `create_multiline_comment` but prefills the comment window with Gitlab’s suggest changes -code block with prefilled code from the visual selection. -Just like the summary, all the different kinds of comments are saved via the -`settings.popup.perform_action` keybinding. +code block with prefilled code from the visual selection. Just like the +summary, all the different kinds of comments are saved via the +`keymaps.popup.perform_action` keybinding. DRAFT NOTES *gitlab.nvim.draft-comments* When you publish a "draft" of any of the above resources, the comment will be added to a review. You can configure the default commenting mode (draft vs live) via the `state.settings.discussion_tree.draft_mode` setting, and you can -toggle the setting with the `state.settings.discussion_tree.toggle_draft_mode` +toggle the setting with the `keymaps.discussion_tree.toggle_draft_mode` keybinding, or by calling the `gitlab.toggle_draft_mode()` function. You may publish all draft comments via the `gitlab.publish_all_drafts()` function, and you can publish an individual comment or note by pressing the -`state.settings.discussion_tree.publish_draft` keybinding. +`keymaps.discussion_tree.publish_draft` keybinding. Draft notes do not support replying or emojis. @@ -372,9 +415,13 @@ else. Using the clipboard register lets you easily use the text outside of nvim. NOTE: The `temp_registers` are also filled with the contents of the popup when -pressing the `settings.popup.perform_action` keybinding, even if the action +pressing the `keymaps.popup.perform_action` keybinding, even if the action that was supposed to be performed fails. +If you don't want the popup contents to be saved to the `temp_registers`, quit +the popup using the `keymaps.pupup.discard_changes` keybinding, which is `ZQ` +by default (compare the builtin |ZQ| command). + DISCUSSIONS AND NOTES *gitlab.nvim.discussions-and-notes* Gitlab groups threads of comments together into "discussions." @@ -386,11 +433,11 @@ action, which will show the discussions in a split window: < You can jump to the comment’s location in the reviewer window by using the -`state.settings.discussion_tree.jump_to_reviewer` key, or to the actual file -with the `state.settings.discussion_tree.jump_to_file` key. +`keymaps.discussion_tree.jump_to_reviewer` keybinding, or to the actual file +with the `keymaps.discussion_tree.jump_to_file` keybinding. Within the discussion tree, you can delete/edit/reply to comments with the -`state.settings.discussion_tree.SOME_ACTION` keybindings. +`keymaps.discussion_tree.SOME_ACTION` keybindings. If you’d like to create a note in an MR (like a comment, but not linked to a specific line) use the `create_note` action. The same keybindings for @@ -466,14 +513,14 @@ emojis that you have responded with. UPLOADING FILES *gitlab.nvim.uploading-files* To attach a file to an MR description, reply, comment, and so forth use the -`settings.popup.perform_linewise_action` keybinding when the popup is open. +`keymaps.popup.perform_linewise_action` keybinding when the popup is open. This will open a picker that will look for files in the directory you specify in the `settings.attachment_dir` folder (this must be an absolute path). When you have picked the file, it will be added to the current buffer at the current line. -Use the `settings.popup.perform_action` to send the changes to Gitlab. +Use the `keymaps.popup.perform_action` to send the changes to Gitlab. MR APPROVALS *gitlab.nvim.mr-approvals* @@ -515,8 +562,8 @@ action. require("gitlab").pipeline() < To re-trigger failed jobs in the pipeline manually, use the -`settings.popup.perform_action` keybinding. To open the log trace of a job in a -new Neovim buffer, use your `settings.popup.perform_linewise_action` +`keymaps.popup.perform_action` keybinding. To open the log trace of a job in a +new Neovim buffer, use your `keymaps.popup.perform_linewise_action` keybinding. @@ -565,34 +612,75 @@ reviewer when checking out a new branch: KEYBINDINGS *gitlab.nvim.keybindings* -The plugin does not set up any keybindings outside of the special buffers it -creates, you need to set them up yourself. Here’s what I’m using (note that -the `` prefix is not necessary, as `gl` does not have a special meaning -in normal mode): +The `gitlab.nvim` plugin sets up a number of default keybindings for the +discussion tree, the popup windows and the reviewer windows, and also some +global keybindings that are available in any buffer. You can find the defaults +in the `keymaps` section of the configuration table (see +|gitlab.nvim.configuring-the-plugin|) and you can see the current buffer-local +mappings by pressing the `keymaps.help` keybinding (`g?` by default). The +`help` mapping is not set in the reviewer windows which are managed by the +Diffview plugin, see |diffview-config-keymaps|. + +You can set any of the mappings to whatever you want. You can also prevent the +plugin from setting up any keybindings whatsoever, or any keybindings for +individual buffers by setting `keymaps.disable_all`, +`keymaps.global.disable_all`, `keymaps.popup.disable_all`, etc. to `true`. +Alternatively, you can disable individual keybindings by setting the value of +the field to `false`, e.g., `keymaps = {global {merge = false}}`, which will +leave all the other keybindings in place. + +The global keymaps all use the `gl` prefix as it does not have a special +meaning in normal mode. You can add your own global keybindings by calling +something like this after the `gitlab.setup()` call: >lua local gitlab = require("gitlab") - local gitlab_server = require("gitlab.server") - vim.keymap.set("n", "glr", gitlab.review) - vim.keymap.set("n", "gls", gitlab.summary) - vim.keymap.set("n", "glA", gitlab.approve) - vim.keymap.set("n", "glR", gitlab.revoke) - vim.keymap.set("n", "glc", gitlab.create_comment) - vim.keymap.set("v", "glc", gitlab.create_multiline_comment) - vim.keymap.set("v", "glC", gitlab.create_comment_suggestion) - vim.keymap.set("n", "glO", gitlab.create_mr) - vim.keymap.set("n", "glm", gitlab.move_to_discussion_tree_from_diagnostic) - vim.keymap.set("n", "gln", gitlab.create_note) - vim.keymap.set("n", "gld", gitlab.toggle_discussions) - vim.keymap.set("n", "glaa", gitlab.add_assignee) - vim.keymap.set("n", "glad", gitlab.delete_assignee) - vim.keymap.set("n", "glra", gitlab.add_reviewer) - vim.keymap.set("n", "glrd", gitlab.delete_reviewer) - vim.keymap.set("n", "glp", gitlab.pipeline) - vim.keymap.set("n", "glo", gitlab.open_in_browser) - vim.keymap.set("n", "glM", gitlab.merge) - vim.keymap.set("n", "glu", gitlab.copy_mr_url) - vim.keymap.set("n", "glP", gitlab.publish_all_drafts) - vim.keymap.set("n", "glD", gitlab.toggle_draft_mode) + vim.keymap.set("n", "gl", gitlab.print_settings, { desc = "Print gitlab.nvim settings"}) +< +See |gitlab.nvim.api| for an overview of available API functions that you can +use in your mappings. + +If you want to set up your own additional keybindings for the discussion tree, +you can put them in the `$XDG_CONFIG_HOME/nvim/after/ftplugin/gitlab.lua` +file. E.g., if you wanted to use the `j` and `k` keys to move to the next +discussion node instead of the next line, you could add to the |ftplugin| file +the following lines: +>lua + vim.keymap.set("n", "j", [[call search('[] @')]], { buffer = 0, desc = "Go to next node" }) + vim.keymap.set("n", "k", [[call search('[] @', 'b')]], { buffer = 0, desc = "Go to previous node" }) +< + +Reviewer keybindings ~ + +Most of the keybindings `gitlab.nvim` sets are normal mode mappings, with the +exception of `keymaps.reviewer.create_comment` and +`keymaps.reviewer.create_suggestion` which work in both normal and visual +mode. In normal mode, these keybindings are |operator|s that accept a |motion| +(with an optional |count|). E.g., `c2j` will create a comment for the current +and the next 2 lines. Similarly, `sip` will create a suggestion for the "inner +paragraph". The operator forces |linewise| visual selection, so it works +correctly even if the motion itself works |characterwise| (e.g., |i(| for +selecting the inner parentheses block). + +The keybindings also work in visual mode, e.g., if you first want to make +sure you are commenting on the right text segment/object, you can do `v4j` to +visually select the current and the next 4 lines, followed by either `c` for a +normal comment or `s` for a suggestion. + +To create a comment or suggestion for the current line, just duplicate the +keybinding: `cc` and `ss`. Alternatively, you can use any motion that moves on +that line only, e.g., `c$`. The same logic applies also when you change these +keybindings, e.g., to something like `c`. + +Delay in keybindings ~ + +You may experience a lag in some of the keybindings, because you have existing +keybinding starting with they same key(s). You can force the `gitlab.nvim` +mapping to use |nowait| and fire immediately (and lose your other mapping for +this buffer) by adding a special field called `nowait` to your keymap. For example, +if the name of the keybinding is `reviewer.create_comment`, then you add the +following to your config: +>lua + keymaps = { reviewer = { create_comment_nowait = true }} < TROUBLESHOOTING *gitlab.nvim.troubleshooting* @@ -668,7 +756,7 @@ in the setup call. require("gitlab").summary() The summary can be edited. Once you have made changes, send them to Gitlab via -the `settings.popup.perform_action` keybinding. +the `keymaps.popup.perform_action` keybinding. *gitlab.nvim.approve* gitlab.approve() ~ @@ -694,8 +782,8 @@ reviewer pane (see the gitlab.nvim.review command), otherwise it will error. >lua require("gitlab").create_comment() -After the comment is typed, submit it to Gitlab via the `settings.popup.perform_action` -keybinding, by default `l`. +After the comment is typed, submit it to Gitlab via the +`keymaps.popup.perform_action` keybinding, by default `ZZ`. *gitlab.nvim.create_multiline_comment* gitlab.create_multiline_comment() ~ @@ -705,8 +793,8 @@ mode, and will use the currently selected lines. >lua require("gitlab").create_multiline_comment() -After the comment is typed, submit it to Gitlab via the |settings.popup.perform_linewise_action| -keybinding, by default `l`. +After the comment is typed, submit it to Gitlab via the +`keymaps.popup.perform_linewise_action` keybinding, by default `ZA`. *gitlab.nvim.create_comment_suggestion* gitlab.create_comment_suggestion() ~ @@ -716,8 +804,8 @@ change suggestion to the currently selected lines). >lua require("gitlab").create_multiline_comment() -After the comment is typed, submit it to Gitlab via the |settings.popup.perform_linewise_action| -keybinding, by default |l| +After the comment is typed, submit it to Gitlab via the +`keymaps.popup.perform_linewise_action` keybinding, by default `ZA`. *gitlab.nvim.create_mr* gitlab.create_mr({opts}) ~ @@ -751,12 +839,12 @@ Starts the process of creating an MR for the currently checked out branch. After selecting all necessary details, you'll be presented with a confirmation window. You can cycle through the individual fields with the keymaps defined -in `settings.popup.keymaps.next_field` and `settings.popup.keymaps.prev_field`. -Both keymaps accept a count, i.g., 2 goes to the 2nd next field. -In the "Delete source branch", "Squash commits", and "Target branch" fields, -you can use the `settings.popup.perform_linewise_action` keymap to either -toggle the Boolean value or to select a new target branch, respectively. -Use the `settings.popup.perform_action` keymap to POST the MR to Gitlab. +in `keymaps.popup.next_field` and `keymaps.popup.prev_field`. Both keymaps +accept a count, i.g., 2 goes to the 2nd next field. In the "Delete source +branch", "Squash commits", and "Target branch" fields, you can use the +`keymaps.popup.perform_linewise_action` keymap to either toggle the Boolean +value or to select a new target branch, respectively. Use the +`keymaps.popup.perform_action` keymap to POST the MR to Gitlab. *gitlab.nvim.move_to_discussion_tree_from_diagnostic* gitlab.move_to_discussion_tree_from_diagnostic() ~ @@ -776,8 +864,8 @@ tied to specific changes in an MR. >lua require("gitlab").create_note() -After the comment is typed, submit it to Gitlab via the `settings.popup.perform_action` -keybinding, by default |s|. +After the comment is typed, submit it to Gitlab via the +`keymaps.popup.perform_action` keybinding, by default `ZZ`. *gitlab.nvim.toggle_discussions* gitlab.toggle_discussions() ~ @@ -786,15 +874,16 @@ Toggles visibility of the discussion tree. >lua require("gitlab").toggle_discussions() -Once the discussion tree is open, a number of different keybindings are available -for interacting with different discussions. Please see the `settings.discussion_tree` -section of the setup call for more information about different keybindings. +Once the discussion tree is open, a number of different keybindings are +available for interacting with different discussions. Please see the +`keymaps.discussion_tree` section of the setup call for more information about +different keybindings. *gitlab.nvim.publish_all_drafts* gitlab.publish_all_drafts() ~ -Publishes all unpublished draft notes. Used to finish a review and make all notes and -comments visible. +Publishes all unpublished draft notes. Used to finish a review and make all +notes and comments visible. >lua require("gitlab").publish_all_drafts() < @@ -853,8 +942,9 @@ Opens up a popup with information about the pipeline for the current merge reque >lua require("gitlab").pipeline() < -To re-trigger failed jobs in the pipeline manually, use the `settings.popup.perform_action` keybinding. -To open the log trace of a job in a new Neovim buffer, use your `settings.popup.perform_linewise_action` +To re-trigger failed jobs in the pipeline manually, use the +`keymaps.popup.perform_action` keybinding. To open the log trace of a job in a +new Neovim buffer, use your `keymaps.popup.perform_linewise_action` keybinding. *gitlab.nvim.open_in_browser* @@ -891,8 +981,8 @@ Gitlab online. You can see the current settings in the Summary view, see • {squash}: (bool) If true, the commits will be squashed. If you enable {squash} you will be prompted for a squash message. To use the default message, leave the popup empty. - Use the `settings.popup.perform_action` to merge the MR with - your message. + Use the `keymaps.popup.perform_action` to merge the MR + with your message. *gitlab.nvim.data* gitlab.data({resources}, {cb}) ~ diff --git a/lua/gitlab/actions/comment.lua b/lua/gitlab/actions/comment.lua index b4c9aa1..4723a36 100644 --- a/lua/gitlab/actions/comment.lua +++ b/lua/gitlab/actions/comment.lua @@ -22,7 +22,7 @@ local M = { } ---Fires the API that sends the comment data to the Go server, called when you "confirm" creation ----via the M.settings.popup.perform_action keybinding +---via the M.settings.keymaps.popup.perform_action keybinding ---@param text string comment text ---@param visual_range LineRange | nil range of visual selection or nil ---@param unlinked boolean if true, the comment is not linked to a line diff --git a/lua/gitlab/actions/common.lua b/lua/gitlab/actions/common.lua index 8ad4f6b..e409454 100644 --- a/lua/gitlab/actions/common.lua +++ b/lua/gitlab/actions/common.lua @@ -263,7 +263,7 @@ M.get_line_number_from_node = function(root_node) end end --- This function (settings.discussion_tree.jump_to_reviewer) will jump the cursor to the reviewer's location associated with the note. The implementation depends on the reviewer +-- This function (settings.keymaps.discussion_tree.jump_to_reviewer) will jump the cursor to the reviewer's location associated with the note. The implementation depends on the reviewer M.jump_to_reviewer = function(tree, callback) local node = tree:get_node() local root_node = M.get_root_node(tree, node) @@ -280,7 +280,7 @@ M.jump_to_reviewer = function(tree, callback) callback() end --- This function (settings.discussion_tree.jump_to_file) will jump to the file changed in a new tab +-- This function (settings.keymaps.discussion_tree.jump_to_file) will jump to the file changed in a new tab M.jump_to_file = function(tree) local node = tree:get_node() local root_node = M.get_root_node(tree, node) diff --git a/lua/gitlab/actions/discussions/init.lua b/lua/gitlab/actions/discussions/init.lua index 2fc59da..cc5421b 100644 --- a/lua/gitlab/actions/discussions/init.lua +++ b/lua/gitlab/actions/discussions/init.lua @@ -74,6 +74,7 @@ M.initialize_discussions = function() reviewer.set_callback_for_file_changed(function() M.refresh_diagnostics_and_winbar() M.modifiable(false) + reviewer.set_reviewer_keymaps() end) reviewer.set_callback_for_reviewer_enter(function() M.modifiable(false) @@ -82,6 +83,7 @@ M.initialize_discussions = function() signs.clear_signs() diagnostics.clear_diagnostics() M.modifiable(true) + reviewer.del_reviewer_keymaps() end) end @@ -219,7 +221,7 @@ M.move_to_discussion_tree = function() end end --- The reply popup will mount in a window when you trigger it (settings.discussion_tree.reply) when hovering over a node in the discussion tree. +-- The reply popup will mount in a window when you trigger it (settings.keymaps.discussion_tree.reply) when hovering over a node in the discussion tree. M.reply = function(tree) if M.is_draft_note(tree) then u.notify("Gitlab does not support replying to draft notes", vim.log.levels.WARN) @@ -241,7 +243,7 @@ M.reply = function(tree) layout:mount() end --- This function (settings.discussion_tree.delete_comment) will trigger a popup prompting you to delete the current comment +-- This function (settings.keymaps.discussion_tree.delete_comment) will trigger a popup prompting you to delete the current comment M.delete_comment = function(tree, unlinked) vim.ui.select({ "Confirm", "Cancel" }, { prompt = "Delete comment?", @@ -267,7 +269,7 @@ M.delete_comment = function(tree, unlinked) end) end --- This function (settings.discussion_tree.edit_comment) will open the edit popup for the current comment in the discussion tree +-- This function (settings.keymaps.discussion_tree.edit_comment) will open the edit popup for the current comment in the discussion tree M.edit_comment = function(tree, unlinked) local edit_popup = Popup(u.create_popup_state("Edit Comment", state.settings.popup.edit)) local current_node = tree:get_node() @@ -312,7 +314,7 @@ M.edit_comment = function(tree, unlinked) end end --- This function (settings.discussion_tree.toggle_discussion_resolved) will toggle the resolved status of the current discussion and send the change to the Go server +-- This function (settings.keymaps.discussion_tree.toggle_discussion_resolved) will toggle the resolved status of the current discussion and send the change to the Go server M.toggle_discussion_resolved = function(tree) local note = tree:get_node() if note == nil then @@ -527,101 +529,215 @@ M.is_current_node_note = function(tree) end M.set_tree_keymaps = function(tree, bufnr, unlinked) - ---Keybindings only relevant for linked (comment) view - if not unlinked then - vim.keymap.set("n", state.settings.discussion_tree.jump_to_file, function() - if M.is_current_node_note(tree) then - common.jump_to_file(tree) - end - end, { buffer = bufnr, desc = "Jump to file" }) - vim.keymap.set("n", state.settings.discussion_tree.jump_to_reviewer, function() - if M.is_current_node_note(tree) then - common.jump_to_reviewer(tree, M.refresh_diagnostics_and_winbar) - end - end, { buffer = bufnr, desc = "Jump to reviewer" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_tree_type, function() - M.toggle_tree_type() - end, { buffer = bufnr, desc = "Toggle tree type between `simple` and `by_file_name`" }) + -- Require keymaps only after user settings have been merged with defaults + local keymaps = require("gitlab.state").settings.keymaps + if keymaps.disable_all or keymaps.discussion_tree.disable_all then + return end - vim.keymap.set("n", state.settings.discussion_tree.refresh_data, function() - u.notify("Refreshing data...", vim.log.levels.INFO) - draft_notes.rebuild_view(unlinked, false) - end, { buffer = bufnr, desc = "Refreshes the view with Gitlab's APIs" }) + ---Keybindings only relevant for linked (comment) view + if not unlinked then + if keymaps.discussion_tree.jump_to_file then + vim.keymap.set("n", keymaps.discussion_tree.jump_to_file, function() + if M.is_current_node_note(tree) then + common.jump_to_file(tree) + end + end, { buffer = bufnr, desc = "Jump to file", nowait = keymaps.discussion_tree.jump_to_file_nowait }) + end - vim.keymap.set("n", state.settings.discussion_tree.edit_comment, function() - if M.is_current_node_note(tree) then - M.edit_comment(tree, unlinked) + if keymaps.discussion_tree.jump_to_reviewer then + vim.keymap.set("n", keymaps.discussion_tree.jump_to_reviewer, function() + if M.is_current_node_note(tree) then + common.jump_to_reviewer(tree, M.refresh_diagnostics_and_winbar) + end + end, { buffer = bufnr, desc = "Jump to reviewer", nowait = keymaps.discussion_tree.jump_to_reviewer_nowait }) end - end, { buffer = bufnr, desc = "Edit comment" }) - vim.keymap.set("n", state.settings.discussion_tree.publish_draft, function() - if M.is_draft_note(tree) then - draft_notes.publish_draft(tree) + + if keymaps.discussion_tree.toggle_tree_type then + vim.keymap.set("n", keymaps.discussion_tree.toggle_tree_type, function() + M.toggle_tree_type() + end, { + buffer = bufnr, + desc = "Change tree type between `simple` and `by_file_name`", + nowait = keymaps.discussion_tree.toggle_tree_type_nowait, + }) end - end, { buffer = bufnr, desc = "Publish draft" }) - vim.keymap.set("n", state.settings.discussion_tree.delete_comment, function() - if M.is_current_node_note(tree) then - M.delete_comment(tree, unlinked) - end - end, { buffer = bufnr, desc = "Delete comment" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_draft_mode, function() - M.toggle_draft_mode() - end, { buffer = bufnr, desc = "Toggle between draft mode and live mode" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_resolved, function() - if M.is_current_node_note(tree) and not M.is_draft_note(tree) then - M.toggle_discussion_resolved(tree) - end - end, { buffer = bufnr, desc = "Toggle resolved" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_node, function() - tree_utils.toggle_node(tree) - end, { buffer = bufnr, desc = "Toggle node" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_all_discussions, function() - tree_utils.toggle_nodes(M.split.winid, tree, unlinked, { - toggle_resolved = true, - toggle_unresolved = true, - keep_current_open = state.settings.discussion_tree.keep_current_open, + end + + if keymaps.discussion_tree.refresh_data then + vim.keymap.set("n", keymaps.discussion_tree.refresh_data, function() + u.notify("Refreshing data...", vim.log.levels.INFO) + draft_notes.rebuild_view(unlinked, false) + end, { + buffer = bufnr, + desc = "Refresh the view with Gitlab's APIs", + nowait = keymaps.discussion_tree.refresh_data_nowait, }) - end, { buffer = bufnr, desc = "Toggle all nodes" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_resolved_discussions, function() - tree_utils.toggle_nodes(M.split.winid, tree, unlinked, { - toggle_resolved = true, - toggle_unresolved = false, - keep_current_open = state.settings.discussion_tree.keep_current_open, + end + + if keymaps.discussion_tree.edit_comment then + vim.keymap.set("n", keymaps.discussion_tree.edit_comment, function() + if M.is_current_node_note(tree) then + M.edit_comment(tree, unlinked) + end + end, { buffer = bufnr, desc = "Edit comment", nowait = keymaps.discussion_tree.edit_comment_nowait }) + end + + if keymaps.discussion_tree.publish_draft then + vim.keymap.set("n", keymaps.discussion_tree.publish_draft, function() + if M.is_draft_note(tree) then + draft_notes.publish_draft(tree) + end + end, { buffer = bufnr, desc = "Publish draft", nowait = keymaps.discussion_tree.publish_draft_nowait }) + end + + if keymaps.discussion_tree.delete_comment then + vim.keymap.set("n", keymaps.discussion_tree.delete_comment, function() + if M.is_current_node_note(tree) then + M.delete_comment(tree, unlinked) + end + end, { buffer = bufnr, desc = "Delete comment", nowait = keymaps.discussion_tree.delete_comment_nowait }) + end + + if keymaps.discussion_tree.toggle_draft_mode then + vim.keymap.set("n", keymaps.discussion_tree.toggle_draft_mode, function() + M.toggle_draft_mode() + end, { + buffer = bufnr, + desc = "Toggle between draft mode and live mode", + nowait = keymaps.discussion_tree.toggle_draft_mode_nowait, }) - end, { buffer = bufnr, desc = "Toggle resolved nodes" }) - vim.keymap.set("n", state.settings.discussion_tree.toggle_unresolved_discussions, function() - tree_utils.toggle_nodes(M.split.winid, tree, unlinked, { - toggle_resolved = false, - toggle_unresolved = true, - keep_current_open = state.settings.discussion_tree.keep_current_open, + end + + if keymaps.discussion_tree.toggle_resolved then + vim.keymap.set("n", keymaps.discussion_tree.toggle_resolved, function() + if M.is_current_node_note(tree) and not M.is_draft_note(tree) then + M.toggle_discussion_resolved(tree) + end + end, { buffer = bufnr, desc = "Toggle resolved", nowait = keymaps.discussion_tree.toggle_resolved_nowait }) + end + + if keymaps.discussion_tree.toggle_node then + vim.keymap.set("n", keymaps.discussion_tree.toggle_node, function() + tree_utils.toggle_node(tree) + end, { buffer = bufnr, desc = "Toggle node", nowait = keymaps.discussion_tree.toggle_node_nowait }) + end + + if keymaps.discussion_tree.toggle_all_discussions then + vim.keymap.set("n", keymaps.discussion_tree.toggle_all_discussions, function() + tree_utils.toggle_nodes(M.split.winid, tree, unlinked, { + toggle_resolved = true, + toggle_unresolved = true, + keep_current_open = state.settings.discussion_tree.keep_current_open, + }) + end, { + buffer = bufnr, + desc = "Toggle all nodes", + nowait = keymaps.discussion_tree.toggle_all_discussions_nowait, }) - 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) - end - end, { buffer = bufnr, desc = "Reply" }) - vim.keymap.set("n", state.settings.discussion_tree.switch_view, function() - winbar.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" }) - vim.keymap.set("n", state.settings.discussion_tree.open_in_browser, function() - common.open_in_browser(tree) - end, { buffer = bufnr, desc = "Open the note in your browser" }) - vim.keymap.set("n", state.settings.discussion_tree.copy_node_url, function() - common.copy_node_url(tree) - end, { buffer = bufnr, desc = "Copy the URL of the current node to clipboard" }) - vim.keymap.set("n", "p", function() - common.print_node(tree) - end, { buffer = bufnr, desc = "Print current node (for debugging)" }) - vim.keymap.set("n", state.settings.discussion_tree.add_emoji, function() - M.add_emoji_to_note(tree, unlinked) - end, { buffer = bufnr, desc = "Add an emoji reaction to the note/comment" }) - vim.keymap.set("n", state.settings.discussion_tree.delete_emoji, function() - M.delete_emoji_from_note(tree, unlinked) - end, { buffer = bufnr, desc = "Remove an emoji reaction from the note/comment" }) + end + + if keymaps.discussion_tree.toggle_resolved_discussions then + vim.keymap.set("n", keymaps.discussion_tree.toggle_resolved_discussions, function() + tree_utils.toggle_nodes(M.split.winid, tree, unlinked, { + toggle_resolved = true, + toggle_unresolved = false, + keep_current_open = state.settings.discussion_tree.keep_current_open, + }) + end, { + buffer = bufnr, + desc = "Toggle resolved nodes", + nowait = keymaps.discussion_tree.toggle_resolved_discussions_nowait, + }) + end + + if keymaps.discussion_tree.toggle_unresolved_discussions then + vim.keymap.set("n", keymaps.discussion_tree.toggle_unresolved_discussions, function() + tree_utils.toggle_nodes(M.split.winid, tree, unlinked, { + toggle_resolved = false, + toggle_unresolved = true, + keep_current_open = state.settings.discussion_tree.keep_current_open, + }) + end, { + buffer = bufnr, + desc = "Toggle unresolved nodes", + nowait = keymaps.discussion_tree.toggle_unresolved_discussions_nowait, + }) + end + + if keymaps.discussion_tree.reply then + vim.keymap.set("n", keymaps.discussion_tree.reply, function() + if M.is_current_node_note(tree) then + M.reply(tree) + end + end, { buffer = bufnr, desc = "Reply", nowait = keymaps.discussion_tree.reply_nowait }) + end + + if keymaps.discussion_tree.switch_view then + vim.keymap.set("n", keymaps.discussion_tree.switch_view, function() + winbar.switch_view_type() + end, { + buffer = bufnr, + desc = "Change view type between discussions and notes", + nowait = keymaps.discussion_tree.switch_view_nowait, + }) + end + + if keymaps.help then + vim.keymap.set("n", keymaps.help, function() + help.open() + end, { buffer = bufnr, desc = "Open help popup", nowait = keymaps.help_nowait }) + end + + if keymaps.discussion_tree.open_in_browser then + vim.keymap.set("n", keymaps.discussion_tree.open_in_browser, function() + common.open_in_browser(tree) + end, { + buffer = bufnr, + desc = "Open the note in your browser", + nowait = keymaps.discussion_tree.open_in_browser_nowait, + }) + end + + if keymaps.discussion_tree.copy_node_url then + vim.keymap.set("n", keymaps.discussion_tree.copy_node_url, function() + common.copy_node_url(tree) + end, { + buffer = bufnr, + desc = "Copy the URL of the current node to clipboard", + nowait = keymaps.discussion_tree.copy_node_url_nowait, + }) + end + + if keymaps.discussion_tree.print_node then + vim.keymap.set("n", keymaps.discussion_tree.print_node, function() + common.print_node(tree) + end, { + buffer = bufnr, + desc = "Print current node (for debugging)", + nowait = keymaps.discussion_tree.print_node_nowait, + }) + end + + if keymaps.discussion_tree.add_emoji then + vim.keymap.set("n", keymaps.discussion_tree.add_emoji, function() + M.add_emoji_to_note(tree, unlinked) + end, { + buffer = bufnr, + desc = "Add an emoji reaction to the note/comment", + nowait = keymaps.discussion_tree.add_emoji_nowait, + }) + end + + if keymaps.discussion_tree.delete_emoji then + vim.keymap.set("n", keymaps.discussion_tree.delete_emoji, function() + M.delete_emoji_from_note(tree, unlinked) + end, { + buffer = bufnr, + desc = "Remove an emoji reaction from the note/comment", + nowait = keymaps.discussion_tree.delete_emoji_nowait, + }) + end emoji.init_popup(tree, bufnr) end diff --git a/lua/gitlab/actions/discussions/tree.lua b/lua/gitlab/actions/discussions/tree.lua index a8954e9..6810c35 100644 --- a/lua/gitlab/actions/discussions/tree.lua +++ b/lua/gitlab/actions/discussions/tree.lua @@ -325,16 +325,17 @@ M.nui_tree_prepare_node = function(node) for i, text in ipairs(texts) do local line = NuiLine() + local expanders = state.settings.discussion_tree.expanders - line:append(string.rep(" ", node._depth - 1)) + line:append(string.rep(expanders.indentation, node._depth - 1)) if i == 1 and node:has_children() then - line:append(node:is_expanded() and " " or " ") + line:append(node:is_expanded() and expanders.expanded or expanders.collapsed) if node.icon then line:append(node.icon .. " ", node.icon_hl) end else - line:append(" ") + line:append(expanders.indentation) end line:append(text, node.text_hl) @@ -368,7 +369,7 @@ end ---@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. +---This function expands/collapses all nodes and their children according to the opts. ---@param tree NuiTree ---@param winid integer ---@param unlinked boolean @@ -433,7 +434,7 @@ M.restore_cursor_position = function(winid, tree, original_node, root_node) end end ----This function (settings.discussion_tree.expand_recursively) expands a node and its children. +---This function 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. @@ -450,7 +451,7 @@ M.expand_recursively = function(tree, node, is_resolved) end end ----This function (settings.discussion_tree.collapse_recursively) collapses a node and its children. +---This function 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. @@ -483,7 +484,7 @@ M.open_node_by_id = function(tree, id) end end --- This function (settings.discussion_tree.toggle_node) expands/collapses the current node and its children +-- This function (settings.keymaps.discussion_tree.toggle_node) expands/collapses the current node and its children M.toggle_node = function(tree) local node = tree:get_node() if node == nil then diff --git a/lua/gitlab/actions/discussions/winbar.lua b/lua/gitlab/actions/discussions/winbar.lua index ee0abd5..09fb993 100644 --- a/lua/gitlab/actions/discussions/winbar.lua +++ b/lua/gitlab/actions/discussions/winbar.lua @@ -61,7 +61,7 @@ local function content() unlinked_draft_notes = #unlinked_draft_notes, resolvable_notes = resolvable_notes, resolved_notes = resolved_notes, - help_keymap = state.settings.help, + help_keymap = state.settings.keymaps.help, } return M.make_winbar(t) @@ -130,7 +130,7 @@ M.make_winbar = function(t) -- Join everything together and return it local separator = "%#Comment#|" local end_section = "%=" - local help = "%#Comment#Help: " .. t.help_keymap:gsub(" ", "") .. " " + local help = "%#Comment#Help: " .. (t.help_keymap and t.help_keymap:gsub(" ", "") .. " " or "unmapped") return string.format( " %s %s %s %s %s %s %s", discussion_title, diff --git a/lua/gitlab/actions/miscellaneous.lua b/lua/gitlab/actions/miscellaneous.lua index 5cf4273..df08066 100644 --- a/lua/gitlab/actions/miscellaneous.lua +++ b/lua/gitlab/actions/miscellaneous.lua @@ -78,14 +78,23 @@ end ---Setup keymaps for cycling popups. The keymap accepts count. ---@param popups table Table of Popups M.set_cycle_popups_keymaps = function(popups) + local keymaps = require("gitlab.state").settings.keymaps + if keymaps.disable_all or keymaps.popup.disable_all then + return + end + local number_of_popups = #popups for i, popup in ipairs(popups) do - popup:map("n", state.settings.popup.keymaps.next_field, function() - vim.api.nvim_set_current_win(popups[next_index(i, number_of_popups, vim.v.count)].winid) - end, { desc = "Go to next field (accepts count)" }) - popup:map("n", state.settings.popup.keymaps.prev_field, function() - vim.api.nvim_set_current_win(popups[prev_index(i, number_of_popups, vim.v.count)].winid) - end, { desc = "Go to previous field (accepts count)" }) + if keymaps.popup.next_field then + popup:map("n", keymaps.popup.next_field, function() + vim.api.nvim_set_current_win(popups[next_index(i, number_of_popups, vim.v.count)].winid) + end, { desc = "Go to next field (accepts count)", nowait = keymaps.popup.next_field_nowait }) + end + if keymaps.popup.prev_field then + popup:map("n", keymaps.popup.prev_field, function() + vim.api.nvim_set_current_win(popups[prev_index(i, number_of_popups, vim.v.count)].winid) + end, { desc = "Go to previous field (accepts count)", nowait = keymaps.popup.prev_field_nowait }) + end end end diff --git a/lua/gitlab/colors.lua b/lua/gitlab/colors.lua index 4b479bf..36b0b00 100644 --- a/lua/gitlab/colors.lua +++ b/lua/gitlab/colors.lua @@ -2,12 +2,20 @@ local state = require("gitlab.state") local u = require("gitlab.utils") local colors = state.settings.colors -local discussion = colors.discussion_tree +-- Set icons into global vim variables for syntax matching +local expanders = state.settings.discussion_tree.expanders +vim.g.gitlab_discussion_tree_expander_open = expanders.expanded +vim.g.gitlab_discussion_tree_expander_closed = expanders.collapsed +vim.g.gitlab_discussion_tree_draft = "" +vim.g.gitlab_discussion_tree_resolved = "✓" +vim.g.gitlab_discussion_tree_unresolved = "-" + +local discussion = colors.discussion_tree vim.api.nvim_set_hl(0, "GitlabUsername", u.get_colors_for_group(discussion.username)) vim.api.nvim_set_hl(0, "GitlabMention", u.get_colors_for_group(discussion.mention)) vim.api.nvim_set_hl(0, "GitlabDate", u.get_colors_for_group(discussion.date)) -vim.api.nvim_set_hl(0, "GitlabChevron", u.get_colors_for_group(discussion.chevron)) +vim.api.nvim_set_hl(0, "GitlabExpander", u.get_colors_for_group(discussion.expander)) 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, "GitlabFileName", u.get_colors_for_group(discussion.file_name)) diff --git a/lua/gitlab/init.lua b/lua/gitlab/init.lua index 8bba13a..81ff454 100644 --- a/lua/gitlab/init.lua +++ b/lua/gitlab/init.lua @@ -34,7 +34,8 @@ return { args = {} end server.build() -- Builds the Go binary if it doesn't exist - state.merge_settings(args) -- Sets keymaps and other settings from setup function + state.merge_settings(args) -- Merges user settings with default settings + state.set_global_keymaps() -- Sets keymaps that are not bound to a specific buffer require("gitlab.colors") -- Sets colors reviewer.init() discussions.initialize_discussions() -- place signs / diagnostics for discussions in reviewer diff --git a/lua/gitlab/reviewer/init.lua b/lua/gitlab/reviewer/init.lua index a3ba61f..1b32e9a 100644 --- a/lua/gitlab/reviewer/init.lua +++ b/lua/gitlab/reviewer/init.lua @@ -276,4 +276,144 @@ M.set_callback_for_reviewer_enter = function(callback) }) end +---Create the line-wise visual selection in the range of the motion and execute the gitlab.nvim API +---function. After that, restore the cursor position and the original operatorfunc. +---@param callback string Name of the gitlab.nvim API function to call +M.execute_callback = function(callback) + return function() + vim.api.nvim_cmd({ cmd = "normal", bang = true, args = { "'[V']" } }, {}) + vim.api.nvim_cmd( + { cmd = "lua", args = { ("require'gitlab'.%s()"):format(callback) }, mods = { lockmarks = true } }, + {} + ) + vim.api.nvim_win_set_cursor(M.old_winnr, M.old_cursor_position) + vim.opt.operatorfunc = M.old_opfunc + end +end + +---Set the operatorfunc that will work on the lines defined by the motion that follows after the +---operator mapping, and enter the operator-pending mode. +---@param cb string Name of the gitlab.nvim API function to call, e.g., "create_multiline_comment". +local function execute_operatorfunc(cb) + M.old_opfunc = vim.opt.operatorfunc + M.old_winnr = vim.api.nvim_get_current_win() + M.old_cursor_position = vim.api.nvim_win_get_cursor(M.old_winnr) + vim.opt.operatorfunc = ("v:lua.require'gitlab.reviewer'.execute_callback'%s'"):format(cb) + vim.api.nvim_feedkeys("g@", "n", false) +end + +---Set keymaps for creating comments, suggestions and for jumping to discussion tree. +---@param bufnr integer Number of the buffer for which the keybindings will be created. +---@param keymaps table The settings keymaps table. +local set_keymaps = function(bufnr, keymaps) + -- Set mappings for creating comments + if keymaps.reviewer.create_comment ~= false then + vim.keymap.set( + "o", + keymaps.reviewer.create_comment, + "$", + { buffer = bufnr, desc = "Create comment for current line", nowait = keymaps.reviewer.create_comment_nowait } + ) + vim.keymap.set( + "n", + keymaps.reviewer.create_comment, + function() + execute_operatorfunc("create_multiline_comment") + end, + { buffer = bufnr, desc = "Create comment for range of motion", nowait = keymaps.reviewer.create_comment_nowait } + ) + vim.keymap.set("v", keymaps.reviewer.create_comment, function() + require("gitlab").create_multiline_comment() + end, { + buffer = bufnr, + desc = "Create comment for selected text", + nowait = keymaps.reviewer.create_comment_nowait, + }) + end + + -- Set mappings for creating suggestions + if keymaps.reviewer.create_suggestion ~= false then + vim.keymap.set("o", keymaps.reviewer.create_suggestion, "$", { + buffer = bufnr, + desc = "Create suggestion for current line", + nowait = keymaps.reviewer.create_suggestion_nowait, + }) + vim.keymap.set("n", keymaps.reviewer.create_suggestion, function() + execute_operatorfunc("create_comment_suggestion") + end, { + buffer = bufnr, + desc = "Create suggestion for range of motion", + nowait = keymaps.reviewer.create_suggestion_nowait, + }) + vim.keymap.set("v", keymaps.reviewer.create_suggestion, function() + require("gitlab").create_comment_suggestion() + end, { + buffer = bufnr, + desc = "Create suggestion for selected text", + nowait = keymaps.reviewer.create_suggestion_nowait, + }) + end + + -- Set mapping for moving to discussion tree + if keymaps.reviewer.move_to_discussion_tree ~= false then + vim.keymap.set("n", keymaps.reviewer.move_to_discussion_tree, function() + require("gitlab").move_to_discussion_tree_from_diagnostic() + end, { buffer = bufnr, desc = "Move to discussion", nowait = keymaps.reviewer.move_to_discussion_tree_nowait }) + end +end + +--- Sets up keymaps for both buffers in the reviewer. +M.set_reviewer_keymaps = function() + -- Require keymaps only after user settings have been merged with defaults + local keymaps = require("gitlab.state").settings.keymaps + if keymaps.disable_all or keymaps.reviewer.disable_all then + return + end + + local view = diffview_lib.get_current_view() + local a = view.cur_layout.a.file.bufnr + local b = view.cur_layout.b.file.bufnr + if a ~= nil and vim.api.nvim_buf_is_loaded(a) then + set_keymaps(a, keymaps) + end + if b ~= nil and vim.api.nvim_buf_is_loaded(b) then + set_keymaps(b, keymaps) + end +end + +---Delete keymaps from reviewer buffers. +---@param bufnr integer Number of the buffer from which the keybindings will be removed. +---@param keymaps table The settings keymaps table. +local del_keymaps = function(bufnr, keymaps) + for _, func in ipairs({ "create_comment", "create_suggestion" }) do + if keymaps.reviewer[func] ~= false then + for _, mode in ipairs({ "n", "o", "v" }) do + pcall(vim.api.nvim_buf_del_keymap, bufnr, mode, keymaps.reviewer[func]) + end + end + end + if keymaps.reviewer.move_to_discussion_tree ~= false then + pcall(vim.api.nvim_buf_del_keymap, bufnr, "n", keymaps.reviewer.move_to_discussion_tree) + end +end + +--- Deletes keymaps from both buffers in the reviewer. +M.del_reviewer_keymaps = function() + -- Require keymaps only after user settings have been merged with defaults + local keymaps = require("gitlab.state").settings.keymaps + if keymaps.disable_all or keymaps.reviewer.disable_all then + return + end + + local view = diffview_lib.get_current_view() + local a = view.cur_layout.a.file.bufnr + local b = view.cur_layout.b.file.bufnr + if a ~= nil and vim.api.nvim_buf_is_loaded(a) then + del_keymaps(a, keymaps) + end + if b ~= nil and vim.api.nvim_buf_is_loaded(b) then + del_keymaps(b, keymaps) + end +end + return M diff --git a/lua/gitlab/state.lua b/lua/gitlab/state.lua index 964aaa0..ef2089b 100644 --- a/lua/gitlab/state.lua +++ b/lua/gitlab/state.lua @@ -60,14 +60,71 @@ M.settings = { insecure = true, }, attachment_dir = "", - help = "g?", - popup = { - keymaps = { + keymaps = { + disable_all = false, + help = "g?", + global = { + disable_all = false, + add_assignee = "glaa", + delete_assignee = "glad", + add_label = "glla", + delete_label = "glld", + add_reviewer = "glra", + delete_reviewer = "glrd", + approve = "glA", + revoke = "glR", + merge = "glM", + create_mr = "glC", + choose_merge_request = "glc", + start_review = "glS", + summary = "gls", + copy_mr_url = "glu", + open_in_browser = "glo", + create_note = "gln", + pipeline = "glp", + toggle_discussions = "gld", + toggle_draft_mode = "glD", + publish_all_drafts = "glP", + }, + popup = { + disable_all = false, next_field = "", prev_field = "", + perform_action = "ZZ", + perform_linewise_action = "ZA", + discard_changes = "ZQ", }, - perform_action = "s", - perform_linewise_action = "l", + discussion_tree = { + disable_all = false, + add_emoji = "Ea", + delete_emoji = "Ed", + delete_comment = "dd", + edit_comment = "e", + reply = "r", + toggle_resolved = "-", + jump_to_file = "o", + jump_to_reviewer = "a", + open_in_browser = "b", + copy_node_url = "u", + switch_view = "c", + toggle_tree_type = "i", + publish_draft = "P", + toggle_draft_mode = "D", + toggle_node = "t", + toggle_all_discussions = "T", + toggle_resolved_discussions = "R", + toggle_unresolved_discussions = "U", + refresh_data = "", + print_node = "p", + }, + reviewer = { + disable_all = false, + create_comment = "c", + create_suggestion = "s", + move_to_discussion_tree = "a", + }, + }, + popup = { width = "40%", height = "60%", border = "rounded", @@ -82,36 +139,22 @@ M.settings = { temp_registers = {}, }, discussion_tree = { + expanders = { + expanded = " ", + collapsed = " ", + indentation = " ", + }, auto_open = true, - switch_view = "S", default_view = "discussions", blacklist = {}, - jump_to_file = "o", - jump_to_reviewer = "m", - edit_comment = "e", - delete_comment = "dd", - refresh_data = "a", - reply = "r", - toggle_node = "t", - add_emoji = "Ea", - delete_emoji = "Ed", - toggle_all_discussions = "T", - toggle_resolved_discussions = "R", - toggle_unresolved_discussions = "U", keep_current_open = false, - publish_draft = "P", - toggle_resolved = "p", position = "left", - open_in_browser = "b", - copy_node_url = "u", size = "20%", relative = "editor", resolved = "✓", unresolved = "-", tree_type = "simple", - toggle_tree_type = "i", draft_mode = false, - toggle_draft_mode = "D", }, create_mr = { target = nil, @@ -181,7 +224,7 @@ M.settings = { username = "Keyword", mention = "WarningMsg", date = "Comment", - chevron = "DiffviewNonText", + expander = "DiffviewNonText", directory = "Directory", directory_icon = "DiffviewFolderSign", file_name = "Normal", @@ -202,6 +245,135 @@ M.unlinked_discussion_tree = { unresolved_expanded = false, } +-- These keymaps are set globally when the plugin is initialized +M.set_global_keymaps = function() + local keymaps = M.settings.keymaps + + if keymaps.disable_all or keymaps.global.disable_all then + return + end + + if keymaps.global.start_review then + vim.keymap.set("n", keymaps.global.start_review, function() + require("gitlab").review() + end, { desc = "Start Gitlab review", nowait = keymaps.global.start_review_nowait }) + end + + if keymaps.global.choose_merge_request then + vim.keymap.set("n", keymaps.global.choose_merge_request, function() + require("gitlab").choose_merge_request() + end, { desc = "Choose MR for review", nowait = keymaps.global.choose_merge_request_nowait }) + end + + if keymaps.global.summary then + vim.keymap.set("n", keymaps.global.summary, function() + require("gitlab").summary() + end, { desc = "Show MR summary", nowait = keymaps.global.summary_nowait }) + end + + if keymaps.global.approve then + vim.keymap.set("n", keymaps.global.approve, function() + require("gitlab").approve() + end, { desc = "Approve MR", nowait = keymaps.global.approve_nowait }) + end + + if keymaps.global.revoke then + vim.keymap.set("n", keymaps.global.revoke, function() + require("gitlab").revoke() + end, { desc = "Revoke approval", nowait = keymaps.global.revoke_nowait }) + end + + if keymaps.global.create_mr then + vim.keymap.set("n", keymaps.global.create_mr, function() + require("gitlab").create_mr() + end, { desc = "Create MR", nowait = keymaps.global.create_mr_nowait }) + end + + if keymaps.global.create_note then + vim.keymap.set("n", keymaps.global.create_note, function() + require("gitlab").create_note() + end, { desc = "Create MR note", nowait = keymaps.global.create_note_nowait }) + end + + if keymaps.global.toggle_discussions then + vim.keymap.set("n", keymaps.global.toggle_discussions, function() + require("gitlab").toggle_discussions() + end, { desc = "Toggle MR discussions", nowait = keymaps.global.toggle_discussions_nowait }) + end + + if keymaps.global.add_assignee then + vim.keymap.set("n", keymaps.global.add_assignee, function() + require("gitlab").add_assignee() + end, { desc = "Add MR assignee", nowait = keymaps.global.add_assignee_nowait }) + end + + if keymaps.global.delete_assignee then + vim.keymap.set("n", keymaps.global.delete_assignee, function() + require("gitlab").delete_assignee() + end, { desc = "Delete MR assignee", nowait = keymaps.global.delete_assignee_nowait }) + end + + if keymaps.global.add_label then + vim.keymap.set("n", keymaps.global.add_label, function() + require("gitlab").add_label() + end, { desc = "Add MR label", nowait = keymaps.global.add_label_nowait }) + end + + if keymaps.global.delete_label then + vim.keymap.set("n", keymaps.global.delete_label, function() + require("gitlab").delete_label() + end, { desc = "Delete MR label", nowait = keymaps.global.delete_label_nowait }) + end + + if keymaps.global.add_reviewer then + vim.keymap.set("n", keymaps.global.add_reviewer, function() + require("gitlab").add_reviewer() + end, { desc = "Add MR reviewer", nowait = keymaps.global.add_reviewer_nowait }) + end + + if keymaps.global.delete_reviewer then + vim.keymap.set("n", keymaps.global.delete_reviewer, function() + require("gitlab").delete_reviewer() + end, { desc = "Delete MR reviewer", nowait = keymaps.global.delete_reviewer_nowait }) + end + + if keymaps.global.pipeline then + vim.keymap.set("n", keymaps.global.pipeline, function() + require("gitlab").pipeline() + end, { desc = "Show MR pipeline status", nowait = keymaps.global.pipeline_nowait }) + end + + if keymaps.global.open_in_browser then + vim.keymap.set("n", keymaps.global.open_in_browser, function() + require("gitlab").open_in_browser() + end, { desc = "Open MR in browser", nowait = keymaps.global.open_in_browser_nowait }) + end + + if keymaps.global.merge then + vim.keymap.set("n", keymaps.global.merge, function() + require("gitlab").merge() + end, { desc = "Merge MR", nowait = keymaps.global.merge_nowait }) + end + + if keymaps.global.copy_mr_url then + vim.keymap.set("n", keymaps.global.copy_mr_url, function() + require("gitlab").copy_mr_url() + end, { desc = "Copy MR url", nowait = keymaps.global.copy_mr_url_nowait }) + end + + if keymaps.global.publish_all_drafts then + vim.keymap.set("n", keymaps.global.publish_all_drafts, function() + require("gitlab").publish_all_drafts() + end, { desc = "Publish all MR comment drafts", nowait = keymaps.global.publish_all_drafts_nowait }) + end + + if keymaps.global.toggle_draft_mode then + vim.keymap.set("n", keymaps.global.toggle_draft_mode, function() + require("gitlab").toggle_draft_mode() + end, { desc = "Toggle MR comment draft mode", nowait = keymaps.global.toggle_draft_mode_nowait }) + end +end + -- Merges user settings into the default settings, overriding them M.merge_settings = function(args) M.settings = u.merge(M.settings, args) @@ -225,9 +397,43 @@ M.merge_settings = function(args) return false end - if M.settings.review_pane ~= nil then + local removed_fields_in_user_config = {} + local removed_settings_fields = { + "discussion_tree.add_emoji", + "discussion_tree.copy_node_url", + "discussion_tree.delete_comment", + "discussion_tree.delete_emoji", + "discussion_tree.edit_comment", + "discussion_tree.jump_to_file", + "discussion_tree.jump_to_reviewer", + "discussion_tree.open_in_browser", + "discussion_tree.publish_draft", + "discussion_tree.refresh_data", + "discussion_tree.reply", + "discussion_tree.switch_view", + "discussion_tree.toggle_all_discussions", + "discussion_tree.toggle_draft_mode", + "discussion_tree.toggle_node", + "discussion_tree.toggle_resolved", + "discussion_tree.toggle_resolved_discussions", + "discussion_tree.toggle_tree_type", + "discussion_tree.toggle_unresolved_discussions", + "help", + "popup.keymaps.next_field", + "popup.keymaps.prev_field", + "popup.perform_action", + "popup.perform_linewise_action", + "review_pane", -- Only relevant for the Delta reviewer + } + for _, field in ipairs(removed_settings_fields) do + if u.get_nested_field(M.settings, field) ~= nil then + table.insert(removed_fields_in_user_config, field) + end + end + + if #removed_fields_in_user_config ~= 0 then u.notify( - "The review_pane field is only relevant for Delta, which has been deprecated, please remove it from your setup function", + "The following settings fields have been removed:\n" .. table.concat(removed_fields_in_user_config, "\n"), vim.log.levels.WARN ) end @@ -284,17 +490,21 @@ end -- These keymaps are buffer specific and are set dynamically when popups mount M.set_popup_keymaps = function(popup, action, linewise_action, opts) + if M.settings.keymaps.disable_all or M.settings.keymaps.popup.disable_all then + return + end + if opts == nil then opts = {} end - if action ~= "Help" then -- Don't show help on the help popup - vim.keymap.set("n", M.settings.help, function() + if action ~= "Help" and M.settings.keymaps.help then -- Don't show help on the help popup + vim.keymap.set("n", M.settings.keymaps.help, function() local help = require("gitlab.actions.help") help.open() - end, { buffer = popup.bufnr, desc = "Open help" }) + end, { buffer = popup.bufnr, desc = "Open help", nowait = M.settings.keymaps.help_nowait }) end - if action ~= nil then - vim.keymap.set("n", M.settings.popup.perform_action, function() + if action ~= nil and M.settings.keymaps.popup.perform_action then + vim.keymap.set("n", M.settings.keymaps.popup.perform_action, function() local text = u.get_buffer_text(popup.bufnr) if opts.action_before_close then action(text, popup.bufnr) @@ -303,16 +513,33 @@ M.set_popup_keymaps = function(popup, action, linewise_action, opts) exit(popup, opts) action(text, popup.bufnr) end - end, { buffer = popup.bufnr, desc = "Perform action" }) + end, { buffer = popup.bufnr, desc = "Perform action", nowait = M.settings.keymaps.popup.perform_action_nowait }) end - if linewise_action ~= nil then - vim.keymap.set("n", M.settings.popup.perform_linewise_action, function() + if linewise_action ~= nil and M.settings.keymaps.popup.perform_action then + vim.keymap.set("n", M.settings.keymaps.popup.perform_linewise_action, function() local bufnr = vim.api.nvim_get_current_buf() local linnr = vim.api.nvim_win_get_cursor(0)[1] local text = u.get_line_content(bufnr, linnr) linewise_action(text) - end, { buffer = popup.bufnr, desc = "Perform linewise action" }) + end, { + buffer = popup.bufnr, + desc = "Perform linewise action", + nowait = M.settings.keymaps.popup.perform_linewise_action_nowait, + }) + end + + if M.settings.keymaps.popup.discard_changes then + vim.keymap.set("n", M.settings.keymaps.popup.discard_changes, function() + local temp_registers = M.settings.popup.temp_registers + M.settings.popup.temp_registers = {} + vim.cmd("quit!") + M.settings.popup.temp_registers = temp_registers + end, { + buffer = popup.bufnr, + desc = "Quit discarding changes", + nowait = M.settings.keymaps.popup.discard_changes_nowait, + }) end if opts.save_to_temp_register then diff --git a/lua/gitlab/utils/init.lua b/lua/gitlab/utils/init.lua index 58f57b9..3018fd4 100644 --- a/lua/gitlab/utils/init.lua +++ b/lua/gitlab/utils/init.lua @@ -739,4 +739,17 @@ M.ensure_table = function(data) return data end +M.get_nested_field = function(table, field) + local subfield = string.match(field, "[^.]+") + local subtable = table[subfield] + if subtable ~= nil then + local new_field = string.gsub(field, "^" .. subfield .. ".?", "") + if new_field ~= "" then + return M.get_nested_field(subtable, new_field) + else + return subtable + end + end +end + return M