diff --git a/README.md b/README.md index 1a4be079..abd907bf 100644 --- a/README.md +++ b/README.md @@ -113,157 +113,9 @@ For more settings, please see `:h gitlab.nvim.connecting-to-gitlab` ## Configuring the Plugin -Here is the default setup function. All of these values are optional, and if you call this function with no values the defaults will be used: +The plugin expects you to call `setup()` and pass in a table of options. All of these values are optional, and if you call this function with no values the defaults will be used. -```lua -require("gitlab").setup({ - port = nil, -- The port of the Go server, which runs in the background, if omitted or `nil` the port will be chosen automatically - log_path = vim.fn.stdpath("cache") .. "/gitlab.nvim.log", -- Log path for the Go server - config_path = nil, -- Custom path for `.gitlab.nvim` file, please read the "Connecting to Gitlab" section - debug = { - go_request = false, - go_response = false, - }, - 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| - }, - }, - connection_settings = { - insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection - }, - 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 - keymaps = { - next_field = "", -- Cycle to the next field. Accepts count. - prev_field = "", -- Cycle to the previous field. Accepts count. - }, - 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) - width = "40%", - height = "60%", - border = "rounded", -- One of "rounded", "single", "double", "solid" - opacity = 1.0, -- From 0.0 (fully transparent) to 1.0 (fully opaque) - comment = nil, -- Individual popup overrides, e.g. { width = "60%", height = "80%", border = "single", opacity = 0.85 }, - edit = nil, - note = nil, - pipeline = nil, - reply = nil, - squash_message = nil, - 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 - 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 - add_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. - }, - choose_merge_request = { - open_reviewer = true, -- Open the reviewer window automatically after switching merge requests - }, - info = { -- Show additional fields in the summary view - 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", - "pipeline", - "branch", - "target_branch", - "delete_branch", - "squash", - "labels", - }, - }, - discussion_signs = { - enabled = true, -- Show diagnostics for gitlab comments in the reviewer - skip_resolved_discussion = false, -- Show diagnostics for resolved discussions - severity = vim.diagnostic.severity.INFO, -- ERROR, WARN, INFO, or HINT - virtual_text = false, -- Whether to show the comment text inline as floating virtual text - priority = 100, -- Higher will override LSP warnings, etc - icons = { - comment = "→|", - range = " |", - }, - }, - pipeline = { - created = "", - pending = "", - preparing = "", - scheduled = "", - running = "", - canceled = "↪", - skipped = "↪", - success = "✓", - failed = "", - }, - create_mr = { - target = nil, -- Default branch to target when creating an MR - template_file = nil, -- Default MR template in .gitlab/merge_request_templates - delete_branch = false, -- Whether the source branch will be marked for deletion - squash = false, -- Whether the commits will be marked for squashing - title_input = { -- Default settings for MR title input window - width = 40, - border = "rounded", - }, - }, - colors = { - discussion_tree = { - username = "Keyword", - date = "Comment", - chevron = "DiffviewNonText", - directory = "Directory", - directory_icon = "DiffviewFolderSign", - file_name = "Normal", - } - } -}) -``` - -## Usage - -First, check out the branch that you want to review locally. - -``` -git checkout feature-branch -``` - -Then open Neovim. To begin, try running the `summary` command or the `review` command. +For a list of all these settings please run `:h gitlab.nvim` which is stored in `doc/gitlab.nvim.txt` ## Keybindings diff --git a/cmd/create_mr.go b/cmd/create_mr.go index edce8ce4..05e88bd2 100644 --- a/cmd/create_mr.go +++ b/cmd/create_mr.go @@ -11,11 +11,12 @@ import ( ) type CreateMrRequest struct { - Title string `json:"title"` - Description string `json:"description"` - TargetBranch string `json:"target_branch"` - DeleteBranch bool `json:"delete_branch"` - Squash bool `json:"squash"` + Title string `json:"title"` + Description string `json:"description"` + TargetBranch string `json:"target_branch"` + DeleteBranch bool `json:"delete_branch"` + Squash bool `json:"squash"` + TargetProjectID int `json:"forked_project_id,omitempty"` } /* createMr creates a merge request */ @@ -59,6 +60,10 @@ func (a *api) createMr(w http.ResponseWriter, r *http.Request) { Squash: &createMrRequest.Squash, } + if createMrRequest.TargetProjectID != 0 { + opts.TargetProjectID = gitlab.Ptr(createMrRequest.TargetProjectID) + } + _, res, err := a.client.CreateMergeRequest(a.projectInfo.ProjectId, &opts) if err != nil { diff --git a/doc/gitlab.nvim.txt b/doc/gitlab.nvim.txt index 140b94ff..e99e2234 100644 --- a/doc/gitlab.nvim.txt +++ b/doc/gitlab.nvim.txt @@ -160,6 +160,9 @@ you call this function with no values the defaults will be used: imply_local = false, -- If true, will attempt to use --imply_local option when calling |:DiffviewOpen| }, }, + connection_settings = { + insecure = false, -- Like curl's --insecure option, ignore bad x509 certificates on connection + }, 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 keymaps = { @@ -192,12 +195,14 @@ you call this function with no values the defaults will be used: 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 + add_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 - toggle_resolved = "p" -- Toggles the resolved status of the whole discussion 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 @@ -208,7 +213,7 @@ you call this function with no values the defaults will be used: 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 and regular mode, where comments are posted immediately + 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. }, @@ -218,7 +223,7 @@ you call this function with no values the defaults will be used: info = { -- Show additional fields in the summary view 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 + fields = { -- The fields listed here will be displayed, in whatever order you choose "author", "created_at", "updated_at", @@ -240,6 +245,7 @@ you call this function with no values the defaults will be used: skip_resolved_discussion = false, -- Show diagnostics for resolved discussions severity = vim.diagnostic.severity.INFO, -- ERROR, WARN, INFO, or HINT virtual_text = false, -- Whether to show the comment text inline as floating virtual text + use_diagnostic_signs = true, -- Show diagnostic sign (depending on the `severity` setting, e.g., I for INFO) along with the comment icon priority = 100, -- Higher will override LSP warnings, etc icons = { comment = "→|", @@ -262,6 +268,10 @@ you call this function with no values the defaults will be used: template_file = nil, -- Default MR template in .gitlab/merge_request_templates delete_branch = false, -- Whether the source branch will be marked for deletion squash = false, -- Whether the commits will be marked for squashing + fork = { + enabled = false, -- If making an MR from a fork + forked_project_id = nil -- The ID of the project you are merging into. If nil, will be prompted. + }, title_input = { -- Default settings for MR title input window width = 40, border = "rounded", @@ -275,7 +285,7 @@ you call this function with no values the defaults will be used: directory = "Directory", directory_icon = "DiffviewFolderSign", file_name = "Normal", - } + } } }) < @@ -413,6 +423,7 @@ have been added to a review. These are the default settings: skip_resolved_discussion = false, -- Show diagnostics for resolved discussions severity = vim.diagnostic.severity.INFO, -- ERROR, WARN, INFO, or HINT virtual_text = false, -- Whether to show the comment text inline as floating virtual text + use_diagnostic_signs = true, -- Show diagnostic sign (depending on the `severity` setting, e.g., I for INFO) along with the comment icon priority = 100, -- Higher will override LSP warnings, etc icons = { comment = "→|", @@ -420,14 +431,19 @@ have been added to a review. These are the default settings: }, }, -When the cursor is on diagnostic line you can view discussion thread by using `vim.diagnostic.show()` +When the cursor is on a diagnostic line you can view the discussion thread by +using `vim.diagnostic.show()`. -You can also jump to discussion tree for the given comment: +You can also jump to the discussion tree for the given comment: >lua require("gitlab").move_to_discussion_tree_from_diagnostic() +Since nvim 0.10 you can use these two function anywhere in the diagnostic +range. In previous versions, you have to move the cursor to the first line of +the diagnostic. + You may skip resolved discussions by toggling `discussion_signs.skip_resolved_discussion` -in your setup function to true. By default, discussions from this plugin +in your setup function to `true`. By default, discussions from this plugin are shown at the INFO severity level (see :h vim.diagnostic.severity). diff --git a/lua/gitlab/actions/create_mr.lua b/lua/gitlab/actions/create_mr.lua index 5f214b15..efe94a75 100644 --- a/lua/gitlab/actions/create_mr.lua +++ b/lua/gitlab/actions/create_mr.lua @@ -14,6 +14,7 @@ local miscellaneous = require("gitlab.actions.miscellaneous") ---@field target? string ---@field title? string ---@field description? string +---@field forked_project_id number? ---@field template_file? string ---@field delete_branch boolean? ---@field squash boolean? @@ -29,6 +30,8 @@ local M = { target = "", title = "", description = "", + forked_project_id = state.settings.create_mr.fork.enabled and state.settings.create_mr.fork.forked_project_id + or nil, }, } @@ -37,6 +40,7 @@ M.reset_state = function() M.mr.title = "" M.mr.target = "" M.mr.description = "" + M.mr.forked_project_id = nil end ---1. If the user has already begun writing an MR, prompt them to @@ -157,8 +161,12 @@ M.add_title = function(mr) prompt = "", default_value = "", on_close = function() end, - on_submit = function(_value) - M.open_confirmation_popup(mr) + on_submit = function() + if state.settings.create_mr.fork.enabled and state.settings.create_mr.fork.forked_project_id == nil then + M.open_fork_popup(mr) + else + M.open_confirmation_popup(mr) + end end, on_change = function(value) mr.title = value @@ -167,6 +175,33 @@ M.add_title = function(mr) input:mount() end +---Sets the ID of the base project when working from a fork +---@param mr Mr +M.open_fork_popup = function(mr) + local input = Input({ + position = "50%", + relative = "editor", + size = state.settings.create_mr.title_input.width, + border = { + style = state.settings.create_mr.title_input.border, + text = { + top = "Forked Project ID", + }, + }, + }, { + prompt = "", + default_value = "", + on_close = function() end, + on_submit = function() + M.open_confirmation_popup(mr) + end, + on_change = function(value) + mr.forked_project_id = tonumber(value) + end, + }) + input:mount() +end + ---5. Show the final popup. ---The function will render a popup containing the MR title and MR description, ---target branch, and the "delete_branch" and "squash" options. All fields are editable. @@ -179,11 +214,13 @@ M.open_confirmation_popup = function(mr) return end - local layout, title_popup, description_popup, target_popup, delete_branch_popup, squash_popup = M.create_layout() + local layout, title_popup, description_popup, target_popup, delete_branch_popup, squash_popup, forked_project_id_popup = + M.create_layout() local popups = { title_popup, description_popup, + forked_project_id_popup, delete_branch_popup, squash_popup, target_popup, @@ -199,12 +236,14 @@ M.open_confirmation_popup = function(mr) local target = vim.fn.trim(u.get_buffer_text(M.target_bufnr)) local delete_branch = u.string_to_bool(u.get_buffer_text(M.delete_branch_bufnr)) local squash = u.string_to_bool(u.get_buffer_text(M.squash_bufnr)) + local forked_project_id = tonumber(u.get_buffer_text(M.forked_project_id_bufnr)) M.mr = { title = title, description = description, target = target, delete_branch = delete_branch, squash = squash, + forked_project_id = forked_project_id, } layout:unmount() M.layout_visible = false @@ -220,6 +259,10 @@ M.open_confirmation_popup = function(mr) vim.api.nvim_buf_set_lines(M.target_bufnr, 0, -1, false, { mr.target }) vim.api.nvim_buf_set_lines(M.delete_branch_bufnr, 0, -1, false, { u.bool_to_string(delete_branch) }) vim.api.nvim_buf_set_lines(M.squash_bufnr, 0, -1, false, { u.bool_to_string(squash) }) + if state.settings.create_mr.fork.enabled then + local forked_id = state.settings.create_mr.fork.forked_project_id or mr.forked_project_id + vim.api.nvim_buf_set_lines(M.forked_project_id_bufnr, 0, -1, false, { tostring(forked_id) }) + end u.switch_can_edit_buf(M.delete_branch_bufnr, false) u.switch_can_edit_buf(M.squash_bufnr, false) @@ -236,6 +279,7 @@ M.open_confirmation_popup = function(mr) state.set_popup_keymaps(target_popup, M.create_mr, M.select_new_target, popup_opts) state.set_popup_keymaps(delete_branch_popup, M.create_mr, miscellaneous.toggle_bool, popup_opts) state.set_popup_keymaps(squash_popup, M.create_mr, miscellaneous.toggle_bool, popup_opts) + state.set_popup_keymaps(forked_project_id_popup, M.create_mr, nil, popup_opts) miscellaneous.set_cycle_popups_keymaps(popups) vim.api.nvim_set_current_buf(M.description_bufnr) @@ -261,6 +305,7 @@ M.create_mr = function() local target = u.get_buffer_text(M.target_bufnr):gsub("\n", " ") local delete_branch = u.string_to_bool(u.get_buffer_text(M.delete_branch_bufnr)) local squash = u.string_to_bool(u.get_buffer_text(M.squash_bufnr)) + local forked_project_id = tonumber(u.get_buffer_text(M.forked_project_id_bufnr)) local body = { title = title, @@ -268,8 +313,11 @@ M.create_mr = function() target_branch = target, delete_branch = delete_branch, squash = squash, + forked_project_id = forked_project_id, } + vim.print(body) + job.run_job("/create_mr", "POST", body, function(data) u.notify(data.message, vim.log.levels.INFO) M.reset_state() @@ -291,18 +339,23 @@ M.create_layout = function() local squash_title = vim.o.columns > 110 and "Squash commits" or "Squash" local squash_popup = Popup(u.create_box_popup_state(squash_title, false)) M.squash_bufnr = squash_popup.bufnr + local forked_project_id_popup = Popup(u.create_box_popup_state("Forked Project ID", false)) + M.forked_project_id_bufnr = forked_project_id_popup.bufnr - local internal_layout - internal_layout = Layout.Box({ + local boxes = {} + if state.settings.create_mr.fork.enabled then + table.insert(boxes, Layout.Box(forked_project_id_popup, { size = { width = 20 } })) + end + table.insert(boxes, Layout.Box(delete_branch_popup, { size = { width = #delete_title + 4 } })) + table.insert(boxes, Layout.Box(squash_popup, { size = { width = #squash_title + 4 } })) + table.insert(boxes, Layout.Box(target_branch_popup, { grow = 1 })) + + local internal_layout = Layout.Box({ Layout.Box({ Layout.Box(title_popup, { grow = 1 }), }, { size = 3 }), Layout.Box(description_popup, { grow = 1 }), - Layout.Box({ - Layout.Box(delete_branch_popup, { size = { width = #delete_title + 4 } }), - Layout.Box(squash_popup, { size = { width = #squash_title + 4 } }), - Layout.Box(target_branch_popup, { grow = 1 }), - }, { size = 3 }), + Layout.Box(boxes, { size = 3 }), }, { dir = "col" }) local layout = Layout({ @@ -316,7 +369,13 @@ M.create_layout = function() layout:mount() - return layout, title_popup, description_popup, target_branch_popup, delete_branch_popup, squash_popup + return layout, + title_popup, + description_popup, + target_branch_popup, + delete_branch_popup, + squash_popup, + forked_project_id_popup end return M diff --git a/lua/gitlab/indicators/diagnostics.lua b/lua/gitlab/indicators/diagnostics.lua index 08741002..42454ac6 100644 --- a/lua/gitlab/indicators/diagnostics.lua +++ b/lua/gitlab/indicators/diagnostics.lua @@ -15,11 +15,14 @@ M.clear_diagnostics = function() end -- Display options for the diagnostic -local display_opts = { - virtual_text = state.settings.discussion_signs.virtual_text, - severity_sort = true, - underline = false, -} +local create_display_opts = function() + return { + virtual_text = state.settings.discussion_signs.virtual_text, + severity_sort = true, + underline = false, + signs = state.settings.discussion_signs.use_diagnostic_signs, + } +end ---Takes some range information and data about a discussion ---and creates a diagnostic to be placed in the reviewer @@ -121,10 +124,10 @@ M.refresh_diagnostics = function() end local new_diagnostics = M.parse_new_diagnostics(filtered_discussions) - set_diagnostics_in_new_sha(diagnostics_namespace, new_diagnostics, display_opts) + set_diagnostics_in_new_sha(diagnostics_namespace, new_diagnostics, create_display_opts()) local old_diagnostics = M.parse_old_diagnostics(filtered_discussions) - set_diagnostics_in_old_sha(diagnostics_namespace, old_diagnostics, display_opts) + set_diagnostics_in_old_sha(diagnostics_namespace, old_diagnostics, create_display_opts()) end) if not ok then diff --git a/lua/gitlab/indicators/signs.lua b/lua/gitlab/indicators/signs.lua index 2b3ac19e..abf56b97 100644 --- a/lua/gitlab/indicators/signs.lua +++ b/lua/gitlab/indicators/signs.lua @@ -2,7 +2,6 @@ local u = require("gitlab.utils") local state = require("gitlab.state") local List = require("gitlab.utils.list") local discussion_sign_name = require("gitlab.indicators.diagnostics").discussion_sign_name -local namespace = require("gitlab.indicators.diagnostics").diagnostics_namespace local M = {} M.clear_signs = function() @@ -32,9 +31,8 @@ M.set_signs = function(diagnostics, bufnr) for _, diagnostic in ipairs(diagnostics) do ---@type SignTable[] local existing_signs = - vim.fn.sign_getplaced(vim.api.nvim_get_current_buf(), { group = "gitlab_discussion" })[1].signs + vim.fn.sign_getplaced(vim.api.nvim_get_current_buf(), { group = discussion_sign_name })[1].signs - local sign_id = string.format("%s__%d", namespace, diagnostic.lnum) if diagnostic.end_lnum then local linenr = diagnostic.lnum + 1 while linenr <= diagnostic.end_lnum do @@ -44,7 +42,7 @@ M.set_signs = function(diagnostics, bufnr) end) if conflicting_comment_sign == nil then vim.fn.sign_place( - sign_id, + linenr, discussion_sign_name, "DiagnosticSign" .. M.severity .. gitlab_range, bufnr, @@ -55,7 +53,7 @@ M.set_signs = function(diagnostics, bufnr) end vim.fn.sign_place( - sign_id, + diagnostic.lnum + 1, discussion_sign_name, "DiagnosticSign" .. M.severity .. gitlab_comment, bufnr, diff --git a/lua/gitlab/state.lua b/lua/gitlab/state.lua index 4bf2abee..2de9e8b7 100644 --- a/lua/gitlab/state.lua +++ b/lua/gitlab/state.lua @@ -116,6 +116,10 @@ M.settings = { template_file = nil, delete_branch = false, squash = false, + fork = { + enabled = false, + forked_project_id = nil, + }, title_input = { width = 40, border = "rounded", @@ -149,6 +153,7 @@ M.settings = { skip_resolved_discussion = false, severity = vim.diagnostic.severity.INFO, virtual_text = false, + use_diagnostic_signs = true, icons = { comment = "→|", range = " |",