Feat: Create Merge Request (#149)
- Adds the ability to create MRs to the plugin - Adds the ability to jump to specific discussions/notes in the browser - Fixes stale icons - Adds debug keybinding for discussion tree for developers
This commit is contained in:
committed by
GitHub
parent
35f0bc16a5
commit
37a53842d0
@@ -3,11 +3,11 @@ local job = require("gitlab.job")
|
||||
local M = {}
|
||||
|
||||
M.approve = function()
|
||||
job.run_job("/approve", "POST")
|
||||
job.run_job("/mr/approve", "POST")
|
||||
end
|
||||
|
||||
M.revoke = function()
|
||||
job.run_job("/revoke", "POST")
|
||||
job.run_job("/mr/revoke", "POST")
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -126,7 +126,7 @@ M.confirm_create_comment = function(text, range, unlinked)
|
||||
|
||||
if unlinked then
|
||||
local body = { comment = text }
|
||||
job.run_job("/comment", "POST", body, function(data)
|
||||
job.run_job("/mr/comment", "POST", body, function(data)
|
||||
u.notify("Note created!", vim.log.levels.INFO)
|
||||
discussions.add_discussion({ data = data, unlinked = true })
|
||||
discussions.refresh_discussion_data()
|
||||
@@ -152,7 +152,7 @@ M.confirm_create_comment = function(text, range, unlinked)
|
||||
line_range = reviewer_info.range_info,
|
||||
}
|
||||
|
||||
job.run_job("/comment", "POST", body, function(data)
|
||||
job.run_job("/mr/comment", "POST", body, function(data)
|
||||
u.notify("Comment created!", vim.log.levels.INFO)
|
||||
discussions.add_discussion({ data = data, unlinked = false })
|
||||
discussions.refresh_discussion_data()
|
||||
|
||||
321
lua/gitlab/actions/create_mr.lua
Normal file
321
lua/gitlab/actions/create_mr.lua
Normal file
@@ -0,0 +1,321 @@
|
||||
-- This module is responsible for creating am MR
|
||||
-- for the current branch
|
||||
local Layout = require("nui.layout")
|
||||
local Input = require("nui.input")
|
||||
local Popup = require("nui.popup")
|
||||
local job = require("gitlab.job")
|
||||
local u = require("gitlab.utils")
|
||||
local state = require("gitlab.state")
|
||||
local miscellaneous = require("gitlab.actions.miscellaneous")
|
||||
|
||||
---@class Mr
|
||||
---@field target? string
|
||||
---@field title? string
|
||||
---@field description? string
|
||||
|
||||
---@class Args
|
||||
---@field target? string
|
||||
---@field template_file? string
|
||||
|
||||
local M = {
|
||||
started = false,
|
||||
layout_visible = false,
|
||||
layout = nil,
|
||||
layout_buf = nil,
|
||||
title_bufnr = nil,
|
||||
description_bufnr = nil,
|
||||
mr = {
|
||||
target = "",
|
||||
title = "",
|
||||
description = "",
|
||||
},
|
||||
}
|
||||
|
||||
M.reset_state = function()
|
||||
M.started = false
|
||||
M.mr.title = ""
|
||||
M.mr.target = ""
|
||||
M.mr.description = ""
|
||||
end
|
||||
|
||||
local title_popup_settings = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Title",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local target_popup_settings = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Target branch",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local description_popup_settings = {
|
||||
buf_options = {
|
||||
filetype = "markdown",
|
||||
},
|
||||
enter = true,
|
||||
focusable = true,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Description",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local title_input_options = {
|
||||
position = "50%",
|
||||
relative = "editor",
|
||||
size = 40,
|
||||
border = {
|
||||
style = "rounded",
|
||||
text = {
|
||||
top = "Title",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
---1. If the user has already begun writing an MR, prompt them to
|
||||
--- continue working on it.
|
||||
---@param args? Args
|
||||
M.start = function(args)
|
||||
if M.started then
|
||||
vim.ui.select({ "Yes", "No" }, { prompt = "Continue your previous MR?" }, function(choice)
|
||||
if choice == "Yes" then
|
||||
M.open_confirmation_popup(M.mr)
|
||||
return
|
||||
else
|
||||
M.reset_state()
|
||||
M.pick_target(args)
|
||||
end
|
||||
end)
|
||||
else
|
||||
M.pick_target(args)
|
||||
end
|
||||
end
|
||||
|
||||
---2. Pick the target branch
|
||||
---@param args? Args
|
||||
M.pick_target = function(args)
|
||||
if not args then
|
||||
args = {}
|
||||
end
|
||||
if args.target ~= nil then
|
||||
M.pick_template({ target = args.target }, args)
|
||||
return
|
||||
end
|
||||
|
||||
if state.settings.create_mr.target ~= nil then
|
||||
M.pick_template({ target = state.settings.create_mr.target }, args)
|
||||
return
|
||||
end
|
||||
|
||||
local all_branch_names = u.get_all_git_branches(true)
|
||||
vim.ui.select(all_branch_names, {
|
||||
prompt = "Choose target branch for merge",
|
||||
}, function(choice)
|
||||
if choice then
|
||||
M.pick_template({ target = choice }, args)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function make_template_path(t)
|
||||
local base_dir = vim.fn.trim(vim.fn.system({ "git", "rev-parse", "--show-toplevel" }))
|
||||
return base_dir
|
||||
.. state.settings.file_separator
|
||||
.. ".gitlab"
|
||||
.. state.settings.file_separator
|
||||
.. "merge_request_templates"
|
||||
.. state.settings.file_separator
|
||||
.. t
|
||||
end
|
||||
|
||||
---3. Pick template (if applicable). This is used as the description
|
||||
---@param mr Mr
|
||||
---@param args Args
|
||||
M.pick_template = function(mr, args)
|
||||
if not args then
|
||||
args = {}
|
||||
end
|
||||
|
||||
local template_file = args.template_file or state.settings.create_mr.template_file
|
||||
if template_file ~= nil then
|
||||
local description = u.read_file(make_template_path(template_file))
|
||||
M.add_title({ target = mr.target, description = description })
|
||||
return
|
||||
end
|
||||
|
||||
local all_templates = u.list_files_in_folder(".gitlab" .. state.settings.file_separator .. "merge_request_templates")
|
||||
if all_templates == nil then
|
||||
M.add_title({ target = mr.target })
|
||||
return
|
||||
end
|
||||
|
||||
local opts = { "Blank Template" }
|
||||
for _, v in ipairs(all_templates) do
|
||||
table.insert(opts, v)
|
||||
end
|
||||
vim.ui.select(opts, {
|
||||
prompt = "Choose Template",
|
||||
}, function(choice)
|
||||
if choice then
|
||||
local description = u.read_file(make_template_path(choice))
|
||||
M.add_title({ target = mr.target, description = description })
|
||||
elseif choice == "Blank Template" then
|
||||
M.add_title({ target = mr.target })
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---4. Prompts the user for the title of the MR
|
||||
---@param mr Mr
|
||||
M.add_title = function(mr)
|
||||
local input = Input(title_input_options, {
|
||||
prompt = "",
|
||||
default_value = "",
|
||||
on_close = function() end,
|
||||
on_submit = function(_value)
|
||||
M.open_confirmation_popup(mr)
|
||||
end,
|
||||
on_change = function(value)
|
||||
mr.title = value
|
||||
end,
|
||||
})
|
||||
input:map("n", "<Esc>", function()
|
||||
input:unmount()
|
||||
end, { noremap = true })
|
||||
|
||||
input:mount()
|
||||
end
|
||||
|
||||
---5. Show the final popup.
|
||||
---The function will render a popup containing the MR title and MR description, and
|
||||
---target branch. The title and description are editable.
|
||||
---@param mr Mr
|
||||
M.open_confirmation_popup = function(mr)
|
||||
M.started = true
|
||||
if M.layout_visible then
|
||||
M.layout:unmount()
|
||||
M.layout_visible = false
|
||||
return
|
||||
end
|
||||
|
||||
local layout, title_popup, description_popup, target_popup = M.create_layout()
|
||||
|
||||
M.layout = layout
|
||||
M.layout_buf = layout.bufnr
|
||||
M.layout_visible = true
|
||||
|
||||
local function exit()
|
||||
local title = vim.fn.trim(u.get_buffer_text(M.title_bufnr))
|
||||
local description = u.get_buffer_text(M.description_bufnr)
|
||||
local target = vim.fn.trim(u.get_buffer_text(target_popup.bufnr))
|
||||
M.mr = {
|
||||
title = title,
|
||||
description = description,
|
||||
target = target,
|
||||
}
|
||||
layout:unmount()
|
||||
M.layout_visible = false
|
||||
end
|
||||
|
||||
local description_lines = mr.description and M.build_description_lines(mr.description) or { "" }
|
||||
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_buf_set_lines(description_popup.bufnr, 0, -1, false, description_lines)
|
||||
vim.api.nvim_buf_set_lines(title_popup.bufnr, 0, -1, false, { mr.title })
|
||||
vim.api.nvim_buf_set_lines(target_popup.bufnr, 0, -1, false, { mr.target })
|
||||
|
||||
local popup_opts = {
|
||||
cb = exit,
|
||||
action_before_close = true,
|
||||
action_before_exit = true,
|
||||
}
|
||||
|
||||
state.set_popup_keymaps(description_popup, M.create_mr, miscellaneous.attach_file, popup_opts)
|
||||
state.set_popup_keymaps(title_popup, M.create_mr, nil, popup_opts)
|
||||
state.set_popup_keymaps(target_popup, M.create_mr, nil, popup_opts)
|
||||
|
||||
vim.api.nvim_set_current_buf(description_popup.bufnr)
|
||||
end)
|
||||
end
|
||||
|
||||
---Builds a lua list of strings that contain the MR description
|
||||
M.build_description_lines = function(template_content)
|
||||
local description_lines = {}
|
||||
for line in template_content:gmatch("[^\n]+") do
|
||||
table.insert(description_lines, line)
|
||||
table.insert(description_lines, "")
|
||||
end
|
||||
|
||||
return description_lines
|
||||
end
|
||||
|
||||
---This function will POST the new MR to create it
|
||||
M.create_mr = function()
|
||||
local description = u.get_buffer_text(M.description_bufnr)
|
||||
local title = u.get_buffer_text(M.title_bufnr):gsub("\n", " ")
|
||||
local target = u.get_buffer_text(M.target_bufnr):gsub("\n", " ")
|
||||
|
||||
local body = {
|
||||
title = title,
|
||||
description = description,
|
||||
target_branch = target,
|
||||
}
|
||||
|
||||
job.run_job("/create_mr", "POST", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
M.reset_state()
|
||||
M.layout:unmount()
|
||||
M.layout_visible = false
|
||||
end)
|
||||
end
|
||||
|
||||
M.create_layout = function()
|
||||
local title_popup = Popup(title_popup_settings)
|
||||
M.title_bufnr = title_popup.bufnr
|
||||
local description_popup = Popup(description_popup_settings)
|
||||
M.description_bufnr = description_popup.bufnr
|
||||
local target_branch_popup = Popup(target_popup_settings)
|
||||
M.target_bufnr = target_branch_popup.bufnr
|
||||
|
||||
local internal_layout
|
||||
internal_layout = Layout.Box({
|
||||
Layout.Box({
|
||||
Layout.Box(title_popup, { grow = 1 }),
|
||||
Layout.Box(target_branch_popup, { grow = 1 }),
|
||||
}, { size = 3 }),
|
||||
Layout.Box(description_popup, { grow = 1 }),
|
||||
}, { dir = "col" })
|
||||
|
||||
local layout = Layout({
|
||||
position = "50%",
|
||||
relative = "editor",
|
||||
size = {
|
||||
width = "95%",
|
||||
height = "95%",
|
||||
},
|
||||
}, internal_layout)
|
||||
|
||||
layout:mount()
|
||||
|
||||
return layout, title_popup, description_popup, target_branch_popup
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -49,6 +49,7 @@
|
||||
---@field resolved_by Author
|
||||
---@field resolved_at string?
|
||||
---@field noteable_iid integer
|
||||
---@field url string?
|
||||
|
||||
---@class UnlinkedNote: Note
|
||||
---@field position nil
|
||||
|
||||
@@ -37,7 +37,7 @@ local M = {
|
||||
---callback with data
|
||||
---@param callback (fun(data: DiscussionData): nil)?
|
||||
M.load_discussions = function(callback)
|
||||
job.run_job("/discussions/list", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
|
||||
job.run_job("/mr/discussions/list", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
|
||||
M.discussions = data.discussions ~= vim.NIL and data.discussions or {}
|
||||
M.unlinked_discussions = data.unlinked_discussions ~= vim.NIL and data.unlinked_discussions or {}
|
||||
if type(callback) == "function" then
|
||||
@@ -52,7 +52,7 @@ M.initialize_discussions = function()
|
||||
-- Setup callback to refresh discussion data, discussion signs and diagnostics whenever the reviewed file changes.
|
||||
reviewer.set_callback_for_file_changed(M.refresh_discussion_data)
|
||||
-- Setup callback to clear signs and diagnostics whenever reviewer is left.
|
||||
reviewer.set_callback_for_reviewer_leave(signs.clear_signs_and_discussions)
|
||||
reviewer.set_callback_for_reviewer_leave(signs.clear_signs_and_diagnostics)
|
||||
end
|
||||
|
||||
---Refresh discussion data, signs, diagnostics, and winbar with new data from API
|
||||
@@ -209,7 +209,7 @@ end
|
||||
M.send_reply = function(tree, discussion_id)
|
||||
return function(text)
|
||||
local body = { discussion_id = discussion_id, reply = text }
|
||||
job.run_job("/reply", "POST", body, function(data)
|
||||
job.run_job("/mr/reply", "POST", body, function(data)
|
||||
u.notify("Sent reply!", vim.log.levels.INFO)
|
||||
M.add_reply_to_tree(tree, data.note, discussion_id)
|
||||
M.load_discussions()
|
||||
@@ -239,7 +239,7 @@ M.send_deletion = function(tree, unlinked)
|
||||
|
||||
local body = { discussion_id = root_node.id, note_id = tonumber(note_id) }
|
||||
|
||||
job.run_job("/comment", "DELETE", body, function(data)
|
||||
job.run_job("/mr/comment", "DELETE", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
if not note_node.is_root then
|
||||
tree:remove_node("-" .. note_id) -- Note is not a discussion root, safe to remove
|
||||
@@ -301,7 +301,7 @@ M.send_edits = function(discussion_id, note_id, unlinked)
|
||||
note_id = note_id,
|
||||
comment = text,
|
||||
}
|
||||
job.run_job("/comment", "PATCH", body, function(data)
|
||||
job.run_job("/mr/comment", "PATCH", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
M.rebuild_discussion_tree()
|
||||
if unlinked then
|
||||
@@ -327,7 +327,7 @@ M.toggle_discussion_resolved = function(tree)
|
||||
resolved = not note.resolved,
|
||||
}
|
||||
|
||||
job.run_job("/discussions/resolve", "PUT", body, function(data)
|
||||
job.run_job("/mr/discussions/resolve", "PUT", body, function(data)
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
M.redraw_resolved_status(tree, note, not note.resolved)
|
||||
M.refresh_discussion_data()
|
||||
@@ -572,6 +572,12 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
end
|
||||
end, { buffer = bufnr, desc = "Jump to reviewer" })
|
||||
end
|
||||
vim.keymap.set("n", state.settings.discussion_tree.open_in_browser, function()
|
||||
M.open_in_browser(tree)
|
||||
end, { buffer = bufnr, desc = "Open the note in your browser" })
|
||||
vim.keymap.set("n", "<leader>p", function()
|
||||
M.print_node(tree)
|
||||
end, { buffer = bufnr, desc = "dev_ Print current node (for debugging)" })
|
||||
end
|
||||
|
||||
M.redraw_resolved_status = function(tree, note, mark_resolved)
|
||||
@@ -683,4 +689,26 @@ M.get_note_location = function(tree)
|
||||
nil
|
||||
end
|
||||
|
||||
---@param tree NuiTree
|
||||
M.open_in_browser = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
local note_node = M.get_note_node(tree, current_node)
|
||||
if note_node == nil then
|
||||
return
|
||||
end
|
||||
local url = note_node.url
|
||||
if url == nil then
|
||||
u.notify("Could not get URL of note", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
u.open_in_browser(url)
|
||||
end
|
||||
|
||||
-- For developers!
|
||||
M.print_node = function(tree)
|
||||
local current_node = tree:get_node()
|
||||
vim.print(current_node)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -11,7 +11,7 @@ local M = {}
|
||||
M.diagnostics_namespace = diagnostics_namespace
|
||||
|
||||
---Clear all signs and diagnostics
|
||||
M.clear_signs_and_discussions = function()
|
||||
M.clear_signs_and_diagnostics = function()
|
||||
vim.fn.sign_unplace(discussion_sign_name)
|
||||
vim.diagnostic.reset(diagnostics_namespace)
|
||||
end
|
||||
|
||||
@@ -131,6 +131,7 @@ M.build_note = function(note, resolve_info)
|
||||
file_name = (type(note.position) == "table" and note.position.new_path),
|
||||
new_line = (type(note.position) == "table" and note.position.new_line),
|
||||
old_line = (type(note.position) == "table" and note.position.old_line),
|
||||
url = state.INFO.web_url .. "#note_" .. note.id,
|
||||
type = "note",
|
||||
}, text_nodes)
|
||||
|
||||
@@ -161,11 +162,11 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
local undefined_type = false
|
||||
local root_new_line = nil
|
||||
local root_old_line = nil
|
||||
local root_url
|
||||
|
||||
for j, note in ipairs(discussion.notes) do
|
||||
if j == 1 then
|
||||
_, root_text, root_text_nodes = M.build_note(note, { resolved = note.resolved, resolvable = note.resolvable })
|
||||
|
||||
root_file_name = (type(note.position) == "table" and note.position.new_path or nil)
|
||||
root_new_line = (type(note.position) == "table" and note.position.new_line or nil)
|
||||
root_old_line = (type(note.position) == "table" and note.position.old_line or nil)
|
||||
@@ -173,6 +174,7 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
root_note_id = tostring(note.id)
|
||||
resolvable = note.resolvable
|
||||
resolved = note.resolved
|
||||
root_url = state.INFO.web_url .. "#note_" .. note.id
|
||||
|
||||
-- This appears to be a Gitlab 🐛 where the "type" is returned as an empty string in some cases
|
||||
-- We link these comments to the old file by default
|
||||
@@ -203,6 +205,7 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
resolvable = resolvable,
|
||||
resolved = resolved,
|
||||
undefined_type = undefined_type,
|
||||
url = root_url,
|
||||
}, body)
|
||||
|
||||
table.insert(t, root_node)
|
||||
|
||||
@@ -52,7 +52,8 @@ end
|
||||
M.update_winbar = function(discussions, unlinked_discussions, base_title)
|
||||
local d = require("gitlab.actions.discussions")
|
||||
local winId = d.split.winid
|
||||
vim.wo[winId].winbar = content(discussions, unlinked_discussions, base_title)
|
||||
local c = content(discussions, unlinked_discussions, base_title)
|
||||
vim.wo[winId].winbar = c
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -46,7 +46,7 @@ M.confirm_merge = function(merge_body, squash_message)
|
||||
merge_body.squash_message = squash_message
|
||||
end
|
||||
|
||||
job.run_job("/merge", "POST", merge_body, function(data)
|
||||
job.run_job("/mr/merge", "POST", merge_body, function(data)
|
||||
reviewer.close()
|
||||
u.notify(data.message, vim.log.levels.INFO)
|
||||
end)
|
||||
|
||||
@@ -3,21 +3,6 @@ local u = require("gitlab.utils")
|
||||
local job = require("gitlab.job")
|
||||
local M = {}
|
||||
|
||||
M.open_in_browser = function()
|
||||
local url = state.INFO.web_url
|
||||
if url == nil then
|
||||
u.notify("Could not get Gitlab URL", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
if vim.fn.has("mac") == 1 then
|
||||
vim.fn.jobstart({ "open", url })
|
||||
elseif vim.fn.has("unix") == 1 then
|
||||
vim.fn.jobstart({ "xdg-open", url })
|
||||
else
|
||||
u.notify("Opening a Gitlab URL is not supported on this OS!", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
M.attach_file = function()
|
||||
local attachment_dir = state.settings.attachment_dir
|
||||
if not attachment_dir or attachment_dir == "" then
|
||||
@@ -40,7 +25,7 @@ M.attach_file = function()
|
||||
end
|
||||
local full_path = attachment_dir .. u.path_separator .. choice
|
||||
local body = { file_path = full_path, file_name = choice }
|
||||
job.run_job("/mr/attachment", "POST", body, function(data)
|
||||
job.run_job("/attachment", "POST", body, function(data)
|
||||
local markdown = data.markdown
|
||||
local current_line = u.get_current_line_number()
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
|
||||
@@ -9,8 +9,8 @@ local summary = require("gitlab.actions.summary")
|
||||
local assignees_and_reviewers = require("gitlab.actions.assignees_and_reviewers")
|
||||
local comment = require("gitlab.actions.comment")
|
||||
local pipeline = require("gitlab.actions.pipeline")
|
||||
local create_mr = require("gitlab.actions.create_mr")
|
||||
local approvals = require("gitlab.actions.approvals")
|
||||
local miscellaneous = require("gitlab.actions.miscellaneous")
|
||||
|
||||
local info = state.dependencies.info
|
||||
local project_members = state.dependencies.project_members
|
||||
@@ -40,6 +40,7 @@ return {
|
||||
create_comment_suggestion = async.sequence({ info, revisions }, comment.create_comment_suggestion),
|
||||
move_to_discussion_tree_from_diagnostic = async.sequence({}, discussions.move_to_discussion_tree),
|
||||
create_note = async.sequence({ info }, comment.create_note),
|
||||
create_mr = async.sequence({}, create_mr.start),
|
||||
review = async.sequence({ u.merge(info, { refresh = true }), revisions }, function()
|
||||
reviewer.open()
|
||||
end),
|
||||
@@ -57,5 +58,11 @@ return {
|
||||
-- Other functions 🤷
|
||||
state = state,
|
||||
print_settings = state.print_settings,
|
||||
open_in_browser = async.sequence({ info }, miscellaneous.open_in_browser),
|
||||
open_in_browser = async.sequence({ info }, function()
|
||||
if state.INFO.web_url == nil then
|
||||
u.notify("Could not get Gitlab URL", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
u.open_in_browser(state.INFO.web_url)
|
||||
end),
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ M.settings = {
|
||||
jump_to_reviewer = "m",
|
||||
edit_comment = "e",
|
||||
delete_comment = "dd",
|
||||
open_in_browser = "b",
|
||||
reply = "r",
|
||||
toggle_node = "t",
|
||||
toggle_resolved = "p",
|
||||
@@ -71,6 +72,10 @@ M.settings = {
|
||||
squash = false,
|
||||
delete_branch = false,
|
||||
},
|
||||
create_mr = {
|
||||
target = nil,
|
||||
template_file = nil,
|
||||
},
|
||||
info = {
|
||||
enabled = true,
|
||||
horizontal = false,
|
||||
@@ -123,8 +128,8 @@ M.settings = {
|
||||
preparing = "",
|
||||
scheduled = "",
|
||||
running = "",
|
||||
canceled = "",
|
||||
skipped = "",
|
||||
canceled = "↪",
|
||||
skipped = "↪",
|
||||
success = "✓",
|
||||
failed = "",
|
||||
},
|
||||
@@ -204,7 +209,7 @@ M.setPluginConfiguration = function()
|
||||
end
|
||||
|
||||
local config_file_path = base_path .. M.settings.file_separator .. ".gitlab.nvim"
|
||||
local config_file_content = u.read_file(config_file_path)
|
||||
local config_file_content = u.read_file(config_file_path, { remove_newlines = true })
|
||||
|
||||
local file_properties = {}
|
||||
if config_file_content ~= nil then
|
||||
@@ -231,10 +236,15 @@ M.setPluginConfiguration = function()
|
||||
return true
|
||||
end
|
||||
|
||||
local function exit(popup, cb)
|
||||
popup:unmount()
|
||||
if cb ~= nil then
|
||||
cb()
|
||||
local function exit(popup, opts)
|
||||
if opts.action_before_exit and opts.cb ~= nil then
|
||||
opts.cb()
|
||||
popup:unmount()
|
||||
else
|
||||
popup:unmount()
|
||||
if opts.cb ~= nil then
|
||||
opts.cb()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -244,7 +254,7 @@ M.set_popup_keymaps = function(popup, action, linewise_action, opts)
|
||||
opts = {}
|
||||
end
|
||||
vim.keymap.set("n", M.settings.popup.exit, function()
|
||||
exit(popup, opts.cb)
|
||||
exit(popup, opts)
|
||||
end, { buffer = popup.bufnr, desc = "Exit popup" })
|
||||
|
||||
if action ~= "Help" then -- Don't show help on the help popup
|
||||
@@ -258,9 +268,9 @@ M.set_popup_keymaps = function(popup, action, linewise_action, opts)
|
||||
local text = u.get_buffer_text(popup.bufnr)
|
||||
if opts.action_before_close then
|
||||
action(text, popup.bufnr)
|
||||
exit(popup)
|
||||
exit(popup, opts)
|
||||
else
|
||||
exit(popup)
|
||||
exit(popup, opts)
|
||||
action(text, popup.bufnr)
|
||||
end
|
||||
end, { buffer = popup.bufnr, desc = "Perform action" })
|
||||
@@ -282,7 +292,7 @@ end
|
||||
-- for each of the actions to occur. This is necessary because some Gitlab behaviors (like
|
||||
-- adding a reviewer) requires some initial state.
|
||||
M.dependencies = {
|
||||
info = { endpoint = "/info", key = "info", state = "INFO", refresh = false },
|
||||
info = { endpoint = "/mr/info", key = "info", state = "INFO", refresh = false },
|
||||
revisions = { endpoint = "/mr/revisions", key = "Revisions", state = "MR_REVISIONS", refresh = false },
|
||||
project_members = {
|
||||
endpoint = "/project/members",
|
||||
|
||||
@@ -382,14 +382,18 @@ M.create_popup_state = function(title, settings, width, height, zindex)
|
||||
return view_opts
|
||||
end
|
||||
|
||||
M.read_file = function(file_path)
|
||||
M.read_file = function(file_path, opts)
|
||||
local file = io.open(file_path, "r")
|
||||
if file == nil then
|
||||
return nil
|
||||
end
|
||||
local file_contents = file:read("*all")
|
||||
file:close()
|
||||
file_contents = string.gsub(file_contents, "\n", "")
|
||||
|
||||
if opts and opts.remove_newlines then
|
||||
file_contents = string.gsub(file_contents, "\n", "")
|
||||
end
|
||||
|
||||
return file_contents
|
||||
end
|
||||
|
||||
@@ -633,9 +637,46 @@ M.get_icon = function(filename)
|
||||
end
|
||||
end
|
||||
|
||||
---@param remote? boolean
|
||||
M.get_all_git_branches = function(remote)
|
||||
local branches = {}
|
||||
|
||||
local handle = remote == true and io.popen("git branch -r 2>&1") or io.popen("git branch 2>&1")
|
||||
|
||||
if handle then
|
||||
for line in handle:lines() do
|
||||
local branch
|
||||
if remote then
|
||||
for res in line:gmatch("origin/([^\n]+)") do
|
||||
branch = res -- Trim /origin
|
||||
end
|
||||
else
|
||||
branch = line:gsub("^%s*%*?%s*", "") -- Trim leading whitespace and the "* " marker for the current branch
|
||||
end
|
||||
table.insert(branches, branch)
|
||||
end
|
||||
handle:close()
|
||||
else
|
||||
print("Error running 'git branch' command.")
|
||||
end
|
||||
|
||||
return branches
|
||||
end
|
||||
|
||||
M.basename = function(str)
|
||||
local name = string.gsub(str, "(.*/)(.*)", "%2")
|
||||
return name
|
||||
end
|
||||
|
||||
---@param url string?
|
||||
M.open_in_browser = function(url)
|
||||
if vim.fn.has("mac") == 1 then
|
||||
vim.fn.jobstart({ "open", url })
|
||||
elseif vim.fn.has("unix") == 1 then
|
||||
vim.fn.jobstart({ "xdg-open", url })
|
||||
else
|
||||
M.notify("Opening a Gitlab URL is not supported on this OS!", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
Reference in New Issue
Block a user