Skip to content
Merged
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
14 changes: 6 additions & 8 deletions lua/glance/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ local ALLOWED_THEME_PALETTE = {
added = true,
deleted = true,
changed = true,
manual = true,
minimap_bg = true,
minimap_viewport_bg = true,
minimap_cursor = true,
Expand Down Expand Up @@ -95,8 +96,7 @@ local ALLOWED_MERGE_KEYMAPS = {
accept_theirs = true,
accept_both_ours_then_theirs = true,
accept_both_theirs_then_ours = true,
ignore_ours = true,
ignore_theirs = true,
keep_base = true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept legacy ignore_ merge key names*

Removing ignore_ours and ignore_theirs from the allowed merge keymap set will now make setup() fail for existing configs that still include those previously supported keys, because validate_known_keys treats them as unknown and errors out. This creates an upgrade-time startup break for users who copied prior defaults, so these keys should be accepted as backward-compatible aliases (or migrated to keep_base) before validation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary

reset_conflict = true,
mark_resolved = true,
}
Expand All @@ -106,8 +106,7 @@ local MERGE_KEYMAP_ORDER = {
'accept_theirs',
'accept_both_ours_then_theirs',
'accept_both_theirs_then_ours',
'ignore_ours',
'ignore_theirs',
'keep_base',
'reset_conflict',
'mark_resolved',
}
Expand Down Expand Up @@ -253,10 +252,9 @@ local BASE_DEFAULTS = {
keymaps = {
accept_ours = '<leader>o',
accept_theirs = '<leader>t',
accept_both_ours_then_theirs = '<leader>b',
accept_both_theirs_then_ours = '<leader>B',
ignore_ours = '<leader>O',
ignore_theirs = '<leader>T',
accept_both_ours_then_theirs = '<leader>O',
accept_both_theirs_then_ours = '<leader>T',
keep_base = '<leader>b',
reset_conflict = '<leader>r',
mark_resolved = '<leader>m',
},
Expand Down
9 changes: 5 additions & 4 deletions lua/glance/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,14 @@ function M.setup_highlights()
local keyword = palette.keyword
local func = palette.func
local type_color = palette.type
local manual_color = palette.manual or palette.number or palette.keyword
local number = palette.number
local param = palette.accent
local selection = palette.selection
local line_hl = palette.line_highlight
local merge_unresolved_bg = blend_hex(bg, palette.logo, 0.16)
local merge_unresolved_bg = blend_hex(bg, palette.changed, 0.2)
local merge_handled_bg = blend_hex(bg, palette.added, 0.16)
local merge_manual_bg = blend_hex(bg, type_color, 0.14)
local merge_manual_bg = blend_hex(bg, manual_color, 0.18)

-- Editor
vim.api.nvim_set_hl(0, 'Normal', { bg = bg, fg = fg })
Expand Down Expand Up @@ -257,9 +258,9 @@ function M.setup_highlights()
vim.api.nvim_set_hl(0, 'GlanceAccentText', { fg = param, bold = true })
vim.api.nvim_set_hl(0, 'GlanceLegendText', { fg = comment, bg = bg })
vim.api.nvim_set_hl(0, 'GlanceLegendHint', { fg = palette.accent, bg = bg, bold = true })
vim.api.nvim_set_hl(0, 'GlanceConflictMarkerUnresolved', { fg = palette.logo, bg = bg, bold = true })
vim.api.nvim_set_hl(0, 'GlanceConflictMarkerUnresolved', { fg = palette.changed, bg = bg, bold = true })
vim.api.nvim_set_hl(0, 'GlanceConflictMarkerHandled', { fg = palette.added, bg = bg, bold = true })
vim.api.nvim_set_hl(0, 'GlanceConflictMarkerManual', { fg = type_color, bg = bg, bold = true })
vim.api.nvim_set_hl(0, 'GlanceConflictMarkerManual', { fg = manual_color, bg = bg, bold = true })
vim.api.nvim_set_hl(0, 'GlanceConflictStateUnresolved', { bg = merge_unresolved_bg })
vim.api.nvim_set_hl(0, 'GlanceConflictStateHandled', { bg = merge_handled_bg })
vim.api.nvim_set_hl(0, 'GlanceConflictStateManual', { bg = merge_manual_bg })
Expand Down
7 changes: 1 addition & 6 deletions lua/glance/merge/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ local DEFINITIONS = {
{ id = 'accept_theirs', short = 'theirs' },
{ id = 'accept_both_ours_then_theirs', short = 'both o/t' },
{ id = 'accept_both_theirs_then_ours', short = 'both t/o' },
{ id = 'ignore_ours', short = 'skip ours' },
{ id = 'ignore_theirs', short = 'skip theirs' },
{ id = 'keep_base', short = 'base' },
{ id = 'reset_conflict', short = 'reset' },
{ id = 'mark_resolved', short = 'resolve' },
}
Expand All @@ -33,10 +32,6 @@ function M.available(conflict)
if conflict.state == 'manual_unresolved' then
actions[#actions + 1] = definition
end
elseif id == 'ignore_ours' or id == 'ignore_theirs' then
if conflict.state ~= 'manual_unresolved' and conflict.state ~= 'manual_resolved' then
actions[#actions + 1] = definition
end
else
actions[#actions + 1] = definition
end
Expand Down
7 changes: 7 additions & 0 deletions lua/glance/merge/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ local function refresh_decorations(diffview)
end

render.decorate(panes(diffview), state.model, state.active_conflict_index)
require('glance.minimap').update_merge(state.model, state.active_conflict_index)
end

local function result_win(diffview)
Expand Down Expand Up @@ -464,6 +465,7 @@ local function rebuild(diffview, file)
active_conflict_index = state.active_conflict_index,
})
layout.equalize(diffview)
require('glance.minimap').update_merge(state.model, state.active_conflict_index)
return merge_model
end

Expand Down Expand Up @@ -491,6 +493,11 @@ function M.open(diffview, file)

rebuild(diffview, file)

local win = result_win(diffview)
if win then
require('glance.minimap').open_merge(win, state.model, state.active_conflict_index)
end

local root = git.repo_root()
if root and config.options.watch.enabled then
diffview.watch_file(root .. '/' .. file.path)
Expand Down
24 changes: 6 additions & 18 deletions lua/glance/merge/model.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1130,28 +1130,16 @@ function M.apply_action(merge_model, index, action)
if action == 'accept_ours' or action == 'accept_theirs' then
local side = action == 'accept_ours' and 'ours' or 'theirs'
conflict.state = side == 'ours' and 'ours' or 'theirs'
if side == 'ours' then
conflict.ours_handled = true
else
conflict.theirs_handled = true
end
conflict.ours_handled = true
conflict.theirs_handled = true
finalize_conflict_action(conflict)
return conflict
end

if action == 'ignore_ours' or action == 'ignore_theirs' then
if conflict.state == 'manual_unresolved' or conflict.state == 'manual_resolved' then
return nil, 'ignore actions are not available for manual merge states'
end

if conflict.state == 'unresolved' then
conflict.state = 'base_only'
end
if action == 'ignore_ours' then
conflict.ours_handled = true
else
conflict.theirs_handled = true
end
if action == 'keep_base' then
conflict.state = 'base_only'
conflict.ours_handled = true
conflict.theirs_handled = true
finalize_conflict_action(conflict)
return conflict
end
Expand Down
32 changes: 26 additions & 6 deletions lua/glance/merge/render.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ local function conflict_state_label(conflict)
end

local function conflict_marker_group(conflict)
if conflict.state == 'manual_unresolved' or conflict.state == 'manual_resolved' then
if conflict.state == 'manual_unresolved' then
return 'GlanceConflictMarkerManual'
end
if conflict.handled then
Expand All @@ -83,7 +83,7 @@ local function conflict_marker_group(conflict)
end

local function conflict_state_group(conflict)
if conflict.state == 'manual_unresolved' or conflict.state == 'manual_resolved' then
if conflict.state == 'manual_unresolved' then
return 'GlanceConflictStateManual'
end
if conflict.handled then
Expand All @@ -92,6 +92,15 @@ local function conflict_state_group(conflict)
return 'GlanceConflictStateUnresolved'
end

local function action_bar_padding()
local minimap = config.options.minimap or {}
if not minimap.enabled then
return ''
end

return string.rep(' ', (minimap.width or 1) + 1)
end

local function result_label(model, active_conflict_index)
local op = model.operation or {}
local parts = {}
Expand All @@ -114,7 +123,7 @@ local function result_label(model, active_conflict_index)
local label = table.concat(parts, ' | ')
local hint = conflict and actions.hint_text(conflict, config.options.merge.keymaps) or ''
if hint ~= '' then
return label .. '%=' .. hint
return label .. '%=' .. hint .. action_bar_padding()
end
return label
end
Expand Down Expand Up @@ -191,7 +200,16 @@ local function add_result_range(buf, start_line, count, line_group, opts)
end

local function zero_line_placeholder(conflict)
return '[' .. conflict_state_label(conflict) .. ']'
return ' ' .. conflict_state_label(conflict) .. ' insert'
end

local function zero_line_anchor(conflict, line_count)
local start_line = conflict.result_range.start or 1
if start_line <= line_count then
return math.max(start_line, 1) - 1, true
end

return math.max(line_count, 1) - 1, false
end

local function decorate_sources(buffers, model, active_conflict_index)
Expand All @@ -210,11 +228,12 @@ local function decorate_sources(buffers, model, active_conflict_index)
number_group = active_number_group,
})

local line_count = math.max(vim.api.nvim_buf_line_count(buffers.result), 1)
local anchor_line = math.min(math.max(conflict.result_range.start, 1), line_count) - 1
if conflict.result_range.count == 0 then
local line_count = math.max(vim.api.nvim_buf_line_count(buffers.result), 1)
local anchor_line, virt_lines_above = zero_line_anchor(conflict, line_count)
local extmark_opts = {
virt_lines = { { { zero_line_placeholder(conflict), conflict_marker_group(conflict) } } },
virt_lines_above = virt_lines_above,
priority = 100,
}
if active_number_group then
Expand All @@ -223,6 +242,7 @@ local function decorate_sources(buffers, model, active_conflict_index)

vim.api.nvim_buf_set_extmark(buffers.result, NS, anchor_line, 0, {
virt_lines = extmark_opts.virt_lines,
virt_lines_above = extmark_opts.virt_lines_above,
priority = extmark_opts.priority,
number_hl_group = extmark_opts.number_hl_group,
})
Expand Down
79 changes: 70 additions & 9 deletions lua/glance/minimap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ local CONTENT_DEBOUNCE_MS = 120
M.buf = nil
M.win = nil
M.target_win = nil
M.mode = nil
M.old_lines = nil -- cached old content for vim.diff()
M.merge_model = nil
M.active_conflict_index = nil
M.cached_pixels = nil -- cached pixel states (only recomputed on content change)
M.pixel_count = 0
M.total_logical = 0
Expand All @@ -37,6 +40,10 @@ local ADD = logic.states.ADD
local DELETE = logic.states.DELETE
local CHANGE = logic.states.CHANGE
local CURSOR = logic.states.CURSOR
local MERGE_UNRESOLVED = logic.states.MERGE_UNRESOLVED
local MERGE_HANDLED = logic.states.MERGE_HANDLED
local MERGE_MANUAL = logic.states.MERGE_MANUAL
local MERGE_ACTIVE = logic.states.MERGE_ACTIVE

-- Dynamic highlight group cache
local hl_cache = {}
Expand All @@ -51,18 +58,27 @@ end

local function colors()
local palette = config.options.theme.palette
local manual = palette.manual or palette.number or palette.keyword
return {
[NONE] = palette.minimap_bg,
[ADD] = palette.added,
[DELETE] = palette.deleted,
[CHANGE] = palette.changed,
[CURSOR] = palette.minimap_cursor,
[MERGE_UNRESOLVED] = palette.changed,
[MERGE_HANDLED] = palette.added,
[MERGE_MANUAL] = manual,
[MERGE_ACTIVE] = palette.minimap_cursor,
}, {
[NONE] = palette.minimap_viewport_bg,
[ADD] = palette.added,
[DELETE] = palette.deleted,
[CHANGE] = palette.changed,
[CURSOR] = palette.minimap_cursor,
[MERGE_UNRESOLVED] = palette.changed,
[MERGE_HANDLED] = palette.added,
[MERGE_MANUAL] = manual,
[MERGE_ACTIVE] = palette.minimap_cursor,
}
end

Expand Down Expand Up @@ -126,7 +142,10 @@ local function has_valid_state()
return M.buf and vim.api.nvim_buf_is_valid(M.buf)
and M.win and vim.api.nvim_win_is_valid(M.win)
and M.target_win and vim.api.nvim_win_is_valid(M.target_win)
and M.old_lines ~= nil
and (
(M.mode == 'diff' and M.old_lines ~= nil)
or (M.mode == 'merge' and M.merge_model ~= nil)
)
end

local function target_buf()
Expand Down Expand Up @@ -335,7 +354,16 @@ function M.full_update(opts)
end

local new_lines = vim.api.nvim_buf_get_lines(new_buf, 0, -1, false)
local line_types, total_lines = logic.compute_line_types(M.old_lines, new_lines)
local line_types, total_lines
if M.mode == 'merge' then
line_types, total_lines = logic.compute_merge_line_types(
M.merge_model.conflicts,
math.max(#new_lines, vim.api.nvim_buf_line_count(new_buf)),
M.active_conflict_index
)
else
line_types, total_lines = logic.compute_line_types(M.old_lines, new_lines)
end
M.total_logical = total_lines
M.cached_pixels = logic.downsample(line_types, total_lines, pixel_count)
M.last_changedtick = changedtick
Expand All @@ -346,17 +374,11 @@ function M.full_update(opts)
render_from_cache()
end

--- Create the floating window and buffer, then do initial render.
--- @param target_win number The new (right) diff pane window
--- @param old_lines string[] Old file content for diff computation
function M.open(target_win, old_lines)
M.close()

local function open_window(target_win)
if not minimap_config().enabled then return end
if not target_win or not vim.api.nvim_win_is_valid(target_win) then return end

M.target_win = target_win
M.old_lines = old_lines

-- Create scratch buffer
M.buf = vim.api.nvim_create_buf(false, true)
Expand Down Expand Up @@ -389,6 +411,42 @@ function M.open(target_win, old_lines)
M.setup_autocmds()
end

--- Create the floating window and buffer, then do initial render.
--- @param target_win number The new (right) diff pane window
--- @param old_lines string[] Old file content for diff computation
function M.open(target_win, old_lines)
M.close()
M.mode = 'diff'
M.old_lines = old_lines
M.merge_model = nil
M.active_conflict_index = nil
open_window(target_win)
end

--- Create a merge minimap on the Result pane.
--- @param target_win number
--- @param merge_model table
--- @param active_conflict_index integer|nil
function M.open_merge(target_win, merge_model, active_conflict_index)
M.close()
M.mode = 'merge'
M.old_lines = nil
M.merge_model = merge_model
M.active_conflict_index = active_conflict_index
open_window(target_win)
end

--- Update merge minimap state after conflict actions, edits, or active conflict changes.
function M.update_merge(merge_model, active_conflict_index)
if M.mode ~= 'merge' or not M.win or not vim.api.nvim_win_is_valid(M.win) then
return
end

M.merge_model = merge_model
M.active_conflict_index = active_conflict_index
M.full_update({ force_recompute = true })
end

--- Set up autocmds for scroll sync and content change tracking.
function M.setup_autocmds()
vim.api.nvim_clear_autocmds({ group = M.augroup })
Expand Down Expand Up @@ -443,7 +501,10 @@ function M.close()
M.win = nil
M.buf = nil
M.target_win = nil
M.mode = nil
M.old_lines = nil
M.merge_model = nil
M.active_conflict_index = nil
M.cached_pixels = nil
M.pixel_count = 0
M.total_logical = 0
Expand Down
Loading
Loading