Skip to content

Commit 6db04ed

Browse files
committed
feat: add first draft of suggestion preview
1 parent c668cc3 commit 6db04ed

13 files changed

Lines changed: 1149 additions & 41 deletions

File tree

doc/gitlab.nvim.txt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,22 @@ you call this function with no values the defaults will be used:
223223
toggle_unresolved_discussions = "U", -- Open or close all unresolved discussions
224224
refresh_data = "<C-R>", -- Refresh the data in the view by hitting Gitlab's APIs again
225225
print_node = "<leader>p", -- Print the current node (for debugging)
226+
edit_suggestion = "se", -- Edit comment with suggestion preview in a new tab
227+
reply_with_suggestion = "sr", -- Reply to comment with a suggestion preview in a new tab
228+
apply_suggestion = "sa", -- Apply the suggestion to the local file with a preview in a new tab
229+
},
230+
suggestion_preview = {
231+
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
232+
discard_changes = "ZQ", -- Close suggestion preview tab and discard changes in local file
233+
attach_file = "ZA", -- Attach a file from the `settings.attachment_dir`
234+
apply_changes_locally = "Zz", -- Only in "apply mode", close suggestion preview tab and write suggestion buffer to local file (no changes posted to Gitlab)
235+
paste_default_suggestion = "glS", -- Paste the default suggestion below the cursor (overrides default "glS" (start review) keybinding for the "Note" buffer)
226236
},
227237
reviewer = {
228238
disable_all = false, -- Disable all default mappings for the reviewer windows
229239
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
230240
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
241+
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
231242
move_to_discussion_tree = "a", -- Jump to the comment in the discussion tree
232243
},
233244
},
@@ -578,9 +589,11 @@ emojis that you have responded with.
578589
UPLOADING FILES *gitlab.nvim.uploading-files*
579590

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

585598
When you have picked the file, it will be added to the current buffer at the
586599
current line.

lua/gitlab/actions/comment.lua

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,32 @@ local M = {
2121
comment_popup = nil,
2222
}
2323

24+
---Decide if the comment is a draft based on the draft popup field.
25+
---@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.
26+
local get_draft_value_from_popup = function()
27+
local buf_is_valid = M.draft_popup and M.draft_popup.bufnr and vim.api.nvim_buf_is_valid(M.draft_popup.bufnr)
28+
if buf_is_valid then
29+
return u.string_to_bool(u.get_buffer_text(M.draft_popup.bufnr))
30+
else
31+
return nil
32+
end
33+
end
34+
2435
---Fires the API that sends the comment data to the Go server, called when you "confirm" creation
2536
---via the M.settings.keymaps.popup.perform_action keybinding
2637
---@param text string comment text
2738
---@param unlinked boolean if true, the comment is not linked to a line
2839
---@param discussion_id string | nil The ID of the discussion to which the reply is responding, nil if not a reply
29-
local confirm_create_comment = function(text, unlinked, discussion_id)
40+
M.confirm_create_comment = function(text, unlinked, discussion_id)
3041
if text == nil then
3142
u.notify("Reviewer did not provide text of change", vim.log.levels.ERROR)
3243
return
3344
end
3445

35-
local is_draft = M.draft_popup and u.string_to_bool(u.get_buffer_text(M.draft_popup.bufnr))
46+
local is_draft = get_draft_value_from_popup()
47+
if is_draft == nil then
48+
is_draft = state.settings.discussion_tree.draft_mode
49+
end
3650

3751
-- Creating a normal reply to a discussion
3852
if discussion_id ~= nil and not is_draft then
@@ -188,13 +202,13 @@ M.create_comment_layout = function(opts)
188202
---Keybinding for focus on draft section
189203
popup.set_popup_keymaps(M.draft_popup, function()
190204
local text = u.get_buffer_text(M.comment_popup.bufnr)
191-
confirm_create_comment(text, unlinked, opts.discussion_id)
205+
M.confirm_create_comment(text, unlinked, opts.discussion_id)
192206
vim.api.nvim_set_current_win(current_win)
193207
end, miscellaneous.toggle_bool, popup.non_editable_popup_opts)
194208

195209
---Keybinding for focus on text section
196210
popup.set_popup_keymaps(M.comment_popup, function(text)
197-
confirm_create_comment(text, unlinked, opts.discussion_id)
211+
M.confirm_create_comment(text, unlinked, opts.discussion_id)
198212
vim.api.nvim_set_current_win(current_win)
199213
end, miscellaneous.attach_file, popup.editable_popup_opts)
200214

@@ -295,6 +309,33 @@ M.create_comment_suggestion = function()
295309
end)
296310
end
297311

