Bug Fixes (#470)
* fix: Restore buffer local settings outside reviewer (#446) * fix: do not show healthcheck alert for warnings (#468) * feat: Add MR URL to the summary details (#467) * fix: make cycling reviewed files faster (#474) * feat(pipeline): display trigger jobs for a pipeline in the pipelines popup (#465) * fix: Jumping to renamed files (#484) --------- Co-authored-by: Jakub F. Bortlík <jakub.bortlik@proton.me> Co-authored-by: Ashish Alex <ashish.alex10@gmail.com>
This commit is contained in:
committed by
GitHub
parent
3b396a5e6b
commit
9f898aa1a8
6
.github/CONTRIBUTING.md
vendored
6
.github/CONTRIBUTING.md
vendored
@@ -10,7 +10,7 @@ It's possible that the feature you want is already implemented, or does not belo
|
||||
|
||||
If you are using Lazy as a plugin manager, the easiest way to work on changes is by setting a specific path for the plugin that points to your repository locally. This is what I do:
|
||||
|
||||
```lua
|
||||
```lua
|
||||
{
|
||||
"harrisoncramer/gitlab.nvim",
|
||||
dependencies = {
|
||||
@@ -54,8 +54,8 @@ $ luacheck --globals vim busted --no-max-line-length -- .
|
||||
|
||||
4. Make the merge request to the `develop` branch of `.gitlab.nvim`
|
||||
|
||||
Please provide a description of the feature, and links to any relevant issues.
|
||||
Please provide a description of the feature, and links to any relevant issues.
|
||||
|
||||
That's it! I'll try to respond to any incoming merge request in a few days. Once we've reviewed it, it will be merged into the develop branch.
|
||||
That's it! I'll try to respond to any incoming merge request in a few days. Once we've reviewed it, it will be merged into the develop branch.
|
||||
|
||||
After some time, if the develop branch is found to be stable, that branch will be merged into `main` and released. When merged into `main` the pipeline will detect whether we're merging in a patch, minor, or major change, and create a new tag (e.g. 1.0.12) and release.
|
||||
|
||||
@@ -42,19 +42,13 @@ type RequestWithPosition interface {
|
||||
func buildCommentPosition(commentWithPositionData RequestWithPosition) *gitlab.PositionOptions {
|
||||
positionData := commentWithPositionData.GetPositionData()
|
||||
|
||||
// If the file has been renamed, then this is a relevant part of the payload
|
||||
oldFileName := positionData.OldFileName
|
||||
if oldFileName == "" {
|
||||
oldFileName = positionData.FileName
|
||||
}
|
||||
|
||||
opt := &gitlab.PositionOptions{
|
||||
PositionType: &positionData.Type,
|
||||
StartSHA: &positionData.StartCommitSHA,
|
||||
HeadSHA: &positionData.HeadCommitSHA,
|
||||
BaseSHA: &positionData.BaseCommitSHA,
|
||||
NewPath: &positionData.FileName,
|
||||
OldPath: &oldFileName,
|
||||
OldPath: &positionData.OldFileName,
|
||||
NewLine: positionData.NewLine,
|
||||
OldLine: positionData.OldLine,
|
||||
}
|
||||
|
||||
@@ -20,16 +20,18 @@ type RetriggerPipelineResponse struct {
|
||||
type PipelineWithJobs struct {
|
||||
Jobs []*gitlab.Job `json:"jobs"`
|
||||
LatestPipeline *gitlab.PipelineInfo `json:"latest_pipeline"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type GetPipelineAndJobsResponse struct {
|
||||
SuccessResponse
|
||||
Pipeline PipelineWithJobs `json:"latest_pipeline"`
|
||||
Pipelines []PipelineWithJobs `json:"latest_pipeline"`
|
||||
}
|
||||
|
||||
type PipelineManager interface {
|
||||
ListProjectPipelines(pid interface{}, opt *gitlab.ListProjectPipelinesOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.PipelineInfo, *gitlab.Response, error)
|
||||
ListPipelineJobs(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Job, *gitlab.Response, error)
|
||||
ListPipelineBridges(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Bridge, *gitlab.Response, error)
|
||||
RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error)
|
||||
}
|
||||
|
||||
@@ -101,7 +103,6 @@ func (a pipelineService) GetPipelineAndJobs(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
|
||||
jobs, res, err := a.client.ListPipelineJobs(a.projectInfo.ProjectId, pipeline.ID, &gitlab.ListJobsOptions{})
|
||||
|
||||
if err != nil {
|
||||
handleError(w, err, "Could not get pipeline jobs", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -112,13 +113,51 @@ func (a pipelineService) GetPipelineAndJobs(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
pipelines := []PipelineWithJobs{}
|
||||
pipelines = append(pipelines, PipelineWithJobs{
|
||||
Jobs: jobs,
|
||||
LatestPipeline: pipeline,
|
||||
Name: "root",
|
||||
})
|
||||
|
||||
bridges, res, err := a.client.ListPipelineBridges(a.projectInfo.ProjectId, pipeline.ID, &gitlab.ListJobsOptions{})
|
||||
|
||||
if err != nil {
|
||||
handleError(w, err, "Could not get pipeline trigger jobs", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if res.StatusCode >= 300 {
|
||||
handleError(w, GenericError{r.URL.Path}, "Could not get pipeline trigger jobs", res.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
for _, bridge := range bridges {
|
||||
if bridge.DownstreamPipeline == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pipelineIdInBridge := bridge.DownstreamPipeline.ID
|
||||
bridgePipelineJobs, res, err := a.client.ListPipelineJobs(bridge.DownstreamPipeline.ProjectID, pipelineIdInBridge, &gitlab.ListJobsOptions{})
|
||||
if err != nil {
|
||||
handleError(w, err, "Could not get jobs for a pipeline from a trigger job", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if res.StatusCode >= 300 {
|
||||
handleError(w, GenericError{r.URL.Path}, "Could not get jobs for a pipeline from a trigger job", res.StatusCode)
|
||||
return
|
||||
}
|
||||
|
||||
pipelines = append(pipelines, PipelineWithJobs{
|
||||
Jobs: bridgePipelineJobs,
|
||||
LatestPipeline: bridge.DownstreamPipeline,
|
||||
Name: bridge.Name,
|
||||
})
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
response := GetPipelineAndJobsResponse{
|
||||
SuccessResponse: SuccessResponse{Message: "Pipeline retrieved"},
|
||||
Pipeline: PipelineWithJobs{
|
||||
LatestPipeline: pipeline,
|
||||
Jobs: jobs,
|
||||
},
|
||||
Pipelines: pipelines,
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(response)
|
||||
|
||||
@@ -27,6 +27,14 @@ func (f fakePipelineManager) ListPipelineJobs(pid interface{}, pipelineID int, o
|
||||
return []*gitlab.Job{}, resp, err
|
||||
}
|
||||
|
||||
func (f fakePipelineManager) ListPipelineBridges(pid interface{}, pipelineID int, opts *gitlab.ListJobsOptions, options ...gitlab.RequestOptionFunc) ([]*gitlab.Bridge, *gitlab.Response, error) {
|
||||
resp, err := f.handleGitlabError()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return []*gitlab.Bridge{}, resp, err
|
||||
}
|
||||
|
||||
func (f fakePipelineManager) RetryPipelineBuild(pid interface{}, pipeline int, options ...gitlab.RequestOptionFunc) (*gitlab.Pipeline, *gitlab.Response, error) {
|
||||
resp, err := f.handleGitlabError()
|
||||
if err != nil {
|
||||
|
||||
@@ -302,6 +302,7 @@ you call this function with no values the defaults will be used:
|
||||
"delete_branch",
|
||||
"squash",
|
||||
"labels",
|
||||
"web_url",
|
||||
},
|
||||
},
|
||||
discussion_signs = {
|
||||
@@ -315,6 +316,7 @@ you call this function with no values the defaults will be used:
|
||||
comment = "→|",
|
||||
range = " |",
|
||||
},
|
||||
skip_old_revision_discussion = false, -- Don't show diagnostics for discussions that were created for earlier MR revisions
|
||||
},
|
||||
pipeline = {
|
||||
created = "",
|
||||
|
||||
@@ -15,7 +15,6 @@ local reviewer = require("gitlab.reviewer")
|
||||
local Location = require("gitlab.reviewer.location")
|
||||
|
||||
local M = {
|
||||
current_win = nil,
|
||||
start_line = nil,
|
||||
end_line = nil,
|
||||
draft_popup = nil,
|
||||
@@ -25,10 +24,9 @@ local M = {
|
||||
---Fires the API that sends the comment data to the Go server, called when you "confirm" creation
|
||||
---via the M.settings.keymaps.popup.perform_action keybinding
|
||||
---@param text string comment text
|
||||
---@param visual_range LineRange | nil range of visual selection or nil
|
||||
---@param unlinked boolean if true, the comment is not linked to a line
|
||||
---@param discussion_id string | nil The ID of the discussion to which the reply is responding, nil if not a reply
|
||||
local confirm_create_comment = function(text, visual_range, unlinked, discussion_id)
|
||||
local confirm_create_comment = function(text, unlinked, discussion_id)
|
||||
if text == nil then
|
||||
u.notify("Reviewer did not provide text of change", vim.log.levels.ERROR)
|
||||
return
|
||||
@@ -75,30 +73,16 @@ local confirm_create_comment = function(text, visual_range, unlinked, discussion
|
||||
return
|
||||
end
|
||||
|
||||
local reviewer_data = reviewer.get_reviewer_data()
|
||||
if reviewer_data == nil then
|
||||
u.notify("Error getting reviewer data", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local location = Location.new(reviewer_data, visual_range)
|
||||
location:build_location_data()
|
||||
local location_data = location.location_data
|
||||
if location_data == nil then
|
||||
u.notify("Error getting location information", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local revision = state.MR_REVISIONS[1]
|
||||
local position_data = {
|
||||
file_name = reviewer_data.file_name,
|
||||
old_file_name = reviewer_data.old_file_name,
|
||||
file_name = M.location.reviewer_data.file_name,
|
||||
old_file_name = M.location.reviewer_data.old_file_name,
|
||||
base_commit_sha = revision.base_commit_sha,
|
||||
start_commit_sha = revision.start_commit_sha,
|
||||
head_commit_sha = revision.head_commit_sha,
|
||||
old_line = location_data.old_line,
|
||||
new_line = location_data.new_line,
|
||||
line_range = location_data.line_range,
|
||||
old_line = M.location.location_data.old_line,
|
||||
new_line = M.location.location_data.new_line,
|
||||
line_range = M.location.location_data.line_range,
|
||||
}
|
||||
|
||||
-- Creating a new comment (linked to specific changes)
|
||||
@@ -148,10 +132,10 @@ M.confirm_edit_comment = function(discussion_id, note_id, unlinked)
|
||||
end
|
||||
|
||||
---@class LayoutOpts
|
||||
---@field ranged boolean
|
||||
---@field unlinked boolean
|
||||
---@field discussion_id string|nil
|
||||
---@field reply boolean|nil
|
||||
---@field file_name string|nil
|
||||
|
||||
---This function sets up the layout and popups needed to create a comment, note and
|
||||
---multi-line comment. It also sets up the basic keybindings for switching between
|
||||
@@ -163,21 +147,24 @@ M.create_comment_layout = function(opts)
|
||||
local title
|
||||
local user_settings
|
||||
if opts.discussion_id ~= nil then
|
||||
title = "Reply"
|
||||
title = "Reply" .. (opts.file_name and string.format(" [%s]", opts.file_name) or "")
|
||||
user_settings = popup_settings.reply
|
||||
elseif opts.unlinked then
|
||||
title = "Note"
|
||||
user_settings = popup_settings.note
|
||||
else
|
||||
title = "Comment"
|
||||
-- TODO: investigate why `old_file_name` is in fact the new name for renamed files!
|
||||
local file_name = M.location.reviewer_data.old_file_name ~= "" and M.location.reviewer_data.old_file_name
|
||||
or M.location.reviewer_data.file_name
|
||||
title =
|
||||
popup.create_title("Comment", file_name, M.location.visual_range.start_line, M.location.visual_range.end_line)
|
||||
user_settings = popup_settings.comment
|
||||
end
|
||||
local settings = u.merge(popup_settings, user_settings or {})
|
||||
|
||||
M.current_win = vim.api.nvim_get_current_win()
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
M.comment_popup = Popup(popup.create_popup_state(title, settings))
|
||||
M.draft_popup = Popup(popup.create_box_popup_state("Draft", false, settings))
|
||||
M.start_line, M.end_line = u.get_visual_selection_boundaries()
|
||||
|
||||
local internal_layout = Layout.Box({
|
||||
Layout.Box(M.comment_popup, { grow = 1 }),
|
||||
@@ -194,22 +181,21 @@ M.create_comment_layout = function(opts)
|
||||
}, internal_layout)
|
||||
|
||||
popup.set_cycle_popups_keymaps({ M.comment_popup, M.draft_popup })
|
||||
popup.set_up_autocommands(M.comment_popup, layout, M.current_win)
|
||||
popup.set_up_autocommands(M.comment_popup, layout, current_win)
|
||||
|
||||
local range = opts.ranged and { start_line = M.start_line, end_line = M.end_line } or nil
|
||||
local unlinked = opts.unlinked or false
|
||||
|
||||
---Keybinding for focus on draft section
|
||||
popup.set_popup_keymaps(M.draft_popup, function()
|
||||
local text = u.get_buffer_text(M.comment_popup.bufnr)
|
||||
confirm_create_comment(text, range, unlinked, opts.discussion_id)
|
||||
vim.api.nvim_set_current_win(M.current_win)
|
||||
confirm_create_comment(text, unlinked, opts.discussion_id)
|
||||
vim.api.nvim_set_current_win(current_win)
|
||||
end, miscellaneous.toggle_bool, popup.non_editable_popup_opts)
|
||||
|
||||
---Keybinding for focus on text section
|
||||
popup.set_popup_keymaps(M.comment_popup, function(text)
|
||||
confirm_create_comment(text, range, unlinked, opts.discussion_id)
|
||||
vim.api.nvim_set_current_win(M.current_win)
|
||||
confirm_create_comment(text, unlinked, opts.discussion_id)
|
||||
vim.api.nvim_set_current_win(current_win)
|
||||
end, miscellaneous.attach_file, popup.editable_popup_opts)
|
||||
|
||||
vim.schedule(function()
|
||||
@@ -223,44 +209,43 @@ end
|
||||
--- This function will open a comment popup in order to create a comment on the changed/updated
|
||||
--- line in the current MR
|
||||
M.create_comment = function()
|
||||
M.location = Location.new()
|
||||
if not M.can_create_comment(false) then
|
||||
return
|
||||
end
|
||||
|
||||
local layout = M.create_comment_layout({ ranged = false, unlinked = false })
|
||||
local layout = M.create_comment_layout({ unlinked = false })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
--- This function will open a multi-line comment popup in order to create a multi-line comment
|
||||
--- on the changed/updated line in the current MR
|
||||
M.create_multiline_comment = function()
|
||||
M.location = Location.new()
|
||||
if not M.can_create_comment(true) then
|
||||
u.press_escape()
|
||||
return
|
||||
end
|
||||
|
||||
local layout = M.create_comment_layout({ ranged = true, unlinked = false })
|
||||
local layout = M.create_comment_layout({ unlinked = false })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
--- This function will open a a popup to create a "note" (e.g. unlinked comment)
|
||||
--- on the changed/updated line in the current MR
|
||||
M.create_note = function()
|
||||
local layout = M.create_comment_layout({ ranged = false, unlinked = true })
|
||||
local layout = M.create_comment_layout({ unlinked = true })
|
||||
layout:mount()
|
||||
end
|
||||
|
||||
---Given the current visually selected area of text, builds text to fill in the
|
||||
---comment popup with a suggested change
|
||||
---@return LineRange|nil
|
||||
---@return integer
|
||||
local build_suggestion = function()
|
||||
local current_line = vim.api.nvim_win_get_cursor(0)[1]
|
||||
M.start_line, M.end_line = u.get_visual_selection_boundaries()
|
||||
|
||||
local range_length = M.end_line - M.start_line
|
||||
local range_length = M.location.visual_range.end_line - M.location.visual_range.start_line
|
||||
local backticks = "```"
|
||||
local selected_lines = u.get_lines(M.start_line, M.end_line)
|
||||
local selected_lines = u.get_lines(M.location.visual_range.start_line, M.location.visual_range.end_line)
|
||||
|
||||
for _, line in ipairs(selected_lines) do
|
||||
if string.match(line, "^```%S*$") then
|
||||
@@ -270,14 +255,14 @@ local build_suggestion = function()
|
||||
end
|
||||
|
||||
local suggestion_start
|
||||
if M.start_line == current_line then
|
||||
if M.location.visual_range.start_line == current_line then
|
||||
suggestion_start = backticks .. "suggestion:-0+" .. range_length
|
||||
elseif M.end_line == current_line then
|
||||
elseif M.location.visual_range.end_line == current_line then
|
||||
suggestion_start = backticks .. "suggestion:-" .. range_length .. "+0"
|
||||
else
|
||||
--- This should never happen afaik
|
||||
u.notify("Unexpected suggestion position", vim.log.levels.ERROR)
|
||||
return nil, 0
|
||||
return nil
|
||||
end
|
||||
suggestion_start = suggestion_start
|
||||
local suggestion_lines = {}
|
||||
@@ -285,21 +270,22 @@ local build_suggestion = function()
|
||||
vim.list_extend(suggestion_lines, selected_lines)
|
||||
table.insert(suggestion_lines, backticks)
|
||||
|
||||
return suggestion_lines, range_length
|
||||
return suggestion_lines
|
||||
end
|
||||
|
||||
--- This function will open a a popup to create a suggestion comment
|
||||
--- on the changed/updated line in the current MR
|
||||
--- See: https://docs.gitlab.com/ee/user/project/merge_requests/reviews/suggestions.html
|
||||
M.create_comment_suggestion = function()
|
||||
M.location = Location.new()
|
||||
if not M.can_create_comment(true) then
|
||||
u.press_escape()
|
||||
return
|
||||
end
|
||||
|
||||
local suggestion_lines, range_length = build_suggestion()
|
||||
local suggestion_lines = build_suggestion()
|
||||
|
||||
local layout = M.create_comment_layout({ ranged = range_length > 0, unlinked = false })
|
||||
local layout = M.create_comment_layout({ unlinked = false })
|
||||
layout:mount()
|
||||
|
||||
vim.schedule(function()
|
||||
@@ -368,6 +354,11 @@ M.can_create_comment = function(must_be_visual)
|
||||
return false
|
||||
end
|
||||
|
||||
if M.location == nil or M.location.location_data == nil then
|
||||
u.notify("Error getting location information", vim.log.levels.ERROR)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ M.get_line_number_from_node = function(root_node)
|
||||
end
|
||||
|
||||
-- This function (settings.keymaps.discussion_tree.jump_to_reviewer) will jump the cursor to the reviewer's location associated with the note. The implementation depends on the reviewer
|
||||
M.jump_to_reviewer = function(tree, callback)
|
||||
M.jump_to_reviewer = function(tree)
|
||||
local node = tree:get_node()
|
||||
local root_node = M.get_root_node(tree, node)
|
||||
if root_node == nil then
|
||||
@@ -277,8 +277,7 @@ M.jump_to_reviewer = function(tree, callback)
|
||||
u.notify("Could not get line number", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
reviewer.jump(root_node.file_name, line_number, is_new_sha)
|
||||
callback()
|
||||
reviewer.jump(root_node.file_name, root_node.old_file_name, line_number, is_new_sha)
|
||||
end
|
||||
|
||||
-- This function (settings.keymaps.discussion_tree.jump_to_file) will jump to the file changed in a new tab
|
||||
@@ -302,7 +301,7 @@ M.jump_to_file = function(tree)
|
||||
end
|
||||
vim.cmd.tabnew()
|
||||
local line_number = get_new_line(root_node) or get_old_line(root_node)
|
||||
if line_number == nil then
|
||||
if line_number == nil or line_number == 0 then
|
||||
line_number = 1
|
||||
end
|
||||
local bufnr = vim.fn.bufnr(root_node.file_name)
|
||||
|
||||
@@ -215,7 +215,7 @@ M.open_confirmation_popup = function(mr)
|
||||
return
|
||||
end
|
||||
|
||||
local layout, title_popup, description_popup, target_popup, delete_branch_popup, squash_popup, forked_project_id_popup =
|
||||
local layout, title_popup, description_popup, target_popup, source_popup, delete_branch_popup, squash_popup, forked_project_id_popup =
|
||||
M.create_layout()
|
||||
|
||||
local popups = {
|
||||
@@ -224,6 +224,7 @@ M.open_confirmation_popup = function(mr)
|
||||
delete_branch_popup,
|
||||
squash_popup,
|
||||
target_popup,
|
||||
source_popup,
|
||||
}
|
||||
|
||||
if state.settings.create_mr.fork.enabled then
|
||||
@@ -261,6 +262,7 @@ M.open_confirmation_popup = function(mr)
|
||||
vim.api.nvim_buf_set_lines(M.description_bufnr, 0, -1, false, description_lines)
|
||||
vim.api.nvim_buf_set_lines(M.title_bufnr, 0, -1, false, { mr.title })
|
||||
vim.api.nvim_buf_set_lines(M.target_bufnr, 0, -1, false, { mr.target })
|
||||
vim.api.nvim_buf_set_lines(M.source_bufnr, 0, -1, false, { require("gitlab.git").get_current_branch() })
|
||||
vim.api.nvim_buf_set_lines(M.delete_branch_bufnr, 0, -1, false, { u.bool_to_string(delete_branch) })
|
||||
vim.api.nvim_buf_set_lines(M.squash_bufnr, 0, -1, false, { u.bool_to_string(squash) })
|
||||
if state.settings.create_mr.fork.enabled then
|
||||
@@ -281,6 +283,7 @@ M.open_confirmation_popup = function(mr)
|
||||
popup.set_popup_keymaps(description_popup, M.create_mr, miscellaneous.attach_file, popup_opts)
|
||||
popup.set_popup_keymaps(title_popup, M.create_mr, nil, popup_opts)
|
||||
popup.set_popup_keymaps(target_popup, M.create_mr, M.select_new_target, popup_opts)
|
||||
popup.set_popup_keymaps(source_popup, M.create_mr, nil, popup_opts)
|
||||
popup.set_popup_keymaps(delete_branch_popup, M.create_mr, miscellaneous.toggle_bool, popup_opts)
|
||||
popup.set_popup_keymaps(squash_popup, M.create_mr, miscellaneous.toggle_bool, popup_opts)
|
||||
popup.set_popup_keymaps(forked_project_id_popup, M.create_mr, nil, popup_opts)
|
||||
@@ -336,6 +339,8 @@ M.create_layout = function()
|
||||
M.description_bufnr = description_popup.bufnr
|
||||
local target_branch_popup = Popup(popup.create_box_popup_state("Target branch", false, settings))
|
||||
M.target_bufnr = target_branch_popup.bufnr
|
||||
local source_branch_popup = Popup(popup.create_box_popup_state("Source branch", false, settings))
|
||||
M.source_bufnr = source_branch_popup.bufnr
|
||||
local delete_title = vim.o.columns > 110 and "Delete source branch" or "Delete source"
|
||||
local delete_branch_popup = Popup(popup.create_box_popup_state(delete_title, false, settings))
|
||||
M.delete_branch_bufnr = delete_branch_popup.bufnr
|
||||
@@ -352,6 +357,7 @@ M.create_layout = function()
|
||||
table.insert(boxes, Layout.Box(delete_branch_popup, { size = { width = #delete_title + 4 } }))
|
||||
table.insert(boxes, Layout.Box(squash_popup, { size = { width = #squash_title + 4 } }))
|
||||
table.insert(boxes, Layout.Box(target_branch_popup, { grow = 1 }))
|
||||
table.insert(boxes, Layout.Box(source_branch_popup, { grow = 1 }))
|
||||
|
||||
local internal_layout = Layout.Box({
|
||||
Layout.Box({
|
||||
@@ -378,6 +384,7 @@ M.create_layout = function()
|
||||
title_popup,
|
||||
description_popup,
|
||||
target_branch_popup,
|
||||
source_branch_popup,
|
||||
delete_branch_popup,
|
||||
squash_popup,
|
||||
forked_project_id_popup
|
||||
|
||||
@@ -15,7 +15,6 @@ local List = require("gitlab.utils.list")
|
||||
local tree_utils = require("gitlab.actions.discussions.tree")
|
||||
local discussions_tree = require("gitlab.actions.discussions.tree")
|
||||
local draft_notes = require("gitlab.actions.draft_notes")
|
||||
local diffview_lib = require("diffview.lib")
|
||||
local signs = require("gitlab.indicators.signs")
|
||||
local diagnostics = require("gitlab.indicators.diagnostics")
|
||||
local winbar = require("gitlab.actions.discussions.winbar")
|
||||
@@ -74,37 +73,25 @@ end
|
||||
M.initialize_discussions = function()
|
||||
state.discussion_tree.last_updated = os.time()
|
||||
signs.setup_signs()
|
||||
reviewer.set_callback_for_file_changed(function()
|
||||
M.refresh_diagnostics()
|
||||
M.modifiable(false)
|
||||
reviewer.set_reviewer_keymaps()
|
||||
reviewer.set_callback_for_file_changed(function(args)
|
||||
diagnostics.place_diagnostics(args.buf)
|
||||
reviewer.update_winid_for_buffer(args.buf)
|
||||
end)
|
||||
reviewer.set_callback_for_reviewer_enter(function()
|
||||
M.modifiable(false)
|
||||
M.refresh_diagnostics()
|
||||
end)
|
||||
reviewer.set_callback_for_buf_read(function(args)
|
||||
vim.api.nvim_buf_set_option(args.buf, "modifiable", false)
|
||||
reviewer.set_keymaps(args.buf)
|
||||
reviewer.set_reviewer_autocommands(args.buf)
|
||||
end)
|
||||
reviewer.set_callback_for_reviewer_leave(function()
|
||||
signs.clear_signs()
|
||||
diagnostics.clear_diagnostics()
|
||||
M.modifiable(true)
|
||||
reviewer.del_reviewer_keymaps()
|
||||
end)
|
||||
end
|
||||
|
||||
--- Ensures that the both buffers in the reviewer are/not modifiable. Relevant if the user is using
|
||||
--- the --imply-local setting
|
||||
M.modifiable = function(bool)
|
||||
local view = diffview_lib.get_current_view()
|
||||
local a = view.cur_layout.a.file.bufnr
|
||||
local b = view.cur_layout.b.file.bufnr
|
||||
if a ~= nil and vim.api.nvim_buf_is_loaded(a) then
|
||||
vim.api.nvim_buf_set_option(a, "modifiable", bool)
|
||||
end
|
||||
if b ~= nil and vim.api.nvim_buf_is_loaded(b) then
|
||||
vim.api.nvim_buf_set_option(b, "modifiable", bool)
|
||||
end
|
||||
end
|
||||
|
||||
--- Take existing data and refresh the diagnostics, the winbar, and the signs
|
||||
--- Take existing data and refresh the diagnostics and the signs
|
||||
M.refresh_diagnostics = function()
|
||||
if state.settings.discussion_signs.enabled then
|
||||
diagnostics.refresh_diagnostics()
|
||||
@@ -115,7 +102,9 @@ 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.open = function(callback)
|
||||
---@param view_type "discussions"|"notes" Defines the view type to select (useful for overriding the default view type when jumping to discussion tree when it's closed).
|
||||
M.open = function(callback, view_type)
|
||||
view_type = view_type and view_type or state.settings.discussion_tree.default_view
|
||||
state.DISCUSSION_DATA.discussions = u.ensure_table(state.DISCUSSION_DATA.discussions)
|
||||
state.DISCUSSION_DATA.unlinked_discussions = u.ensure_table(state.DISCUSSION_DATA.unlinked_discussions)
|
||||
state.DRAFT_NOTES = u.ensure_table(state.DRAFT_NOTES)
|
||||
@@ -136,7 +125,7 @@ M.open = function(callback)
|
||||
|
||||
-- Initialize winbar module with data from buffers
|
||||
winbar.set_buffers(M.linked_bufnr, M.unlinked_bufnr)
|
||||
winbar.switch_view_type(state.settings.discussion_tree.default_view)
|
||||
winbar.switch_view_type(view_type)
|
||||
|
||||
local current_window = vim.api.nvim_get_current_win() -- Save user's current window in case they switched while content was loading
|
||||
vim.api.nvim_set_current_win(M.split.winid)
|
||||
@@ -146,7 +135,7 @@ M.open = function(callback)
|
||||
M.rebuild_unlinked_discussion_tree()
|
||||
|
||||
-- Set default buffer
|
||||
local default_buffer = winbar.bufnr_map[state.settings.discussion_tree.default_view]
|
||||
local default_buffer = winbar.bufnr_map[view_type]
|
||||
vim.api.nvim_set_current_buf(default_buffer)
|
||||
common.switch_can_edit_bufs(false, M.linked_bufnr, M.unlinked_bufnr)
|
||||
|
||||
@@ -192,12 +181,13 @@ M.move_to_discussion_tree = function()
|
||||
discussion_node:expand()
|
||||
end
|
||||
M.discussion_tree:render()
|
||||
vim.api.nvim_win_set_cursor(M.split.winid, { line_number, 0 })
|
||||
vim.api.nvim_set_current_win(M.split.winid)
|
||||
winbar.switch_view_type("discussions")
|
||||
vim.api.nvim_win_set_cursor(M.split.winid, { line_number, 0 })
|
||||
end
|
||||
|
||||
if not M.split_visible then
|
||||
M.toggle(jump_after_tree_opened)
|
||||
M.open(jump_after_tree_opened, "discussions")
|
||||
else
|
||||
jump_after_tree_opened()
|
||||
end
|
||||
@@ -247,10 +237,10 @@ M.reply = function(tree)
|
||||
local comment = require("gitlab.actions.comment")
|
||||
local unlinked = tree.bufnr == M.unlinked_bufnr
|
||||
local layout = comment.create_comment_layout({
|
||||
ranged = false,
|
||||
discussion_id = discussion_id,
|
||||
unlinked = unlinked,
|
||||
reply = true,
|
||||
file_name = discussion_node.file_name,
|
||||
})
|
||||
|
||||
layout:mount()
|
||||
@@ -284,7 +274,6 @@ end
|
||||
|
||||
-- This function (settings.keymaps.discussion_tree.edit_comment) will open the edit popup for the current comment in the discussion tree
|
||||
M.edit_comment = function(tree, unlinked)
|
||||
local edit_popup = Popup(popup.create_popup_state("Edit Comment", state.settings.popup.edit))
|
||||
local current_node = tree:get_node()
|
||||
local note_node = common.get_note_node(tree, current_node)
|
||||
local root_node = common.get_root_node(tree, current_node)
|
||||
@@ -292,6 +281,9 @@ M.edit_comment = function(tree, unlinked)
|
||||
u.notify("Could not get root or note node", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
local title = "Edit Comment"
|
||||
title = root_node.file_name ~= nil and string.format("%s [%s]", title, root_node.file_name) or title
|
||||
local edit_popup = Popup(popup.create_popup_state(title, state.settings.popup.edit))
|
||||
|
||||
popup.set_up_autocommands(edit_popup, nil, vim.api.nvim_get_current_win())
|
||||
|
||||
@@ -587,7 +579,7 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
if keymaps.discussion_tree.jump_to_reviewer then
|
||||
vim.keymap.set("n", keymaps.discussion_tree.jump_to_reviewer, function()
|
||||
if M.is_current_node_note(tree) then
|
||||
common.jump_to_reviewer(tree, M.refresh_diagnostics)
|
||||
common.jump_to_reviewer(tree)
|
||||
end
|
||||
end, { buffer = bufnr, desc = "Jump to reviewer", nowait = keymaps.discussion_tree.jump_to_reviewer_nowait })
|
||||
end
|
||||
@@ -757,6 +749,16 @@ M.set_tree_keymaps = function(tree, bufnr, unlinked)
|
||||
})
|
||||
end
|
||||
|
||||
if keymaps.discussion_tree.print_node then
|
||||
vim.keymap.set("n", keymaps.discussion_tree.print_node, function()
|
||||
common.print_node(tree)
|
||||
end, {
|
||||
buffer = bufnr,
|
||||
desc = "Print current node (for debugging)",
|
||||
nowait = keymaps.discussion_tree.print_node_nowait,
|
||||
})
|
||||
end
|
||||
|
||||
if keymaps.discussion_tree.add_emoji then
|
||||
vim.keymap.set("n", keymaps.discussion_tree.add_emoji, function()
|
||||
M.add_emoji_to_note(tree, unlinked)
|
||||
|
||||
@@ -28,6 +28,8 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
local root_note_id = ""
|
||||
---@type string?
|
||||
local root_file_name = ""
|
||||
---@type string?
|
||||
local root_old_file_name = ""
|
||||
---@type string
|
||||
local root_id
|
||||
local root_text_nodes = {}
|
||||
@@ -43,6 +45,7 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
if j == 1 then
|
||||
_, root_text, root_text_nodes = M.build_note(note, { resolved = note.resolved, resolvable = note.resolvable })
|
||||
root_file_name = (type(note.position) == "table" and note.position.new_path or nil)
|
||||
root_old_file_name = (type(note.position) == "table" and note.position.old_path or nil)
|
||||
root_new_line = (type(note.position) == "table" and note.position.new_line or nil)
|
||||
root_old_line = (type(note.position) == "table" and note.position.old_line or nil)
|
||||
root_id = discussion.id
|
||||
@@ -79,6 +82,7 @@ M.add_discussions_to_table = function(items, unlinked)
|
||||
id = root_id,
|
||||
root_note_id = root_note_id,
|
||||
file_name = root_file_name,
|
||||
old_file_name = root_old_file_name,
|
||||
new_line = root_new_line,
|
||||
old_line = root_old_line,
|
||||
resolvable = resolvable,
|
||||
|
||||
@@ -254,9 +254,8 @@ M.get_mode = function()
|
||||
end
|
||||
end
|
||||
|
||||
---Sets the current view type (if provided an argument)
|
||||
---and then updates the view
|
||||
---@param override any
|
||||
---Toggles the current view type (or sets it to `override`) and then updates the view.
|
||||
---@param override "discussions"|"notes" Defines the view type to select.
|
||||
M.switch_view_type = function(override)
|
||||
if override then
|
||||
M.current_view_type = override
|
||||
|
||||
@@ -7,41 +7,77 @@ local job = require("gitlab.job")
|
||||
local u = require("gitlab.utils")
|
||||
local popup = require("gitlab.popup")
|
||||
local M = {
|
||||
pipeline_jobs = nil,
|
||||
pipeline_jobs = {},
|
||||
latest_pipeline = nil,
|
||||
pipeline_popup = nil,
|
||||
}
|
||||
|
||||
local function get_latest_pipeline()
|
||||
local pipeline = state.PIPELINE and state.PIPELINE.latest_pipeline
|
||||
if type(pipeline) ~= "table" or (type(pipeline) == "table" and u.table_size(pipeline) == 0) then
|
||||
u.notify("Pipeline not found", vim.log.levels.WARN)
|
||||
return
|
||||
local function get_latest_pipelines(count)
|
||||
count = count or 1 -- Default to 1 if count is not provided
|
||||
local pipelines = {}
|
||||
|
||||
if not state.PIPELINE then
|
||||
u.notify("Pipeline state is not initialized", vim.log.levels.WARN)
|
||||
return nil
|
||||
end
|
||||
return pipeline
|
||||
|
||||
for i = 1, math.max(count, #state.PIPELINE) do
|
||||
local pipeline = state.PIPELINE[i].latest_pipeline
|
||||
if type(pipeline) == "table" and u.table_size(pipeline) > 0 then
|
||||
table.insert(pipelines, pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
if #pipelines == 0 then
|
||||
u.notify("No valid pipelines found", vim.log.levels.WARN)
|
||||
return nil
|
||||
end
|
||||
return pipelines
|
||||
end
|
||||
|
||||
local function get_pipeline_jobs()
|
||||
M.latest_pipeline = get_latest_pipeline()
|
||||
if not M.latest_pipeline then
|
||||
return
|
||||
end
|
||||
return u.reverse(type(state.PIPELINE.jobs) == "table" and state.PIPELINE.jobs or {})
|
||||
local function get_pipeline_jobs(idx)
|
||||
return u.reverse(type(state.PIPELINE[idx].jobs) == "table" and state.PIPELINE[idx].jobs or {})
|
||||
end
|
||||
|
||||
-- The function will render the Pipeline state in a popup
|
||||
M.open = function()
|
||||
M.pipeline_jobs = get_pipeline_jobs()
|
||||
M.latest_pipeline = get_latest_pipeline()
|
||||
if M.latest_pipeline == nil then
|
||||
M.latest_pipelines = get_latest_pipelines()
|
||||
if not M.latest_pipelines then
|
||||
return
|
||||
end
|
||||
if not M.latest_pipelines or #M.latest_pipelines == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local width = string.len(M.latest_pipeline.web_url) + 10
|
||||
local height = 6 + #M.pipeline_jobs + 3
|
||||
local max_width = 0
|
||||
local total_height = 0
|
||||
local pipelines_data = {}
|
||||
|
||||
for idx, pipeline in ipairs(M.latest_pipelines) do
|
||||
local width = string.len(pipeline.web_url) + 10
|
||||
max_width = math.max(max_width, width)
|
||||
|
||||
local pipeline_jobs = get_pipeline_jobs(idx)
|
||||
for _, j in ipairs(pipeline_jobs) do
|
||||
table.insert(M.pipeline_jobs, j)
|
||||
end
|
||||
|
||||
local pipeline_status = M.get_pipeline_status(idx, false)
|
||||
local height = 6 + #pipeline_jobs + 3
|
||||
total_height = total_height + height
|
||||
|
||||
table.insert(pipelines_data, {
|
||||
pipeline = pipeline,
|
||||
pipeline_status = pipeline_status,
|
||||
jobs = pipeline_jobs,
|
||||
width = width,
|
||||
height = 6 + #pipeline_jobs + 3,
|
||||
lines = {},
|
||||
})
|
||||
end
|
||||
|
||||
local pipeline_popup =
|
||||
Popup(popup.create_popup_state("Loading Pipeline...", state.settings.popup.pipeline, width, height, 60))
|
||||
Popup(popup.create_popup_state("Loading Pipelines...", state.settings.popup.pipeline, max_width, total_height, 60))
|
||||
popup.set_up_autocommands(pipeline_popup, nil, vim.api.nvim_get_current_win())
|
||||
M.pipeline_popup = pipeline_popup
|
||||
pipeline_popup:mount()
|
||||
@@ -49,68 +85,103 @@ M.open = function()
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
vim.opt_local.wrap = false
|
||||
|
||||
local lines = {}
|
||||
|
||||
u.switch_can_edit_buf(bufnr, true)
|
||||
table.insert(lines, "Status: " .. M.get_pipeline_status(false))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, string.format("Last Run: %s", u.time_since(M.latest_pipeline.created_at)))
|
||||
table.insert(lines, string.format("Url: %s", M.latest_pipeline.web_url))
|
||||
table.insert(lines, string.format("Triggered By: %s", M.latest_pipeline.source))
|
||||
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Jobs:")
|
||||
local all_lines = {}
|
||||
for i, data in ipairs(pipelines_data) do
|
||||
local pipeline = data.pipeline
|
||||
local lines = data.lines
|
||||
|
||||
local longest_title = u.get_longest_string(u.map(M.pipeline_jobs, function(v)
|
||||
return v.name
|
||||
end))
|
||||
table.insert(lines, data.pipeline_status)
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, string.format("Last Run: %s", u.time_since(pipeline.created_at)))
|
||||
table.insert(lines, string.format("Url: %s", pipeline.web_url))
|
||||
table.insert(lines, string.format("Triggered By: %s", pipeline.source))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "Jobs:")
|
||||
|
||||
local function row_offset(name)
|
||||
local offset = longest_title - string.len(name)
|
||||
local res = string.rep(" ", offset + 5)
|
||||
return res
|
||||
end
|
||||
local longest_title = u.get_longest_string(u.map(data.jobs, function(v)
|
||||
return v.name
|
||||
end))
|
||||
|
||||
for _, pipeline_job in ipairs(M.pipeline_jobs) do
|
||||
local offset = row_offset(pipeline_job.name)
|
||||
local row = string.format(
|
||||
"%s%s %s (%s)",
|
||||
pipeline_job.name,
|
||||
offset,
|
||||
state.settings.pipeline[pipeline_job.status] or "*",
|
||||
pipeline_job.status or ""
|
||||
)
|
||||
local function row_offset(name)
|
||||
local offset = longest_title - string.len(name)
|
||||
local res = string.rep(" ", offset + 5)
|
||||
return res
|
||||
end
|
||||
|
||||
table.insert(lines, row)
|
||||
for _, pipeline_job in ipairs(data.jobs) do
|
||||
local offset = row_offset(pipeline_job.name)
|
||||
local row = string.format(
|
||||
"%s%s %s (%s)",
|
||||
pipeline_job.name,
|
||||
offset,
|
||||
state.settings.pipeline[pipeline_job.status] or "*",
|
||||
pipeline_job.status or ""
|
||||
)
|
||||
table.insert(lines, row)
|
||||
end
|
||||
|
||||
-- Add separator between pipelines
|
||||
if i < #pipelines_data then
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, string.rep("-", max_width))
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
table.insert(all_lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
M.color_status(M.latest_pipeline.status, bufnr, lines[1], 1)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, all_lines)
|
||||
|
||||
for i, pipeline_job in ipairs(M.pipeline_jobs) do
|
||||
M.color_status(pipeline_job.status, bufnr, lines[7 + i], 7 + i)
|
||||
local line_offset = 0
|
||||
for _, data in ipairs(pipelines_data) do
|
||||
local pipeline = data.pipeline
|
||||
local lines = data.lines
|
||||
|
||||
M.color_status(pipeline.status, bufnr, all_lines[line_offset + 1], line_offset + 1)
|
||||
|
||||
for j, pipeline_job in ipairs(data.jobs) do
|
||||
M.color_status(pipeline_job.status, bufnr, all_lines[line_offset + 7 + j], line_offset + 7 + j)
|
||||
end
|
||||
|
||||
line_offset = line_offset + #lines
|
||||
end
|
||||
|
||||
pipeline_popup.border:set_text("top", "Pipeline Status", "center")
|
||||
pipeline_popup.border:set_text("top", "Pipelines Status", "center")
|
||||
popup.set_popup_keymaps(pipeline_popup, M.retrigger, M.see_logs)
|
||||
u.switch_can_edit_buf(bufnr, false)
|
||||
end)
|
||||
end
|
||||
|
||||
M.retrigger = function()
|
||||
M.latest_pipeline = get_latest_pipeline()
|
||||
if not M.latest_pipeline then
|
||||
return
|
||||
end
|
||||
if M.latest_pipeline.status ~= "failed" then
|
||||
u.notify("Pipeline is not in a failed state!", vim.log.levels.WARN)
|
||||
local pipelines = get_latest_pipelines()
|
||||
if not pipelines then
|
||||
return
|
||||
end
|
||||
|
||||
job.run_job("/pipeline/" .. M.latest_pipeline.id, "POST", nil, function()
|
||||
u.notify("Pipeline re-triggered!", vim.log.levels.INFO)
|
||||
end)
|
||||
local failed_pipelines = {}
|
||||
|
||||
for idx, pipeline in ipairs(pipelines) do
|
||||
local pipeline_jobs = get_pipeline_jobs(idx)
|
||||
for _, pjob in ipairs(pipeline_jobs) do
|
||||
if pjob.status == "failed" then
|
||||
if pipeline.status ~= "failed" then
|
||||
u.notify("Pipeline is not in a failed state!", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
if not failed_pipelines[pipeline.id] then
|
||||
job.run_job("/pipeline/trigger/" .. pipeline.id, "POST", nil, function()
|
||||
u.notify("Pipeline " .. pipeline.id .. " re-triggered!", vim.log.levels.INFO)
|
||||
end)
|
||||
failed_pipelines[pipeline.id] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.see_logs = function()
|
||||
@@ -173,12 +244,8 @@ end
|
||||
---colorize the pipeline icon.
|
||||
---@param wrap_with_color boolean
|
||||
---@return string
|
||||
M.get_pipeline_icon = function(wrap_with_color)
|
||||
M.latest_pipeline = get_latest_pipeline()
|
||||
if not M.latest_pipeline then
|
||||
return ""
|
||||
end
|
||||
local symbol = state.settings.pipeline[M.latest_pipeline.status]
|
||||
M.get_pipeline_icon = function(idx, wrap_with_color)
|
||||
local symbol = state.settings.pipeline[state.PIPELINE[idx].latest_pipeline.status]
|
||||
if not wrap_with_color then
|
||||
return symbol
|
||||
end
|
||||
@@ -196,12 +263,13 @@ end
|
||||
---colorize the pipeline icon.
|
||||
---@param wrap_with_color boolean
|
||||
---@return string
|
||||
M.get_pipeline_status = function(wrap_with_color)
|
||||
M.latest_pipeline = get_latest_pipeline()
|
||||
if not M.latest_pipeline then
|
||||
return ""
|
||||
end
|
||||
return string.format("%s (%s)", M.get_pipeline_icon(wrap_with_color), M.latest_pipeline.status)
|
||||
M.get_pipeline_status = function(idx, wrap_with_color)
|
||||
return string.format(
|
||||
"[%s]: Status: %s (%s)",
|
||||
state.PIPELINE[idx].name,
|
||||
M.get_pipeline_icon(idx, wrap_with_color),
|
||||
state.PIPELINE[idx].latest_pipeline.status
|
||||
)
|
||||
end
|
||||
|
||||
M.color_status = function(status, bufnr, status_line, linnr)
|
||||
|
||||
@@ -139,6 +139,7 @@ M.build_info_lines = function()
|
||||
return pipeline.status
|
||||
end,
|
||||
},
|
||||
web_url = { title = "MR URL", content = info.web_url },
|
||||
}
|
||||
|
||||
local longest_used = ""
|
||||
|
||||
@@ -120,11 +120,11 @@
|
||||
---Relevant for renamed files only, the name of the file in the previous commit
|
||||
---@field old_file_name string
|
||||
---@field current_bufnr integer
|
||||
---@field new_sha_win_id integer
|
||||
---@field old_sha_win_id integer
|
||||
---@field opposite_bufnr integer
|
||||
---@field new_line_from_buf integer
|
||||
---@field old_line_from_buf integer
|
||||
---@field new_sha_focused boolean
|
||||
---@field current_win_id integer
|
||||
|
||||
---@class LocationData
|
||||
---@field old_line integer | nil
|
||||
@@ -346,3 +346,15 @@
|
||||
---@field perform_action? string -- Once in normal mode, does action (like saving comment or applying description edit, etc)
|
||||
---@field perform_linewise_action? string -- Once in normal mode, does the linewise action (see logs for this job, etc)
|
||||
---@field discard_changes? string -- Quit the popup discarding changes, the popup content is not? saved to the `temp_registers` (see `:h gitlab.nvim.temp-registers`)
|
||||
|
||||
---@class List The base class for all list objects
|
||||
---@field new function -- Creates a new List from a table
|
||||
---@field map function -- Mutates a given list
|
||||
---@field filter function -- Filters a given list
|
||||
---@field partition function -- Partitions a given list into two lists
|
||||
---@field reduce function -- Applies a function to reduce the list to a single value
|
||||
---@field sort function -- Sorts the list in place based on a comparator function
|
||||
---@field find function -- Returns the first element that satisfies the callback
|
||||
---@field slice function -- Returns a portion of the list between start and end indices
|
||||
---@field includes function -- Returns true if any of the elements can satisfy the callback
|
||||
---@field values function -- Returns an iterator over the list's values
|
||||
|
||||
@@ -139,7 +139,7 @@ M.check = function(return_results)
|
||||
end
|
||||
|
||||
if return_results then
|
||||
return #warnings + #errors == 0
|
||||
return #errors == 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -230,9 +230,9 @@ end
|
||||
---This is in order to build the payload for Gitlab correctly by setting the old line and new line.
|
||||
---@param old_line number|nil
|
||||
---@param new_line number|nil
|
||||
---@param is_current_sha_focused boolean
|
||||
---@param new_sha_focused boolean
|
||||
---@return string|nil
|
||||
function M.get_modification_type(old_line, new_line, is_current_sha_focused)
|
||||
function M.get_modification_type(old_line, new_line, new_sha_focused)
|
||||
local hunk_and_diff_data = parse_hunks_and_diff(state.INFO.diff_refs.base_sha)
|
||||
if hunk_and_diff_data.hunks == nil then
|
||||
return
|
||||
@@ -240,7 +240,7 @@ function M.get_modification_type(old_line, new_line, is_current_sha_focused)
|
||||
|
||||
local hunks = hunk_and_diff_data.hunks
|
||||
local all_diff_output = hunk_and_diff_data.all_diff_output
|
||||
return is_current_sha_focused and get_modification_type_from_new_sha(new_line, hunks, all_diff_output)
|
||||
return new_sha_focused and get_modification_type_from_new_sha(new_line, hunks, all_diff_output)
|
||||
or get_modification_type_from_old_sha(old_line, new_line, hunks, all_diff_output)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
local u = require("gitlab.utils")
|
||||
local state = require("gitlab.state")
|
||||
local reviewer = require("gitlab.reviewer")
|
||||
local List = require("gitlab.utils.list")
|
||||
|
||||
local M = {}
|
||||
@@ -11,24 +10,24 @@ local M = {}
|
||||
---@field resolved boolean|nil
|
||||
---@field created_at string|nil
|
||||
|
||||
---Return true if discussion has a placeable diagnostic, false otherwise.
|
||||
---@param note NoteWithValues
|
||||
---@param file string
|
||||
---@return boolean
|
||||
local filter_discussions_and_notes = function(note, file)
|
||||
local filter_discussions_and_notes = function(note)
|
||||
---Do not include unlinked notes
|
||||
return note.position ~= nil
|
||||
and (note.position.new_path == file or note.position.old_path == file)
|
||||
---Skip resolved discussions if user wants to
|
||||
and not (state.settings.discussion_signs.skip_resolved_discussion and note.resolvable and note.resolved)
|
||||
---Skip discussions from old revisions
|
||||
and not (
|
||||
state.settings.discussion_signs.skip_old_revision_discussion
|
||||
and note.created_at ~= nil
|
||||
and u.from_iso_format_date_to_timestamp(note.created_at)
|
||||
<= u.from_iso_format_date_to_timestamp(state.MR_REVISIONS[1].created_at)
|
||||
)
|
||||
end
|
||||
|
||||
---Filter all discussions which are relevant for currently visible signs and diagnostics.
|
||||
---Filter all discussions and drafts which have placeable signs and diagnostics.
|
||||
---@return Discussion|DraftNote[]
|
||||
M.filter_placeable_discussions = function()
|
||||
local discussions = u.ensure_table(state.DISCUSSION_DATA and state.DISCUSSION_DATA.discussions or {})
|
||||
@@ -41,18 +40,13 @@ M.filter_placeable_discussions = function()
|
||||
draft_notes = {}
|
||||
end
|
||||
|
||||
local file = reviewer.get_current_file_path()
|
||||
if not file then
|
||||
return {}
|
||||
end
|
||||
|
||||
local filtered_discussions = List.new(discussions):filter(function(discussion)
|
||||
local first_note = discussion.notes[1]
|
||||
return type(first_note.position) == "table" and filter_discussions_and_notes(first_note, file)
|
||||
return type(first_note.position) == "table" and filter_discussions_and_notes(first_note)
|
||||
end)
|
||||
|
||||
local filtered_draft_notes = List.new(draft_notes):filter(function(note)
|
||||
return filter_discussions_and_notes(note, file)
|
||||
return filter_discussions_and_notes(note)
|
||||
end)
|
||||
|
||||
return u.join(filtered_discussions, filtered_draft_notes)
|
||||
|
||||
@@ -85,47 +85,62 @@ local create_multiline_diagnostic = function(d_or_n)
|
||||
}, d_or_n)
|
||||
end
|
||||
|
||||
---Set diagnostics in currently new SHA.
|
||||
---Set diagnostics in the given buffer.
|
||||
---@param namespace number namespace for diagnostics
|
||||
---@param bufnr number the bufnr for placing the diagnostics
|
||||
---@param diagnostics table see :h vim.diagnostic.set
|
||||
---@param opts table? see :h vim.diagnostic.set
|
||||
local set_diagnostics_in_new_sha = function(namespace, diagnostics, opts)
|
||||
local view = diffview_lib.get_current_view()
|
||||
if not view then
|
||||
return
|
||||
end
|
||||
vim.diagnostic.set(namespace, view.cur_layout.b.file.bufnr, diagnostics, opts)
|
||||
require("gitlab.indicators.signs").set_signs(diagnostics, view.cur_layout.b.file.bufnr)
|
||||
local set_diagnostics = function(namespace, bufnr, diagnostics, opts)
|
||||
vim.diagnostic.set(namespace, bufnr, diagnostics, opts)
|
||||
require("gitlab.indicators.signs").set_signs(diagnostics, bufnr)
|
||||
end
|
||||
|
||||
---Set diagnostics in old SHA.
|
||||
---@param namespace number namespace for diagnostics
|
||||
---@param diagnostics table see :h vim.diagnostic.set
|
||||
---@param opts table? see :h vim.diagnostic.set
|
||||
local set_diagnostics_in_old_sha = function(namespace, diagnostics, opts)
|
||||
local view = diffview_lib.get_current_view()
|
||||
if not view then
|
||||
return
|
||||
end
|
||||
vim.diagnostic.set(namespace, view.cur_layout.a.file.bufnr, diagnostics, opts)
|
||||
require("gitlab.indicators.signs").set_signs(diagnostics, view.cur_layout.a.file.bufnr)
|
||||
end
|
||||
|
||||
---Refresh the diagnostics for the currently reviewed file
|
||||
---Refresh the diagnostics for all the reviewed files, and place diagnostics for the currently
|
||||
---visible buffers.
|
||||
M.refresh_diagnostics = function()
|
||||
require("gitlab.indicators.signs").clear_signs()
|
||||
M.clear_diagnostics()
|
||||
M.placeable_discussions = indicators_common.filter_placeable_discussions()
|
||||
|
||||
local view = diffview_lib.get_current_view()
|
||||
if view == nil then
|
||||
u.notify("Could not find Diffview view", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
M.place_diagnostics(view.cur_layout.a.file.bufnr)
|
||||
M.place_diagnostics(view.cur_layout.b.file.bufnr)
|
||||
end
|
||||
|
||||
---Filter and place the diagnostics for the given buffer.
|
||||
---@param bufnr number The number of the buffer for placing diagnostics.
|
||||
M.place_diagnostics = function(bufnr)
|
||||
if not state.settings.discussion_signs.enabled then
|
||||
return
|
||||
end
|
||||
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 ok, err = pcall(function()
|
||||
require("gitlab.indicators.signs").clear_signs()
|
||||
M.clear_diagnostics()
|
||||
local filtered_discussions = indicators_common.filter_placeable_discussions()
|
||||
if filtered_discussions == nil then
|
||||
local file_discussions = List.new(M.placeable_discussions):filter(function(discussion_or_note)
|
||||
local note = discussion_or_note.notes and discussion_or_note.notes[1] or discussion_or_note
|
||||
return note.position.new_path == view.cur_layout.b.file.path
|
||||
or note.position.old_path == view.cur_layout.a.file.path
|
||||
end)
|
||||
|
||||
if #file_discussions == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local new_diagnostics = M.parse_new_diagnostics(filtered_discussions)
|
||||
set_diagnostics_in_new_sha(diagnostics_namespace, new_diagnostics, create_display_opts())
|
||||
local new_diagnostics, old_diagnostics = List.new(file_discussions):partition(indicators_common.is_new_sha)
|
||||
|
||||
local old_diagnostics = M.parse_old_diagnostics(filtered_discussions)
|
||||
set_diagnostics_in_old_sha(diagnostics_namespace, old_diagnostics, create_display_opts())
|
||||
if bufnr == view.cur_layout.a.file.bufnr then
|
||||
set_diagnostics(diagnostics_namespace, bufnr, M.parse_diagnostics(old_diagnostics), create_display_opts())
|
||||
elseif bufnr == view.cur_layout.b.file.bufnr then
|
||||
set_diagnostics(diagnostics_namespace, bufnr, M.parse_diagnostics(new_diagnostics), create_display_opts())
|
||||
end
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
@@ -134,24 +149,13 @@ M.refresh_diagnostics = function()
|
||||
end
|
||||
|
||||
---Iterates over each discussion and returns a list of tables with sign
|
||||
---data, for instance group, priority, line number etc for the new SHA
|
||||
---@param discussions Discussion[]
|
||||
---data, for instance group, priority, line number etc
|
||||
---@param discussions List
|
||||
---@return DiagnosticTable[]
|
||||
M.parse_new_diagnostics = function(discussions)
|
||||
local new_diagnostics = List.new(discussions):filter(indicators_common.is_new_sha)
|
||||
local single_line = new_diagnostics:filter(indicators_common.is_single_line):map(create_single_line_diagnostic)
|
||||
local multi_line = new_diagnostics:filter(indicators_common.is_multi_line):map(create_multiline_diagnostic)
|
||||
return u.combine(single_line, multi_line)
|
||||
end
|
||||
|
||||
---Iterates over each discussion and returns a list of tables with sign
|
||||
---data, for instance group, priority, line number etc for the old SHA
|
||||
---@param discussions Discussion[]
|
||||
---@return DiagnosticTable[]
|
||||
M.parse_old_diagnostics = function(discussions)
|
||||
local old_diagnostics = List.new(discussions):filter(indicators_common.is_old_sha)
|
||||
local single_line = old_diagnostics:filter(indicators_common.is_single_line):map(create_single_line_diagnostic)
|
||||
local multi_line = old_diagnostics:filter(indicators_common.is_multi_line):map(create_multiline_diagnostic)
|
||||
M.parse_diagnostics = function(discussions)
|
||||
local single_line, multi_line = discussions:partition(indicators_common.is_single_line)
|
||||
single_line = single_line:map(create_single_line_diagnostic)
|
||||
multi_line = multi_line:map(create_multiline_diagnostic)
|
||||
return u.combine(single_line, multi_line)
|
||||
end
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ M.set_popup_keymaps = function(popup, action, linewise_action, opts)
|
||||
settings.popup.temp_registers = temp_registers
|
||||
end, {
|
||||
buffer = popup.bufnr,
|
||||
desc = "Quit discarding changes",
|
||||
desc = "Quit, discarding changes",
|
||||
nowait = settings.keymaps.popup.discard_changes_nowait,
|
||||
})
|
||||
end
|
||||
@@ -236,4 +236,16 @@ M.set_cycle_popups_keymaps = function(popups)
|
||||
end
|
||||
end
|
||||
|
||||
---Create the title for the comment popup.
|
||||
---@param title string The main title, e.g., "Comment".
|
||||
---@param file_name string Name of file for which comment is created.
|
||||
---@param start_line integer Start of the line range.
|
||||
---@param end_line integer End of the line range.
|
||||
---@return string title The full title of the popup.
|
||||
M.create_title = function(title, file_name, start_line, end_line)
|
||||
local range = start_line < end_line and string.format("-%s", end_line) or ""
|
||||
local position = string.format("%s%s", start_line, range)
|
||||
return string.format("%s [%s:%s]", title, file_name, position)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -16,6 +16,7 @@ local M = {
|
||||
bufnr = nil,
|
||||
tabnr = nil,
|
||||
stored_win = nil,
|
||||
buf_winids = {},
|
||||
}
|
||||
|
||||
-- Checks for legacy installations, only Diffview is supported.
|
||||
@@ -62,6 +63,8 @@ M.open = function()
|
||||
vim.api.nvim_command(string.format("%s %s..%s", diffview_open_command, diff_refs.base_sha, diff_refs.head_sha))
|
||||
|
||||
M.is_open = true
|
||||
local cur_view = diffview_lib.get_current_view()
|
||||
M.diffview_layout = cur_view.cur_layout
|
||||
M.tabnr = vim.api.nvim_get_current_tabpage()
|
||||
|
||||
if state.settings.discussion_diagnostic ~= nil or state.settings.discussion_sign ~= nil then
|
||||
@@ -77,9 +80,11 @@ M.open = function()
|
||||
M.tabnr = nil
|
||||
end
|
||||
end
|
||||
require("diffview.config").user_emitter:on("view_closed", function(_, ...)
|
||||
M.is_open = false
|
||||
on_diffview_closed(...)
|
||||
require("diffview.config").user_emitter:on("view_closed", function(_, args)
|
||||
if M.tabnr == args.tabpage then
|
||||
M.is_open = false
|
||||
on_diffview_closed(args)
|
||||
end
|
||||
end)
|
||||
|
||||
if state.settings.discussion_tree.auto_open then
|
||||
@@ -100,10 +105,11 @@ M.close = function()
|
||||
end
|
||||
|
||||
--- Jumps to the location provided in the reviewer window
|
||||
---@param file_name string
|
||||
---@param line_number number
|
||||
---@param new_buffer boolean
|
||||
M.jump = function(file_name, line_number, new_buffer)
|
||||
---@param file_name string The file name after change.
|
||||
---@param old_file_name string The file name before change (different from file_name for renamed/moved files).
|
||||
---@param line_number number Line number from the discussion node.
|
||||
---@param new_buffer boolean If true, jump to the NEW SHA.
|
||||
M.jump = function(file_name, old_file_name, line_number, new_buffer)
|
||||
if M.tabnr == nil then
|
||||
u.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
|
||||
return
|
||||
@@ -116,8 +122,8 @@ M.jump = function(file_name, line_number, new_buffer)
|
||||
end
|
||||
|
||||
local files = view.panel:ordered_file_list()
|
||||
local file = List.new(files):find(function(file)
|
||||
return file.path == file_name
|
||||
local file = List.new(files):find(function(f)
|
||||
return new_buffer and f.path == file_name or f.oldpath == old_file_name
|
||||
end)
|
||||
if file == nil then
|
||||
u.notify(
|
||||
@@ -148,9 +154,13 @@ end
|
||||
|
||||
---Get the data from diffview, such as line information and file name. May be used by
|
||||
---other modules such as the comment module to create line codes or set diagnostics
|
||||
---@param current_win integer The ID of the currently focused window
|
||||
---@return DiffviewInfo | nil
|
||||
M.get_reviewer_data = function()
|
||||
M.get_reviewer_data = function(current_win)
|
||||
local view = diffview_lib.get_current_view()
|
||||
if view == nil then
|
||||
return
|
||||
end
|
||||
local layout = view.cur_layout
|
||||
local old_win = u.get_window_id_by_buffer_id(layout.a.file.bufnr)
|
||||
local new_win = u.get_window_id_by_buffer_id(layout.b.file.bufnr)
|
||||
@@ -169,9 +179,9 @@ M.get_reviewer_data = function()
|
||||
local new_line = vim.api.nvim_win_get_cursor(new_win)[1]
|
||||
local old_line = vim.api.nvim_win_get_cursor(old_win)[1]
|
||||
|
||||
local is_current_sha_focused = M.is_current_sha_focused()
|
||||
local new_sha_focused = M.is_new_sha_focused(current_win)
|
||||
|
||||
local modification_type = hunks.get_modification_type(old_line, new_line, is_current_sha_focused)
|
||||
local modification_type = hunks.get_modification_type(old_line, new_line, new_sha_focused)
|
||||
if modification_type == nil then
|
||||
u.notify("Error getting modification type", vim.log.levels.ERROR)
|
||||
return
|
||||
@@ -181,32 +191,32 @@ M.get_reviewer_data = function()
|
||||
u.notify("Comments on unmodified lines will be placed in the old file", vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
local current_bufnr = is_current_sha_focused and layout.b.file.bufnr or layout.a.file.bufnr
|
||||
local opposite_bufnr = is_current_sha_focused and layout.a.file.bufnr or layout.b.file.bufnr
|
||||
local old_sha_win_id = u.get_window_id_by_buffer_id(layout.a.file.bufnr)
|
||||
local new_sha_win_id = u.get_window_id_by_buffer_id(layout.b.file.bufnr)
|
||||
local current_bufnr = new_sha_focused and layout.b.file.bufnr or layout.a.file.bufnr
|
||||
local opposite_bufnr = new_sha_focused and layout.a.file.bufnr or layout.b.file.bufnr
|
||||
|
||||
return {
|
||||
-- TODO: swap 'a' and 'b' to fix lua/gitlab/actions/comment.lua:158, and hopefully also
|
||||
-- lua/gitlab/indicators/diagnostics.lua:129.
|
||||
file_name = layout.a.file.path,
|
||||
old_file_name = M.is_file_renamed() and layout.b.file.path or "",
|
||||
old_line_from_buf = old_line,
|
||||
new_line_from_buf = new_line,
|
||||
modification_type = modification_type,
|
||||
new_sha_win_id = new_sha_win_id,
|
||||
current_bufnr = current_bufnr,
|
||||
old_sha_win_id = old_sha_win_id,
|
||||
opposite_bufnr = opposite_bufnr,
|
||||
new_sha_focused = new_sha_focused,
|
||||
current_win_id = current_win,
|
||||
}
|
||||
end
|
||||
|
||||
---Return whether user is focused on the new version of the file
|
||||
---@param current_win integer The ID of the currently focused window
|
||||
---@return boolean
|
||||
M.is_current_sha_focused = function()
|
||||
M.is_new_sha_focused = function(current_win)
|
||||
local view = diffview_lib.get_current_view()
|
||||
local layout = view.cur_layout
|
||||
local b_win = u.get_window_id_by_buffer_id(layout.b.file.bufnr)
|
||||
local a_win = u.get_window_id_by_buffer_id(layout.a.file.bufnr)
|
||||
local current_win = require("gitlab.actions.comment").current_win
|
||||
if a_win ~= current_win and b_win ~= current_win then
|
||||
current_win = M.stored_win
|
||||
M.stored_win = nil
|
||||
@@ -248,7 +258,7 @@ M.does_file_have_changes = function()
|
||||
return file_data.stats.additions > 0 or file_data.stats.deletions > 0
|
||||
end
|
||||
|
||||
---Diffview exposes events which can be used to setup autocommands.
|
||||
---Run callback every time the buffer in one of the two reviewer windows changes.
|
||||
---@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", {})
|
||||
@@ -256,7 +266,6 @@ M.set_callback_for_file_changed = function(callback)
|
||||
pattern = { "DiffviewDiffBufWinEnter" },
|
||||
group = group,
|
||||
callback = function(...)
|
||||
M.stored_win = vim.api.nvim_get_current_win()
|
||||
if M.tabnr == vim.api.nvim_get_current_tabpage() then
|
||||
callback(...)
|
||||
end
|
||||
@@ -264,7 +273,22 @@ M.set_callback_for_file_changed = function(callback)
|
||||
})
|
||||
end
|
||||
|
||||
---Diffview exposes events which can be used to setup autocommands.
|
||||
---Run callback the first time a new diff buffer is created and loaded into a window.
|
||||
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
|
||||
M.set_callback_for_buf_read = function(callback)
|
||||
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.buf_read", {})
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = { "DiffviewDiffBufRead" },
|
||||
group = group,
|
||||
callback = function(...)
|
||||
if vim.api.nvim_get_current_tabpage() == M.tabnr then
|
||||
callback(...)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---Run callback when the reviewer is closed or the user switches to another tab.
|
||||
---@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", {})
|
||||
@@ -272,20 +296,25 @@ M.set_callback_for_reviewer_leave = function(callback)
|
||||
pattern = { "DiffviewViewLeave", "DiffviewViewClosed" },
|
||||
group = group,
|
||||
callback = function(...)
|
||||
if M.tabnr == vim.api.nvim_get_current_tabpage() then
|
||||
if vim.api.nvim_get_current_tabpage() == M.tabnr then
|
||||
callback(...)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---Run callback when the reviewer is opened for the first time or the view is entered from another
|
||||
---tab page.
|
||||
---@param callback fun(opts: table) - for more information about opts see callback in :h nvim_create_autocmd
|
||||
M.set_callback_for_reviewer_enter = function(callback)
|
||||
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.enter", {})
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = { "DiffviewViewOpened" },
|
||||
pattern = { "DiffviewViewEnter", "DiffviewViewOpened" },
|
||||
group = group,
|
||||
callback = function(...)
|
||||
callback(...)
|
||||
if vim.api.nvim_get_current_tabpage() == M.tabnr then
|
||||
callback(...)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
@@ -325,8 +354,16 @@ end
|
||||
|
||||
---Set keymaps for creating comments, suggestions and for jumping to discussion tree.
|
||||
---@param bufnr integer Number of the buffer for which the keybindings will be created.
|
||||
---@param keymaps table The settings keymaps table.
|
||||
local set_keymaps = function(bufnr, keymaps)
|
||||
M.set_keymaps = function(bufnr)
|
||||
if bufnr == nil or not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
-- Require keymaps only after user settings have been merged with defaults
|
||||
local keymaps = require("gitlab.state").settings.keymaps
|
||||
if keymaps.disable_all or keymaps.reviewer.disable_all then
|
||||
return
|
||||
end
|
||||
|
||||
-- Set mappings for creating comments
|
||||
if keymaps.reviewer.create_comment ~= false then
|
||||
-- Set keymap for repeated operator keybinding
|
||||
@@ -399,29 +436,17 @@ local set_keymaps = function(bufnr, keymaps)
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets up keymaps for both buffers in the reviewer.
|
||||
M.set_reviewer_keymaps = function()
|
||||
---Delete keymaps from reviewer buffers.
|
||||
---@param bufnr integer Number of the buffer from which the keybindings will be removed.
|
||||
local del_keymaps = function(bufnr)
|
||||
if bufnr == nil or not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
return
|
||||
end
|
||||
-- Require keymaps only after user settings have been merged with defaults
|
||||
local keymaps = require("gitlab.state").settings.keymaps
|
||||
if keymaps.disable_all or keymaps.reviewer.disable_all then
|
||||
return
|
||||
end
|
||||
|
||||
local view = diffview_lib.get_current_view()
|
||||
local a = view.cur_layout.a.file.bufnr
|
||||
local b = view.cur_layout.b.file.bufnr
|
||||
if a ~= nil and vim.api.nvim_buf_is_loaded(a) then
|
||||
set_keymaps(a, keymaps)
|
||||
end
|
||||
if b ~= nil and vim.api.nvim_buf_is_loaded(b) then
|
||||
set_keymaps(b, keymaps)
|
||||
end
|
||||
end
|
||||
|
||||
---Delete keymaps from reviewer buffers.
|
||||
---@param bufnr integer Number of the buffer from which the keybindings will be removed.
|
||||
---@param keymaps table The settings keymaps table.
|
||||
local del_keymaps = function(bufnr, keymaps)
|
||||
for _, func in ipairs({ "create_comment", "create_suggestion" }) do
|
||||
if keymaps.reviewer[func] ~= false then
|
||||
for _, mode in ipairs({ "n", "o", "v" }) do
|
||||
@@ -434,23 +459,33 @@ local del_keymaps = function(bufnr, keymaps)
|
||||
end
|
||||
end
|
||||
|
||||
--- Deletes keymaps from both buffers in the reviewer.
|
||||
M.del_reviewer_keymaps = function()
|
||||
-- Require keymaps only after user settings have been merged with defaults
|
||||
local keymaps = require("gitlab.state").settings.keymaps
|
||||
if keymaps.disable_all or keymaps.reviewer.disable_all then
|
||||
return
|
||||
end
|
||||
--- Set up autocaommands that will take care of setting and unsetting buffer-local options and keymaps
|
||||
M.set_reviewer_autocommands = function(bufnr)
|
||||
local group = vim.api.nvim_create_augroup("gitlab.diffview.autocommand.win_enter." .. bufnr, {})
|
||||
vim.api.nvim_create_autocmd({ "WinEnter", "BufWinEnter" }, {
|
||||
group = group,
|
||||
buffer = bufnr,
|
||||
callback = function()
|
||||
if vim.api.nvim_get_current_win() == M.buf_winids[bufnr] then
|
||||
M.stored_win = vim.api.nvim_get_current_win()
|
||||
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
|
||||
M.set_keymaps(bufnr)
|
||||
else
|
||||
if M.diffview_layout.b.id == M.buf_winids[bufnr] then
|
||||
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
|
||||
end
|
||||
del_keymaps(bufnr)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local view = diffview_lib.get_current_view()
|
||||
local a = view.cur_layout.a.file.bufnr
|
||||
local b = view.cur_layout.b.file.bufnr
|
||||
if a ~= nil and vim.api.nvim_buf_is_loaded(a) then
|
||||
del_keymaps(a, keymaps)
|
||||
end
|
||||
if b ~= nil and vim.api.nvim_buf_is_loaded(b) then
|
||||
del_keymaps(b, keymaps)
|
||||
end
|
||||
--- Update the stored winid for a given reviewer buffer. This is necessary for the
|
||||
--- M.set_reviewer_autocommands function to work correctly in cases like when the user closes one of
|
||||
--- the original reviewer windows and Diffview automatically creates a new pair
|
||||
--- of reviewer windows or the user wipes out a buffer and Diffview reloads it with a different ID.
|
||||
M.update_winid_for_buffer = function(bufnr)
|
||||
M.buf_winids[bufnr] = vim.fn.bufwinid(bufnr)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -7,6 +7,7 @@ local state = require("gitlab.state")
|
||||
---@field reviewer_data DiffviewInfo
|
||||
---@field run function
|
||||
---@field build_location_data function
|
||||
---@field visual_range table
|
||||
|
||||
---@class ReviewerLineInfo
|
||||
---@field old_line integer|nil
|
||||
@@ -19,16 +20,21 @@ local state = require("gitlab.state")
|
||||
|
||||
local Location = {}
|
||||
Location.__index = Location
|
||||
---@param reviewer_data DiffviewInfo
|
||||
---@param visual_range LineRange | nil
|
||||
---@return Location
|
||||
function Location.new(reviewer_data, visual_range)
|
||||
---The new() function returns nil when the location cannot be created due to missing
|
||||
---reviewer data.
|
||||
---@return Location | nil
|
||||
function Location.new()
|
||||
local current_win = vim.api.nvim_get_current_win()
|
||||
local reviewer_data = require("gitlab.reviewer").get_reviewer_data(current_win)
|
||||
if reviewer_data == nil then
|
||||
return nil
|
||||
end
|
||||
local location = {}
|
||||
local instance = setmetatable(location, Location)
|
||||
instance.reviewer_data = reviewer_data
|
||||
instance.visual_range = visual_range
|
||||
instance.base_sha = state.INFO.diff_refs.base_sha
|
||||
instance.head_sha = state.INFO.diff_refs.head_sha
|
||||
instance:build_location_data()
|
||||
return instance
|
||||
end
|
||||
|
||||
@@ -37,11 +43,13 @@ end
|
||||
function Location:build_location_data()
|
||||
---@type DiffviewInfo
|
||||
local reviewer_data = self.reviewer_data
|
||||
---@type LineRange | nil
|
||||
local visual_range = self.visual_range
|
||||
|
||||
local start_line, end_line = u.get_visual_selection_boundaries()
|
||||
---@type LineRange
|
||||
self.visual_range = { start_line = start_line, end_line = end_line }
|
||||
|
||||
---@type LocationData
|
||||
local location_data = {
|
||||
self.location_data = {
|
||||
old_line = nil,
|
||||
new_line = nil,
|
||||
line_range = nil,
|
||||
@@ -51,30 +59,29 @@ function Location:build_location_data()
|
||||
-- Comment on deleted line: Include only old_line in payload.
|
||||
-- The line was not found in any hunks, send both lines.
|
||||
if reviewer_data.modification_type == "added" then
|
||||
location_data.old_line = nil
|
||||
location_data.new_line = reviewer_data.new_line_from_buf
|
||||
self.location_data.old_line = nil
|
||||
self.location_data.new_line = reviewer_data.new_line_from_buf
|
||||
elseif reviewer_data.modification_type == "deleted" then
|
||||
location_data.old_line = reviewer_data.old_line_from_buf
|
||||
location_data.new_line = nil
|
||||
self.location_data.old_line = reviewer_data.old_line_from_buf
|
||||
self.location_data.new_line = nil
|
||||
elseif
|
||||
reviewer_data.modification_type == "unmodified" or reviewer_data.modification_type == "bad_file_unmodified"
|
||||
then
|
||||
location_data.old_line = reviewer_data.old_line_from_buf
|
||||
location_data.new_line = reviewer_data.new_line_from_buf
|
||||
self.location_data.old_line = reviewer_data.old_line_from_buf
|
||||
self.location_data.new_line = reviewer_data.new_line_from_buf
|
||||
end
|
||||
|
||||
self.location_data = location_data
|
||||
if visual_range == nil then
|
||||
return
|
||||
else
|
||||
if end_line > start_line then
|
||||
self.location_data.line_range = {
|
||||
start = {},
|
||||
["end"] = {},
|
||||
}
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
self:set_start_range(visual_range)
|
||||
self:set_end_range(visual_range)
|
||||
self:set_start_range()
|
||||
self:set_end_range()
|
||||
|
||||
-- Ranged comments should always use the end of the range.
|
||||
-- Otherwise they will not highlight the full comment in Gitlab.
|
||||
@@ -90,9 +97,7 @@ end
|
||||
---@param line number
|
||||
---@return number|nil
|
||||
function Location:get_line_number_from_new_sha(line)
|
||||
local reviewer = require("gitlab.reviewer")
|
||||
local is_current_sha_focused = reviewer.is_current_sha_focused()
|
||||
if is_current_sha_focused then
|
||||
if self.reviewer_data.new_sha_focused then
|
||||
return line
|
||||
end
|
||||
-- Otherwise we want to get the matching line in the opposite buffer
|
||||
@@ -111,9 +116,7 @@ end
|
||||
---@param line number
|
||||
---@return number|nil
|
||||
function Location:get_line_number_from_old_sha(line)
|
||||
local reviewer = require("gitlab.reviewer")
|
||||
local is_current_sha_focused = reviewer.is_current_sha_focused()
|
||||
if not is_current_sha_focused then
|
||||
if not self.reviewer_data.new_sha_focused then
|
||||
return line
|
||||
end
|
||||
|
||||
@@ -131,32 +134,24 @@ end
|
||||
-- the reviewer is focused in.
|
||||
---@return number|nil
|
||||
function Location:get_current_line()
|
||||
local reviewer = require("gitlab.reviewer")
|
||||
local win_id = reviewer.is_current_sha_focused() and self.reviewer_data.new_sha_win_id
|
||||
or self.reviewer_data.old_sha_win_id
|
||||
if win_id == nil then
|
||||
if self.reviewer_data.current_win_id == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local current_line = vim.api.nvim_win_get_cursor(win_id)[1]
|
||||
local current_line = vim.api.nvim_win_get_cursor(self.reviewer_data.current_win_id)[1]
|
||||
return current_line
|
||||
end
|
||||
|
||||
-- Given a new_line and old_line from the start of a ranged comment, returns the start
|
||||
-- range information for the Gitlab payload
|
||||
---@param visual_range LineRange
|
||||
---@return ReviewerLineInfo|nil
|
||||
function Location:set_start_range(visual_range)
|
||||
-- Given a modification type, a visual selection range, and the hunk data, sets the start range
|
||||
-- information to the location_data for the Gitlab payload
|
||||
function Location:set_start_range()
|
||||
local current_file = require("gitlab.reviewer").get_current_file_path()
|
||||
if current_file == nil then
|
||||
u.notify("Error getting current file from Diffview", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
local reviewer = require("gitlab.reviewer")
|
||||
local is_current_sha_focused = reviewer.is_current_sha_focused()
|
||||
local win_id = is_current_sha_focused and self.reviewer_data.new_sha_win_id or self.reviewer_data.old_sha_win_id
|
||||
if win_id == nil then
|
||||
if self.reviewer_data.current_win_id == nil then
|
||||
u.notify("Error getting window number of SHA for start range", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
@@ -167,8 +162,8 @@ function Location:set_start_range(visual_range)
|
||||
return
|
||||
end
|
||||
|
||||
local new_line = self:get_line_number_from_new_sha(visual_range.start_line)
|
||||
local old_line = self:get_line_number_from_old_sha(visual_range.start_line)
|
||||
local new_line = self:get_line_number_from_new_sha(self.visual_range.start_line)
|
||||
local old_line = self:get_line_number_from_old_sha(self.visual_range.start_line)
|
||||
if
|
||||
(new_line == nil and self.reviewer_data.modification_type ~= "deleted")
|
||||
or (old_line == nil and self.reviewer_data.modification_type ~= "added")
|
||||
@@ -177,7 +172,7 @@ function Location:set_start_range(visual_range)
|
||||
return
|
||||
end
|
||||
|
||||
local modification_type = hunks.get_modification_type(old_line, new_line, is_current_sha_focused)
|
||||
local modification_type = hunks.get_modification_type(old_line, new_line, self.reviewer_data.new_sha_focused)
|
||||
if modification_type == nil then
|
||||
u.notify("Error getting modification type for start of range", vim.log.levels.ERROR)
|
||||
return
|
||||
@@ -190,10 +185,9 @@ function Location:set_start_range(visual_range)
|
||||
}
|
||||
end
|
||||
|
||||
-- Given a modification type, a range, and the hunk data, returns the end range information
|
||||
-- for the Gitlab payload
|
||||
---@param visual_range LineRange
|
||||
function Location:set_end_range(visual_range)
|
||||
-- Given a modification type, a visual selection range, and the hunk data, sets the end range
|
||||
-- information to the location_data for the Gitlab payload
|
||||
function Location:set_end_range()
|
||||
local current_file = require("gitlab.reviewer").get_current_file_path()
|
||||
if current_file == nil then
|
||||
u.notify("Error getting current file from Diffview", vim.log.levels.ERROR)
|
||||
@@ -206,8 +200,8 @@ function Location:set_end_range(visual_range)
|
||||
return
|
||||
end
|
||||
|
||||
local new_line = self:get_line_number_from_new_sha(visual_range.end_line)
|
||||
local old_line = self:get_line_number_from_old_sha(visual_range.end_line)
|
||||
local new_line = self:get_line_number_from_new_sha(self.visual_range.end_line)
|
||||
local old_line = self:get_line_number_from_old_sha(self.visual_range.end_line)
|
||||
|
||||
if
|
||||
(new_line == nil and self.reviewer_data.modification_type ~= "deleted")
|
||||
@@ -217,9 +211,7 @@ function Location:set_end_range(visual_range)
|
||||
return
|
||||
end
|
||||
|
||||
local reviewer = require("gitlab.reviewer")
|
||||
local is_current_sha_focused = reviewer.is_current_sha_focused()
|
||||
local modification_type = hunks.get_modification_type(old_line, new_line, is_current_sha_focused)
|
||||
local modification_type = hunks.get_modification_type(old_line, new_line, self.reviewer_data.new_sha_focused)
|
||||
if modification_type == nil then
|
||||
u.notify("Error getting modification type for end of range", vim.log.levels.ERROR)
|
||||
return
|
||||
|
||||
@@ -208,6 +208,7 @@ M.settings = {
|
||||
"delete_branch",
|
||||
"squash",
|
||||
"labels",
|
||||
"web_url",
|
||||
},
|
||||
},
|
||||
discussion_signs = {
|
||||
|
||||
@@ -587,19 +587,20 @@ end
|
||||
M.check_visual_mode = function()
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
if mode ~= "v" and mode ~= "V" then
|
||||
M.notify("Code suggestions and multiline comments are only available in visual mode", vim.log.levels.WARN)
|
||||
M.notify("Code suggestions and multiline comments are only available in visual mode", vim.log.levels.ERROR)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---Return start line and end line of visual selection.
|
||||
---Exists visual mode in order to access marks "<" , ">"
|
||||
---@return integer start,integer end Start line and end line
|
||||
M.get_visual_selection_boundaries = function()
|
||||
M.press_escape()
|
||||
local start_line = vim.api.nvim_buf_get_mark(0, "<")[1]
|
||||
local end_line = vim.api.nvim_buf_get_mark(0, ">")[1]
|
||||
local start_line = vim.fn.line("v")
|
||||
local end_line = vim.fn.line(".")
|
||||
if start_line > end_line then
|
||||
start_line, end_line = end_line, start_line
|
||||
end
|
||||
return start_line, end_line
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user