@@ -7,8 +7,14 @@ local config = {
77 show_history = " DiffConflictsShowHistory" ,
88 with_history = " DiffConflictsWithHistory" ,
99 },
10+ qol = {
11+ advance_on_save = true ,
12+ quit_on_done = true ,
13+ },
1014}
1115
16+ local advance_augroup = vim .api .nvim_create_augroup (" diffconflicts.nvim.advance" , { clear = false })
17+
1218local function is_jj_repo ()
1319 local buf_path = vim .api .nvim_buf_get_name (0 )
1420 local start = buf_path ~= " " and vim .fn .fnamemodify (buf_path , " :p:h" ) or vim .uv .cwd ()
@@ -204,8 +210,13 @@ local function jj_setup_diff_splits(conflicts)
204210 local conflicted_content = vim .api .nvim_buf_get_lines (0 , 0 , - 1 , false )
205211 local original_filetype = vim .bo .filetype
206212
213+ local left_buf = vim .api .nvim_get_current_buf ()
214+ local left_win = vim .api .nvim_get_current_win ()
215+
207216 vim .cmd .vsplit ({ mods = { split = " belowright" } })
208217 vim .cmd .enew ()
218+ local right_win = vim .api .nvim_get_current_win ()
219+ local right_buf = vim .api .nvim_get_current_buf ()
209220 local right_side = jj_get_content_for_side (" right_side" , conflicts , vim .deepcopy (conflicted_content ))
210221 vim .api .nvim_buf_set_lines (0 , 0 , - 1 , false , right_side )
211222 vim .cmd .file (" snapshot" )
@@ -214,12 +225,48 @@ local function jj_setup_diff_splits(conflicts)
214225 vim .cmd .diffthis ()
215226
216227 vim .cmd .wincmd (" p" )
228+ -- Ensure we are back on the original buffer/window.
229+ if vim .api .nvim_get_current_buf () ~= left_buf and vim .api .nvim_win_is_valid (left_win ) then
230+ vim .api .nvim_set_current_win (left_win )
231+ end
217232 local left_side = jj_get_content_for_side (" left_side" , conflicts , vim .deepcopy (conflicted_content ))
218233 vim .api .nvim_buf_set_lines (0 , 0 , - 1 , false , left_side )
219234 vim .cmd .diffthis ()
220235
221236 vim .cmd .diffupdate ()
222237 vim .fn .cursor (conflicts [1 ].top_line , 1 )
238+
239+ -- QoL: save-to-advance (and optionally quit when done).
240+ if config .qol and config .qol .advance_on_save then
241+ vim .api .nvim_clear_autocmds ({ group = advance_augroup , buffer = left_buf })
242+ vim .api .nvim_create_autocmd (" BufWritePost" , {
243+ group = advance_augroup ,
244+ buffer = left_buf ,
245+ callback = function ()
246+ -- Close the snapshot window/buffer if still around.
247+ if vim .api .nvim_win_is_valid (right_win ) then
248+ pcall (vim .api .nvim_win_close , right_win , true )
249+ end
250+ if vim .api .nvim_buf_is_valid (right_buf ) then
251+ pcall (vim .api .nvim_buf_delete , right_buf , { force = true })
252+ end
253+
254+ -- If there are more conflicts, reopen diff view; otherwise optionally quit.
255+ local lines = vim .api .nvim_buf_get_lines (left_buf , 0 , - 1 , false )
256+ if buffer_looks_like_jj_conflict (lines ) then
257+ -- Re-run using inferred marker length so we always match the buffer.
258+ jj_run (false , detect_jj_marker_length_from_buffer (lines ), nil )
259+ return
260+ end
261+
262+ if config .qol and config .qol .quit_on_done then
263+ -- If we were launched as a mergetool, leaving Neovim is the smoothest way
264+ -- to hand control back to the VCS tooling.
265+ pcall (vim .cmd , " qa" )
266+ end
267+ end ,
268+ })
269+ end
223270end
224271
225272local function jj_setup_history_view (base_path , left_path , right_path )
@@ -324,14 +371,65 @@ local function has_conflicts()
324371 return conflict_count > 0
325372end
326373
374+ local function detect_jj_marker_length_from_buffer (lines )
375+ -- jj conflict markers look like:
376+ -- <<<<<<< <description>
377+ -- %%%%%%% <description>
378+ -- +++++++ <description>
379+ -- >>>>>>> <description>
380+ --
381+ -- The marker length is configurable; infer it from the first "<<<<<<<" line.
382+ for _ , line in ipairs (lines ) do
383+ local run = line :match (" ^(<+)%s.+$" )
384+ if run then
385+ return # run
386+ end
387+ end
388+ return nil
389+ end
390+
391+ local function buffer_looks_like_jj_conflict (lines )
392+ local has_top = false
393+ local has_diff = false
394+ local has_snapshot = false
395+ local has_bottom = false
396+
397+ for _ , line in ipairs (lines ) do
398+ if not has_top and line :match (" ^<+%s.+$" ) then
399+ has_top = true
400+ elseif not has_diff and line :match (" ^%%+%%+%s.+$" ) then
401+ -- "%%%%%%%" in Lua patterns needs escaping; this matches 2+ '%' chars then space.
402+ has_diff = true
403+ elseif not has_snapshot and line :match (" ^%+%+%s.+$" ) then
404+ -- "+++++++" (2+ '+' chars then space)
405+ has_snapshot = true
406+ elseif not has_bottom and line :match (" ^>+%s.+$" ) then
407+ has_bottom = true
408+ end
409+ end
410+
411+ return has_top and has_bottom and (has_diff or has_snapshot )
412+ end
413+
327414local function diff_confl ()
328415 if effective_vcs () == " jj" then
329416 jj_run (false , nil , nil )
330417 return
331418 end
332419
420+ -- If we were invoked on a jj-style conflict buffer outside of a jj repo
421+ -- (e.g. `jj resolve` temp paths), fall back to jj parsing anyway.
422+ do
423+ local lines = vim .api .nvim_buf_get_lines (0 , 0 , - 1 , false )
424+ if buffer_looks_like_jj_conflict (lines ) then
425+ jj_run (false , detect_jj_marker_length_from_buffer (lines ), nil )
426+ return
427+ end
428+ end
429+
333430 local orig_buf = vim .api .nvim_get_current_buf ()
334431 local orig_ft = vim .bo .filetype
432+ local left_win = vim .api .nvim_get_current_win ()
335433
336434 local conflict_style
337435 if config .vcs == " git" then
@@ -344,14 +442,16 @@ local function diff_confl()
344442 -- Set up the right-hand side.
345443 vim .cmd (" rightb vsplit" )
346444 vim .cmd (" enew" )
445+ local right_win = vim .api .nvim_get_current_win ()
446+ local right_buf = vim .api .nvim_get_current_buf ()
347447 vim .cmd (' silent execute "read #" .. ' .. orig_buf )
348448 vim .api .nvim_buf_set_lines (0 , 0 , 1 , false , {}) -- Delete the first line
349449 vim .cmd (" silent file RCONFL" )
350450 vim .bo .filetype = orig_ft
351451 vim .cmd (" diffthis" )
352452
353- vim .cmd (" silent g/^<<<<<<< /,/^=======\\ r\\ ?$/d" )
354- vim .cmd (" silent g/^>>>>>>> /d" )
453+ vim .cmd (" silent! g/^<<<<<<< /,/^=======\\ r\\ ?$/d" )
454+ vim .cmd (" silent! g/^>>>>>>> /d" )
355455
356456 vim .bo .modifiable = false
357457 vim .bo .readonly = true
@@ -364,13 +464,42 @@ local function diff_confl()
364464 vim .cmd (" diffthis" )
365465
366466 if conflict_style :lower () == " diff3" or conflict_style :lower () == " zdiff3" then
367- vim .cmd (" silent g/^||||||| \\ ?/,/^>>>>>>> /d" )
467+ vim .cmd (" silent! g/^||||||| \\ ?/,/^>>>>>>> /d" )
368468 else
369- vim .cmd (" silent g/^=======\\ r\\ ?$/,/^>>>>>>> /d" )
469+ vim .cmd (" silent! g/^=======\\ r\\ ?$/,/^>>>>>>> /d" )
370470 end
371- vim .cmd (" silent g/^<<<<<<< /d" )
471+ vim .cmd (" silent! g/^<<<<<<< /d" )
372472
373473 vim .cmd (" diffupdate" )
474+
475+ -- QoL: save-to-advance (and optionally quit when done).
476+ if config .qol and config .qol .advance_on_save then
477+ vim .api .nvim_clear_autocmds ({ group = advance_augroup , buffer = orig_buf })
478+ vim .api .nvim_create_autocmd (" BufWritePost" , {
479+ group = advance_augroup ,
480+ buffer = orig_buf ,
481+ callback = function ()
482+ if vim .api .nvim_win_is_valid (right_win ) then
483+ pcall (vim .api .nvim_win_close , right_win , true )
484+ end
485+ if vim .api .nvim_buf_is_valid (right_buf ) then
486+ pcall (vim .api .nvim_buf_delete , right_buf , { force = true })
487+ end
488+ if vim .api .nvim_win_is_valid (left_win ) then
489+ pcall (vim .api .nvim_set_current_win , left_win )
490+ end
491+
492+ if has_conflicts () then
493+ diff_confl ()
494+ return
495+ end
496+
497+ if config .qol and config .qol .quit_on_done then
498+ pcall (vim .cmd , " qa" )
499+ end
500+ end ,
501+ })
502+ end
374503end
375504
376505local function show_history ()
0 commit comments