Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions doc/gitlab.nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,22 @@ you call this function with no values the defaults will be used:
toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions
refresh_data = "<C-R>", -- Refresh the data in the view by hitting Gitlab's APIs again
print_node = "<leader>p", -- Print the current node (for debugging)
edit_suggestion = "se", -- Edit comment with suggestion preview in a new tab
reply_with_suggestion = "sr", -- Reply to comment with a suggestion preview in a new tab
apply_suggestion = "sa", -- Apply the suggestion to the local file with a preview in a new tab
},
suggestion_preview = {
apply_changes = "ZZ", -- Close suggestion preview tab, and post comment to Gitlab (discarding changes to local file). In "apply mode", accept suggestion, commit changes, then push to remote and resolve thread
discard_changes = "ZQ", -- Close suggestion preview tab and discard changes in local file
attach_file = "ZA", -- Attach a file from the `settings.attachment_dir`
apply_changes_locally = "Zz", -- Only in "apply mode", close suggestion preview tab and write suggestion buffer to local file (no changes posted to Gitlab)
paste_default_suggestion = "glS", -- Paste the default suggestion below the cursor (overrides default "glS" (start review) keybinding for the "Note" buffer)
},
reviewer = {
disable_all = false, -- Disable all default mappings for the reviewer windows
create_comment = "c", -- Create a comment for the lines that the following {motion} moves over. Repeat the key(s) for creating comment for the current line
create_suggestion = "s", -- Create a suggestion for the lines that the following {motion} moves over. Repeat the key(s) for creating comment for the current line
create_suggestion_with_preview = "S", -- In a new tab create a suggestion with a diff preview for the lines that the following {motion} moves over. Repeat the key(s) for creating comment for the current line
move_to_discussion_tree = "a", -- Jump to the comment in the discussion tree
},
},
Expand Down Expand Up @@ -578,9 +589,11 @@ emojis that you have responded with.
UPLOADING FILES *gitlab.nvim.uploading-files*

To attach a file to an MR description, reply, comment, and so forth use the
`keymaps.popup.perform_linewise_action` keybinding when the popup is open.
This will open a picker that will look for files in the directory you specify
in the `settings.attachment_dir` folder (this must be an absolute path).
`keymaps.popup.perform_linewise_action` keybinding when the popup is open (or
the `keymaps.suggestion_preview.attach_file` in the comment buffer of the
suggestion preview). This will open a picker that will look for files in the
directory you specify in the `settings.attachment_dir` folder (this must be an
absolute path).

When you have picked the file, it will be added to the current buffer at the
current line.
Expand Down
49 changes: 45 additions & 4 deletions lua/gitlab/actions/comment.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,32 @@ local M = {
comment_popup = nil,
}

---Decide if the comment is a draft based on the draft popup field.
---@return boolean|nil is_draft True if the draft popup exists and the string it contains converts to `true`. If the draft popup does not exist, return nil.
local get_draft_value_from_popup = function()
local buf_is_valid = M.draft_popup and M.draft_popup.bufnr and vim.api.nvim_buf_is_valid(M.draft_popup.bufnr)
if buf_is_valid then
return u.string_to_bool(u.get_buffer_text(M.draft_popup.bufnr))
else
return nil
end
end

---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 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, unlinked, discussion_id)
M.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
end

local is_draft = M.draft_popup and u.string_to_bool(u.get_buffer_text(M.draft_popup.bufnr))
local is_draft = get_draft_value_from_popup()
if is_draft == nil then
is_draft = state.settings.discussion_tree.draft_mode
end

-- Creating a normal reply to a discussion
if discussion_id ~= nil and not is_draft then
Expand Down Expand Up @@ -188,13 +202,13 @@ M.create_comment_layout = function(opts)
---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, unlinked, opts.discussion_id)
M.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, unlinked, opts.discussion_id)
M.confirm_create_comment(text, unlinked, opts.discussion_id)
vim.api.nvim_set_current_win(current_win)
end, miscellaneous.attach_file, popup.editable_popup_opts)

Expand Down Expand Up @@ -295,6 +309,33 @@ M.create_comment_suggestion = function()
end)
end

