Multiline comment and suggestion (#66)
This MR adds the ability to leave multi-line comments and suggested changes to an MR. The features are only supported for `diffview` because we plan to deprecate `delta` as a reviewer soon.
This commit is contained in:
119
.editorconfig
Normal file
119
.editorconfig
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# see https://github.com/CppCXY/EmmyLuaCodeStyle
|
||||||
|
[*.lua]
|
||||||
|
# [basic]
|
||||||
|
|
||||||
|
# optional space/tab
|
||||||
|
indent_style = space
|
||||||
|
# if indent_style is space, this is valid
|
||||||
|
indent_size = 2
|
||||||
|
# if indent_style is tab, this is valid
|
||||||
|
tab_width = 2
|
||||||
|
# none/single/double
|
||||||
|
quote_style = "double"
|
||||||
|
|
||||||
|
continuation_indent = 2
|
||||||
|
|
||||||
|
#optional keep/never/always/smart
|
||||||
|
trailing_table_separator = keep
|
||||||
|
|
||||||
|
# keep/remove/remove_table_only/remove_string_only
|
||||||
|
call_arg_parentheses = keep
|
||||||
|
|
||||||
|
detect_end_of_line = false
|
||||||
|
|
||||||
|
# this will check text end with new line
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
# [space]
|
||||||
|
space_around_table_field_list = true
|
||||||
|
|
||||||
|
space_before_attribute = true
|
||||||
|
|
||||||
|
space_before_function_open_parenthesis = false
|
||||||
|
|
||||||
|
space_before_function_call_open_parenthesis = false
|
||||||
|
|
||||||
|
space_before_closure_open_parenthesis = false
|
||||||
|
|
||||||
|
space_before_function_call_single_arg = true
|
||||||
|
|
||||||
|
space_before_open_square_bracket = false
|
||||||
|
|
||||||
|
space_inside_function_call_parentheses = false
|
||||||
|
|
||||||
|
space_inside_function_param_list_parentheses = false
|
||||||
|
|
||||||
|
space_inside_square_brackets = false
|
||||||
|
|
||||||
|
# like t[#t+1] = 1
|
||||||
|
space_around_table_append_operator = true
|
||||||
|
|
||||||
|
ignore_spaces_inside_function_call = false
|
||||||
|
|
||||||
|
space_before_inline_comment = 1
|
||||||
|
|
||||||
|
# [operator space]
|
||||||
|
space_around_math_operator = true
|
||||||
|
|
||||||
|
space_after_comma = true
|
||||||
|
|
||||||
|
space_after_comma_in_for_statement = true
|
||||||
|
|
||||||
|
space_around_concat_operator = true
|
||||||
|
|
||||||
|
# [align]
|
||||||
|
|
||||||
|
align_call_args = false
|
||||||
|
|
||||||
|
align_function_params = true
|
||||||
|
|
||||||
|
align_continuous_assign_statement = true
|
||||||
|
|
||||||
|
align_continuous_rect_table_field = true
|
||||||
|
|
||||||
|
align_if_branch = true
|
||||||
|
|
||||||
|
align_array_table = true
|
||||||
|
|
||||||
|
# [indent]
|
||||||
|
|
||||||
|
never_indent_before_if_condition = false
|
||||||
|
|
||||||
|
never_indent_comment_on_if_branch = false
|
||||||
|
|
||||||
|
# [line space]
|
||||||
|
|
||||||
|
# The following configuration supports four expressions
|
||||||
|
# keep
|
||||||
|
# fixed(n)
|
||||||
|
# min(n)
|
||||||
|
# max(n)
|
||||||
|
# for eg. min(2)
|
||||||
|
|
||||||
|
line_space_after_if_statement = keep
|
||||||
|
|
||||||
|
line_space_after_do_statement = keep
|
||||||
|
|
||||||
|
line_space_after_while_statement = keep
|
||||||
|
|
||||||
|
line_space_after_repeat_statement = keep
|
||||||
|
|
||||||
|
line_space_after_for_statement = keep
|
||||||
|
|
||||||
|
line_space_after_local_or_assign_statement = keep
|
||||||
|
|
||||||
|
line_space_after_function_statement = fixed(2)
|
||||||
|
|
||||||
|
line_space_after_expression_statement = keep
|
||||||
|
|
||||||
|
line_space_after_comment = keep
|
||||||
|
|
||||||
|
# [line break]
|
||||||
|
break_all_list_when_line_exceed = false
|
||||||
|
|
||||||
|
auto_collapse_lines = false
|
||||||
|
|
||||||
|
# [preference]
|
||||||
|
ignore_space_after_colon = true
|
||||||
|
|
||||||
|
remove_call_expression_list_finish_comma = false
|
||||||
@@ -158,13 +158,16 @@ The upper part of the popup contains the title, which can also be edited and sen
|
|||||||
|
|
||||||
### Reviewing Diffs
|
### Reviewing Diffs
|
||||||
|
|
||||||
The `review` action will open a diff of the changes. You can leave comments using the `create_comment` action.
|
The `review` action will open a diff of the changes. You can leave comments using the `create_comment` action or for multiline comments use `create_multiline_comment` in visual mode.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
require("gitlab").review()
|
require("gitlab").review()
|
||||||
require("gitlab").create_comment()
|
require("gitlab").create_comment()
|
||||||
|
require("gitlab").create_multiline_comment()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For suggesting changes you can use `create_comment_suggestion` in visual mode which works similar to `create_multiline_comment` but prefills the comment window with gitlab [suggest changes](https://docs.gitlab.com/ee/user/project/merge_requests/reviews/suggestions.html) code block with prefilled code from visual selection.
|
||||||
|
|
||||||
The reviewer is Delta by default, but you can configure the plugin to use Diffview instead.
|
The reviewer is Delta by default, but you can configure the plugin to use Diffview instead.
|
||||||
|
|
||||||
### Discussions and Notes
|
### Discussions and Notes
|
||||||
@@ -246,6 +249,8 @@ vim.keymap.set("n", "<leader>gls", gitlab.summary)
|
|||||||
vim.keymap.set("n", "<leader>glA", gitlab.approve)
|
vim.keymap.set("n", "<leader>glA", gitlab.approve)
|
||||||
vim.keymap.set("n", "<leader>glR", gitlab.revoke)
|
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_comment_suggestion)
|
||||||
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)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -12,14 +14,29 @@ import (
|
|||||||
const mrVersionsUrl = "%s/api/v4/projects/%s/merge_requests/%d/versions"
|
const mrVersionsUrl = "%s/api/v4/projects/%s/merge_requests/%d/versions"
|
||||||
|
|
||||||
type PostCommentRequest struct {
|
type PostCommentRequest struct {
|
||||||
Comment string `json:"comment"`
|
Comment string `json:"comment"`
|
||||||
FileName string `json:"file_name"`
|
FileName string `json:"file_name"`
|
||||||
NewLine int `json:"new_line"`
|
NewLine *int `json:"new_line,omitempty"`
|
||||||
OldLine int `json:"old_line"`
|
OldLine *int `json:"old_line,omitempty"`
|
||||||
HeadCommitSHA string `json:"head_commit_sha"`
|
HeadCommitSHA string `json:"head_commit_sha"`
|
||||||
BaseCommitSHA string `json:"base_commit_sha"`
|
BaseCommitSHA string `json:"base_commit_sha"`
|
||||||
StartCommitSHA string `json:"start_commit_sha"`
|
StartCommitSHA string `json:"start_commit_sha"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
LineRange *LineRange `json:"line_range,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineRange represents the range of a note.
|
||||||
|
type LineRange struct {
|
||||||
|
StartRange *LinePosition `json:"start"`
|
||||||
|
EndRange *LinePosition `json:"end"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinePosition represents a position in a line range.
|
||||||
|
// unlike gitlab struct this does not contain LineCode with sha1 of filename
|
||||||
|
type LinePosition struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
OldLine int `json:"old_line"`
|
||||||
|
NewLine int `json:"new_line"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteCommentRequest struct {
|
type DeleteCommentRequest struct {
|
||||||
@@ -115,16 +132,42 @@ func PostComment(w http.ResponseWriter, r *http.Request) {
|
|||||||
/* If we are leaving a comment on a line, leave position. Otherwise,
|
/* If we are leaving a comment on a line, leave position. Otherwise,
|
||||||
we are leaving a note (unlinked comment) */
|
we are leaving a note (unlinked comment) */
|
||||||
if postCommentRequest.FileName != "" {
|
if postCommentRequest.FileName != "" {
|
||||||
opt.Position = &gitlab.NotePosition{
|
opt.Position = &gitlab.PositionOptions{
|
||||||
PositionType: "text",
|
PositionType: &postCommentRequest.Type,
|
||||||
StartSHA: postCommentRequest.StartCommitSHA,
|
StartSHA: &postCommentRequest.StartCommitSHA,
|
||||||
HeadSHA: postCommentRequest.HeadCommitSHA,
|
HeadSHA: &postCommentRequest.HeadCommitSHA,
|
||||||
BaseSHA: postCommentRequest.BaseCommitSHA,
|
BaseSHA: &postCommentRequest.BaseCommitSHA,
|
||||||
NewPath: postCommentRequest.FileName,
|
NewPath: &postCommentRequest.FileName,
|
||||||
OldPath: postCommentRequest.FileName,
|
OldPath: &postCommentRequest.FileName,
|
||||||
NewLine: postCommentRequest.NewLine,
|
NewLine: postCommentRequest.NewLine,
|
||||||
OldLine: postCommentRequest.OldLine,
|
OldLine: postCommentRequest.OldLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if postCommentRequest.LineRange != nil {
|
||||||
|
var format = "%x_%d_%d"
|
||||||
|
var start_filename_sha1 = fmt.Sprintf(
|
||||||
|
format,
|
||||||
|
sha1.Sum([]byte(postCommentRequest.FileName)),
|
||||||
|
postCommentRequest.LineRange.StartRange.OldLine,
|
||||||
|
postCommentRequest.LineRange.StartRange.NewLine,
|
||||||
|
)
|
||||||
|
var end_filename_sha1 = fmt.Sprintf(
|
||||||
|
format,
|
||||||
|
sha1.Sum([]byte(postCommentRequest.FileName)),
|
||||||
|
postCommentRequest.LineRange.EndRange.OldLine,
|
||||||
|
postCommentRequest.LineRange.EndRange.NewLine,
|
||||||
|
)
|
||||||
|
opt.Position.LineRange = &gitlab.LineRangeOptions{
|
||||||
|
Start: &gitlab.LinePositionOptions{
|
||||||
|
Type: &postCommentRequest.LineRange.StartRange.Type,
|
||||||
|
LineCode: &start_filename_sha1,
|
||||||
|
},
|
||||||
|
End: &gitlab.LinePositionOptions{
|
||||||
|
Type: &postCommentRequest.LineRange.EndRange.Type,
|
||||||
|
LineCode: &end_filename_sha1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
discussion, _, err := c.git.Discussions.CreateMergeRequestDiscussion(c.projectId, c.mergeId, &opt)
|
discussion, _, err := c.git.Discussions.CreateMergeRequestDiscussion(c.projectId, c.mergeId, &opt)
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -2,7 +2,7 @@ module gitlab.com/harrisoncramer/gitlab.nvim
|
|||||||
|
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require github.com/xanzy/go-gitlab v0.83.0
|
require github.com/xanzy/go-gitlab v0.93.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -21,6 +21,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/xanzy/go-gitlab v0.83.0 h1:37p0MpTPNbsTMKX/JnmJtY8Ch1sFiJzVF342+RvZEGw=
|
github.com/xanzy/go-gitlab v0.83.0 h1:37p0MpTPNbsTMKX/JnmJtY8Ch1sFiJzVF342+RvZEGw=
|
||||||
github.com/xanzy/go-gitlab v0.83.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
|
github.com/xanzy/go-gitlab v0.83.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
|
||||||
|
github.com/xanzy/go-gitlab v0.93.2 h1:kNNf3BYNYn/Zkig0B89fma12l36VLcYSGu7OnaRlRDg=
|
||||||
|
github.com/xanzy/go-gitlab v0.93.2/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
|
|||||||
@@ -1,35 +1,126 @@
|
|||||||
-- This module is responsible for creating new comments
|
-- This module is responsible for creating new comments
|
||||||
-- in the reviewer's buffer. The reviewer will pass back
|
-- in the reviewer's buffer. The reviewer will pass back
|
||||||
-- to this module the data required to make the API calls
|
-- to this module the data required to make the API calls
|
||||||
local Popup = require("nui.popup")
|
local Popup = require("nui.popup")
|
||||||
local state = require("gitlab.state")
|
local state = require("gitlab.state")
|
||||||
local job = require("gitlab.job")
|
local job = require("gitlab.job")
|
||||||
local u = require("gitlab.utils")
|
local u = require("gitlab.utils")
|
||||||
local discussions = require("gitlab.actions.discussions")
|
local discussions = require("gitlab.actions.discussions")
|
||||||
local miscellaneous = require("gitlab.actions.miscellaneous")
|
local miscellaneous = require("gitlab.actions.miscellaneous")
|
||||||
local reviewer = require("gitlab.reviewer")
|
local reviewer = require("gitlab.reviewer")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local comment_popup = Popup(u.create_popup_state("Comment", "40%", "60%"))
|
local comment_popup = Popup(u.create_popup_state("Comment", "40%", "60%"))
|
||||||
local note_popup = Popup(u.create_popup_state("Note", "40%", "60%"))
|
local note_popup = Popup(u.create_popup_state("Note", "40%", "60%"))
|
||||||
|
|
||||||
-- This function will open a comment popup in order to create a comment on the changed/updated line in the current MR
|
-- This function will open a comment popup in order to create a comment on the changed/updated
|
||||||
M.create_comment = function()
|
-- line in the current MR
|
||||||
|
M.create_comment = function()
|
||||||
comment_popup:mount()
|
comment_popup:mount()
|
||||||
state.set_popup_keymaps(comment_popup, function(text)
|
state.set_popup_keymaps(comment_popup, function(text)
|
||||||
M.confirm_create_comment(text)
|
M.confirm_create_comment(text)
|
||||||
end, miscellaneous.attach_file)
|
end, miscellaneous.attach_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.create_note = function()
|
---Create multiline comment for the last selection.
|
||||||
note_popup:mount()
|
M.create_multiline_comment = function()
|
||||||
state.set_popup_keymaps(note_popup, function(text)
|
if not u.check_visual_mode() then
|
||||||
M.confirm_create_comment(text, true)
|
return
|
||||||
|
end
|
||||||
|
local start_line, end_line = u.get_visual_selection_boundaries()
|
||||||
|
comment_popup:mount()
|
||||||
|
state.set_popup_keymaps(comment_popup, function(text)
|
||||||
|
M.confirm_create_comment(text, { start_line = start_line, end_line = end_line })
|
||||||
end, miscellaneous.attach_file)
|
end, miscellaneous.attach_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- This function (settings.popup.perform_action) will send the comment to the Go server
|
---Create comment prepopulated with gitlab suggestion
|
||||||
M.confirm_create_comment = function(text, unlinked)
|
---https://docs.gitlab.com/ee/user/project/merge_requests/reviews/suggestions.html
|
||||||
|
M.create_comment_suggestion = function()
|
||||||
|
if not u.check_visual_mode() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local start_line, end_line = u.get_visual_selection_boundaries()
|
||||||
|
local current_line = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local range = end_line - start_line
|
||||||
|
local backticks = "```"
|
||||||
|
local selected_lines = reviewer.get_lines(start_line, end_line)
|
||||||
|
|
||||||
|
if selected_lines == nil then
|
||||||
|
-- TODO: remove when delta is supported
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in ipairs(selected_lines) do
|
||||||
|
if string.match(line, "^```$") then
|
||||||
|
backticks = "````"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local suggestion_start
|
||||||
|
if start_line == current_line then
|
||||||
|
suggestion_start = backticks .. "suggestion:-0+" .. range
|
||||||
|
elseif end_line == current_line then
|
||||||
|
suggestion_start = backticks .. "suggestion:-" .. range .. "+0"
|
||||||
|
else
|
||||||
|
-- This should never happen afaik
|
||||||
|
vim.notify("Unexpected suggestion position", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
suggestion_start = suggestion_start
|
||||||
|
local suggestion_lines = {}
|
||||||
|
table.insert(suggestion_lines, suggestion_start)
|
||||||
|
vim.list_extend(suggestion_lines, selected_lines)
|
||||||
|
table.insert(suggestion_lines, backticks)
|
||||||
|
|
||||||
|
comment_popup:mount()
|
||||||
|
vim.api.nvim_buf_set_lines(comment_popup.bufnr, 0, 0, false, suggestion_lines)
|
||||||
|
state.set_popup_keymaps(comment_popup, function(text)
|
||||||
|
if range > 0 then
|
||||||
|
M.confirm_create_comment(text, { start_line = start_line, end_line = end_line })
|
||||||
|
else
|
||||||
|
M.confirm_create_comment(text, nil)
|
||||||
|
end
|
||||||
|
end, miscellaneous.attach_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
M.create_note = function()
|
||||||
|
note_popup:mount()
|
||||||
|
state.set_popup_keymaps(note_popup, function(text)
|
||||||
|
M.confirm_create_comment(text, nil, true)
|
||||||
|
end, miscellaneous.attach_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class LineRange
|
||||||
|
---@field start_line integer
|
||||||
|
---@field end_line integer
|
||||||
|
|
||||||
|
---@class ReviewerLineInfo
|
||||||
|
---@field old_line integer
|
||||||
|
---@field new_line integer
|
||||||
|
---@field type string either "new" or "old"
|
||||||
|
|
||||||
|
---@class ReviewerRangeInfo
|
||||||
|
---@field start ReviewerLineInfo
|
||||||
|
---@field end ReviewerLineInfo
|
||||||
|
|
||||||
|
---@class ReviewerInfo
|
||||||
|
---@field file_name string
|
||||||
|
---@field old_line integer | nil
|
||||||
|
---@field new_line integer | nil
|
||||||
|
---@field range_info ReviewerRangeInfo
|
||||||
|
|
||||||
|
---This function (settings.popup.perform_action) will send the comment to the Go server
|
||||||
|
---@param text string comment text
|
||||||
|
---@param range LineRange | nil range of visuel selection or nil
|
||||||
|
---@param unlinked boolean | nil if true, the comment is not linked to a line
|
||||||
|
M.confirm_create_comment = function(text, range, unlinked)
|
||||||
|
if text == nil then
|
||||||
|
vim.notify("Reviewer did not provide text of change", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
if unlinked then
|
if unlinked then
|
||||||
local body = { comment = text }
|
local body = { comment = text }
|
||||||
job.run_job("/comment", "POST", body, function(data)
|
job.run_job("/comment", "POST", body, function(data)
|
||||||
@@ -39,38 +130,22 @@ M.confirm_create_comment = function(text, unlinked)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local file_name, line_numbers, error = reviewer.get_location()
|
local reviewer_info = reviewer.get_location(range)
|
||||||
|
if not reviewer_info then
|
||||||
if error then
|
|
||||||
vim.notify(error, vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if file_name == nil then
|
|
||||||
vim.notify("Reviewer did not provide file name", vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if line_numbers == nil then
|
|
||||||
vim.notify("Reviewer did not provide line numbers of change", vim.log.levels.ERROR)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if text == nil then
|
|
||||||
vim.notify("Reviewer did not provide text of change", vim.log.levels.ERROR)
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local revision = state.MR_REVISIONS[1]
|
local revision = state.MR_REVISIONS[1]
|
||||||
local body = {
|
local body = {
|
||||||
comment = text,
|
comment = text,
|
||||||
file_name = file_name,
|
file_name = reviewer_info.file_name,
|
||||||
old_line = line_numbers.old_line,
|
old_line = reviewer_info.old_line,
|
||||||
new_line = line_numbers.new_line,
|
new_line = reviewer_info.new_line,
|
||||||
base_commit_sha = revision.base_commit_sha,
|
base_commit_sha = revision.base_commit_sha,
|
||||||
start_commit_sha = revision.start_commit_sha,
|
start_commit_sha = revision.start_commit_sha,
|
||||||
head_commit_sha = revision.head_commit_sha,
|
head_commit_sha = revision.head_commit_sha,
|
||||||
type = "modification"
|
type = "text",
|
||||||
|
line_range = reviewer_info.range_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
job.run_job("/comment", "POST", body, function(data)
|
job.run_job("/comment", "POST", body, function(data)
|
||||||
|
|||||||
@@ -25,25 +25,27 @@ return {
|
|||||||
u.has_reviewer(args.reviewer or "delta")
|
u.has_reviewer(args.reviewer or "delta")
|
||||||
end,
|
end,
|
||||||
-- Global Actions 🌎
|
-- Global Actions 🌎
|
||||||
summary = async.sequence({ info }, summary.summary),
|
summary = async.sequence({ info }, summary.summary),
|
||||||
approve = async.sequence({ info }, approvals.approve),
|
approve = async.sequence({ info }, approvals.approve),
|
||||||
revoke = async.sequence({ info }, approvals.revoke),
|
revoke = async.sequence({ info }, approvals.revoke),
|
||||||
add_reviewer = async.sequence({ info, project_members }, assignees_and_reviewers.add_reviewer),
|
add_reviewer = async.sequence({ info, project_members }, assignees_and_reviewers.add_reviewer),
|
||||||
delete_reviewer = async.sequence({ info, project_members }, assignees_and_reviewers.delete_reviewer),
|
delete_reviewer = async.sequence({ info, project_members }, assignees_and_reviewers.delete_reviewer),
|
||||||
add_assignee = async.sequence({ info, project_members }, assignees_and_reviewers.add_assignee),
|
add_assignee = async.sequence({ info, project_members }, assignees_and_reviewers.add_assignee),
|
||||||
delete_assignee = async.sequence({ info, project_members }, assignees_and_reviewers.delete_assignee),
|
delete_assignee = async.sequence({ info, project_members }, assignees_and_reviewers.delete_assignee),
|
||||||
create_comment = async.sequence({ info, revisions }, comment.create_comment),
|
create_comment = async.sequence({ info, revisions }, comment.create_comment),
|
||||||
create_note = async.sequence({ info }, comment.create_note),
|
create_multiline_comment = async.sequence({ info, revisions }, comment.create_multiline_comment),
|
||||||
review = async.sequence({ u.merge(info, { refresh = true }) }, function() reviewer.open() end),
|
create_comment_suggestion = async.sequence({ info, revisions }, comment.create_comment_suggestion),
|
||||||
pipeline = async.sequence({ info }, pipeline.open),
|
create_note = async.sequence({ info }, comment.create_note),
|
||||||
|
review = async.sequence({ u.merge(info, { refresh = true }) }, function() reviewer.open() end),
|
||||||
|
pipeline = async.sequence({ info }, pipeline.open),
|
||||||
-- Discussion Tree Actions 🌴
|
-- Discussion Tree Actions 🌴
|
||||||
toggle_discussions = async.sequence({ info }, discussions.toggle),
|
toggle_discussions = async.sequence({ info }, discussions.toggle),
|
||||||
edit_comment = async.sequence({ info }, discussions.edit_comment),
|
edit_comment = async.sequence({ info }, discussions.edit_comment),
|
||||||
delete_comment = async.sequence({ info }, discussions.delete_comment),
|
delete_comment = async.sequence({ info }, discussions.delete_comment),
|
||||||
toggle_resolved = async.sequence({ info }, discussions.toggle_resolved),
|
toggle_resolved = async.sequence({ info }, discussions.toggle_resolved),
|
||||||
reply = async.sequence({ info }, discussions.reply),
|
reply = async.sequence({ info }, discussions.reply),
|
||||||
-- Other functions 🤷
|
-- Other functions 🤷
|
||||||
state = state,
|
state = state,
|
||||||
print_settings = state.print_settings,
|
print_settings = state.print_settings,
|
||||||
open_in_browser = async.sequence({ info }, miscellaneous.open_in_browser),
|
open_in_browser = async.sequence({ info }, miscellaneous.open_in_browser),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
-- This Module contains all of the code specific to the Delta reviewer.
|
-- This Module contains all of the code specific to the Delta reviewer.
|
||||||
local state = require("gitlab.state")
|
local state = require("gitlab.state")
|
||||||
local u = require("gitlab.utils")
|
local u = require("gitlab.utils")
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
bufnr = nil
|
bufnr = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Public Functions
|
-- Public Functions
|
||||||
-- These functions are exposed externally and are used
|
-- These functions are exposed externally and are used
|
||||||
-- when the reviewer is consumed by other code. They must follow the specification
|
-- when the reviewer is consumed by other code. They must follow the specification
|
||||||
-- outlined in the reviewer/init.lua file
|
-- outlined in the reviewer/init.lua file
|
||||||
M.open = function()
|
M.open = function()
|
||||||
local current_buf = vim.api.nvim_get_current_buf()
|
local current_buf = vim.api.nvim_get_current_buf()
|
||||||
if current_buf == state.discussion_buf then
|
if current_buf == state.discussion_buf then
|
||||||
vim.api.nvim_command("wincmd w")
|
vim.api.nvim_command("wincmd w")
|
||||||
@@ -23,20 +23,22 @@ M.open = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local term_command_template =
|
local term_command_template =
|
||||||
"GIT_PAGER='delta --hunk-header-style omit --line-numbers --paging never --file-added-label %s --file-removed-label %s --file-modified-label %s' git diff %s...HEAD"
|
"GIT_PAGER='delta --hunk-header-style omit --line-numbers --paging never --file-added-label %s --file-removed-label %s --file-modified-label %s' git diff %s...HEAD"
|
||||||
|
|
||||||
local term_command = string.format(term_command_template,
|
local term_command = string.format(
|
||||||
|
term_command_template,
|
||||||
state.settings.review_pane.delta.added_file,
|
state.settings.review_pane.delta.added_file,
|
||||||
state.settings.review_pane.delta.removed_file,
|
state.settings.review_pane.delta.removed_file,
|
||||||
state.settings.review_pane.delta.modified_file,
|
state.settings.review_pane.delta.modified_file,
|
||||||
state.INFO.target_branch)
|
state.INFO.target_branch
|
||||||
|
)
|
||||||
|
|
||||||
vim.fn.termopen(term_command) -- Calls delta and sends the output to the currently blank buffer
|
vim.fn.termopen(term_command) -- Calls delta and sends the output to the currently blank buffer
|
||||||
M.bufnr = vim.api.nvim_get_current_buf()
|
M.bufnr = vim.api.nvim_get_current_buf()
|
||||||
M.winnr = vim.api.nvim_get_current_win()
|
M.winnr = vim.api.nvim_get_current_win()
|
||||||
end
|
end
|
||||||
|
|
||||||
M.jump = function(file_name, new_line, old_line)
|
M.jump = function(file_name, new_line, old_line)
|
||||||
local linnr, error = M.get_jump_location(file_name, new_line, old_line)
|
local linnr, error = M.get_jump_location(file_name, new_line, old_line)
|
||||||
if error ~= nil then
|
if error ~= nil then
|
||||||
vim.notify(error, vim.log.levels.ERROR)
|
vim.notify(error, vim.log.levels.ERROR)
|
||||||
@@ -47,25 +49,47 @@ M.jump = function(file_name, new_line, old_line)
|
|||||||
u.jump_to_buffer(M.bufnr, linnr)
|
u.jump_to_buffer(M.bufnr, linnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
M.get_location = function()
|
---Get the location of a line within the delta buffer. If range is specified, then also the location
|
||||||
if M.bufnr == nil then return nil, nil, "Delta reviewer must be initialized first" end
|
---of the lines in range.
|
||||||
|
---@param range LineRange | nil Line range to get location for
|
||||||
|
---@return ReviewerInfo | nil nil is returned only if error was encountered
|
||||||
|
M.get_location = function(range)
|
||||||
|
if M.bufnr == nil then
|
||||||
|
vim.notify("Delta reviewer must be initialized first", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if range then
|
||||||
|
vim.notify("Multiline comments are not yet supported for delta reviewer", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
if bufnr ~= M.bufnr then return nil, nil, "Line location can only be determined within reviewer window" end
|
if bufnr ~= M.bufnr then
|
||||||
|
vim.notify("Line location can only be determined within reviewer window")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local line_num = u.get_current_line_number()
|
local line_num = u.get_current_line_number()
|
||||||
local file_name = M.get_file_from_review_buffer(u.get_current_line_number())
|
local file_name = M.get_file_from_review_buffer(u.get_current_line_number())
|
||||||
|
|
||||||
local range, error = M.get_review_buffer_range(file_name)
|
local review_range, error = M.get_review_buffer_range(file_name)
|
||||||
|
|
||||||
if error ~= nil then return nil, nil, error end
|
if error ~= nil then
|
||||||
if range == nil then return nil, nil, "Review buffer range could not be identified" end
|
vim.notify(error, vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if review_range == nil then
|
||||||
|
vim.notify("Review buffer range could not be identified", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- In case the comment is left on a line without change information, we
|
-- In case the comment is left on a line without change information, we
|
||||||
-- iterate backward until we find it within the range of the changes
|
-- iterate backward until we find it within the range of the changes
|
||||||
local current_line_changes = nil
|
local current_line_changes = nil
|
||||||
local num = line_num
|
local num = line_num
|
||||||
while range ~= nil and num >= range[1] and current_line_changes == nil do
|
while review_range ~= nil and num >= review_range[1] and current_line_changes == nil do
|
||||||
local content = u.get_line_content(M.bufnr, num)
|
local content = u.get_line_content(M.bufnr, num)
|
||||||
local change_nums = M.get_change_nums(content)
|
local change_nums = M.get_change_nums(content)
|
||||||
current_line_changes = change_nums
|
current_line_changes = change_nums
|
||||||
@@ -73,12 +97,13 @@ M.get_location = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if current_line_changes == nil then
|
if current_line_changes == nil then
|
||||||
return nil, nil, "Could not find current line change information"
|
vim.notify("Could not find current line change information", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local new_line_num = line_num + 1
|
local new_line_num = line_num + 1
|
||||||
local next_line_changes = nil
|
local next_line_changes = nil
|
||||||
while range ~= nil and new_line_num <= range[2] and next_line_changes == nil do
|
while review_range ~= nil and new_line_num <= review_range[2] and next_line_changes == nil do
|
||||||
local content = u.get_line_content(M.bufnr, new_line_num)
|
local content = u.get_line_content(M.bufnr, new_line_num)
|
||||||
local change_nums = M.get_change_nums(content)
|
local change_nums = M.get_change_nums(content)
|
||||||
next_line_changes = change_nums
|
next_line_changes = change_nums
|
||||||
@@ -86,41 +111,53 @@ M.get_location = function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if next_line_changes == nil then
|
if next_line_changes == nil then
|
||||||
return nil, nil, "Could not find next line change information"
|
vim.notify("Could not find next line change information", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local result = { file_name = file_name }
|
||||||
-- This is actually a modified line if these conditions are met
|
-- This is actually a modified line if these conditions are met
|
||||||
if (current_line_changes.old_line and not current_line_changes.new_line and not next_line_changes.old_line and next_line_changes.new_line) then
|
if
|
||||||
do
|
current_line_changes.old_line
|
||||||
current_line_changes = {
|
and not current_line_changes.new_line
|
||||||
old_line = current_line_changes.old,
|
and not next_line_changes.old_line
|
||||||
new_line = next_line_changes.new_line
|
and next_line_changes.new_line
|
||||||
}
|
then
|
||||||
end
|
result.old_line = current_line_changes.old
|
||||||
|
result.new_line = next_line_changes.new_line
|
||||||
|
else
|
||||||
|
vim.notify("Could not determine line location", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
return file_name, current_line_changes
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Helper Functions 🤝
|
-- Helper Functions 🤝
|
||||||
-- These functions are not exported and should be private
|
-- These functions are not exported and should be private
|
||||||
-- to the delta reviewer, they are used to support the public functions
|
-- to the delta reviewer, they are used to support the public functions
|
||||||
M.get_jump_location = function(file_name, new_line, old_line)
|
M.get_jump_location = function(file_name, new_line, old_line)
|
||||||
local range, error = M.get_review_buffer_range(file_name)
|
local range, error = M.get_review_buffer_range(file_name)
|
||||||
if error ~= nil then return nil, error end
|
if error ~= nil then
|
||||||
if range == nil then return nil, "Review buffer range could not be identified" end
|
return nil, error
|
||||||
|
end
|
||||||
|
if range == nil then
|
||||||
|
return nil, "Review buffer range could not be identified"
|
||||||
|
end
|
||||||
|
|
||||||
local linnr = nil
|
local linnr = nil
|
||||||
|
|
||||||
local lines = M.get_review_buffer_lines(range)
|
local lines = M.get_review_buffer_lines(range)
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
local line_data = M.get_change_nums(line.line_content)
|
local line_data = M.get_change_nums(line.line_content)
|
||||||
if old_line == line_data.old_line and new_line == line_data.new_line then
|
if line_data and old_line == line_data.old_line and new_line == line_data.new_line then
|
||||||
linnr = line.line_number
|
linnr = line.line_number
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if linnr == nil then return nil, "Could not find matching line" end
|
if linnr == nil then
|
||||||
|
return nil, "Could not find matching line"
|
||||||
|
end
|
||||||
return linnr, nil
|
return linnr, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -134,32 +171,39 @@ M.get_file_from_review_buffer = function(linenr)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.get_change_nums = function(line)
|
M.get_change_nums = function(line)
|
||||||
local data, _ = line:match("(.-)" .. "│" .. "(.*)")
|
local data, _ = line:match("(.-)" .. "│" .. "(.*)")
|
||||||
local line_data = {}
|
local line_data = {}
|
||||||
if data == nil then return nil end
|
if data == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
if data ~= nil then
|
if data ~= nil and data ~= "" then
|
||||||
local old_line = u.trim(u.get_first_chunk(data, "[^" .. "⋮" .. "]+"))
|
local old_line = u.trim(u.get_first_chunk(data, "[^" .. "⋮" .. "]+"))
|
||||||
local new_line = u.trim(u.get_last_chunk(data, "[^" .. "⋮" .. "]+"))
|
local new_line = u.trim(u.get_last_chunk(data, "[^" .. "⋮" .. "]+"))
|
||||||
line_data.new_line = tonumber(new_line)
|
line_data.new_line = tonumber(new_line)
|
||||||
line_data.old_line = tonumber(old_line)
|
line_data.old_line = tonumber(old_line)
|
||||||
end
|
end
|
||||||
|
|
||||||
if line_data.new_line == nil and line_data.old_line == nil then return nil end
|
if line_data.new_line == nil and line_data.old_line == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
return line_data
|
return line_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
M.get_review_buffer_range = function(file_name)
|
M.get_review_buffer_range = function(file_name)
|
||||||
if M.bufnr == nil then return nil, "Delta reviewer must be initialized first" end
|
if M.bufnr == nil then
|
||||||
|
return nil, "Delta reviewer must be initialized first"
|
||||||
|
end
|
||||||
local lines = vim.api.nvim_buf_get_lines(M.bufnr, 0, -1, false)
|
local lines = vim.api.nvim_buf_get_lines(M.bufnr, 0, -1, false)
|
||||||
local start = nil
|
local start = nil
|
||||||
local stop = nil
|
local stop = nil
|
||||||
|
|
||||||
for i, line in ipairs(lines) do
|
for i, line in ipairs(lines) do
|
||||||
if start ~= nil and stop ~= nil then return { start, stop } end
|
if start ~= nil and stop ~= nil then
|
||||||
|
return { start, stop }
|
||||||
|
end
|
||||||
if M.starts_with_file_symbol(line) then
|
if M.starts_with_file_symbol(line) then
|
||||||
-- Check if the file name matches the node name
|
-- Check if the file name matches the node name
|
||||||
local delta_file_name = u.get_last_chunk(line)
|
local delta_file_name = u.get_last_chunk(line)
|
||||||
@@ -173,7 +217,9 @@ M.get_review_buffer_range = function(file_name)
|
|||||||
|
|
||||||
-- We've reached the end of the file, set "stop" in case we already found start
|
-- We've reached the end of the file, set "stop" in case we already found start
|
||||||
stop = #lines
|
stop = #lines
|
||||||
if start ~= nil and stop ~= nil then return { start, stop } end
|
if start ~= nil and stop ~= nil then
|
||||||
|
return { start, stop }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.starts_with_file_symbol = function(line)
|
M.starts_with_file_symbol = function(line)
|
||||||
@@ -200,4 +246,12 @@ M.get_review_buffer_lines = function(review_buffer_range)
|
|||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Return content between start_line and end_line
|
||||||
|
---@param start_line integer
|
||||||
|
---@param end_line integer
|
||||||
|
---@return string[] | nil
|
||||||
|
M.get_lines = function(start_line, end_line)
|
||||||
|
vim.notify("Getting lines in delta is not supported yet", vim.log.levels.ERROR)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
-- This Module contains all of the code specific to the Diffview reviewer.
|
-- This Module contains all of the code specific to the Diffview reviewer.
|
||||||
local state = require("gitlab.state")
|
local u = require("gitlab.utils")
|
||||||
|
local state = require("gitlab.state")
|
||||||
local async_ok, async = pcall(require, "diffview.async")
|
local async_ok, async = pcall(require, "diffview.async")
|
||||||
|
|
||||||
local M = {
|
local M = {
|
||||||
bufnr = nil,
|
bufnr = nil,
|
||||||
tabnr = nil
|
tabnr = nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Public Functions
|
-- Public Functions
|
||||||
-- These functions are exposed externally and are used
|
-- These functions are exposed externally and are used
|
||||||
-- when the reviewer is consumed by other code. They must follow the specification
|
-- when the reviewer is consumed by other code. They must follow the specification
|
||||||
-- outlined in the reviewer/init.lua file
|
-- outlined in the reviewer/init.lua file
|
||||||
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()
|
||||||
end
|
end
|
||||||
|
|
||||||
M.jump = function(file_name, new_line, old_line)
|
M.jump = function(file_name, new_line, old_line)
|
||||||
if M.tabnr == nil then
|
if M.tabnr == nil then
|
||||||
vim.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
|
vim.notify("Can't jump to Diffvew. Is it open?", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
@@ -49,12 +50,25 @@ M.jump = function(file_name, new_line, old_line)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
M.get_location = function()
|
---Get the location of a line within the diffview. If range is specified, then also the location
|
||||||
if M.tabnr == nil then return nil, nil, "Diffview reviewer must be initialized first" end
|
---of the lines in range.
|
||||||
|
---@param range LineRange | nil Line range to get location for
|
||||||
|
---@return ReviewerInfo | nil nil is returned only if error was encountered
|
||||||
|
M.get_location = function(range)
|
||||||
|
if M.tabnr == nil then
|
||||||
|
vim.notify("Diffview reviewer must be initialized first")
|
||||||
|
return
|
||||||
|
end
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
local current_line = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
|
||||||
-- check if we are in the diffview tab
|
-- check if we are in the diffview tab
|
||||||
local tabnr = vim.api.nvim_get_current_tabpage()
|
local tabnr = vim.api.nvim_get_current_tabpage()
|
||||||
if tabnr ~= M.tabnr then return nil, nil, "Line location can only be determined within reviewer window" end
|
if tabnr ~= M.tabnr then
|
||||||
|
vim.notify("Line location can only be determined within reviewer window")
|
||||||
|
return
|
||||||
|
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 = require("diffview.lib").get_current_view()
|
||||||
if view == nil then
|
if view == nil then
|
||||||
@@ -62,18 +76,81 @@ M.get_location = function()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local layout = view.cur_layout
|
local layout = view.cur_layout
|
||||||
local file_name = nil
|
local result = {}
|
||||||
local current_line_changes = nil
|
local type
|
||||||
|
local is_new
|
||||||
if layout.a.file.bufnr == bufnr then
|
if layout.a.file.bufnr == bufnr then
|
||||||
file_name = layout.a.file.path
|
result.file_name = layout.a.file.path
|
||||||
current_line_changes = { new_line = nil, old_line = vim.api.nvim_win_get_cursor(0)[1] }
|
result.old_line = current_line
|
||||||
return file_name, current_line_changes
|
type = "old"
|
||||||
|
is_new = false
|
||||||
elseif layout.b.file.bufnr == bufnr then
|
elseif layout.b.file.bufnr == bufnr then
|
||||||
file_name = layout.b.file.path
|
result.file_name = layout.b.file.path
|
||||||
current_line_changes = { new_line = vim.api.nvim_win_get_cursor(0)[1], old_line = nil }
|
result.new_line = current_line
|
||||||
return file_name, current_line_changes
|
type = "new"
|
||||||
|
is_new = true
|
||||||
|
else
|
||||||
|
vim.notify("Line location can only be determined within reviewer window")
|
||||||
|
return
|
||||||
end
|
end
|
||||||
return nil, nil, "Line location can only be determined within reviewer window"
|
|
||||||
|
local hunks = u.parse_hunk_headers(result.file_name, state.INFO.target_branch)
|
||||||
|
if hunks == nil then
|
||||||
|
vim.notify("Could not parse hunks", vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local current_line_info
|
||||||
|
if is_new then
|
||||||
|
current_line_info = u.get_lines_from_hunks(hunks, result.new_line, is_new)
|
||||||
|
else
|
||||||
|
current_line_info = u.get_lines_from_hunks(hunks, result.old_line, is_new)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If single line comment is outside of changed lines then we need to specify both new line and old line
|
||||||
|
-- otherwise the API returns error.
|
||||||
|
-- https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff
|
||||||
|
if not current_line_info.in_hunk then
|
||||||
|
result.old_line = current_line_info.old_line
|
||||||
|
result.new_line = current_line_info.new_line
|
||||||
|
end
|
||||||
|
|
||||||
|
if range == nil then
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
result.range_info = { start = {}, ["end"] = {} }
|
||||||
|
if current_line == range.start_line then
|
||||||
|
result.range_info.start.old_line = current_line_info.old_line
|
||||||
|
result.range_info.start.new_line = current_line_info.new_line
|
||||||
|
result.range_info.start.type = type
|
||||||
|
else
|
||||||
|
local start_line_info = u.get_lines_from_hunks(hunks, range.start_line, is_new)
|
||||||
|
result.range_info.start.old_line = start_line_info.old_line
|
||||||
|
result.range_info.start.new_line = start_line_info.new_line
|
||||||
|
result.range_info.start.type = type
|
||||||
|
end
|
||||||
|
|
||||||
|
if current_line == range.end_line then
|
||||||
|
result.range_info["end"].old_line = current_line_info.old_line
|
||||||
|
result.range_info["end"].new_line = current_line_info.new_line
|
||||||
|
result.range_info["end"].type = type
|
||||||
|
else
|
||||||
|
local end_line_info = u.get_lines_from_hunks(hunks, range.end_line, is_new)
|
||||||
|
result.range_info["end"].old_line = end_line_info.old_line
|
||||||
|
result.range_info["end"].new_line = end_line_info.new_line
|
||||||
|
result.range_info["end"].type = type
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return content between start_line and end_line
|
||||||
|
---@param start_line integer
|
||||||
|
---@param end_line integer
|
||||||
|
---@return string[]
|
||||||
|
M.get_lines = function(start_line, end_line)
|
||||||
|
return vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ local M = {
|
|||||||
|
|
||||||
local reviewer_map = {
|
local reviewer_map = {
|
||||||
delta = delta,
|
delta = delta,
|
||||||
diffview = diffview
|
diffview = diffview,
|
||||||
}
|
}
|
||||||
|
|
||||||
M.init = function()
|
M.init = function()
|
||||||
@@ -31,9 +31,12 @@ M.init = function()
|
|||||||
-- • {interval} The old_line of the change
|
-- • {interval} The old_line of the change
|
||||||
|
|
||||||
M.get_location = reviewer.get_location
|
M.get_location = reviewer.get_location
|
||||||
-- Returns the current location (based on cursor) from the reviewer window in format:
|
-- Parameters:
|
||||||
-- file_name, {new_line, old_line}, error
|
-- • {range} LineRange if function was triggered from visual selection
|
||||||
|
-- Returns the current location (based on cursor) from the reviewer window as ReviewerInfo class
|
||||||
|
|
||||||
|
M.get_lines = reviewer.get_lines
|
||||||
|
-- Returns the content of the file in the current location in the reviewer window
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
local Job = require("plenary.job")
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
M.get_current_line_number = function()
|
M.get_current_line_number = function()
|
||||||
return vim.api.nvim_call_function('line', { '.' })
|
return vim.api.nvim_call_function("line", { "." })
|
||||||
end
|
end
|
||||||
|
|
||||||
M.has_reviewer = function(reviewer)
|
M.has_reviewer = function(reviewer)
|
||||||
@@ -62,13 +63,13 @@ M.format_date = function(date_string)
|
|||||||
day = date_table.day,
|
day = date_table.day,
|
||||||
hour = date_table.hour,
|
hour = date_table.hour,
|
||||||
min = date_table.min,
|
min = date_table.min,
|
||||||
sec = date_table.sec
|
sec = date_table.sec,
|
||||||
})
|
})
|
||||||
|
|
||||||
local time_diff = current_date - date
|
local time_diff = current_date - date
|
||||||
|
|
||||||
local function pluralize(num, word)
|
local function pluralize(num, word)
|
||||||
return num .. string.format(" %s", word) .. (num > 1 and "s" or '') .. " ago"
|
return num .. string.format(" %s", word) .. (num > 1 and "s" or "") .. " ago"
|
||||||
end
|
end
|
||||||
|
|
||||||
if time_diff < 60 then
|
if time_diff < 60 then
|
||||||
@@ -86,7 +87,9 @@ M.format_date = function(date_string)
|
|||||||
end
|
end
|
||||||
|
|
||||||
M.jump_to_file = function(filename, line_number)
|
M.jump_to_file = function(filename, line_number)
|
||||||
if line_number == nil then line_number = 1 end
|
if line_number == nil then
|
||||||
|
line_number = 1
|
||||||
|
end
|
||||||
local bufnr = vim.fn.bufnr(filename)
|
local bufnr = vim.fn.bufnr(filename)
|
||||||
if bufnr ~= -1 then
|
if bufnr ~= -1 then
|
||||||
M.jump_to_buffer(bufnr, line_number)
|
M.jump_to_buffer(bufnr, line_number)
|
||||||
@@ -106,7 +109,7 @@ end
|
|||||||
M.create_popup_state = function(title, width, height)
|
M.create_popup_state = function(title, width, height)
|
||||||
return {
|
return {
|
||||||
buf_options = {
|
buf_options = {
|
||||||
filetype = 'markdown'
|
filetype = "markdown",
|
||||||
},
|
},
|
||||||
relative = "editor",
|
relative = "editor",
|
||||||
enter = true,
|
enter = true,
|
||||||
@@ -114,7 +117,7 @@ M.create_popup_state = function(title, width, height)
|
|||||||
border = {
|
border = {
|
||||||
style = "rounded",
|
style = "rounded",
|
||||||
text = {
|
text = {
|
||||||
top = title
|
top = title,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
position = "50%",
|
position = "50%",
|
||||||
@@ -142,7 +145,7 @@ M.join = function(tbl, separator)
|
|||||||
|
|
||||||
-- Remove the trailing separator
|
-- Remove the trailing separator
|
||||||
if separator ~= "" then
|
if separator ~= "" then
|
||||||
result = result:sub(1, - #separator - 1)
|
result = result:sub(1, -#separator - 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -169,16 +172,16 @@ M.read_file = function(file_path)
|
|||||||
end
|
end
|
||||||
|
|
||||||
M.current_file_path = function()
|
M.current_file_path = function()
|
||||||
local path = debug.getinfo(1, 'S').source:sub(2)
|
local path = debug.getinfo(1, "S").source:sub(2)
|
||||||
return vim.fn.fnamemodify(path, ':p')
|
return vim.fn.fnamemodify(path, ":p")
|
||||||
end
|
end
|
||||||
|
|
||||||
local random = math.random
|
local random = math.random
|
||||||
M.uuid = function()
|
M.uuid = function()
|
||||||
local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
local template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
||||||
return string.gsub(template, '[xy]', function(c)
|
return string.gsub(template, "[xy]", function(c)
|
||||||
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
|
local v = (c == "x") and random(0, 0xf) or random(8, 0xb)
|
||||||
return string.format('%x', v)
|
return string.format("%x", v)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -192,7 +195,9 @@ end
|
|||||||
|
|
||||||
M.table_size = function(t)
|
M.table_size = function(t)
|
||||||
local count = 0
|
local count = 0
|
||||||
for _ in pairs(t) do count = count + 1 end
|
for _ in pairs(t) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
return count
|
return count
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -247,11 +252,7 @@ end
|
|||||||
|
|
||||||
M.get_line_content = function(bufnr, start)
|
M.get_line_content = function(bufnr, start)
|
||||||
local current_buffer = vim.api.nvim_get_current_buf()
|
local current_buffer = vim.api.nvim_get_current_buf()
|
||||||
local lines = vim.api.nvim_buf_get_lines(
|
local lines = vim.api.nvim_buf_get_lines(bufnr ~= nil and bufnr or current_buffer, start - 1, start, false)
|
||||||
bufnr ~= nil and bufnr or current_buffer,
|
|
||||||
start - 1,
|
|
||||||
start,
|
|
||||||
false)
|
|
||||||
|
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
return line
|
return line
|
||||||
@@ -267,8 +268,8 @@ M.get_win_from_buf = function(bufnr)
|
|||||||
end
|
end
|
||||||
|
|
||||||
M.switch_can_edit_buf = function(buf, bool)
|
M.switch_can_edit_buf = function(buf, bool)
|
||||||
vim.api.nvim_buf_set_option(buf, 'modifiable', bool)
|
vim.api.nvim_set_option_value("modifiable", bool, { buf = buf })
|
||||||
vim.api.nvim_buf_set_option(buf, "readonly", not bool)
|
vim.api.nvim_set_option_value("readonly", not bool, { buf = buf })
|
||||||
end
|
end
|
||||||
|
|
||||||
M.list_files_in_folder = function(folder_path)
|
M.list_files_in_folder = function(folder_path)
|
||||||
@@ -278,12 +279,14 @@ M.list_files_in_folder = function(folder_path)
|
|||||||
|
|
||||||
local folder_ok, folder = pcall(vim.fn.readdir, folder_path)
|
local folder_ok, folder = pcall(vim.fn.readdir, folder_path)
|
||||||
|
|
||||||
if not folder_ok then return nil end
|
if not folder_ok then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
local files = {}
|
local files = {}
|
||||||
if folder ~= nil then
|
if folder ~= nil then
|
||||||
for _, file in ipairs(folder) do
|
for _, file in ipairs(folder) do
|
||||||
local file_path = folder_path .. (M.is_windows() and "\\" or '/') .. file
|
local file_path = folder_path .. (M.is_windows() and "\\" or "/") .. file
|
||||||
local timestamp = vim.fn.getftime(file_path)
|
local timestamp = vim.fn.getftime(file_path)
|
||||||
table.insert(files, { name = file, timestamp = timestamp })
|
table.insert(files, { name = file, timestamp = timestamp })
|
||||||
end
|
end
|
||||||
@@ -310,4 +313,149 @@ M.reverse = function(list)
|
|||||||
return rev
|
return rev
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@class Hunk
|
||||||
|
---@field old_line integer
|
||||||
|
---@field old_range integer
|
||||||
|
---@field new_line integer
|
||||||
|
---@field new_range integer
|
||||||
|
|
||||||
|
---Parse git diff hunks.
|
||||||
|
---@param file_path string Path to file.
|
||||||
|
---@param base_branch string Git base branch of merge request.
|
||||||
|
---@return Hunk[] list of hunks.
|
||||||
|
M.parse_hunk_headers = function(file_path, base_branch)
|
||||||
|
local hunks = {}
|
||||||
|
|
||||||
|
local diff_job = Job:new({
|
||||||
|
command = "git",
|
||||||
|
args = { "diff", "--minimal", "--unified=0", "--no-color", base_branch, "--", file_path },
|
||||||
|
on_exit = function(j, return_code)
|
||||||
|
if return_code == 0 then
|
||||||
|
for _, line in ipairs(j:result()) do
|
||||||
|
if line:sub(1, 2) == "@@" then
|
||||||
|
-- match:
|
||||||
|
-- @@ -23 +23 @@ ...
|
||||||
|
-- @@ -23,0 +23 @@ ...
|
||||||
|
-- @@ -41,0 +42,4 @@ ...
|
||||||
|
local old_start, old_range, new_start, new_range = line:match("@@+ %-(%d+),?(%d*) %+(%d+),?(%d*) @@+")
|
||||||
|
|
||||||
|
table.insert(hunks, {
|
||||||
|
old_line = tonumber(old_start),
|
||||||
|
old_range = tonumber(old_range) or 0,
|
||||||
|
new_line = tonumber(new_start),
|
||||||
|
new_range = tonumber(new_range) or 0,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify("Failed to get git diff: " .. j:stderr(), vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
diff_job:sync()
|
||||||
|
|
||||||
|
return hunks
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class LineDiffInfo
|
||||||
|
---@field old_line integer
|
||||||
|
---@field new_line integer
|
||||||
|
---@field in_hunk boolean
|
||||||
|
|
||||||
|
---Search git diff hunks to find old and new line number corresponding to target line.
|
||||||
|
---This function does not check if target line is outside of boundaries of file.
|
||||||
|
---@param hunks Hunk[] git diff parsed hunks.
|
||||||
|
---@param target_line integer line number to search for - based on is_new paramter the search is
|
||||||
|
---either in new lines or old lines of hunks.
|
||||||
|
---@param is_new boolean whether to search for new line or old line
|
||||||
|
---@return LineDiffInfo
|
||||||
|
M.get_lines_from_hunks = function(hunks, target_line, is_new)
|
||||||
|
if #hunks == 0 then
|
||||||
|
-- If there are zero hunks, return target_line for both old and new lines
|
||||||
|
return { old_line = target_line, new_line = target_line, in_hunk = false }
|
||||||
|
end
|
||||||
|
local current_new_line = 0
|
||||||
|
local current_old_line = 0
|
||||||
|
if is_new then
|
||||||
|
for _, hunk in ipairs(hunks) do
|
||||||
|
-- target line is before current hunk
|
||||||
|
if target_line < hunk.new_line then
|
||||||
|
return {
|
||||||
|
old_line = current_old_line + (target_line - current_new_line),
|
||||||
|
new_line = target_line,
|
||||||
|
in_hunk = false,
|
||||||
|
}
|
||||||
|
-- target line is within the current hunk
|
||||||
|
elseif hunk.new_line <= target_line and target_line <= (hunk.new_line + hunk.new_range) then
|
||||||
|
-- this is interesting magic of gitlab calculation
|
||||||
|
return {
|
||||||
|
old_line = hunk.old_line + hunk.old_range + 1,
|
||||||
|
new_line = target_line,
|
||||||
|
in_hunk = true,
|
||||||
|
}
|
||||||
|
-- target line is after the current hunk
|
||||||
|
else
|
||||||
|
current_new_line = hunk.new_line + hunk.new_range
|
||||||
|
current_old_line = hunk.old_line + hunk.old_range
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- target line is after last hunk
|
||||||
|
return {
|
||||||
|
old_line = current_old_line + (target_line - current_new_line),
|
||||||
|
new_line = target_line,
|
||||||
|
in_hunk = false,
|
||||||
|
}
|
||||||
|
else
|
||||||
|
for _, hunk in ipairs(hunks) do
|
||||||
|
-- target line is before current hunk
|
||||||
|
if target_line < hunk.old_line then
|
||||||
|
return {
|
||||||
|
old_line = target_line,
|
||||||
|
new_line = current_new_line + (target_line - current_old_line),
|
||||||
|
in_hunk = false,
|
||||||
|
}
|
||||||
|
-- target line is within the current hunk
|
||||||
|
elseif hunk.old_line <= target_line and target_line <= (hunk.old_line + hunk.old_range) then
|
||||||
|
return {
|
||||||
|
old_line = target_line,
|
||||||
|
new_line = hunk.new_line,
|
||||||
|
in_hunk = true,
|
||||||
|
}
|
||||||
|
-- target line is after the current hunk
|
||||||
|
else
|
||||||
|
current_new_line = hunk.new_line + hunk.new_range
|
||||||
|
current_old_line = hunk.old_line + hunk.old_range
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- target line is after last hunk
|
||||||
|
return {
|
||||||
|
old_line = current_old_line + (target_line - current_new_line),
|
||||||
|
new_line = target_line,
|
||||||
|
in_hunk = false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if current mode is visual mode
|
||||||
|
---@return boolean true if current mode is visual mode
|
||||||
|
M.check_visual_mode = function()
|
||||||
|
local mode = vim.api.nvim_get_mode().mode
|
||||||
|
if mode ~= "v" and mode ~= "V" then
|
||||||
|
vim.notify("Code suggestions are only available in visual mode", vim.log.levels.WARN)
|
||||||
|
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,integer start line and end line
|
||||||
|
M.get_visual_selection_boundaries = function()
|
||||||
|
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Esc>", false, true, true), "nx", false)
|
||||||
|
local start_line = vim.api.nvim_buf_get_mark(0, "<")[1]
|
||||||
|
local end_line = vim.api.nvim_buf_get_mark(0, ">")[1]
|
||||||
|
return start_line, end_line
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user