312+
--- This function will create a new tab with a suggestion preview for the changed/updated line in
313+
--- the current MR.
314+
M.create_comment_with_suggestion = function()
315+
M.location = Location.new()
316+
if not M.can_create_comment(true) then
317+
u.press_escape()
318+
return
319+
end
320+
321+
local old_file_name = M.location.reviewer_data.old_file_name ~= "" and M.location.reviewer_data.old_file_name
322+
or M.location.reviewer_data.file_name
323+
local is_new_sha = M.location.reviewer_data.new_sha_focused
324+
325+
---@type ShowPreviewOpts
326+
local opts = {
327+
old_file_name = old_file_name,
328+
new_file_name = M.location.reviewer_data.file_name,
329+
start_line = M.location.visual_range.start_line,
330+
end_line = M.location.visual_range.end_line,
331+
is_new_sha = is_new_sha,
332+
revision = is_new_sha and "HEAD" or require("gitlab.state").INFO.target_branch,
333+
note_header = "comment",
334+
comment_type = "new",
335+
}
336+
require("gitlab.actions.suggestions").show_preview(opts)
337+
end
338+
298339
---Returns true if it's possible to create an Inline Comment
299340
---@param must_be_visual boolean True if current mode must be visual
300341
---@return boolean

lua/gitlab/actions/common.lua

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,32 @@ M.get_note_node = function(tree, node)
173173
end
174174
end
175175

176+
---Gather all lines from immediate children that aren't note nodes
177+
---@param tree NuiTree
178+
---@return string[] List of individual note lines
179+
M.get_note_lines = function(tree)
180+
local current_node = tree:get_node()
181+
local note_node = M.get_note_node(tree, current_node)
182+
if note_node == nil then
183+
u.notify("Could not get note node", vim.log.levels.ERROR)
184+
return {}
185+
end
186+
local lines = List.new(note_node:get_child_ids()):reduce(function(agg, child_id)
187+
local child_node = tree:get_node(child_id)
188+
if child_node ~= nil and not child_node:has_children() then
189+
local line = tree:get_node(child_id).text
190+
table.insert(agg, line)
191+
end
192+
return agg
193+
end, {})
194+
return lines
195+
end
196+
176197
---Takes a node and returns the line where the note is positioned in the new SHA. If
177198
---the line is not in the new SHA, returns nil
178199
---@param node NuiTree.Node
179200
---@return number|nil
180-
local function get_new_line(node)
201+
M.get_new_line = function(node)
181202
---@type GitlabLineRange|nil
182203
local range = node.range
183204
if range == nil then
@@ -253,17 +274,19 @@ end
253274
---@param root_node NuiTree.Node
254275
---@return integer|nil line_number
255276
---@return boolean is_new_sha True if line number refers to NEW SHA
277+
---@return integer|nil end_line
256278
M.get_line_number_from_node = function(root_node)
257279
if root_node.range then
258-
local line_number, _, is_new_sha = M.get_line_numbers_for_range(
280+
local line_number, end_line, is_new_sha = M.get_line_numbers_for_range(
259281
root_node.old_line,
260282
root_node.new_line,
261283
root_node.range.start.line_code,
262284
root_node.range["end"].line_code
263285
)
264-
return line_number, is_new_sha
286+
return line_number, is_new_sha, end_line
265287
else
266-
return M.get_line_number(root_node.id)
288+
local start_line, is_new_sha = M.get_line_number(root_node.id)
289+
return start_line, is_new_sha, start_line
267290
end
268291
end
269292

@@ -303,7 +326,7 @@ M.jump_to_file = function(tree)
303326
return
304327
end
305328
vim.cmd.tabnew()
306-
local line_number = get_new_line(root_node) or get_old_line(root_node)
329+
local line_number = M.get_new_line(root_node) or get_old_line(root_node)
307330
if line_number == nil or line_number == 0 then
308331
line_number = 1
309332
end
@@ -319,4 +342,31 @@ M.jump_to_file = function(tree)
319342
vim.api.nvim_win_set_cursor(0, { line_number, 0 })
320343
end
321344

345+
---Determine whether commented line has changed since making the comment.
346+
---@param tree NuiTree The current discussion tree instance.
347+
---@param note_node NuiTree.Node The main node of the note containing the note author etc.
348+
---@return boolean line_changed True if any of the notes in the thread is a system note starting with "changed this line".
349+
M.commented_line_has_changed = function(tree, note_node)
350+
local line_changed = List.new(note_node:get_child_ids()):includes(function(child_id)
351+
local child_node = tree:get_node(child_id)
352+
if child_node == nil then
353+
return false
354+
end
355+
356+
-- Inspect note bodies or recourse to child notes.
357+
if child_node.type == "note_body" then
358+
local line = tree:get_node(child_id).text
359+
if string.match(line, "^changed this line") and note_node.system then
360+
return true
361+
end
362+
elseif child_node.type == "note" and M.commented_line_has_changed(tree, child_node) then
363+
return true
364+
end
365+
366+
return false
367+
end)
368+
369+
return line_changed
370+
end
371+
322372
return M

0 commit comments

Comments
 (0)