Skip to content

Commit 8d220a3

Browse files
authored
conflict support 6 - add 3-pane merge completion actions
1 parent 11caee5 commit 8d220a3

16 files changed

Lines changed: 1151 additions & 77 deletions

lua/glance/config.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,29 @@ local ALLOWED_MERGE_KEYMAPS = {
9696
accept_theirs = true,
9797
accept_both_ours_then_theirs = true,
9898
accept_both_theirs_then_ours = true,
99+
accept_all_ours = true,
100+
accept_all_theirs = true,
99101
keep_base = true,
100102
reset_conflict = true,
103+
reset_result = true,
101104
mark_resolved = true,
105+
complete_merge = true,
106+
continue_operation = true,
102107
}
103108

104109
local MERGE_KEYMAP_ORDER = {
105110
'accept_ours',
106111
'accept_theirs',
107112
'accept_both_ours_then_theirs',
108113
'accept_both_theirs_then_ours',
114+
'accept_all_ours',
115+
'accept_all_theirs',
109116
'keep_base',
110117
'reset_conflict',
118+
'reset_result',
111119
'mark_resolved',
120+
'complete_merge',
121+
'continue_operation',
112122
}
113123

114124
local ALLOWED_FILETREE_WINDOW = {
@@ -254,9 +264,14 @@ local BASE_DEFAULTS = {
254264
accept_theirs = '<leader>t',
255265
accept_both_ours_then_theirs = '<leader>O',
256266
accept_both_theirs_then_ours = '<leader>T',
267+
accept_all_ours = '<leader>ao',
268+
accept_all_theirs = '<leader>at',
257269
keep_base = '<leader>b',
258270
reset_conflict = '<leader>r',
271+
reset_result = '<leader>R',
259272
mark_resolved = '<leader>m',
273+
complete_merge = '<leader>c',
274+
continue_operation = '<leader>C',
260275
},
261276
},
262277
keymaps = {

lua/glance/filetree.lua

Lines changed: 174 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ M.last_cursor_line = nil -- Tracks the previous cursor line for arrow-key snappi
1616
M.repo_head_oid = nil
1717
M.repo_snapshot_key = ''
1818
M.repo_status_output = ''
19+
M.operation_context = nil
1920

2021
local function filetree_options()
2122
return config.options.windows.filetree
@@ -79,13 +80,6 @@ local function add_legend_key_highlights(highlights, line, text)
7980
end
8081
end
8182

82-
local function legend_line_count()
83-
if filetree_config().show_legend then
84-
return 7
85-
end
86-
return 0
87-
end
88-
8983
local function files_empty(files)
9084
files = files or {}
9185
return #(files.conflicts or {}) == 0
@@ -212,34 +206,105 @@ local function snap_to_nearest_file(line, prefer_up)
212206
scan(line - 1, 1, -1)
213207
end
214208

215-
local function add_legend(lines, highlights)
209+
local function operation_label(context)
210+
local labels = {
211+
merge = 'merge',
212+
rebase = 'rebase',
213+
cherry_pick = 'cherry-pick',
214+
revert = 'revert',
215+
}
216+
return labels[context and context.kind] or 'operation'
217+
end
218+
219+
local function operation_title(context)
220+
local labels = {
221+
merge = 'Merge',
222+
rebase = 'Rebase',
223+
cherry_pick = 'Cherry-pick',
224+
revert = 'Revert',
225+
}
226+
return labels[context and context.kind] or 'Operation'
227+
end
228+
229+
local function display_key(lhs)
230+
if type(lhs) ~= 'string' then
231+
return ''
232+
end
233+
234+
return lhs:gsub('<Leader>', '\\'):gsub('<leader>', '\\')
235+
end
236+
237+
local function add_legend(lines, highlights, operation_context)
216238
local km = config.options.keymaps
239+
local merge_km = config.options.merge.keymaps or {}
217240
local title = ' actions'
218-
local commit_line = ' [' .. km.commit .. '] commit staged changes'
241+
local commit_action = 'commit staged changes'
242+
if operation_context and operation_context.kind == 'merge' then
243+
commit_action = 'commit merge'
244+
end
245+
local commit_line = ' [' .. km.commit .. '] ' .. commit_action
246+
local continue_line = nil
247+
if operation_context and operation_context.kind and operation_context.kind ~= 'merge' then
248+
local continue_key = display_key(merge_km.continue_operation)
249+
if continue_key ~= '' then
250+
continue_line = ' [' .. continue_key .. '] continue ' .. operation_label(operation_context)
251+
end
252+
end
219253
local log_line = ' [' .. km.log .. '] browse commit history'
220-
local stage_line = ' [' .. km.stage_file .. '] stage [' .. km.stage_all .. '] stage all'
221-
local unstage_line = ' [' .. km.unstage_file .. '] unstage [' .. km.unstage_all .. '] unstage all'
222-
local discard_line = ' [' .. km.discard_file .. '] discard [' .. km.discard_all .. '] discard all'
254+
local action_lines = {
255+
' [' .. km.stage_file .. '] stage [' .. km.stage_all .. '] stage all',
256+
' [' .. km.unstage_file .. '] unstage [' .. km.unstage_all .. '] unstage all',
257+
' [' .. km.discard_file .. '] discard [' .. km.discard_all .. '] discard all',
258+
commit_line,
259+
}
260+
if continue_line then
261+
action_lines[#action_lines + 1] = continue_line
262+
end
263+
action_lines[#action_lines + 1] = log_line
223264

224265
lines[#lines + 1] = title
225-
lines[#lines + 1] = stage_line
226-
lines[#lines + 1] = unstage_line
227-
lines[#lines + 1] = discard_line
228-
lines[#lines + 1] = commit_line
229-
lines[#lines + 1] = log_line
266+
for _, line in ipairs(action_lines) do
267+
lines[#lines + 1] = line
268+
end
230269
lines[#lines + 1] = ''
231270

232271
add_highlight(highlights, 1, 2, #title, 'GlanceLegendTitle')
233-
add_highlight(highlights, 2, 0, #stage_line, 'GlanceLegendText')
234-
add_highlight(highlights, 3, 0, #unstage_line, 'GlanceLegendText')
235-
add_highlight(highlights, 4, 0, #discard_line, 'GlanceLegendText')
236-
add_highlight(highlights, 5, 0, #commit_line, 'GlanceLegendText')
237-
add_highlight(highlights, 6, 0, #log_line, 'GlanceLegendText')
238-
add_legend_key_highlights(highlights, 2, stage_line)
239-
add_legend_key_highlights(highlights, 3, unstage_line)
240-
add_legend_key_highlights(highlights, 4, discard_line)
241-
add_legend_key_highlights(highlights, 5, commit_line)
242-
add_legend_key_highlights(highlights, 6, log_line)
272+
for index, line in ipairs(action_lines) do
273+
local line_number = index + 1
274+
add_highlight(highlights, line_number, 0, #line, 'GlanceLegendText')
275+
add_legend_key_highlights(highlights, line_number, line)
276+
end
277+
end
278+
279+
local function merge_ready_message()
280+
local km = config.options.keymaps
281+
return {
282+
title = ' Merge ready to complete',
283+
detail = ' Press ' .. display_key(km.commit) .. ' to commit the merge',
284+
}
285+
end
286+
287+
local function operation_ready_message(context)
288+
if not context or not context.kind then
289+
return nil
290+
end
291+
292+
if context.kind == 'merge' then
293+
return merge_ready_message()
294+
end
295+
296+
local key = display_key(config.options.merge.keymaps.continue_operation)
297+
local detail
298+
if key ~= '' then
299+
detail = ' Press ' .. key .. ' to continue'
300+
else
301+
detail = ' Continue the ' .. operation_label(context) .. ' from the filetree'
302+
end
303+
304+
return {
305+
title = ' ' .. operation_title(context) .. ' ready to continue',
306+
detail = detail,
307+
}
243308
end
244309

245310
--- Create the file tree buffer with appropriate settings.
@@ -273,7 +338,8 @@ function M.create_buf()
273338
end
274339

275340
--- Render the file list with section headers into the buffer.
276-
function M.render(files)
341+
function M.render(files, opts)
342+
opts = opts or {}
277343
files = files or {}
278344
files = {
279345
conflicts = files.conflicts or {},
@@ -283,22 +349,24 @@ function M.render(files)
283349
}
284350

285351
M.files = files
352+
M.operation_context = opts.operation_context
286353
M.line_map = {}
287354
M.selected_line = nil
288355

289356
local lines = {}
290357
local highlights = {} -- { line, col_start, col_end, hl_group }
291358
if filetree_config().show_legend then
292-
add_legend(lines, highlights)
359+
add_legend(lines, highlights, opts.operation_context)
293360
end
361+
local legend_lines = #lines
294362

295363
local function add_section(title, file_list)
296364
if #file_list == 0 then
297365
return
298366
end
299367

300368
-- Blank line before section (except at the very start)
301-
if #lines > legend_line_count() then
369+
if #lines > legend_lines then
302370
table.insert(lines, '')
303371
-- line_map entry is nil by default (no action needed)
304372
end
@@ -329,9 +397,17 @@ function M.render(files)
329397
add_section('Untracked', files.untracked)
330398

331399
-- Handle empty state
332-
if #lines == legend_line_count() then
333-
lines[#lines + 1] = ' No changes found'
334-
add_highlight(highlights, #lines, 0, 20, 'Comment')
400+
if #lines == legend_lines then
401+
local ready = operation_ready_message(opts.operation_context)
402+
if ready then
403+
lines[#lines + 1] = ready.title
404+
add_highlight(highlights, #lines, 0, #ready.title, 'GlanceSectionHeader')
405+
lines[#lines + 1] = ready.detail
406+
add_highlight(highlights, #lines, 0, #ready.detail, 'Comment')
407+
else
408+
lines[#lines + 1] = ' No changes found'
409+
add_highlight(highlights, #lines, 0, 20, 'Comment')
410+
end
335411
end
336412

337413
-- Write to buffer
@@ -511,7 +587,10 @@ function M.apply_status_snapshot(snapshot)
511587
M.repo_head_oid = snapshot.head_oid
512588
M.repo_snapshot_key = snapshot.key or ''
513589
M.repo_status_output = snapshot.output or ''
514-
M.render(snapshot.files)
590+
local operation_context = snapshot.operation_context or require('glance.git').get_operation_context()
591+
M.render(snapshot.files, {
592+
operation_context = operation_context,
593+
})
515594
restore_selection(saved_line)
516595
end
517596

@@ -633,8 +712,9 @@ function M.toggle()
633712
end
634713
end
635714

636-
local function confirm_action(message, accept_label)
637-
return vim.fn.confirm(message, '&' .. accept_label .. '\n&Cancel', 2) == 1
715+
local function confirm_action(message, accept_label, cancel_label)
716+
cancel_label = cancel_label or '&Cancel'
717+
return vim.fn.confirm(message, '&' .. accept_label .. '\n' .. cancel_label, 2) == 1
638718
end
639719

640720
local function active_diff_matches_file(file)
@@ -731,6 +811,13 @@ local function refresh_after_discard(active_file)
731811
end
732812
end
733813

814+
local function notify_operation_continue_error(context, err)
815+
vim.notify(
816+
'glance: failed to continue ' .. operation_label(context) .. ': ' .. tostring(err),
817+
vim.log.levels.ERROR
818+
)
819+
end
820+
734821
function M.discard_selected_file()
735822
local file = M.get_selected_file()
736823
if not file then
@@ -956,6 +1043,55 @@ function M.open_commit_editor()
9561043
})
9571044
end
9581045

1046+
function M.continue_operation()
1047+
local git = require('glance.git')
1048+
local context = git.get_operation_context()
1049+
1050+
if not context.kind then
1051+
vim.notify('glance: no merge operation is ready to continue', vim.log.levels.WARN)
1052+
return false
1053+
end
1054+
1055+
local snapshot = git.get_status_snapshot()
1056+
if #((snapshot.files or {}).conflicts or {}) > 0 then
1057+
vim.notify('glance: resolve all conflicts before continuing ' .. operation_label(context), vim.log.levels.WARN)
1058+
return false
1059+
end
1060+
1061+
if context.kind == 'merge' then
1062+
M.open_commit_editor()
1063+
return true
1064+
end
1065+
1066+
if not confirm_action('Continue ' .. operation_label(context) .. '?', 'Continue', 'Ca&ncel') then
1067+
return false
1068+
end
1069+
1070+
local ok, err = git.continue_operation(context)
1071+
if not ok then
1072+
notify_operation_continue_error(context, err)
1073+
M.refresh()
1074+
return false
1075+
end
1076+
1077+
M.refresh()
1078+
1079+
if #(M.files.conflicts or {}) > 0 then
1080+
vim.notify('glance: ' .. operation_label(context) .. ' stopped at new conflicts', vim.log.levels.WARN)
1081+
require('glance.ui').open_file(M.files.conflicts[1])
1082+
return true
1083+
end
1084+
1085+
local after_context = git.get_operation_context()
1086+
if after_context.kind then
1087+
vim.notify('glance: ' .. operation_label(context) .. ' is still in progress', vim.log.levels.INFO)
1088+
else
1089+
vim.notify('glance: ' .. operation_label(context) .. ' complete', vim.log.levels.INFO)
1090+
end
1091+
1092+
return true
1093+
end
1094+
9591095
function M.open_log_view()
9601096
local log_view = require('glance.log_view')
9611097
if log_view.is_open() then
@@ -970,6 +1106,7 @@ end
9701106
function M.setup_keymaps()
9711107
local opts = { noremap = true, silent = true, buffer = M.buf }
9721108
local km = config.options.keymaps
1109+
local merge_km = config.options.merge.keymaps or {}
9731110

9741111
vim.keymap.set('n', 'j', function() for _ = 1, vim.v.count1 do M.move_down() end end, opts)
9751112
vim.keymap.set('n', 'k', function() for _ = 1, vim.v.count1 do M.move_up() end end, opts)
@@ -988,6 +1125,7 @@ function M.setup_keymaps()
9881125
vim.keymap.set('n', km.unstage_all, function() M.unstage_all() end, opts)
9891126
vim.keymap.set('n', km.discard_file, function() M.discard_selected_file() end, opts)
9901127
vim.keymap.set('n', km.discard_all, function() M.discard_all() end, opts)
1128+
vim.keymap.set('n', merge_km.continue_operation, function() M.continue_operation() end, opts)
9911129
vim.keymap.set('n', km.open_file, function()
9921130
local file = M.get_selected_file()
9931131
if file then

0 commit comments

Comments
 (0)