Rebased all commits, v0.0.1

added a new file

First major commit

Successful POST of new comment 🚀

Updated README

Updated README 📕

Added more infrastructure

Creating async job

More setup

Getting arguments from Neovim -> Lua -> Golang

Moved commands

Added getProjectInfo command

Adding make comment command

Setting up arguments for MakeComment command

Removed extraneous comments

Setup basic function for adding comments

Lint fixes

Handling bad requests correctly

Better formatting

Printing success message

Adding utility table print

Set comment from popup UI

Added mappings for closing and sending text

Moved popup into separate file

Added comment

Cleaned up code and added approve command

Initialize project information

Removed extraneous import

Don't initialize project in non-gitlab directories

Setup approve command

Set up revoke and approve commands correctly

Cleaned up redundant code

Moved get current branch command

Reorganization of the code

First attempt to add step installing Go binary

Adjusted path to binary

Added install bin check

Do Lua method

Fixed install step

Tweaked binPath + bin

Added basic readme information 📗

Removed .luarc.json file

Adding diffview command

Added string_starts function

Made base branch configurable

Added note to readme

Fixed readme

Added diffview dep to readme

Update README.md

Update README.md

Update README.md

Update README.md

Renamed files

Set up developer workflow

Updated README

Removed dev note

Refactor and moving around files

Fixed ft/after mappings

Setup read command

Added read summary command

Got rid of filetype bindings and set up commands

Set correct filetype for comment buffer

Added read() command to README

Updated review -> summary

Fixed issue with diffview buffers

Added command for getting and showing all comments (out of order)

Better error message

Adding more code to handle showing comments

Added ability to jump from comment to specific changed buffer line

Initial refactor

Added simple comment action

Fixed error message

More cleanup

Fixed bug with M.PROJECT_ID

Leaving comment refactor

Fixed comment

Cleaned up old code

Added missing exit command

Check gitlab repo status before initialization

Better help strings

Added ListDiscussions command

Added Go code

Darkened metadata, filtered out non-real discussions

Removed dummy log

Sort the discussions by most recent activity

Grab hash of current discussion

Wired up reply action in Lua code

Moved to NUI Table

Adding basic jump-to-file ability

More tweaks

Allow jump anywhere in the tree

Ability to reply directly in the buffer window

Jump to location in file

Don't jump if no refresh is set

Cleaned up mappings + other code

Get rid of gitlab CLI dependency

Fixed discussions bug

Don't initialize client on main/master branches

Moved comment into separate module

Moved lua modules into separate files

Modularized library and state

Slightly better error/exception handling

Added license file

Updated readme

Moved into todo.md file

Added todo file

Standardized naming conventions (snake_case in Lua, camelCase in Go)

Moved common popup state into utils folder

Cleaned up keymapping functions

Note on install

Changing bin path

Updated README

Chnaged from success to info

Redirect output to /dev/null on build

Checking install code

Removed print statement

Slight reorganization

Setting up delete comment

Set up confirmation modal

Passing in node ID to delete_comment

Functioning comment deletion

Added delete_comment command

Updated README

Furhter modularized discussion code

Cleaned up and refactored reply code

Update README.md

Added ability to edit comments

Updated todos

Fixed main/master base branch issue

Set up keybinding rules

Updated todo.md

Removed diffview dependency

Slight cleanup 🧹

Trying something out...

Trying something for the binary...

Trying again

Fixed install for non-lazy users

Update README.md

Update README.md

Update README.md

Update README.md

Update README.md
This commit is contained in:
Harrison Cramer
2022-10-22 19:28:19 +00:00
commit bf37b1eae7
27 changed files with 1614 additions and 0 deletions

18
lua/gitlab/approve.lua Normal file
View File

@@ -0,0 +1,18 @@
local u = require("gitlab.utils")
local state = require("gitlab.state")
local Job = require("plenary.job")
local M = {}
-- Approves the current merge request
M.approve = function()
if u.base_invalid() then return end
Job:new({
command = state.BIN,
args = { "approve", state.PROJECT_ID },
on_stdout = u.print_success,
on_stderr = u.print_error
}):start()
end
return M

187
lua/gitlab/comment.lua Normal file
View File