--- This function will create a new tab with a suggestion preview for the changed/updated line in
--- the current MR.
M.create_comment_with_suggestion = function()
M.location = Location.new()
if not M.can_create_comment(true) then
u.press_escape()
return
end

local old_file_name = M.location.reviewer_data.old_file_name ~= "" and M.location.reviewer_data.old_file_name
or M.location.reviewer_data.file_name
local is_new_sha = M.location.reviewer_data.new_sha_focused

---@type ShowPreviewOpts
local opts = {
old_file_name = old_file_name,
new_file_name = M.location.reviewer_data.file_name,
start_line = M.location.visual_range.start_line,
end_line = M.location.visual_range.end_line,
is_new_sha = is_new_sha,
revision = is_new_sha and "HEAD" or require("gitlab.state").INFO.target_branch,
note_header = "comment",
comment_type = "new",
}
require("gitlab.actions.suggestions").show_preview(opts)
end

---Returns true if it's possible to create an Inline Comment
---@param must_be_visual boolean True if current mode must be visual
---@return boolean
Expand Down
60 changes: 55 additions & 5 deletions lua/gitlab/actions/common.lua
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,32 @@ M.get_note_node = function(tree, node)
end
end

---Gather all lines from immediate children that aren't note nodes
---@param tree NuiTree
---@return string[] List of individual note lines
M.get_note_lines = function(tree)
local current_node = tree:get_node()
local note_node = M.get_note_node(tree, current_node)
if note_node == nil then
u.notify("Could not get note node", vim.log.levels.ERROR)
return {}
end
local lines = List.new(note_node:get_child_ids()):reduce(function(agg, child_id)
local child_node = tree:get_node(child_id)
if child_node ~= nil and not child_node:has_children() then
local line = tree:get_node(child_id).text
table.insert(agg, line)
end
return agg
end, {})
return lines
end

---Takes a node and returns the line where the note is positioned in the new SHA. If
---the line is not in the new SHA, returns nil
---@param node NuiTree.Node
---@return number|nil
local function get_new_line(node)
M.get_new_line = function(node)
---@type GitlabLineRange|nil
local range = node.range
if range == nil then
Expand Down Expand Up @@ -253,17 +274,19 @@ end
---@param root_node NuiTree.Node
---@return integer|nil line_number
---@return boolean is_new_sha True if line number refers to NEW SHA
---@return integer|nil end_line
M.get_line_number_from_node = function(root_node)
if root_node.range then
local line_number, _, is_new_sha = M.get_line_numbers_for_range(
local line_number, end_line, is_new_sha = M.get_line_numbers_for_range(
root_node.old_line,
root_node.new_line,
root_node.range.start.line_code,
root_node.range["end"].line_code
)
return line_number, is_new_sha
return line_number, is_new_sha, end_line
else
return M.get_line_number(root_node.id)
local start_line, is_new_sha = M.get_line_number(root_node.id)
return start_line, is_new_sha, start_line
end
end

Expand Down Expand Up @@ -303,7 +326,7 @@ M.jump_to_file = function(tree)
return
end
vim.cmd.tabnew()
local line_number = get_new_line(root_node) or get_old_line(root_node)
local line_number = M.get_new_line(root_node) or get_old_line(root_node)
if line_number == nil or line_number == 0 then
line_number = 1
end
Expand All @@ -319,4 +342,31 @@ M.jump_to_file = function(tree)
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
end

---Determine whether commented line has changed since making the comment.
---@param tree NuiTree The current discussion tree instance.
---@param note_node NuiTree.Node The main node of the note containing the note author etc.
---@return boolean line_changed True if any of the notes in the thread is a system note starting with "changed this line".
M.commented_line_has_changed = function(tree, note_node)
local line_changed = List.new(note_node:get_child_ids()):includes(function(child_id)
local child_node = tree:get_node(child_id)
if child_node == nil then
return false
end

-- Inspect note bodies or recourse to child notes.
if child_node.type == "note_body" then
local line = tree:get_node(child_id).text
if string.match(line, "^changed this line") and note_node.system then
return true
end
elseif child_node.type == "note" and M.commented_line_has_changed(tree, child_node) then
return true
end

return false
end)

return line_changed
end

return M
Loading
Loading