2.0.0 (#196)
This MR is a #MAJOR breaking change to the plugin. While the plugin will continue to work for users with their existing settings, they will be informed of outdated configuration (diagnostics and signs have been simplified) the next time they open the reviewer. Fix: Trim trailing slash from custom URLs Update: .github/CONTRIBUTING.md, .github/ISSUE_TEMPLATE/bug_report.md Feat: Improve discussion tree toggling (#192) Fix: Toggle modified notes (#188) Fix: Toggle discussion nodes correctly Feat: Show Help keymap in discussion tree winbar Fix: Enable toggling nodes from the note body Fix: Enable toggling resolved status from child nodes Fix: Only try to show emoji popup on note nodes Feat: Add keymap for toggling tree type Fix: Disable tree type toggling in Notes Fix Multi Line Issues (Large Refactor) (#197) Fix: Multi-line discussions. The calculation of a range for a multiline comment has been consolidated and moved into the location.lua file. This does not attempt to fix diagnostics. Refactor: It refactors the discussions code to split hunk parsing and management into a separate module Fix: Don't allow comments on modified buffers #194 by preventing comments on the reviewer when using --imply-local and when the working tree is dirty entirely. Refactor: It introduces a new List class for data aggregation, filtering, etc. Fix: It removes redundant API calls and refreshes from the discussion pane Fix: Location provider (#198) Fix: add nil check for Diffview performance issue (#199) Fix: Switch Tabs During Comment Creation (#200) Fix: Check if file is modified (#201) Fix: Off-By-One Issue in Old SHA (#202) Fix: Rebuild Diagnostics + Signs (#203) Fix: Off-By-One Issue in New SHA (#205) Fix: Reviewer Jumps to wrong location (#206) BREAKING CHANGE: Changes configuration of diagnostics and signs in the setup call.
This commit is contained in:
committed by
GitHub
parent
f6a5238d4b
commit
b5b475ce8b
@@ -1,3 +1,4 @@
|
||||
local List = require("gitlab.utils.list")
|
||||
local has_devicons, devicons = pcall(require, "nvim-web-devicons")
|
||||
local M = {}
|
||||
|
||||
@@ -28,6 +29,14 @@ M.get_last_word = function(sentence, divider)
|
||||
return words[#words] or ""
|
||||
end
|
||||
|
||||
---Returns whether a string ends with a substring
|
||||
---@param str string
|
||||
---@param ending string
|
||||
---@return boolean
|
||||
M.ends_with = function(str, ending)
|
||||
return ending == "" or str:sub(-#ending) == ending
|
||||
end
|
||||
|
||||
M.filter = function(input_table, value_to_remove)
|
||||
local resultTable = {}
|
||||
for _, v in ipairs(input_table) do
|
||||
@@ -58,6 +67,21 @@ M.merge = function(defaults, overrides)
|
||||
return vim.tbl_deep_extend("force", defaults, overrides)
|
||||
end
|
||||
|
||||
---Combines two list-like (non associative) tables, keeping values from both
|
||||
---@param t1 table The first table
|
||||
---@param ... table[] The first table
|
||||
---@return table
|
||||
M.combine = function(t1, ...)
|
||||
local result = t1
|
||||
local tables = { ... }
|
||||
for _, t in ipairs(tables) do
|
||||
for _, v in ipairs(t) do
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---Pluralizes the input word, e.g. "3 cows"
|
||||
---@param num integer The count of the item/word
|
||||
---@param word string The word to pluralize
|
||||
@@ -373,26 +397,6 @@ M.difference = function(a, b)
|
||||
return not_included
|
||||
end
|
||||
|
||||
M.jump_to_file = function(filename, line_number)
|
||||
if line_number == nil then
|
||||
line_number = 1
|
||||
end
|
||||
local bufnr = vim.fn.bufnr(filename)
|
||||
if bufnr ~= -1 then
|
||||
M.jump_to_buffer(bufnr, line_number)
|
||||
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
|
||||
|
||||
M.jump_to_buffer = function(bufnr, line_number)
|
||||
vim.cmd("buffer " .. bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
|
||||
end
|
||||
|
||||
---Get the popup view_opts
|
||||
---@param title string The string to appear on top of the popup
|
||||
---@param settings table User defined popup settings
|
||||
@@ -484,14 +488,10 @@ M.get_window_id_by_buffer_id = function(buffer_id)
|
||||
local tabpage = vim.api.nvim_get_current_tabpage()
|
||||
local windows = vim.api.nvim_tabpage_list_wins(tabpage)
|
||||
|
||||
for _, win_id in ipairs(windows) do
|
||||
return List.new(windows):find(function(win_id)
|
||||
local buf_id = vim.api.nvim_win_get_buf(win_id)
|
||||
if buf_id == buffer_id then
|
||||
return win_id
|
||||
end
|
||||
end
|
||||
|
||||
return nil -- Buffer ID not found in any window
|
||||
return buf_id == buffer_id
|
||||
end)
|
||||
end
|
||||
|
||||
M.list_files_in_folder = function(folder_path)
|
||||
@@ -507,166 +507,21 @@ M.list_files_in_folder = function(folder_path)
|
||||
|
||||
local files = {}
|
||||
if folder ~= nil then
|
||||
for _, file in ipairs(folder) do
|
||||
local file_path = folder_path .. M.path_separator .. file
|
||||
local timestamp = vim.fn.getftime(file_path)
|
||||
table.insert(files, { name = file, timestamp = timestamp })
|
||||
end
|
||||
files = List.new(folder)
|
||||
:map(function(file)
|
||||
local file_path = folder_path .. M.path_separator .. file
|
||||
local timestamp = vim.fn.getftime(file_path)
|
||||
return { name = file, timestamp = timestamp }
|
||||
end)
|
||||
:sort(function(a, b)
|
||||
return a.timestamp > b.timestamp
|
||||
end)
|
||||
:map(function(file)
|
||||
return file.name
|
||||
end)
|
||||
end
|
||||
|
||||
-- Sort the table by timestamp in descending order (newest first)
|
||||
table.sort(files, function(a, b)
|
||||
return a.timestamp > b.timestamp
|
||||
end)
|
||||
|
||||
local result = {}
|
||||
for _, file in ipairs(files) do
|
||||
table.insert(result, file.name)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---@class Hunk
|
||||
---@field old_line integer
|
||||
---@field old_range integer
|
||||
---@field new_line integer
|
||||
---@field new_range integer
|
||||
|
||||
---@class HunksAndDiff
|
||||
---@field hunks Hunk[] list of hunks
|
||||
---@field all_diff_output table The data from the git diff command
|
||||
|
||||
---Turn hunk line into Lua table
|
||||
---@param line table
|
||||
---@return Hunk|nil
|
||||
M.parse_possible_hunk_headers = function(line)
|
||||
if line:sub(1, 2) == "@@" then
|
||||
-- match:
|
||||
-- @@ -23 +23 @@ ...
|
||||
-- @@ -23,0 +23 @@ ...
|
||||
-- @@ -41,0 +42,4 @@ ...
|
||||
local old_start, old_range, new_start, new_range = line:match("@@+ %-(%d+),?(%d*) %+(%d+),?(%d*) @@+")
|
||||
|
||||
return {
|
||||
old_line = tonumber(old_start),
|
||||
old_range = tonumber(old_range) or 0,
|
||||
new_line = tonumber(new_start),
|
||||
new_range = tonumber(new_range) or 0,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---Parse git diff hunks.
|
||||
---@param file_path string Path to file.
|
||||
---@param base_branch string Git base branch of merge request.
|
||||
---@return HunksAndDiff
|
||||
M.parse_hunk_headers = function(file_path, base_branch)
|
||||
local hunks = {}
|
||||
local all_diff_output = {}
|
||||
|
||||
local Job = require("plenary.job")
|
||||
|
||||
local diff_job = Job:new({
|
||||
command = "git",
|
||||
args = { "diff", "--minimal", "--unified=0", "--no-color", base_branch, "--", file_path },
|
||||
on_exit = function(j, return_code)
|
||||
if return_code == 0 then
|
||||
all_diff_output = j:result()
|
||||
for _, line in ipairs(all_diff_output) do
|
||||
local hunk = M.parse_possible_hunk_headers(line)
|
||||
if hunk ~= nil then
|
||||
table.insert(hunks, hunk)
|
||||
end
|
||||
end
|
||||
else
|
||||
M.notify("Failed to get git diff: " .. j:stderr(), vim.log.levels.WARN)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
diff_job:sync()
|
||||
|
||||
return { hunks = hunks, all_diff_output = all_diff_output }
|
||||
end
|
||||
|
||||
---@class LineDiffInfo
|
||||
---@field old_line integer
|
||||
---@field new_line integer
|
||||
---@field in_hunk boolean
|
||||
|
||||
---Search git diff hunks to find old and new line number corresponding to target line.
|
||||
---This function does not check if target line is outside of boundaries of file.
|
||||
---@param hunks Hunk[] git diff parsed hunks.
|
||||
---@param target_line integer line number to search for - based on is_new paramter the search is
|
||||
---either in new lines or old lines of hunks.
|
||||
---@param is_new boolean whether to search for new line or old line
|
||||
---@return LineDiffInfo
|
||||
M.get_lines_from_hunks = function(hunks, target_line, is_new)
|
||||
if #hunks == 0 then
|
||||
-- If there are zero hunks, return target_line for both old and new lines
|
||||
return { old_line = target_line, new_line = target_line, in_hunk = false }
|
||||
end
|
||||
local current_new_line = 0
|
||||
local current_old_line = 0
|
||||
if is_new then
|
||||
for _, hunk in ipairs(hunks) do
|
||||
-- target line is before current hunk
|
||||
if target_line < hunk.new_line then
|
||||
return {
|
||||
old_line = current_old_line + (target_line - current_new_line),
|
||||
new_line = target_line,
|
||||
in_hunk = false,
|
||||
}
|
||||
-- target line is within the current hunk
|
||||
elseif hunk.new_line <= target_line and target_line <= (hunk.new_line + hunk.new_range) then
|
||||
-- this is interesting magic of gitlab calculation
|
||||
return {
|
||||
old_line = hunk.old_line + hunk.old_range + 1,
|
||||
new_line = target_line,
|
||||
in_hunk = true,
|
||||
}
|
||||
-- target line is after the current hunk
|
||||
else
|
||||
current_new_line = hunk.new_line + hunk.new_range
|
||||
current_old_line = hunk.old_line + hunk.old_range
|
||||
end
|
||||
end
|
||||
-- target line is after last hunk
|
||||
return {
|
||||
old_line = current_old_line + (target_line - current_new_line),
|
||||
new_line = target_line,
|
||||
in_hunk = false,
|
||||
}
|
||||
else
|
||||
for _, hunk in ipairs(hunks) do
|
||||
-- target line is before current hunk
|
||||
if target_line < hunk.old_line then
|
||||
return {
|
||||
old_line = target_line,
|
||||
new_line = current_new_line + (target_line - current_old_line),
|
||||
in_hunk = false,
|
||||
}
|
||||
-- target line is within the current hunk
|
||||
elseif hunk.old_line <= target_line and target_line <= (hunk.old_line + hunk.old_range) then
|
||||
return {
|
||||
old_line = target_line,
|
||||
new_line = hunk.new_line,
|
||||
in_hunk = true,
|
||||
}
|
||||
-- target line is after the current hunk
|
||||
else
|
||||
current_new_line = hunk.new_line + hunk.new_range
|
||||
current_old_line = hunk.old_line + hunk.old_range
|
||||
end
|
||||
end
|
||||
-- target line is after last hunk
|
||||
return {
|
||||
old_line = current_old_line + (target_line - current_new_line),
|
||||
new_line = target_line,
|
||||
in_hunk = false,
|
||||
}
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
---Check if current mode is visual mode
|
||||
@@ -707,6 +562,14 @@ M.get_icon = function(filename)
|
||||
end
|
||||
end
|
||||
|
||||
---Return content between start_line and end_line
|
||||
---@param start_line integer
|
||||
---@param end_line integer
|
||||
---@return string[]
|
||||
M.get_lines = function(start_line, end_line)
|
||||
return vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
|
||||
end
|
||||
|
||||
M.make_comma_separated_readable = function(str)
|
||||
return string.gsub(str, ",", ", ")
|
||||
end
|
||||
@@ -731,7 +594,7 @@ M.get_all_git_branches = function(remote)
|
||||
end
|
||||
handle:close()
|
||||
else
|
||||
print("Error running 'git branch' command.")
|
||||
M.notify("Error running 'git branch' command.", vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
return branches
|
||||
@@ -753,4 +616,11 @@ M.open_in_browser = function(url)
|
||||
end
|
||||
end
|
||||
|
||||
---Trims the trailing slash from a URL
|
||||
---@param s string
|
||||
---@return string
|
||||
M.trim_slash = function(s)
|
||||
return (s:gsub("/+$", ""))
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
74
lua/gitlab/utils/list.lua
Normal file
74
lua/gitlab/utils/list.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
local List = {}
|
||||
List.__index = List
|
||||
|
||||
function List.new(t)
|
||||
local list = t or {}
|
||||
setmetatable(list, List)
|
||||
return list
|
||||
end
|
||||
|
||||
---Mutates a given list
|
||||
---@generic T
|
||||
---@param func fun(v: T):T
|
||||
---@return List<T> @Returns a new list of elements mutated by func
|
||||
function List:map(func)
|
||||
local result = List.new()
|
||||
for _, v in ipairs(self) do
|
||||
table.insert(result, func(v))
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---Filters a given list
|
||||
---@generic T
|
||||
---@param func fun(v: T):boolean
|
||||
---@return List<T> @Returns a new list of elements for which func returns true
|
||||
function List:filter(func)
|
||||
local result = List.new()
|
||||
for _, v in ipairs(self) do
|
||||
if func(v) == true then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function List:reduce(func, agg)
|
||||
for i, v in ipairs(self) do
|
||||
agg = func(agg, v, i)
|
||||
end
|
||||
return agg
|
||||
end
|
||||
|
||||
function List:sort(func)
|
||||
local result = List.new(self)
|
||||
table.sort(result, func)
|
||||
return result
|
||||
end
|
||||
|
||||
function List:find(func)
|
||||
for _, v in ipairs(self) do
|
||||
if func(v) == true then
|
||||
return v
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function List:slice(first, last, step)
|
||||
local sliced = List.new()
|
||||
for i = first or 1, last or #self, step or 1 do
|
||||
sliced[#sliced + 1] = self[i]
|
||||
end
|
||||
return sliced
|
||||
end
|
||||
|
||||
function List:values()
|
||||
local result = {}
|
||||
for _, v in ipairs(self) do
|
||||
table.insert(result, v)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
return List
|
||||
Reference in New Issue
Block a user