@@ -126,106 +126,117 @@ M.get_location = function(range)
126126 return
127127 end
128128
129- local bufnr = vim .api .nvim_get_current_buf ()
130-
131129 -- If there's a range, use the start of the visual selection, not the current line
132130 local current_line = range and range .start_line or vim .api .nvim_win_get_cursor (0 )[1 ]
133131
134- -- check if we are in the diffview tab
132+ -- Check if we are in the diffview tab
135133 local tabnr = vim .api .nvim_get_current_tabpage ()
136134 if tabnr ~= M .tabnr then
137135 u .notify (" Line location can only be determined within reviewer window" , vim .log .levels .ERROR )
138136 return
139137 end
140138
141- -- check if we are in the diffview buffer
139+ -- Check if we are in the diffview buffer
142140 local view = diffview_lib .get_current_view ()
143141 if view == nil then
144142 u .notify (" Could not find Diffview view" , vim .log .levels .ERROR )
145143 return
146144 end
145+
147146 local layout = view .cur_layout
148- local result = {}
149- local type
150- local is_new
151-
152- if
153- layout .a .file .bufnr == bufnr
154- or (M .lines_are_same (view .cur_layout ) and layout .b .file .bufnr == bufnr and range == nil )
155- then
156- result .file_name = layout .a .file .path
157- result .old_line = current_line
158- type = " old"
159- is_new = false
160- elseif layout .b .file .bufnr == bufnr then
161- result .file_name = layout .b .file .path
162- result .new_line = current_line
163- type = " new"
164- is_new = true
165- else
166- u .notify (" Line location can only be determined within reviewer window" )
147+
148+ --- @type ReviewerInfo
149+ local reviewer_info = {
150+ file_name = layout .a .file .path ,
151+ new_line = nil ,
152+ old_line = nil ,
153+ range_info = nil ,
154+ }
155+
156+ local a_win = u .get_window_id_by_buffer_id (layout .a .file .bufnr )
157+ local b_win = u .get_window_id_by_buffer_id (layout .b .file .bufnr )
158+ local current_win = vim .fn .win_getid ()
159+ local is_current_sha = current_win == b_win
160+
161+ if a_win == nil or b_win == nil then
162+ u .notify (" Error retrieving window IDs for current files" , vim .log .levels .ERROR )
167163 return
168164 end
169165
170- local hunks = u . parse_hunk_headers ( result . file_name , state . INFO . target_branch )
171- if hunks == nil then
172- u .notify (" Could not parse hunks " , vim .log .levels .ERROR )
166+ local current_file = M . get_current_file ( )
167+ if current_file == nil then
168+ u .notify (" Error retrieving current file from Diffview " , vim .log .levels .ERROR )
173169 return
174170 end
175171
176- local current_line_info
177- if is_new then
178- current_line_info = u .get_lines_from_hunks (hunks , result .new_line , is_new )
179- else
180- current_line_info = u .get_lines_from_hunks (hunks , result .old_line , is_new )
172+ local a_linenr = vim .api .nvim_win_get_cursor (a_win )[1 ]
173+ local b_linenr = vim .api .nvim_win_get_cursor (b_win )[1 ]
174+
175+ local data = u .parse_hunk_headers (current_file , state .INFO .target_branch )
176+
177+ if data .hunks == nil then
178+ u .notify (" Could not parse hunks" , vim .log .levels .ERROR )
179+ return
181180 end
182181
183- -- If single line comment is outside of changed lines then we need to specify both new line and old line
184- -- otherwise the API returns error.
185- -- https://docs.gitlab.com/ee/api/discussions.html#create-a-new-thread-in-the-merge-request-diff
186- if not current_line_info . in_hunk then
187- result . old_line = current_line_info . old_line
188- result . new_line = current_line_info . new_line
182+ -- Will be different depending on focused window.
183+ local modification_type =
184+ M . get_modification_type ( a_linenr , b_linenr , is_current_sha , data . hunks , data . all_diff_output )
185+
186+ if modification_type == " bad_file_unmodified " then
187+ u . notify ( " Comments on unmodified lines will be placed in the old file " , vim . log . levels . WARN )
189188 end
190189
191- -- If users leave single-line comments in the new buffer that should be in the old buffer, we can
192- -- tell because the line will not have changed. Send the correct payload.
193- if M .lines_are_same (view .cur_layout ) and layout .b .file .bufnr == bufnr and range == nil then
194- local a_win = u .get_win_from_buf (layout .a .file .bufnr )
195- local a_cursor = vim .api .nvim_win_get_cursor (a_win )[1 ]
196- result .old_line = a_cursor
197- result .new_line = a_cursor
198- type = " old"
190+ -- Comment on new line: Include only new_line in payload.
191+ if modification_type == " added" then
192+ reviewer_info .old_line = nil
193+ reviewer_info .new_line = b_linenr
194+ -- Comment on deleted line: Include only new_line in payload.
195+ elseif modification_type == " deleted" then
196+ reviewer_info .old_line = a_linenr
197+ reviewer_info .new_line = nil
198+ -- The line was not found in any hunks, only send the old line number
199+ elseif modification_type == " unmodified" or modification_type == " bad_file_unmodified" then
200+ reviewer_info .old_line = a_linenr
201+ reviewer_info .new_line = b_linenr
199202 end
200203
201204 if range == nil then
202- return result
205+ return reviewer_info
203206 end
204207
205- result .range_info = { start = {}, [" end" ] = {} }
208+ -- If leaving a multi-line comment, we want to also add range_info to the payload.
209+ local is_new = reviewer_info .new_line ~= nil
210+ local current_line_info = is_new and u .get_lines_from_hunks (data .hunks , reviewer_info .new_line , is_new )
211+ or u .get_lines_from_hunks (data .hunks , reviewer_info .old_line , is_new )
212+ local type = is_new and " new" or " old"
213+
214+ --- @type ReviewerRangeInfo
215+ local range_info = { start = {}, [" end" ] = {} }
216+
206217 if current_line == range .start_line then
207- result . range_info .start .old_line = current_line_info .old_line
208- result . range_info .start .new_line = current_line_info .new_line
209- result . range_info .start .type = type
218+ range_info .start .old_line = current_line_info .old_line
219+ range_info .start .new_line = current_line_info .new_line
220+ range_info .start .type = type
210221 else
211- local start_line_info = u .get_lines_from_hunks (hunks , range .start_line , is_new )
212- result . range_info .start .old_line = start_line_info .old_line
213- result . range_info .start .new_line = start_line_info .new_line
214- result . range_info .start .type = type
222+ local start_line_info = u .get_lines_from_hunks (data . hunks , range .start_line , is_new )
223+ range_info .start .old_line = start_line_info .old_line
224+ range_info .start .new_line = start_line_info .new_line
225+ range_info .start .type = type
215226 end
216-
217227 if current_line == range .end_line then
218- result . range_info [" end" ].old_line = current_line_info .old_line
219- result . range_info [" end" ].new_line = current_line_info .new_line
220- result . range_info [" end" ].type = type
228+ range_info [" end" ].old_line = current_line_info .old_line
229+ range_info [" end" ].new_line = current_line_info .new_line
230+ range_info [" end" ].type = type
221231 else
222- local end_line_info = u .get_lines_from_hunks (hunks , range .end_line , is_new )
223- result . range_info [" end" ].old_line = end_line_info .old_line
224- result . range_info [" end" ].new_line = end_line_info .new_line
225- result . range_info [" end" ].type = type
232+ local end_line_info = u .get_lines_from_hunks (data . hunks , range .end_line , is_new )
233+ range_info [" end" ].old_line = end_line_info .old_line
234+ range_info [" end" ].new_line = end_line_info .new_line
235+ range_info [" end" ].type = type
226236 end
227237
228- return result
238+ reviewer_info .range_info = range_info
239+ return reviewer_info
229240end
230241
231242--- Return content between start_line and end_line
@@ -236,12 +247,9 @@ M.get_lines = function(start_line, end_line)
236247 return vim .api .nvim_buf_get_lines (0 , start_line - 1 , end_line , false )
237248end
238249
250+ --- Checks whether the lines in the two buffers are the same
239251--- @return boolean
240- M .lines_are_same = function (layout )
241- local a_win = u .get_win_from_buf (layout .a .file .bufnr )
242- local b_win = u .get_win_from_buf (layout .b .file .bufnr )
243- local a_cursor = vim .api .nvim_win_get_cursor (a_win )[1 ]
244- local b_cursor = vim .api .nvim_win_get_cursor (b_win )[1 ]
252+ M .lines_are_same = function (layout , a_cursor , b_cursor )
245253 local line_a = u .get_line_content (layout .a .file .bufnr , a_cursor )
246254 local line_b = u .get_line_content (layout .b .file .bufnr , b_cursor )
247255 return line_a == line_b
@@ -324,4 +332,101 @@ M.set_callback_for_reviewer_leave = function(callback)
324332 })
325333end
326334
335+ --- Returns whether the comment is on a deleted line, added line, or unmodified line.
336+ --- This is in order to build the payload for Gitlab correctly by setting the old line and new line.
337+ --- @param a_linenr number
338+ --- @param b_linenr number
339+ --- @param is_current_sha boolean
340+ --- @param hunks Hunk[] A list of hunks
341+ --- @param all_diff_output table The raw diff output
342+ function M .get_modification_type (a_linenr , b_linenr , is_current_sha , hunks , all_diff_output )
343+ for _ , hunk in ipairs (hunks ) do
344+ local old_line_end = hunk .old_line + hunk .old_range
345+ local new_line_end = hunk .new_line + hunk .new_range
346+
347+ if is_current_sha then
348+ -- If it is a single line change and neither hunk has a range, then it's added
349+ if b_linenr >= hunk .new_line and b_linenr <= new_line_end then
350+ if hunk .new_range == 0 and hunk .old_range == 0 then
351+ return " added"
352+ end
353+ -- If leaving a comment on the new window, we may be commenting on an added line
354+ -- or on an unmodified line. To tell, we have to check whether the line itself is
355+ -- prefixed with "+" and only return "added" if it is.
356+ if M .line_was_added (b_linenr , hunk , all_diff_output ) then
357+ return " added"
358+ end
359+ end
360+ else
361+ -- It's a deletion if it's in the range of the hunks and the new
362+ -- range is zero, since that is only a deletion hunk, or if we find
363+ -- a match in another hunk with a range, and the corresponding line is prefixed
364+ -- with a "-" only. If it is, then it's a deletion.
365+ if a_linenr >= hunk .old_line and a_linenr <= old_line_end and hunk .old_range == 0 then
366+ return " deleted"
367+ end
368+ if
369+ (a_linenr >= hunk .old_line and a_linenr <= old_line_end )
370+ or (a_linenr >= hunk .new_line and b_linenr <= new_line_end )
371+ then
372+ if M .line_was_removed (a_linenr , hunk , all_diff_output ) then
373+ return " deleted"
374+ end
375+ end
376+ end
377+ end
378+
379+ -- If we can't find the line, this means the user is either trying to leave
380+ -- a comment on an unchanged line in the new or old file SHA. This is only
381+ -- allowed in the old file
382+ return is_current_sha and " bad_file_unmodified" or " unmodified"
383+ end
384+
385+ --- @param linnr number
386+ --- @param hunk Hunk
387+ --- @param all_diff_output table
388+ M .line_was_removed = function (linnr , hunk , all_diff_output )
389+ for matching_line_index , line in ipairs (all_diff_output ) do
390+ local found_hunk = u .parse_possible_hunk_headers (line )
391+ if found_hunk ~= nil and vim .deep_equal (found_hunk , hunk ) then
392+ -- We found a matching hunk, now we need to iterate over the lines from the raw diff output
393+ -- at that hunk until we reach the line we are looking for. When the indexes match we check
394+ -- to see if that line is deleted or not.
395+ for hunk_line_index = found_hunk .old_line , hunk .old_line + hunk .old_range - 1 , 1 do
396+ local line_content = all_diff_output [matching_line_index + 1 ]
397+ if hunk_line_index == linnr then
398+ if string.match (line_content , " ^%-" ) then
399+ return " deleted"
400+ end
401+ end
402+ end
403+ end
404+ end
405+ end
406+
407+ --- @param linnr number
408+ --- @param hunk Hunk
409+ --- @param all_diff_output table
410+ M .line_was_added = function (linnr , hunk , all_diff_output )
411+ for matching_line_index , line in ipairs (all_diff_output ) do
412+ local found_hunk = u .parse_possible_hunk_headers (line )
413+ if found_hunk ~= nil and vim .deep_equal (found_hunk , hunk ) then
414+ -- For added lines, we only want to iterate over the part of the diff that has has new lines,
415+ -- so we skip over the old range. We then keep track of the increment to the original new line index,
416+ -- and iterate until we reach the end of the total range of this hunk. If we arrive at the matching
417+ -- index for the line number, we check to see if the line was added.
418+ local i = 0
419+ local old_range = (found_hunk .old_range == 0 and found_hunk .old_line ~= 0 ) and 1 or found_hunk .old_range
420+ for hunk_line_index = matching_line_index + old_range + 1 , matching_line_index + old_range + found_hunk .new_range , 1 do
421+ local line_content = all_diff_output [hunk_line_index ]
422+ if (found_hunk .new_line + i ) == linnr then
423+ if string.match (line_content , " ^%+" ) then
424+ return " added"
425+ end
426+ end
427+ i = i + 1
428+ end
429+ end
430+ end
431+ end
327432return M
0 commit comments