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.
This commit is contained in:
65
README.md
65
README.md
@@ -20,10 +20,10 @@ https://github.com/harrisoncramer/gitlab.nvim/assets/32515581/50f44eaf-5f99-4cb3
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
1. Install Go
|
1. Install Go
|
||||||
3. Add configuration (see Installation section)
|
2. Add configuration (see Installation section)
|
||||||
4. Checkout your feature branch: `git checkout feature-branch`
|
3. Checkout your feature branch: `git checkout feature-branch`
|
||||||
5. Open Neovim
|
4. Open Neovim
|
||||||
6. Run `:lua require("gitlab").review()` to open the reviewer pane
|
5. Run `:lua require("gitlab").review()` to open the reviewer pane
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -112,6 +112,36 @@ require("gitlab").setup({
|
|||||||
resolved = '✓', -- Symbol to show next to resolved discussions
|
resolved = '✓', -- Symbol to show next to resolved discussions
|
||||||
unresolved = '✖', -- Symbol to show next to unresolved discussions
|
unresolved = '✖', -- Symbol to show next to unresolved discussions
|
||||||
},
|
},
|
||||||
|
discussion_sign_and_diagnostic = {
|
||||||
|
skip_resolved_discussion = false,
|
||||||
|
skip_old_revision_discussion = true,
|
||||||
|
},
|
||||||
|
discussion_sign = {
|
||||||
|
-- See :h sign_define for details about sign configuration.
|
||||||
|
enabled = true,
|
||||||
|
text = "💬",
|
||||||
|
linehl = nil,
|
||||||
|
texthl = nil,
|
||||||
|
culhl = nil,
|
||||||
|
numhl = nil,
|
||||||
|
priority = 20, -- Priority of sign, the lower the number the higher the priority
|
||||||
|
helper_signs = {
|
||||||
|
-- For multiline comments the helper signs are used to indicate the whole context
|
||||||
|
-- Priority of helper signs is lower than the main sign (-1).
|
||||||
|
enabled = true,
|
||||||
|
start = "↑",
|
||||||
|
mid = "|",
|
||||||
|
["end"] = "↓",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
discussion_diagnostic = {
|
||||||
|
-- If you want to customize diagnostics for discussions you can make special config
|
||||||
|
-- for namespace `gitlab_discussion`. See :h vim.diagnostic.config
|
||||||
|
enabled = true,
|
||||||
|
severity = vim.diagnostic.severity.INFO,
|
||||||
|
code = nil, -- see :h diagnostic-structure
|
||||||
|
display_opts = {}, -- see opts in vim.diagnostic.set
|
||||||
|
},
|
||||||
pipeline = {
|
pipeline = {
|
||||||
created = "",
|
created = "",
|
||||||
pending = "",
|
pending = "",
|
||||||
@@ -188,6 +218,32 @@ If you'd like to create a note in an MR (like a comment, but not linked to a spe
|
|||||||
require("gitlab").create_note()
|
require("gitlab").create_note()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Discussions signs and diagnostics
|
||||||
|
|
||||||
|
By default when reviewing files you will see signs and diagnostics ( if enabled in configuration ). When cursor is on diagnostic line you can view discussion thread by using `vim.diagnostic.show`. You can also jump to discussion tree where you can reply, edit or delete discussion.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
require("gitlab").move_to_discussion_tree_from_diagnostic()
|
||||||
|
```
|
||||||
|
|
||||||
|
The `discussion_sign` configuration controls the display of signs for discussions. The `enabled` option turns on/off the signs. `text` sets the sign text. `linehl`, `texthl`, `culhl`, and `numhl` customize the line highlighting, text highlighting, column highlighting, and number highlighting respectively. Keep in mind that these can be overridden by other configuration (for example diffview.nvim highlights). `priority` controls the sign priority order (when multiple signs are placed on the same line, the sign with highest priority is used). The `helper_signs` table configures additional signs for multiline discussions in order to show the whole context. `enabled` turns on/off the helper signs. `start`, `mid`, and `end` set the helper sign text.
|
||||||
|
|
||||||
|
The `discussion_diagnostic` configuration customizes the diagnostic display for discussions. The `enabled` option turns on/off the diagnostics. `severity` sets the diagnostic severity level and should be set to one of `vim.diagnostic.severity.ERROR`, `vim.diagnostic.severity.WARN`, or `vim.diagnostic.severity.INFO`, `vim.diagnostic.severity.HINT`. `code` specifies a diagnostic code. `display_opts` configures the diagnostic display options where you can configure values like (this is dirrectly used as opts in vim.diagnostic.set):
|
||||||
|
|
||||||
|
- `virtual_text` - Show virtual text for diagnostics.
|
||||||
|
- `underline` - Underline text for diagnostics.
|
||||||
|
|
||||||
|
Diagnostics for discussions use the `gitlab_discussion` namespace. See `:h vim.diagnostic.config` and `:h diagnostic-structure` for more details.
|
||||||
|
|
||||||
|
Signs and diagnostics have common settings in `discussion_sign_and_diagnostics`. This allows customizing if discussions that are resolved or no longer relevant should still display visual indicators in the editor:
|
||||||
|
|
||||||
|
- `skip_resolved_discussion` - Whether to skip showing signs and diagnostics for resolved discussions. Default is `false`, meaning signs and diagnostics will be shown for resolved discussions.
|
||||||
|
- `skip_old_revision_discussion` - Whether to skip showing signs and diagnostics for discussions on outdated diff revisions. Default is `true`, meaning signs and diagnostics won't be shown for discussions no longer relevant to the current diff.
|
||||||
|
|
||||||
|
#### Limitations
|
||||||
|
|
||||||
|
When checking multiline diagnostic the cursor must be on the "main" line of diagnostic -> where the `discussion_sign.text` is shown otherwise `vim.diagnostic.show` and `jump_to_discussion_tree_from_diagnostic` will not work.
|
||||||
|
|
||||||
### Uploading Files
|
### Uploading Files
|
||||||
|
|
||||||
To attach a file to an MR description, reply, comment, and so forth use the `settings.popup.perform_linewise_action` keybinding when the the popup is open. This will open a picker that will look in the directory you specify in the `settings.attachment_dir` folder (this must be an absolute path) for files.
|
To attach a file to an MR description, reply, comment, and so forth use the `settings.popup.perform_linewise_action` keybinding when the the popup is open. This will open a picker that will look in the directory you specify in the `settings.attachment_dir` folder (this must be an absolute path) for files.
|
||||||
@@ -249,6 +305,7 @@ vim.keymap.set("n", "<leader>glR", gitlab.revoke)
|
|||||||
vim.keymap.set("n", "<leader>glc", gitlab.create_comment)
|
vim.keymap.set("n", "<leader>glc", gitlab.create_comment)
|
||||||
vim.keymap.set("v", "<leader>glc", gitlab.create_multiline_comment)
|
vim.keymap.set("v", "<leader>glc", gitlab.create_multiline_comment)
|
||||||
vim.keymap.set("v", "<leader>glC", gitlab.create_comment_suggestion)
|
vim.keymap.set("v", "<leader>glC", gitlab.create_comment_suggestion)
|
||||||
|
vim.keymap.set("n", "<leader>glm", gitlab.move_to_discussion_tree_from_diagnostic)
|
||||||
vim.keymap.set("n", "<leader>gln", gitlab.create_note)
|
vim.keymap.set("n", "<leader>gln", gitlab.create_note)
|
||||||
vim.keymap.set("n", "<leader>gld", gitlab.toggle_discussions)
|
vim.keymap.set("n", "<leader>gld", gitlab.toggle_discussions)
|
||||||
vim.keymap.set("n", "<leader>glaa", gitlab.add_assignee)
|
vim.keymap.set("n", "<leader>glaa", gitlab.add_assignee)
|
||||||
|
|||||||
14
cmd/git.go
14
cmd/git.go
@@ -34,7 +34,19 @@ func ExtractGitInfo(getProjectRemoteUrl func() (string, error), getCurrentBranch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// play with regex at: https://regex101.com/r/P2jSGh/1
|
// play with regex at: https://regex101.com/r/P2jSGh/1
|
||||||
re := regexp.MustCompile(`(?:^git@.+:|^https?:\/\/.+?[^\/:]\/)(.+)\/([^\/]+)\.git$`)
|
/*
|
||||||
|
This should match following formats:
|
||||||
|
namespace: namespace, projectName: dummy-test-repo:
|
||||||
|
https://gitlab.com/namespace/dummy-test-repo.git
|
||||||
|
git@gitlab.com:namespace/dummy-test-repo.git
|
||||||
|
ssh://git@gitlab.com/namespace/dummy-test-repo.git
|
||||||
|
|
||||||
|
namespace: namespace/subnamespace, projectName: dummy-test-repo:
|
||||||
|
ssh://git@gitlab.com/namespace/subnamespace/dummy-test-repo
|
||||||
|
https://git@gitlab.com/namespace/subnamespace/dummy-test-repo.git
|
||||||
|
git@git@gitlab.com:namespace/subnamespace/dummy-test-repo.git
|
||||||
|
*/
|
||||||
|
re := regexp.MustCompile(`(?:^https?:\/\/|^ssh:\/\/|^git@)(?:[^\/:]+)[\/:](.*)\/([^\/]+?)(?:\.git)?$`)
|
||||||
matches := re.FindStringSubmatch(url)
|
matches := re.FindStringSubmatch(url)
|
||||||
if len(matches) != 3 {
|
if len(matches) != 3 {
|
||||||
return GitProjectInfo{}, fmt.Errorf("Invalid Git URL format: %s", url)
|
return GitProjectInfo{}, fmt.Errorf("Invalid Git URL format: %s", url)
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ M.confirm_create_comment = function(text, range, unlinked)
|
|||||||
job.run_job("/comment", "POST", body, function(data)
|
job.run_job("/comment", "POST", body, function(data)
|
||||||
u.notify("Comment created!", vim.log.levels.INFO)
|
u.notify("Comment created!", vim.log.levels.INFO)
|
||||||
discussions.add_discussion({ data = data, unlinked = false })
|
discussions.add_discussion({ data = data, unlinked = false })
|
||||||
|
discussions.refresh_discussion_data()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -13,33 +13,443 @@ local miscellaneous = require("gitlab.actions.miscellaneous")
|
|||||||
|
|
||||||
local edit_popup = Popup(u.create_popup_state("Edit Comment", "80%", "80%"))
|
local edit_popup = Popup(u.create_popup_state("Edit Comment", "80%", "80%"))
|
||||||
local reply_popup = Popup(u.create_popup_state("Reply", "80%", "80%"))
|
local reply_popup = Popup(u.create_popup_state("Reply", "80%", "80%"))
|
||||||
|
local discussion_sign_name = "gitlab_discussion"
|
||||||
|
local discussion_helper_sign_start = "gitlab_discussion_helper_start"
|
||||||
|
local discussion_helper_sign_mid = "gitlab_discussion_helper_mid"
|
||||||
|
local discussion_helper_sign_end = "gitlab_discussion_helper_end"
|
||||||
|
local diagnostics_namespace = vim.api.nvim_create_namespace(discussion_sign_name)
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
layout_visible = false,
|
layout_visible = false,
|
||||||
layout = nil,
|
layout = nil,
|
||||||
layout_buf = nil,
|
layout_buf = nil,
|
||||||
|
---@type Discussion[]
|
||||||
discussions = {},
|
discussions = {},
|
||||||
|
---@type UnlinkedDiscussion[]
|
||||||
unlinked_discussions = {},
|
unlinked_discussions = {},
|
||||||
linked_section_bufnr = -1,
|
linked_section = nil,
|
||||||
unlinked_section_bufnr = -1,
|
unlinked_section = nil,
|
||||||
|
discussion_tree = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Opens the discussion tree, sets the keybindings. It also
|
---@class Author
|
||||||
-- creates the tree for notes (which are not linked to specific lines of code)
|
---@field id integer
|
||||||
M.toggle = function()
|
---@field username string
|
||||||
|
---@field email string
|
||||||
|
---@field name string
|
||||||
|
---@field state string
|
||||||
|
---@field avatar_url string
|
||||||
|
---@field web_url string
|
||||||
|
|
||||||
|
---@class LinePosition
|
||||||
|
---@field line_code string
|
||||||
|
---@field type string
|
||||||
|
|
||||||
|
---@class GitlabLineRange
|
||||||
|
---@field start LinePosition
|
||||||
|
---@field end LinePosition
|
||||||
|
|
||||||
|
---@class NotePosition
|
||||||
|
---@field base_sha string
|
||||||
|
---@field start_sha string
|
||||||
|
---@field head_sha string
|
||||||
|
---@field position_type string
|
||||||
|
---@field new_path string?
|
||||||
|
---@field new_line integer?
|
||||||
|
---@field old_path string?
|
||||||
|
---@field old_line integer?
|
||||||
|
---@field line_range GitlabLineRange?
|
||||||
|
|
||||||
|
---@class Note
|
||||||
|
---@field id integer
|
||||||
|
---@field type string
|
||||||
|
---@field body string
|
||||||
|
---@field attachment string
|
||||||
|
---@field title string
|
||||||
|
---@field file_name string
|
||||||
|
---@field author Author
|
||||||
|
---@field system boolean
|
||||||
|
---@field expires_at string?
|
||||||
|
---@field updated_at string?
|
||||||
|
---@field created_at string?
|
||||||
|
---@field noteable_id integer
|
||||||
|
---@field noteable_type string
|
||||||
|
---@field commit_id string
|
||||||
|
---@field position NotePosition
|
||||||
|
---@field resolvable boolean
|
||||||
|
---@field resolved boolean
|
||||||
|
---@field resolved_by Author
|
||||||
|
---@field resolved_at string?
|
||||||
|
---@field noteable_iid integer
|
||||||
|
|
||||||
|
---@class UnlinkedNote: Note
|
||||||
|
---@field position nil
|
||||||
|
|
||||||
|
---@class Discussion
|
||||||
|
---@field id string
|
||||||
|
---@field individual_note boolean
|
||||||
|
---@field notes Note[]
|
||||||
|
|
||||||
|
---@class UnlinkedDiscussion: Discussion
|
||||||
|
---@field notes UnlinkedNote[]
|
||||||
|
|
||||||
|
---@class DiscussionData
|
||||||
|
---@field discussions Discussion[]
|
||||||
|
---@field unlinked_discussions UnlinkedDiscussion[]
|
||||||
|
|
||||||
|
---Load the discussion data, storage them in M.discussions and M.unlinked_discussions and call
|
||||||
|
---callback with data
|
||||||
|
---@param callback fun(data: DiscussionData): nil
|
||||||
|
M.load_discussions = function(callback)
|
||||||
|
job.run_job("/discussions", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
|
||||||
|
M.discussions = data.discussions
|
||||||
|
M.unlinked_discussions = data.unlinked_discussions
|
||||||
|
callback(data)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Parse line code and return old and new line numbers
|
||||||
|
---@param line_code string gitlab line code -> 588440f66559714280628a4f9799f0c4eb880a4a_10_10
|
||||||
|
---@return number?
|
||||||
|
---@return number?
|
||||||
|
local function _parse_line_code(line_code)
|
||||||
|
local line_code_regex = "%w+_(%d+)_(%d+)"
|
||||||
|
local old_line, new_line = line_code:match(line_code_regex)
|
||||||
|
return tonumber(old_line), tonumber(new_line)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Filter all discussions which are relevant for currently visible signs and diagnostscs.
|
||||||
|
---@return Discussion[]?
|
||||||
|
M.filter_discussions_for_signs_and_diagnostics = function()
|
||||||
|
if type(M.discussions) ~= "table" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local file = reviewer.get_current_file()
|
||||||
|
if not file then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local discussions = {}
|
||||||
|
for _, discussion in ipairs(M.discussions) do
|
||||||
|
local first_note = discussion.notes[1]
|
||||||
|
if
|
||||||
|
type(first_note.position) == "table"
|
||||||
|
and (first_note.position.new_path == file or first_note.position.old_path == file)
|
||||||
|
then
|
||||||
|
if
|
||||||
|
--Skip resolved discussions
|
||||||
|
not (
|
||||||
|
state.settings.discussion_sign_and_diagnostic.skip_resolved_discussion
|
||||||
|
and first_note.resolvable
|
||||||
|
and first_note.resolved
|
||||||
|
)
|
||||||
|
--Skip discussions from old revisions
|
||||||
|
and not (
|
||||||
|
state.settings.discussion_sign_and_diagnostic.skip_old_revision_discussion
|
||||||
|
and first_note.position.base_sha ~= state.MR_REVISIONS[1].base_sha
|
||||||
|
)
|
||||||
|
then
|
||||||
|
table.insert(discussions, discussion)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return discussions
|
||||||
|
end
|
||||||
|
|
||||||
|
---Refresh the discussion signs for currently loaded file in reviewer For convinience we use same
|
||||||
|
---string for sign name and sign group ( currently there is only one sign needed)
|
||||||
|
M.refresh_signs = function()
|
||||||
|
local diagnostics = M.filter_discussions_for_signs_and_diagnostics()
|
||||||
|
if diagnostics == nil then
|
||||||
|
vim.diagnostic.reset(diagnostics_namespace)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_signs = {}
|
||||||
|
local old_signs = {}
|
||||||
|
for _, discussion in ipairs(diagnostics) do
|
||||||
|
local first_note = discussion.notes[1]
|
||||||
|
local base_sign = {
|
||||||
|
name = discussion_sign_name,
|
||||||
|
group = discussion_sign_name,
|
||||||
|
priority = state.settings.discussion_sign.priority,
|
||||||
|
}
|
||||||
|
local base_helper_sign = {
|
||||||
|
name = discussion_sign_name,
|
||||||
|
group = discussion_sign_name,
|
||||||
|
priority = state.settings.discussion_sign.priority - 1,
|
||||||
|
}
|
||||||
|
if first_note.position.line_range ~= nil then
|
||||||
|
local start_old_line, start_new_line = _parse_line_code(first_note.position.line_range.start.line_code)
|
||||||
|
local end_old_line, end_new_line = _parse_line_code(first_note.position.line_range["end"].line_code)
|
||||||
|
local discussion_line, start_line, end_line
|
||||||
|
if first_note.position.line_range.start.type == "new" then
|
||||||
|
table.insert(
|
||||||
|
new_signs,
|
||||||
|
vim.tbl_deep_extend("force", {
|
||||||
|
id = first_note.id,
|
||||||
|
lnum = first_note.position.new_line,
|
||||||
|
}, base_sign)
|
||||||
|
)
|
||||||
|
discussion_line = first_note.position.new_line
|
||||||
|
start_line = start_new_line
|
||||||
|
end_line = end_new_line
|
||||||
|
elseif first_note.position.line_range.start.type == "old" then
|
||||||
|
table.insert(
|
||||||
|
old_signs,
|
||||||
|
vim.tbl_deep_extend("force", {
|
||||||
|
id = first_note.id,
|
||||||
|
lnum = first_note.position.old_line,
|
||||||
|
}, base_sign)
|
||||||
|
)
|
||||||
|
discussion_line = first_note.position.old_line
|
||||||
|
start_line = start_old_line
|
||||||
|
end_line = end_old_line
|
||||||
|
end
|
||||||
|
-- Helper signs does not have specific ids currently.
|
||||||
|
if state.settings.discussion_sign.helper_signs.enabled then
|
||||||
|
local helper_signs = {}
|
||||||
|
if start_line > end_line then
|
||||||
|
start_line, end_line = end_line, start_line
|
||||||
|
end
|
||||||
|
for i = start_line, end_line do
|
||||||
|
if i ~= discussion_line then
|
||||||
|
local sign_name
|
||||||
|
if i == start_line then
|
||||||
|
sign_name = discussion_helper_sign_start
|
||||||
|
elseif i == end_line then
|
||||||
|
sign_name = discussion_helper_sign_end
|
||||||
|
else
|
||||||
|
sign_name = discussion_helper_sign_mid
|
||||||
|
end
|
||||||
|
table.insert(
|
||||||
|
helper_signs,
|
||||||
|
vim.tbl_deep_extend("keep", {
|
||||||
|
name = sign_name,
|
||||||
|
lnum = i,
|
||||||
|
}, base_helper_sign)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if first_note.position.line_range.start.type == "new" then
|
||||||
|
vim.list_extend(new_signs, helper_signs)
|
||||||
|
elseif first_note.position.line_range.start.type == "old" then
|
||||||
|
vim.list_extend(old_signs, helper_signs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local sign = vim.tbl_deep_extend("force", {
|
||||||
|
id = first_note.id,
|
||||||
|
}, base_sign)
|
||||||
|
if first_note.position.new_line ~= nil then
|
||||||
|
table.insert(new_signs, vim.tbl_deep_extend("force", { lnum = first_note.position.new_line }, sign))
|
||||||
|
end
|
||||||
|
if first_note.position.old_line ~= nil then
|
||||||
|
table.insert(old_signs, vim.tbl_deep_extend("force", { lnum = first_note.position.old_line }, sign))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vim.fn.sign_unplace(discussion_sign_name)
|
||||||
|
reviewer.place_sign(old_signs, "old")
|
||||||
|
reviewer.place_sign(new_signs, "new")
|
||||||
|
end
|
||||||
|
|
||||||
|
---Refresh the diagnostics for the currently reviewed file
|
||||||
|
M.refresh_diagnostics = function()
|
||||||
|
-- Keep in mind that diagnostic line numbers use 0-based indexing while line numbers use
|
||||||
|
-- 1-based indexing
|
||||||
|
local diagnostics = M.filter_discussions_for_signs_and_diagnostics()
|
||||||
|
if diagnostics == nil then
|
||||||
|
vim.diagnostic.reset(diagnostics_namespace)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_diagnostics = {}
|
||||||
|
local old_diagnostics = {}
|
||||||
|
for _, discussion in ipairs(diagnostics) do
|
||||||
|
local first_note = discussion.notes[1]
|
||||||
|
local message = ""
|
||||||
|
for _, note in ipairs(discussion.notes) do
|
||||||
|
message = message .. M.build_note_header(note) .. "\n" .. note.body .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
local diagnostic = {
|
||||||
|
message = message,
|
||||||
|
col = 0,
|
||||||
|
severity = state.settings.discussion_diagnostic.severity,
|
||||||
|
user_data = { discussion_id = discussion.id, header = M.build_note_header(discussion.notes[1]) },
|
||||||
|
source = "gitlab",
|
||||||
|
code = state.settings.discussion_diagnostic.code,
|
||||||
|
}
|
||||||
|
if first_note.position.line_range ~= nil then
|
||||||
|
-- Diagnostics for line range discussions are tricky - you need to set lnum to
|
||||||
|
-- line number equal to note.position.new_line or note.position.old_line because that is
|
||||||
|
-- only line where you can trigger the diagnostic show. This also need to be in sinc
|
||||||
|
-- with the sign placement.
|
||||||
|
local start_old_line, start_new_line = _parse_line_code(first_note.position.line_range.start.line_code)
|
||||||
|
local end_old_line, end_new_line = _parse_line_code(first_note.position.line_range["end"].line_code)
|
||||||
|
if first_note.position.line_range.start.type == "new" then
|
||||||
|
local new_diagnostic
|
||||||
|
if first_note.position.new_line == start_new_line then
|
||||||
|
new_diagnostic = {
|
||||||
|
lnum = start_new_line - 1,
|
||||||
|
end_lnum = end_new_line - 1,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
new_diagnostic = {
|
||||||
|
lnum = end_new_line - 1,
|
||||||
|
end_lnum = start_new_line - 1,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
new_diagnostic = vim.tbl_deep_extend("force", new_diagnostic, diagnostic)
|
||||||
|
table.insert(new_diagnostics, new_diagnostic)
|
||||||
|
elseif first_note.position.line_range.start.type == "old" then
|
||||||
|
local old_diagnostic
|
||||||
|
if first_note.position.old_line == start_old_line then
|
||||||
|
old_diagnostic = {
|
||||||
|
lnum = start_old_line - 1,
|
||||||
|
end_lnum = end_old_line - 1,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
old_diagnostic = {
|
||||||
|
lnum = end_old_line - 1,
|
||||||
|
end_lnum = start_old_line - 1,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
old_diagnostic = vim.tbl_deep_extend("force", old_diagnostic, diagnostic)
|
||||||
|
table.insert(old_diagnostics, old_diagnostic)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Diagnostics for single line discussions.
|
||||||
|
if first_note.position.new_line ~= nil then
|
||||||
|
local new_diagnostic = {
|
||||||
|
lnum = first_note.position.new_line - 1,
|
||||||
|
}
|
||||||
|
new_diagnostic = vim.tbl_deep_extend("force", new_diagnostic, diagnostic)
|
||||||
|
table.insert(new_diagnostics, new_diagnostic)
|
||||||
|
end
|
||||||
|
if first_note.position.old_line ~= nil then
|
||||||
|
local old_diagnostic = {
|
||||||
|
lnum = first_note.position.old_line - 1,
|
||||||
|
}
|
||||||
|
old_diagnostic = vim.tbl_deep_extend("force", old_diagnostic, diagnostic)
|
||||||
|
table.insert(old_diagnostics, old_diagnostic)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.diagnostic.reset(diagnostics_namespace)
|
||||||
|
reviewer.set_diagnostics(
|
||||||
|
diagnostics_namespace,
|
||||||
|
new_diagnostics,
|
||||||
|
"new",
|
||||||
|
state.settings.discussion_diagnostic.display_opts
|
||||||
|
)
|
||||||
|
reviewer.set_diagnostics(
|
||||||
|
diagnostics_namespace,
|
||||||
|
old_diagnostics,
|
||||||
|
"old",
|
||||||
|
state.settings.discussion_diagnostic.display_opts
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Refresh discussion data, discussion signs and diagnostics
|
||||||
|
M.refresh_discussion_data = function()
|
||||||
|
M.load_discussions(function()
|
||||||
|
if state.settings.discussion_sign.enabled then
|
||||||
|
M.refresh_signs()
|
||||||
|
end
|
||||||
|
if state.settings.discussion_diagnostic.enabled then
|
||||||
|
M.refresh_diagnostics()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Define signs for discussions if not already defined
|
||||||
|
M.setup_signs = function()
|
||||||
|
local discussion_sign = state.settings.discussion_sign
|
||||||
|
local signs = {
|
||||||
|
[discussion_sign_name] = discussion_sign.text,
|
||||||
|
[discussion_helper_sign_start] = discussion_sign.helper_signs.start,
|
||||||
|
[discussion_helper_sign_mid] = discussion_sign.helper_signs.mid,
|
||||||
|
[discussion_helper_sign_end] = discussion_sign.helper_signs["end"],
|
||||||
|
}
|
||||||
|
for sign_name, sign_text in pairs(signs) do
|
||||||
|
if #vim.fn.sign_getdefined(sign_name) == 0 then
|
||||||
|
vim.fn.sign_define(sign_name, {
|
||||||
|
text = sign_text,
|
||||||
|
linehl = discussion_sign.linehl,
|
||||||
|
texthl = discussion_sign.texthl,
|
||||||
|
culhl = discussion_sign.culhl,
|
||||||
|
numhl = discussion_sign.numhl,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Initialize everything for discussions like setup of signs, callbacks for reviewer, etc.
|
||||||
|
M.initialize_discussions = function()
|
||||||
|
M.setup_signs()
|
||||||
|
M.setup_refresh_discussion_data_callback()
|
||||||
|
M.setup_leave_reviewer_callback()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Setup callback to refresh discussion data, discussion signs and diagnostics whenever the
|
||||||
|
---reviewed file changes.
|
||||||
|
M.setup_refresh_discussion_data_callback = function()
|
||||||
|
reviewer.set_callback_for_file_changed(M.refresh_discussion_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Clear all signs and diagnostics
|
||||||
|
M.clear_signs_and_discussions = function()
|
||||||
|
vim.fn.sign_unplace(discussion_sign_name)
|
||||||
|
vim.diagnostic.reset(diagnostics_namespace)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Setup callback to clear signs and diagnostics whenever reviewer is left.
|
||||||
|
M.setup_leave_reviewer_callback = function()
|
||||||
|
reviewer.set_callback_for_reviewer_leave(M.clear_signs_and_discussions)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.refresh_discussion_tree = function()
|
||||||
|
if M.layout_visible == false then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(M.discussions) == "table" then
|
||||||
|
M.rebuild_discussion_tree()
|
||||||
|
end
|
||||||
|
if type(M.unlinked_discussions) == "table" then
|
||||||
|
M.rebuild_unlinked_discussion_tree()
|
||||||
|
end
|
||||||
|
|
||||||
|
M.switch_can_edit_bufs(true)
|
||||||
|
M.add_empty_titles({
|
||||||
|
{ M.linked_section.bufnr, M.discussions, "No Discussions for this MR" },
|
||||||
|
{ M.unlinked_section.bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
|
||||||
|
})
|
||||||
|
M.switch_can_edit_bufs(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Opens the discussion tree, sets the keybindings. It also
|
||||||
|
---creates the tree for notes (which are not linked to specific lines of code)
|
||||||
|
---@param callback function?
|
||||||
|
M.toggle = function(callback)
|
||||||
if M.layout_visible then
|
if M.layout_visible then
|
||||||
M.layout:unmount()
|
M.layout:unmount()
|
||||||
M.layout_visible = false
|
M.layout_visible = false
|
||||||
|
M.discussion_tree = nil
|
||||||
|
M.linked_section = nil
|
||||||
|
M.unlinked_section = nil
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local linked_section, unlinked_section, layout = M.create_layout()
|
local linked_section, unlinked_section, layout = M.create_layout()
|
||||||
M.linked_section_bufnr = linked_section.bufnr
|
M.linked_section = linked_section
|
||||||
M.unlinked_section_bufnr = unlinked_section.bufnr
|
M.unlinked_section = unlinked_section
|
||||||
|
|
||||||
job.run_job("/discussions", "POST", { blacklist = state.settings.discussion_tree.blacklist }, function(data)
|
M.load_discussions(function()
|
||||||
if type(data.discussions) ~= "table" and type(data.unlinked_discussions) ~= "table" then
|
if type(M.discussions) ~= "table" and type(M.unlinked_discussions) ~= "table" then
|
||||||
u.notify("No discussions or notes for this MR", vim.log.levels.WARN)
|
vim.notify("No discussions or notes for this MR", vim.log.levels.WARN)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -50,27 +460,67 @@ M.toggle = function()
|
|||||||
M.layout_visible = true
|
M.layout_visible = true
|
||||||
M.layout_buf = layout.bufnr
|
M.layout_buf = layout.bufnr
|
||||||
state.discussion_buf = layout.bufnr
|
state.discussion_buf = layout.bufnr
|
||||||
|
M.refresh_discussion_tree()
|
||||||
M.discussions = data.discussions
|
if type(callback) == "function" then
|
||||||
M.unlinked_discussions = data.unlinked_discussions
|
callback()
|
||||||
|
|
||||||
if type(data.discussions) == "table" then
|
|
||||||
M.rebuild_discussion_tree()
|
|
||||||
end
|
end
|
||||||
if type(data.unlinked_discussions) == "table" then
|
|
||||||
M.rebuild_unlinked_discussion_tree()
|
|
||||||
end
|
|
||||||
|
|
||||||
M.switch_can_edit_bufs(true)
|
|
||||||
M.add_empty_titles({
|
|
||||||
{ linked_section.bufnr, data.discussions, "No Discussions for this MR" },
|
|
||||||
{ unlinked_section.bufnr, data.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
|
|
||||||
})
|
|
||||||
|
|
||||||
M.switch_can_edit_bufs(false)
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Move to the discussion tree at the discussion from diagnostic on current line.
|
||||||
|
M.move_to_discussion_tree = function()
|
||||||
|
local current_line = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local diagnostics = vim.diagnostic.get(0, { namespace = diagnostics_namespace, lnum = current_line - 1 })
|
||||||
|
|
||||||
|
---Function used to jump to the discussion tree after the menu selection.
|
||||||
|
local jump_after_menu_selection = function(diagnostic)
|
||||||
|
---Function used to jump to the discussion tree after the discussion tree is opened.
|
||||||
|
local jump_after_tree_opened = function()
|
||||||
|
-- All diagnostics in `diagnotics_namespace` have diagnostic_id
|
||||||
|
local discussion_id = diagnostic.user_data.discussion_id
|
||||||
|
local discussion_node, line_number = M.discussion_tree:get_node("-" .. discussion_id)
|
||||||
|
if discussion_node == {} or discussion_node == nil then
|
||||||
|
vim.notify("Discussion not found", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not discussion_node:is_expanded() then
|
||||||
|
for _, child in ipairs(discussion_node:get_child_ids()) do
|
||||||
|
M.discussion_tree:get_node(child):expand()
|
||||||
|
end
|
||||||
|
discussion_node:expand()
|
||||||
|
end
|
||||||
|
M.discussion_tree:render()
|
||||||
|
vim.api.nvim_win_set_cursor(M.linked_section.winid, { line_number, 0 })
|
||||||
|
vim.api.nvim_set_current_win(M.linked_section.winid)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not M.layout_visible then
|
||||||
|
M.toggle(jump_after_tree_opened)
|
||||||
|
else
|
||||||
|
jump_after_tree_opened()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #diagnostics == 0 then
|
||||||
|
vim.notify("No diagnostics for this line", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
elseif #diagnostics > 1 then
|
||||||
|
vim.ui.select(diagnostics, {
|
||||||
|
prompt = "Choose discussion to jump to",
|
||||||
|
format_item = function(diagnostic)
|
||||||
|
return diagnostic.message
|
||||||
|
end,
|
||||||
|
}, function(diagnostic)
|
||||||
|
if not diagnostic then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
jump_after_menu_selection(diagnostic)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
jump_after_menu_selection(diagnostics[1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- The reply popup will mount in a window when you trigger it (settings.discussion_tree.reply) when hovering over a node in the discussion tree.
|
-- The reply popup will mount in a window when you trigger it (settings.discussion_tree.reply) when hovering over a node in the discussion tree.
|
||||||
M.reply = function(tree)
|
M.reply = function(tree)
|
||||||
local node = tree:get_node()
|
local node = tree:get_node()
|
||||||
@@ -127,13 +577,13 @@ M.send_deletion = function(tree, unlinked)
|
|||||||
M.discussions = u.remove_first_value(M.discussions)
|
M.discussions = u.remove_first_value(M.discussions)
|
||||||
M.rebuild_discussion_tree()
|
M.rebuild_discussion_tree()
|
||||||
end
|
end
|
||||||
end
|
|
||||||
M.switch_can_edit_bufs(true)
|
M.switch_can_edit_bufs(true)
|
||||||
M.add_empty_titles({
|
M.add_empty_titles({
|
||||||
{ M.linked_section_bufnr, M.discussions, "No Discussions for this MR" },
|
{ M.linked_section.bufnr, M.discussions, "No Discussions for this MR" },
|
||||||
{ M.unlinked_section_bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
|
{ M.unlinked_section.bufnr, M.unlinked_discussions, "No Notes (Unlinked Discussions) for this MR" },
|
||||||
})
|
})
|
||||||
M.switch_can_edit_bufs(false)
|
M.switch_can_edit_bufs(false)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -255,31 +705,31 @@ end
|
|||||||
|
|
||||||
M.rebuild_discussion_tree = function()
|
M.rebuild_discussion_tree = function()
|
||||||
M.switch_can_edit_bufs(true)
|
M.switch_can_edit_bufs(true)
|
||||||
vim.api.nvim_buf_set_lines(M.linked_section_bufnr, 0, -1, false, {})
|
vim.api.nvim_buf_set_lines(M.linked_section.bufnr, 0, -1, false, {})
|
||||||
local discussion_tree_nodes = M.add_discussions_to_table(M.discussions)
|
local discussion_tree_nodes = M.add_discussions_to_table(M.discussions)
|
||||||
local discussion_tree = NuiTree({ nodes = discussion_tree_nodes, bufnr = M.linked_section_bufnr })
|
local discussion_tree = NuiTree({ nodes = discussion_tree_nodes, bufnr = M.linked_section.bufnr })
|
||||||
discussion_tree:render()
|
discussion_tree:render()
|
||||||
M.set_tree_keymaps(discussion_tree, M.linked_section_bufnr, false)
|
M.set_tree_keymaps(discussion_tree, M.linked_section.bufnr, false)
|
||||||
M.discussion_tree = discussion_tree
|
M.discussion_tree = discussion_tree
|
||||||
M.switch_can_edit_bufs(false)
|
M.switch_can_edit_bufs(false)
|
||||||
vim.api.nvim_buf_set_option(M.linked_section_bufnr, "filetype", "gitlab")
|
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.linked_section.bufnr })
|
||||||
end
|
end
|
||||||
|
|
||||||
M.rebuild_unlinked_discussion_tree = function()
|
M.rebuild_unlinked_discussion_tree = function()
|
||||||
M.switch_can_edit_bufs(true)
|
M.switch_can_edit_bufs(true)
|
||||||
vim.api.nvim_buf_set_lines(M.unlinked_section_bufnr, 0, -1, false, {})
|
vim.api.nvim_buf_set_lines(M.unlinked_section.bufnr, 0, -1, false, {})
|
||||||
local unlinked_discussion_tree_nodes = M.add_discussions_to_table(M.unlinked_discussions)
|
local unlinked_discussion_tree_nodes = M.add_discussions_to_table(M.unlinked_discussions)
|
||||||
local unlinked_discussion_tree = NuiTree({ nodes = unlinked_discussion_tree_nodes, bufnr = M.unlinked_section_bufnr })
|
local unlinked_discussion_tree = NuiTree({ nodes = unlinked_discussion_tree_nodes, bufnr = M.unlinked_section.bufnr })
|
||||||
unlinked_discussion_tree:render()
|
unlinked_discussion_tree:render()
|
||||||
M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_section_bufnr, true)
|
M.set_tree_keymaps(unlinked_discussion_tree, M.unlinked_section.bufnr, true)
|
||||||
M.unlinked_discussion_tree = unlinked_discussion_tree
|
M.unlinked_discussion_tree = unlinked_discussion_tree
|
||||||
M.switch_can_edit_bufs(false)
|
M.switch_can_edit_bufs(false)
|
||||||
vim.api.nvim_buf_set_option(M.unlinked_section_bufnr, "filetype", "gitlab")
|
vim.api.nvim_set_option_value("filetype", "gitlab", { buf = M.unlinked_section.bufnr })
|
||||||
end
|
end
|
||||||
|
|
||||||
M.switch_can_edit_bufs = function(bool)
|
M.switch_can_edit_bufs = function(bool)
|
||||||
u.switch_can_edit_buf(M.unlinked_section_bufnr, bool)
|
u.switch_can_edit_buf(M.unlinked_section.bufnr, bool)
|
||||||
u.switch_can_edit_buf(M.linked_section_bufnr, bool)
|
u.switch_can_edit_buf(M.linked_section.bufnr, bool)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.add_discussion = function(arg)
|
M.add_discussion = function(arg)
|
||||||
@@ -289,8 +739,7 @@ M.add_discussion = function(arg)
|
|||||||
M.unlinked_discussions = {}
|
M.unlinked_discussions = {}
|
||||||
end
|
end
|
||||||
table.insert(M.unlinked_discussions, 1, discussion)
|
table.insert(M.unlinked_discussions, 1, discussion)
|
||||||
local bufinfo = vim.fn.getbufinfo(M.unlinked_section_bufnr)
|
if M.unlinked_section ~= nil then
|
||||||
if u.table_size(bufinfo) ~= 0 then
|
|
||||||
M.rebuild_unlinked_discussion_tree()
|
M.rebuild_unlinked_discussion_tree()
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
@@ -299,8 +748,7 @@ M.add_discussion = function(arg)
|
|||||||
M.discussions = {}
|
M.discussions = {}
|
||||||
end
|
end
|
||||||
table.insert(M.discussions, 1, discussion)
|
table.insert(M.discussions, 1, discussion)
|
||||||
local bufinfo = vim.fn.getbufinfo(M.unlinked_section_bufnr)
|
if M.linked_section ~= nil then
|
||||||
if u.table_size(bufinfo) ~= 0 then
|
|
||||||
M.rebuild_discussion_tree()
|
M.rebuild_discussion_tree()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -442,6 +890,13 @@ local attach_uuid = function(str)
|
|||||||
return { text = str, id = u.uuid() }
|
return { text = str, id = u.uuid() }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Build note header from note.
|
||||||
|
---@param note Note
|
||||||
|
---@return string
|
||||||
|
M.build_note_header = function(note)
|
||||||
|
return "@" .. note.author.username .. " " .. u.format_date(note.created_at)
|
||||||
|
end
|
||||||
|
|
||||||
M.build_note_body = function(note, resolve_info)
|
M.build_note_body = function(note, resolve_info)
|
||||||
local text_nodes = {}
|
local text_nodes = {}
|
||||||
for bodyLine in note.body:gmatch("[^\n]+") do
|
for bodyLine in note.body:gmatch("[^\n]+") do
|
||||||
@@ -464,7 +919,7 @@ M.build_note_body = function(note, resolve_info)
|
|||||||
or state.settings.discussion_tree.unresolved
|
or state.settings.discussion_tree.unresolved
|
||||||
end
|
end
|
||||||
|
|
||||||
local noteHeader = "@" .. note.author.username .. " " .. u.format_date(note.created_at) .. " " .. resolve_symbol
|
local noteHeader = M.build_note_header(note) .. " " .. resolve_symbol
|
||||||
|
|
||||||
return noteHeader, text_nodes
|
return noteHeader, text_nodes
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ return {
|
|||||||
state.merge_settings(args) -- Sets keymaps and other settings from setup function
|
state.merge_settings(args) -- Sets keymaps and other settings from setup function
|
||||||
require("gitlab.colors") -- Sets colors
|
require("gitlab.colors") -- Sets colors
|
||||||
reviewer.init()
|
reviewer.init()
|
||||||
|
discussions.initialize_discussions() -- place signs / diagnostics for discussions in reviewer
|
||||||
end,
|
end,
|
||||||
-- Global Actions 🌎
|
-- Global Actions 🌎
|
||||||
summary = async.sequence({ info }, summary.summary),
|
summary = async.sequence({ info }, summary.summary),
|
||||||
@@ -36,8 +37,9 @@ return {
|
|||||||
create_comment = async.sequence({ info, revisions }, comment.create_comment),
|
create_comment = async.sequence({ info, revisions }, comment.create_comment),
|
||||||
create_multiline_comment = async.sequence({ info, revisions }, comment.create_multiline_comment),
|
create_multiline_comment = async.sequence({ info, revisions }, comment.create_multiline_comment),
|
||||||
create_comment_suggestion = async.sequence({ info, revisions }, comment.create_comment_suggestion),
|
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_note = async.sequence({ info }, comment.create_note),
|
||||||
review = async.sequence({ u.merge(info, { refresh = true }) }, function()
|
review = async.sequence({ u.merge(info, { refresh = true }), revisions }, function()
|
||||||
reviewer.open()
|
reviewer.open()
|
||||||
end),
|
end),
|
||||||
pipeline = async.sequence({ info }, pipeline.open),
|
pipeline = async.sequence({ info }, pipeline.open),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
local u = require("gitlab.utils")
|
local u = require("gitlab.utils")
|
||||||
local state = require("gitlab.state")
|
local state = require("gitlab.state")
|
||||||
local async_ok, async = pcall(require, "diffview.async")
|
local async_ok, async = pcall(require, "diffview.async")
|
||||||
|
local diffview_lib = require("diffview.lib")
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
bufnr = nil,
|
bufnr = nil,
|
||||||
@@ -11,6 +12,17 @@ local M = {
|
|||||||
M.open = function()
|
M.open = function()
|
||||||
vim.api.nvim_command(string.format("DiffviewOpen %s", state.INFO.target_branch))
|
vim.api.nvim_command(string.format("DiffviewOpen %s", state.INFO.target_branch))
|
||||||
M.tabnr = vim.api.nvim_get_current_tabpage()
|
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
|
end
|
||||||
|
|
||||||
M.jump = function(file_name, new_line, old_line)
|
M.jump = function(file_name, new_line, old_line)
|
||||||
@@ -20,7 +32,7 @@ M.jump = function(file_name, new_line, old_line)
|
|||||||
end
|
end
|
||||||
vim.api.nvim_set_current_tabpage(M.tabnr)
|
vim.api.nvim_set_current_tabpage(M.tabnr)
|
||||||
vim.cmd("DiffviewFocusFiles")
|
vim.cmd("DiffviewFocusFiles")
|
||||||
local view = require("diffview.lib").get_current_view()
|
local view = diffview_lib.get_current_view()
|
||||||
if view == nil then
|
if view == nil then
|
||||||
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
|
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
@@ -66,7 +78,7 @@ M.get_location = function(range)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- check if we are in the diffview buffer
|
-- check if we are in the diffview buffer
|
||||||
local view = require("diffview.lib").get_current_view()
|
local view = diffview_lib.get_current_view()
|
||||||
if view == nil then
|
if view == nil then
|
||||||
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
|
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
@@ -149,4 +161,83 @@ M.get_lines = function(start_line, end_line)
|
|||||||
return vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
|
return vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
|
||||||
end
|
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
|
return M
|
||||||
|
|||||||
@@ -36,6 +36,36 @@ M.init = function()
|
|||||||
|
|
||||||
M.get_lines = reviewer.get_lines
|
M.get_lines = reviewer.get_lines
|
||||||
-- Returns the content of the file in the current location in the reviewer window
|
-- Returns the content of the file in the current location in the reviewer window
|
||||||
|
|
||||||
|
M.get_current_file = reviewer.get_current_file
|
||||||
|
-- Get currently loaded file
|
||||||
|
|
||||||
|
M.place_sign = reviewer.place_sign
|
||||||
|
-- Places a sign on the line for currently reviewed file.
|
||||||
|
-- Parameters:
|
||||||
|
-- • {id} The sign id
|
||||||
|
-- • {sign} The sign to place
|
||||||
|
-- • {group} The sign group to place on
|
||||||
|
-- • {new_line} The line to place the sign on
|
||||||
|
-- • {old_line} The buffer number to place the sign on
|
||||||
|
|
||||||
|
M.set_callback_for_file_changed = reviewer.set_callback_for_file_changed
|
||||||
|
-- Call callback whenever the file changes
|
||||||
|
-- Parameters:
|
||||||
|
-- • {callback} The callback to call
|
||||||
|
|
||||||
|
M.set_callback_for_reviewer_leave = reviewer.set_callback_for_reviewer_leave
|
||||||
|
-- Call callback whenever the reviewer is left
|
||||||
|
-- Parameters:
|
||||||
|
-- • {callback} The callback to call
|
||||||
|
|
||||||
|
M.set_diagnostics = reviewer.set_diagnostics
|
||||||
|
-- Set diagnostics for currently reviewed file
|
||||||
|
-- Parameters:
|
||||||
|
-- • {namespace} The namespace for diagnostics
|
||||||
|
-- • {diagnostics} The diagnostics to set
|
||||||
|
-- • {type} "new" if diagnostic should be in file after changes else "old"
|
||||||
|
-- • {opts} see opts in :h vim.diagnostic.set
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -33,6 +33,36 @@ M.settings = {
|
|||||||
resolved = "✓",
|
resolved = "✓",
|
||||||
unresolved = "",
|
unresolved = "",
|
||||||
},
|
},
|
||||||
|
discussion_sign_and_diagnostic = {
|
||||||
|
skip_resolved_discussion = false,
|
||||||
|
skip_old_revision_discussion = false,
|
||||||
|
},
|
||||||
|
discussion_sign = {
|
||||||
|
-- See :h sign_define for details about sign configuration.
|
||||||
|
enabled = true,
|
||||||
|
text = "💬",
|
||||||
|
linehl = nil,
|
||||||
|
texthl = nil,
|
||||||
|
culhl = nil,
|
||||||
|
numhl = nil,
|
||||||
|
priority = 20,
|
||||||
|
helper_signs = {
|
||||||
|
-- For multiline comments the helper signs are used to indicate the whole context
|
||||||
|
-- Priority of helper signs is lower than the main sign (-1).
|
||||||
|
enabled = true,
|
||||||
|
start = "↑",
|
||||||
|
mid = "|",
|
||||||
|
["end"] = "↓",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
discussion_diagnostic = {
|
||||||
|
-- If you want to customize diagnostics for discussions you can make special config
|
||||||
|
-- for namespace `gitlab_discussion`. See :h vim.diagnostic.config
|
||||||
|
enabled = true,
|
||||||
|
severity = vim.diagnostic.severity.INFO,
|
||||||
|
code = nil, -- see :h diagnostic-structure
|
||||||
|
display_opts = {}, -- this is dirrectly used as opts in vim.diagnostic.set, see :h vim.diagnostic.config.
|
||||||
|
},
|
||||||
pipeline = {
|
pipeline = {
|
||||||
created = "",
|
created = "",
|
||||||
pending = "",
|
pending = "",
|
||||||
|
|||||||
Reference in New Issue
Block a user