@@ -0,0 +1,187 @@
local Menu = require("nui.menu")
local NuiTree = require("nui.tree")
local Job = require("plenary.job")
local state = require("gitlab.state")
local u = require("gitlab.utils")
local keymaps = require("gitlab.keymaps")
local Popup = require("nui.popup")
local M = {}
local commentPopup = Popup(u.create_popup_state("Comment", "40%", "60%"))
local editPopup = Popup(u.create_popup_state("Edit Comment", "80%", "80%"))
M.create_comment = function()
if u.base_invalid() then return end
commentPopup:mount()
keymaps.set_popup_keymaps(commentPopup, M.confirm_create_comment)
end
-- Sends the comment to Gitlab
M.confirm_create_comment = function(text)
if u.base_invalid() then return end
local relative_file_path = u.get_relative_file_path()
local current_line_number = u.get_current_line_number()
Job:new({
command = state.BIN,
args = {
"comment",
state.PROJECT_ID,
current_line_number,
relative_file_path,
text,
},
-- TODO: Render the tree after comment creation. Refresh?
on_stdout = u.print_success,
on_stderr = u.print_error
}):start()
end
M.delete_comment = function()
local menu = Menu({
position = "50%",
size = {
width = 25,
},
border = {
style = "single",
text = {
top = "Delete Comment?",
top_align = "center",
},
},
win_options = {
winhighlight = "Normal:Normal,FloatBorder:Normal",
},
}, {
lines = {
Menu.item("Confirm"),
Menu.item("Cancel"),
},
max_width = 20,
keymap = {
focus_next = state.keymaps.dialogue.focus_next,
focus_prev = state.keymaps.dialogue.focus_prev,
close = state.keymaps.dialogue.close,
submit = state.keymaps.dialogue.submit,
},
on_submit = function(item)
if item.text == "Confirm" then
local note_id
local node = state.tree:get_node()
if node.is_note then
note_id = node:get_id()
end
local parentId = node:get_parent_id()
while (parentId ~= nil) do
node = state.tree:get_node(parentId)
parentId = node:get_parent_id()
if node.is_note then
note_id = node:get_id()
end
end
local discussion_id = node:get_id()
discussion_id = string.sub(discussion_id, 2) -- Remove the "-" at the start
note_id = string.sub(note_id, 2) -- Remove the "-" at the start
Job:new({
command = state.BIN,
args = {
"deleteComment",
state.PROJECT_ID,
discussion_id,
note_id,
},
on_stdout = function(_, line)
vim.schedule(function()
if line ~= nil and line ~= "" then
require("notify")(line, "info")
state.tree:remove_node("-" .. note_id)
local discussion_node = state.tree:get_node("-" .. discussion_id)
if not discussion_node:has_children() then
state.tree:remove_node("-" .. discussion_id)
end
state.tree:render()
end
end)
end,
on_stderr = u.print_error
}):start()
end
end,
})
menu:mount()
end
M.edit_comment = function()
if u.base_invalid() then return end
local node = state.tree:get_node()
if node.is_discussion then return end
if node.is_body then
local parentId = node:get_parent_id()
node = state.tree:get_node(parentId) -- Get the node for the comment
end
editPopup:mount()
local note_id = string.sub(node:get_id(), 2) -- Remove the "-" at the start
local discussion_id = node:get_parent_id()
discussion_id = string.sub(discussion_id, 2) -- Remove the "-" at the start
state.ACTIVE_DISCUSSION = discussion_id
state.ACTIVE_NOTE = note_id
local lines = {}
local childrenIds = node:get_child_ids()
for _, value in ipairs(childrenIds) do
local line = state.tree:get_node(value).text
table.insert(lines, line)
end
local currentBuffer = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
keymaps.set_popup_keymaps(editPopup, M.send_edits)
end
M.send_edits = function(text)
Job:new({
command = state.BIN,
args = {
"editComment",
state.PROJECT_ID,
state.ACTIVE_DISCUSSION,
state.ACTIVE_NOTE,
text,
},
on_stdout = function(_, line)
local note = vim.json.decode(line)
if note == nil then
require("notify")("There was an issue editing the note", "error")
return
end
vim.schedule(function()
local node = state.tree:get_node("-" .. state.ACTIVE_NOTE)
local childrenIds = node:get_child_ids()
for _, value in ipairs(childrenIds) do
state.tree:remove_node(value)
end
local newNoteTextNodes = {}
for bodyLine in note.body:gmatch("[^\n]+") do
table.insert(newNoteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
end
state.tree:set_nodes(newNoteTextNodes, "-" .. state.ACTIVE_NOTE)
state.tree:render()
local buf = vim.api.nvim_get_current_buf()
u.darken_metadata(buf, '')
require("notify")("Edited comment!")
end)
end,
on_stderr = u.print_error
}):start()
end
return M

207
lua/gitlab/discussions.lua Normal file
View File

@@ -0,0 +1,207 @@
local u = require("gitlab.utils")
local NuiTree = require("nui.tree")
local state = require("gitlab.state")
local Job = require("plenary.job")
local Popup = require("nui.popup")
local keymaps = require("gitlab.keymaps")
local M = {}
local replyPopup = Popup(u.create_popup_state("Reply", "80%", "80%"))
M.reply = function()
if u.base_invalid() then return end
replyPopup:mount()
keymaps.set_popup_keymaps(replyPopup, M.send_reply)
end
M.send_reply = function(text)
Job:new({
command = state.BIN,
args = {
"reply",
state.PROJECT_ID,
state.ACTIVE_DISCUSSION,
text,
},
on_stdout = function(_, line)
local note = vim.json.decode(line)
if note == nil then
require("notify")("There was an issue creating the note", "error")
return
end
local note_node = M.build_note(note)
note_node:expand()
state.tree:add_node(note_node, "-" .. state.ACTIVE_DISCUSSION)
vim.schedule(function()
state.tree:render()
local buf = vim.api.nvim_get_current_buf()
u.darken_metadata(buf, '')
require("notify")("Sent reply!")
end)
end,
on_stderr = u.print_error
}):start()
end
-- Places all of the discussions into a readable list
M.list_discussions = function()
if u.base_invalid() then return end
Job:new({
command = state.BIN,
args = { "listDiscussions", state.PROJECT_ID },
on_stdout = function(_, line)
local discussions = vim.json.decode(line)
M.discussions = discussions
vim.schedule(function()
vim.cmd.tabnew()
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_command("vsplit")
vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown')
vim.api.nvim_set_current_buf(buf)
if discussions == nil then
require("notify")("No discussions found for this MR", "warn")
else
local allDiscussions = {}
for i, discussion in ipairs(discussions) do
local discussionChildren = {}
for _, note in ipairs(discussion.notes) do
local note_node = M.build_note(note)
if i == 1 then
note_node:expand()
end
table.insert(discussionChildren, note_node)
end
local discussionNode = NuiTree.Node({
text = discussion.id,
id = discussion.id,
is_discussion = true
},
discussionChildren)
if i == 1 then
discussionNode:expand()
end
table.insert(allDiscussions, discussionNode)
end
state.tree = NuiTree({ nodes = allDiscussions, bufnr = buf })
M.set_tree_keymaps(buf)
state.tree:render()
vim.api.nvim_buf_set_option(buf, 'filetype', 'markdown')
u.darken_metadata(buf, '')
if not is_refresh then
M.jump_to_file()
end
end
end)
end,
on_stderr = u.print_error,
}):start()
end
M.jump_to_file = function()
local node = state.tree:get_node()
if node == nil then return end
local childrenIds = node:get_child_ids()
-- We have selected a note node
if node.file_name ~= nil then
u.jump_to_file(node.file_name, node.line_number)
elseif node.is_body then
local parentId = node:get_parent_id()
local parent = state.tree:get_node(parentId)
if parent == nil then return end
u.jump_to_file(parent.file_name, parent.line_number)
else
local firstChild = state.tree:get_node(childrenIds[1])
if firstChild == nil then return end
u.jump_to_file(firstChild.file_name, firstChild.line_number)
end
end
M.set_tree_keymaps = function(buf)
-- Jump to file location where comment was left
vim.keymap.set('n', state.keymaps.discussion_tree.jump_to_location, function()
M.jump_to_file()
end, { buffer = true })
vim.keymap.set('n', state.keymaps.discussion_tree.edit_comment, function()
require("gitlab.comment").edit_comment()
end, { buffer = true })
vim.keymap.set('n', state.keymaps.discussion_tree.delete_comment, function()
require("gitlab.comment").delete_comment()
end)
-- Expand/collapse the current node
vim.keymap.set('n', state.keymaps.discussion_tree.toggle_node, function()
local node = state.tree:get_node()
if node == nil then return end
local children = node:get_child_ids()
if node == nil then return end
if node:is_expanded() then
node:collapse()
for _, child in ipairs(children) do
state.tree:get_node(child):collapse()
end
else
for _, child in ipairs(children) do
state.tree:get_node(child):expand()
end
node:expand()
end
state.tree:render()
u.darken_metadata(buf, '')
end,
{ buffer = true })
vim.keymap.set('n', 'r', function()
local node = state.tree:get_node()
if node == nil then return end
-- Get closest discussion parent
if node.is_body then
local parentId = node:get_parent_id()
local parent = state.tree:get_node(parentId)
if parent == nil then return end
parentId = parent:get_parent_id()
parent = state.tree:get_node(parentId)
if parent == nil then return end
node = parent
elseif node.is_note then
local parentId = node:get_parent_id()
local parent = state.tree:get_node(parentId)
if parent == nil then return end
node = parent
end
state.ACTIVE_DISCUSSION = node.id
M.reply()
end, { buffer = true })
end
M.build_note = function(note)
local noteTextNodes = {}
for bodyLine in note.body:gmatch("[^\n]+") do
table.insert(noteTextNodes, NuiTree.Node({ text = bodyLine, is_body = true }, {}))
end
local noteHeader = "@" ..
note.author.username .. " on " .. u.format_date(note.created_at)
local note_node = NuiTree.Node(
{
text = noteHeader,
id = note.id,
file_name = note.position.new_path,
line_number = note.position.new_line,
is_note = true
}, noteTextNodes)
return note_node
end
return M

79
lua/gitlab/init.lua Normal file
View File

@@ -0,0 +1,79 @@
local Job = require("plenary.job")
local state = require("gitlab.state")
local discussions = require("gitlab.discussions")
local summary = require("gitlab.summary")
local keymaps = require("gitlab.keymaps")
local comment = require("gitlab.comment")
local approve = require("gitlab.approve")
local revoke = require("gitlab.revoke")
local u = require("gitlab.utils")
-- Root Module Scope
local M = {}
M.summary = summary.summary
M.approve = approve.approve
M.revoke = revoke.revoke
M.create_comment = comment.create_comment
M.list_discussions = discussions.list_discussions
M.edit_comment = comment.edit_comment
M.delete_comment = comment.delete_comment
M.reply = discussions.reply
-- Builds the Go binary, initializes the plugin, fetches MR info
local projectData = {}
local function current_file_path()
local path = debug.getinfo(1, 'S').source:sub(2)
return vim.fn.fnamemodify(path, ':p')
end
M.setup = function(args)
local file_path = current_file_path()
local parent_dir = vim.fn.fnamemodify(file_path, ":h:h:h")
state.BIN_PATH = parent_dir
state.BIN = parent_dir .. "/bin"
local binExists = io.open(state.BIN, "r")
if not binExists or args.dev == true then
local command = string.format("cd %s && make", state.BIN_PATH)
local installCode = os.execute(command .. "> /dev/null")
if installCode ~= 0 then
require("notify")("Could not install gitlab.nvim! Do you have Go installed?", "error")
return
end
end
if args.project_id == nil then
error("No project ID provided!")
end
state.PROJECT_ID = args.project_id
if args.base_branch ~= nil then
state.BASE_BRANCH = args.base_branch
end
if u.is_gitlab_repo() then
Job:new({
command = state.BIN,
args = { "info", state.PROJECT_ID },
on_stdout = function(_, line)
table.insert(projectData, line)
end,
on_stderr = u.print_error,
on_exit = function()
if projectData[1] ~= nil then
local parsed_ok, data = pcall(vim.json.decode, projectData[1])
if parsed_ok ~= true then
require("notify")("Failed calling setup. Could not get project data.", "error")
else
state.INFO = data
end
end
end,
}):start()
end
keymaps.set_keymap_keys(args.keymaps)
end
return M

22
lua/gitlab/keymaps.lua Normal file
View File

@@ -0,0 +1,22 @@
local u = require("gitlab.utils")
local state = require("gitlab.state")
local M = {}
M.set_popup_keymaps = function(popup, action)
vim.keymap.set('n', state.keymaps.popup.exit, function() u.exit(popup) end, { buffer = true })
vim.keymap.set('n', ':', '', { buffer = true })
if action ~= nil then
vim.keymap.set('n', state.keymaps.popup.perform_action, function()
local text = u.get_buffer_text(popup.bufnr)
popup:unmount()
action(text)
end, { buffer = true })
end
end
M.set_keymap_keys = function(keyTable)
if keyTable == nil then return end
state.keymaps = u.merge_tables(state.keymaps, keyTable)
end
return M

17
lua/gitlab/revoke.lua Normal file
View File

@@ -0,0 +1,17 @@
local u = require("gitlab.utils")
local state = require("gitlab.state")
local M = {}
local Job = require("plenary.job")
-- Revokes approval for the current merge request
M.revoke = function()
if u.base_invalid() then return end
Job:new({
command = state.BIN,
args = { "revoke", state.PROJECT_ID },
on_stdout = u.print_success,
on_stderr = u.print_error
}):start()
end
return M

29
lua/gitlab/state.lua Normal file
View File

@@ -0,0 +1,29 @@
local M = {}
M.BIN_PATH = nil
M.BIN = nil
M.PROJECT_ID = nil
M.ACTIVE_DISCUSSION = nil
M.ACTIVE_NOTE = nil
M.BASE_BRANCH = "main"
M.keymaps = {
popup = {
exit = "<Esc>",
perform_action = "<leader>s",
},
discussion_tree = {
jump_to_location = "o",
edit_comment = "e",
delete_comment = "dd",
reply_to_comment = "r",
toggle_node = "t",
},
dialogue = {
focus_next = { "j", "<Down>", "<Tab>" },
focus_prev = { "k", "<Up>", "<S-Tab>" },
close = { "<Esc>", "<C-c>" },
submit = { "<CR>", "<Space>" },
}
}
return M

27
lua/gitlab/summary.lua Normal file
View File

@@ -0,0 +1,27 @@
local state = require("gitlab.state")
local Popup = require("nui.popup")
local u = require("gitlab.utils")
local keymaps = require("gitlab.keymaps")
local summaryPopup = Popup(u.create_popup_state("Loading Summary...", "80%", "80%"))
local M = {}
M.summary = function()
if u.base_invalid() then return end
summaryPopup:mount()
local currentBuffer = vim.api.nvim_get_current_buf()
local title = state.INFO.title
local description = state.INFO.description
local lines = {}
for line in description:gmatch("[^\n]+") do
table.insert(lines, line)
table.insert(lines, "")
end
vim.schedule(function()
vim.api.nvim_buf_set_lines(currentBuffer, 0, -1, false, lines)
vim.api.nvim_buf_set_option(currentBuffer, "modifiable", false)
summaryPopup.border:set_text("top", title, "center")
keymaps.set_popup_keymaps(summaryPopup)
end)
end
return M

226
lua/gitlab/utils/init.lua Normal file
View File

@@ -0,0 +1,226 @@
local state = require("gitlab.state")
local function get_git_root()
local output = vim.fn.system('git rev-parse --show-toplevel 2>/dev/null')
if vim.v.shell_error == 0 then
return vim.fn.substitute(output, '\n', '', '')
else
return nil
end
end
local function get_relative_file_path()
local git_root = get_git_root()
if git_root ~= nil then
local current_file = vim.fn.expand('%:p')
return vim.fn.substitute(current_file, git_root .. '/', '', '')
else
return nil
end
end
local get_current_line_number = function()
return vim.api.nvim_call_function('line', { '.' })
end
function P(...)
local objects = {}
for i = 1, select("#", ...) do
local v = select(i, ...)
table.insert(objects, vim.inspect(v))
end
print(table.concat(objects, "\n"))
return ...
end
local function get_buffer_text(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local text = table.concat(lines, "\n")
return text
end
local feature_branch_exists = function(base_branch)
local is_git_branch = io.popen("git rev-parse --is-inside-work-tree 2>/dev/null"):read("*a")
if is_git_branch == "true\n" then
for line in io.popen("git branch 2>/dev/null"):lines() do
line = line:gsub("%s+", "")
if line == base_branch then
return true
end
end
end
return false
end
local string_starts = function(str, start)
return str:sub(1, #start) == start
end
local press_enter = function()
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<CR>", false, true, true), "n", false)
end
local base_invalid = function()
local current_branch_raw = io.popen("git rev-parse --abbrev-ref HEAD"):read("*a")
local current_branch = string.gsub(current_branch_raw, "\n", "")
if current_branch == "main" or current_branch == "master" then
require("notify")('On ' .. current_branch .. ' branch, no MRs available', "error")
return true
end
local base = state.BASE_BRANCH
local hasBaseBranch = feature_branch_exists(base)
if not hasBaseBranch then
require("notify")('No base branch. If this is a Gitlab repository, please check your setup function!', "error")
return true
end
end
local format_date = function(date_string)
local year, month, day, hour, min, sec = date_string:match("(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)")
local date = os.time({ year = year, month = month, day = day, hour = hour, min = min, sec = sec })
-- Format date into human-readable string without leading zeros
local formatted_date = os.date("%A, %B %e at %l:%M %p", date)
return formatted_date
end
local add_comment_sign = function(line_number)
local bufnr = vim.api.nvim_get_current_buf()
vim.cmd("sign define piet text= texthl=Substitute")
vim.fn.sign_place(0, "piet", "piet", bufnr, { lnum = line_number })
end
local function is_gitlab_repo()
local current_dir = vim.fn.getcwd()
-- check if it contains a .git folder
local git_dir = current_dir .. "/.git"
if vim.fn.isdirectory(git_dir) == 0 then
return false
end
local git_cmd = 'git remote get-url origin'
local handle = io.popen(git_cmd)
local result = handle:read("*a")
handle:close()
-- check if the remote URL is a Gitlab URL
if string.match(result, "gitlab%.com") then
return true
else
return false
end
end
local function jump_to_file(filename, line_number)
vim.api.nvim_command("wincmd l")
local bufnr = vim.fn.bufnr(filename)
if bufnr ~= -1 then
-- Buffer is already open, switch to it
vim.cmd("buffer " .. bufnr)
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
return
end
-- If buffer is not already open, open it
vim.cmd("edit " .. filename)
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
end
local function find_value_by_id(tbl, id)
for i = 1, #tbl do
if tbl[i].id == tonumber(id) then
return tbl[i]
end
end
return nil
end
vim.cmd("highlight Gray guifg=#888888")
local function darken_metadata(bufnr, regex)
local num_lines = vim.api.nvim_buf_line_count(bufnr)
for i = 0, num_lines - 1 do
local line = vim.api.nvim_buf_get_lines(bufnr, i, i + 1, false)[1]
if string.match(line, regex) then
vim.api.nvim_buf_add_highlight(bufnr, -1, 'Gray', i, 0, -1)
end
end
end
local function print_success(_, line)
if line ~= nil and line ~= "" then
require("notify")(line, "info")
end
end
local function print_error(_, line)
if line ~= nil and line ~= "" then
require("notify")(line, "error")
end
end
local function exit(popup)
popup:unmount()
end
local create_popup_state = function(title, width, height)
return {
buf_options = {
filetype = 'markdown'
},
enter = true,
focusable = true,
border = {
style = "rounded",
text = {
top = title
},
},
position = "50%",
size = {
width = width,
height = height,
},
}
end
local M = {}
M.merge_tables = function(defaults, overrides)
local result = {}
for key, value in pairs(defaults) do
if type(value) == "table" then
result[key] = M.merge_tables(value, overrides[key] or {})
else
result[key] = overrides[key] or value
end
end
return result
end
M.get_relative_file_path = get_relative_file_path
M.get_current_line_number = get_current_line_number
M.get_buffer_text = get_buffer_text
M.feature_branch_exists = feature_branch_exists
M.press_enter = press_enter
M.string_starts = string_starts
M.base_invalid = base_invalid
M.format_date = format_date
M.add_comment_sign = add_comment_sign
M.jump_to_file = jump_to_file
M.find_value_by_id = find_value_by_id
M.is_gitlab_repo = is_gitlab_repo
M.darken_metadata = darken_metadata
M.print_success = print_success
M.print_error = print_error
M.create_popup_state = create_popup_state
M.exit = exit
M.P = P
return M