Files
gitlab.nvim/lua/gitlab/reviewer/diffview.lua
johnybx 58c3dcc9ec Discussion sign and diagnostics (#78)
This MR adds support for in-line comments in the review pane. This allows you to view comments (as diagnostics) directly in the Neovim buffers that you are reviewing. You can then jump to them directly in the discussion tree if you want to reply, edit, and so forth.
2023-11-13 09:06:04 -05:00

244 lines
8.0 KiB
Lua

-- This Module contains all of the reviewer code for diffview
local u = require("gitlab.utils")
local state = require("gitlab.state")
local async_ok, async = pcall(require, "diffview.async")
local diffview_lib = require("diffview.lib")
local M = {
bufnr = nil,
tabnr = nil,
}
M.open = function()
vim.api.nvim_command(string.format("DiffviewOpen %s", state.INFO.target_branch))
M.tabnr = vim.api.nvim_get_current_tabpage()
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.close", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewViewClosed" },
group = group,
callback = function()
--Check if our diffview tab was closed
if vim.api.nvim_tabpage_is_valid(M.tabnr) then
M.tabnr = nil
end
end,
})
end
M.jump = function(file_name, new_line, old_line)
if M.tabnr == nil then
u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
return
end
vim.api.nvim_set_current_tabpage(M.tabnr)
vim.cmd("DiffviewFocusFiles")
local view = diffview_lib.get_current_view()
if view == nil then
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
local files = view.panel:ordered_file_list()
local layout = view.cur_layout
for _, file in ipairs(files) do
if file.path == file_name then
if not async_ok then
u.notify("Could not load Diffview async", vim.log.levels.ERROR)
return
end
async.await(view:set_file(file))
if new_line ~= nil then
layout.b:focus()
vim.api.nvim_win_set_cursor(0, { tonumber(new_line), 0 })
elseif old_line ~= nil then
layout.a:focus()
vim.api.nvim_win_set_cursor(0, { tonumber(old_line), 0 })
end
break
end
end
end
---Get the location of a line within the diffview. If range is specified, then also the location
---of the lines in range.
---@param range LineRange | nil Line range to get location for
---@return ReviewerInfo | nil nil is returned only if error was encountered
M.get_location = function(range)
if M.tabnr == nil then
u.notify("Diffview reviewer must be initialized first")
return
end
local bufnr = vim.api.nvim_get_current_buf()
local current_line = vim.api.nvim_win_get_cursor(0)[1]
-- check if we are in the diffview tab
local tabnr = vim.api.nvim_get_current_tabpage()
if tabnr ~= M.tabnr then
u.notify("Line location can only be determined within reviewer window")
return
end
-- check if we are in the diffview buffer
local view = diffview_lib.get_current_view()
if view == nil then
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
return
end
local layout = view.cur_layout
local result = {}
local type
local is_new
if layout.a.file.bufnr == bufnr then
result.file_name = layout.a.file.path
result.old_line = current_line
type = "old"
is_new = false
elseif layout.b.file.bufnr == bufnr then
result.file_name = layout.b.file.path
result.new_line = current_line
type = "new"
is_new = true
else
u.notify("Line location can only be determined within reviewer window")
return
end
local hunks = u.parse_hunk_headers(result.file_name, state.INFO.target_branch)
if hunks == nil then
u.notify("Could not parse hunks", vim.log.levels.ERROR)
return
end
local current_line_info
if is_new then
current_line_info = u.get_lines_from_hunks(hunks, result.new_line, is_new)
else
current_line_info = u.get_lines_from_hunks(hunks, result.old_line, is_new)
end
-- If single line comment is outside of changed lines then we need to specify both new line and old line
-- otherwise the API returns error.
-- https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff
if not current_line_info.in_hunk then
result.old_line = current_line_info.old_line
result.new_line = current_line_info.new_line
end
if range == nil then
return result
end
result.range_info = { start = {}, ["end"] = {} }
if current_line == range.start_line then
result.range_info.start.old_line = current_line_info.old_line
result.range_info.start.new_line = current_line_info.new_line
result.range_info.start.type = type
else
local start_line_info = u.get_lines_from_hunks(hunks, range.start_line, is_new)
result.range_info.start.old_line = start_line_info.old_line
result.range_info.start.new_line = start_line_info.new_line
result.range_info.start.type = type
end
if current_line == range.end_line then
result.range_info["end"].old_line = current_line_info.old_line
result.range_info["end"].new_line = current_line_info.new_line
result.range_info["end"].type = type
else
local end_line_info = u.get_lines_from_hunks(hunks, range.end_line, is_new)
result.range_info["end"].old_line = end_line_info.old_line
result.range_info["end"].new_line = end_line_info.new_line
result.range_info["end"].type = type
end
return result
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
---Get currently shown file
M.get_current_file = function()
local view = diffview_lib.get_current_view()
if not view then
return
end
return view.panel.cur_file.path
end
---Place a sign in currently reviewed file. Use new line for identifing lines after changes, old
---line for identifing lines before changes and both if line was not changed.
---@param signs table table of signs. See :h sign_placelist
---@param type string "new" if diagnostic should be in file after changes else "old"
M.place_sign = function(signs, type)
local view = diffview_lib.get_current_view()
if not view then
return
end
if type == "new" then
for _, sign in ipairs(signs) do
sign.buffer = view.cur_layout.b.file.bufnr
end
elseif type == "old" then
for _, sign in ipairs(signs) do
sign.buffer = view.cur_layout.a.file.bufnr
end
end
vim.fn.sign_placelist(signs)
end
---Set diagnostics in currently reviewed file.
---@param namespace integer namespace for diagnostics
---@param diagnostics table see :h vim.diagnostic.set
---@param type string "new" if diagnostic should be in file after changes else "old"
---@param opts table? see :h vim.diagnostic.set
M.set_diagnostics = function(namespace, diagnostics, type, opts)
local view = diffview_lib.get_current_view()
if not view then
return
end
if type == "new" and view.cur_layout.b.file.bufnr then
vim.diagnostic.set(namespace, view.cur_layout.b.file.bufnr, diagnostics, opts)
elseif type == "old" and view.cur_layout.a.file.bufnr then
vim.diagnostic.set(namespace, view.cur_layout.a.file.bufnr, diagnostics, opts)
else
vim.notify("Unknown diagnostic type", vim.log.levels.ERROR)
end
end
---Diffview exposes events which can be used to setup autocommands.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_file_changed = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.file_changed", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewDiffBufWinEnter", "DiffviewViewEnter" },
group = group,
callback = function(...)
if M.tabnr == vim.api.nvim_get_current_tabpage() then
callback(...)
end
end,
})
end
---Diffview exposes events which can be used to setup autocommands.
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
M.set_callback_for_reviewer_leave = function(callback)
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.leave", {})
vim.api.nvim_create_autocmd("User", {
pattern = { "DiffviewViewLeave", "DiffviewViewClosed" },
group = group,
callback = function(...)
if M.tabnr == vim.api.nvim_get_current_tabpage() then
callback(...)
end
end,
})
end
return M