Skip to content

Commit 168c8fc

Browse files
authored
feat: Show context for merge conflicts in the winbar (#285)
1 parent 0987307 commit 168c8fc

File tree

14 files changed

+343
-176
lines changed

14 files changed

+343
-176
lines changed

.luarc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"Lua.diagnostics.disable": [
44
"assign-type-mismatch",
55
"cast-type-mismatch"
6-
]
6+
],
7+
"workspace.checkThirdParty": false
78
}

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,18 @@ require("diffview").setup({
187187
default = {
188188
-- Config for changed files, and staged files in diff views.
189189
layout = "diff2_horizontal",
190+
winbar_info = false, -- See ':h diffview-config-view.x.winbar_info'
190191
},
191192
merge_tool = {
192193
-- Config for conflicted files in diff views during a merge or rebase.
193194
layout = "diff3_horizontal",
194195
disable_diagnostics = true, -- Temporarily disable diagnostics for conflict buffers while in the view.
196+
winbar_info = true, -- See ':h diffview-config-view.x.winbar_info'
195197
},
196198
file_history = {
197199
-- Config for changed files in file history views.
198200
layout = "diff2_horizontal",
201+
winbar_info = false, -- See ':h diffview-config-view.x.winbar_info'
199202
},
200203
},
201204
file_panel = {

doc/diffview.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,12 @@ view.x.layout *diffview-config-view.x.layout*
406406
│ │
407407
└──────────────┘
408408

409+
view.x.winbar_info *diffview-config-view.x.winbar_info*
410+
Type: `boolean`, Default: (see example config above)
411+
412+
Use the 'winbar' to show contextual info about the different versions
413+
of a file in a diff.
414+
409415
*diffview-config-view.merge_tool.disable_diagnostics*
410416
view.merge_tool.disable_diagnostics
411417
Type: `boolean`, Default: `true`

doc/diffview_defaults.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,18 @@ require("diffview").setup({
3535
default = {
3636
-- Config for changed files, and staged files in diff views.
3737
layout = "diff2_horizontal",
38+
winbar_info = false, -- See |diffview-config-view.x.winbar_info|
3839
},
3940
merge_tool = {
4041
-- Config for conflicted files in diff views during a merge or rebase.
4142
layout = "diff3_horizontal",
4243
disable_diagnostics = true, -- Temporarily disable diagnostics for conflict buffers while in the view.
44+
winbar_info = true, -- See |diffview-config-view.x.winbar_info|
4345
},
4446
file_history = {
4547
-- Config for changed files in file history views.
4648
layout = "diff2_horizontal",
49+
winbar_info = false, -- See |diffview-config-view.x.winbar_info|
4750
},
4851
},
4952
file_panel = {

lua/diffview/config.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,16 @@ M.defaults = {
5353
view = {
5454
default = {
5555
layout = "diff2_horizontal",
56+
winbar_info = false,
5657
},
5758
merge_tool = {
5859
layout = "diff3_horizontal",
5960
disable_diagnostics = true,
61+
winbar_info = true,
6062
},
6163
file_history = {
6264
layout = "diff2_horizontal",
65+
winbar_info = false,
6366
},
6467
},
6568
file_panel = {

lua/diffview/scene/file_entry.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ local fstat_cache = {}
3636
---@field stats GitStats
3737
---@field kind vcs.FileKind
3838
---@field commit Commit|nil
39+
---@field merge_ctx vcs.MergeContext?
3940
---@field active boolean
4041
local FileEntry = oop.create_class("FileEntry")
4142

@@ -49,6 +50,7 @@ local FileEntry = oop.create_class("FileEntry")
4950
---@field stats GitStats
5051
---@field kind vcs.FileKind
5152
---@field commit? Commit
53+
---@field merge_ctx? vcs.MergeContext
5254

5355
---FileEntry constructor
5456
---@param opt FileEntry.init.Opt
@@ -66,6 +68,7 @@ function FileEntry:init(opt)
6668
self.stats = opt.stats
6769
self.kind = opt.kind
6870
self.commit = opt.commit
71+
self.merge_ctx = opt.merge_ctx
6972
self.active = false
7073
end
7174

@@ -125,6 +128,7 @@ function FileEntry:convert_layout(target_layout)
125128
c = utils.tbl_access(self.layout, "c.file") or create_file(self.revs.c, "c"),
126129
d = utils.tbl_access(self.layout, "d.file") or create_file(self.revs.d, "d"),
127130
})
131+
self:update_merge_context()
128132
end
129133

130134
---@param stat? table
@@ -166,6 +170,40 @@ function FileEntry:validate_stage_buffers(stat)
166170
end
167171
end
168172

173+
---Update winbar info
174+
---@param ctx? vcs.MergeContext
175+
function FileEntry:update_merge_context(ctx)
176+
ctx = ctx or self.merge_ctx
177+
if ctx then self.merge_ctx = ctx else return end
178+
179+
local layout = self.layout --[[@as Diff4 ]]
180+
181+
if layout.a then
182+
layout.a.file.winbar = (" OURS (Current changes) %s %s"):format(
183+
(ctx.ours.hash):sub(1, 10),
184+
ctx.ours.ref_names and ("(" .. ctx.ours.ref_names .. ")") or ""
185+
)
186+
end
187+
188+
if layout.b then
189+
layout.b.file.winbar = " LOCAL (Working tree)"
190+
end
191+
192+
if layout.c then
193+
layout.c.file.winbar = (" THEIRS (Incoming changes) %s %s"):format(
194+
(ctx.theirs.hash):sub(1, 10),
195+
ctx.theirs.ref_names and ("(" .. ctx.theirs.ref_names .. ")") or ""
196+
)
197+
end
198+
199+
if layout.d then
200+
layout.d.file.winbar = (" BASE (Common ancestor) %s %s"):format(
201+
(ctx.base.hash):sub(1, 10),
202+
ctx.base.ref_names and ("(" .. ctx.base.ref_names .. ")") or ""
203+
)
204+
end
205+
end
206+
169207
---@static
170208
---@param adapter VCSAdapter
171209
function FileEntry.update_index_stat(adapter, stat)

lua/diffview/scene/views/diff/diff_view.lua

Lines changed: 112 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ local M = {}
3737
---@field commit_log_panel CommitLogPanel
3838
---@field files FileDict
3939
---@field file_idx integer
40+
---@field merge_ctx? vcs.MergeContext
4041
---@field initialized boolean
4142
---@field valid boolean
4243
---@field watcher any UV fs poll handle.
@@ -325,125 +326,133 @@ DiffView.update_files = debounce.debounce_trailing(100, true, vim.schedule_wrap(
325326
callback(err)
326327
end
327328
return
328-
else
329-
perf:lap("received new file list")
330-
local files = {
331-
{ cur_files = self.files.conflicting, new_files = new_files.conflicting },
332-
{ cur_files = self.files.working, new_files = new_files.working },
333-
{ cur_files = self.files.staged, new_files = new_files.staged },
334-
}
335-
336-
async.util.scheduler()
337-
338-
for _, v in ipairs(files) do
339-
---@param aa FileEntry
340-
---@param bb FileEntry
341-
local diff = Diff(v.cur_files, v.new_files, function(aa, bb)
342-
return aa.path == bb.path and aa.oldpath == bb.oldpath
343-
end)
344-
local script = diff:create_edit_script()
345-
346-
local ai = 1
347-
local bi = 1
348-
349-
for _, opr in ipairs(script) do
350-
if opr == EditToken.NOOP then
351-
-- Update status and stats
352-
local a_stats = v.cur_files[ai].stats
353-
local b_stats = v.new_files[bi].stats
354-
355-
if a_stats then
356-
v.cur_files[ai].stats = vim.tbl_extend("force", a_stats, b_stats or {})
357-
else
358-
v.cur_files[ai].stats = v.new_files[bi].stats
359-
end
329+
end
360330

361-
v.cur_files[ai].status = v.new_files[bi].status
362-
v.cur_files[ai]:validate_stage_buffers(index_stat)
331+
perf:lap("received new file list")
363332

364-
if new_head then
365-
v.cur_files[ai]:update_heads(new_head)
366-
end
333+
local files = {
334+
{ cur_files = self.files.conflicting, new_files = new_files.conflicting },
335+
{ cur_files = self.files.working, new_files = new_files.working },
336+
{ cur_files = self.files.staged, new_files = new_files.staged },
337+
}
367338

368-
ai = ai + 1
369-
bi = bi + 1
370-
371-
elseif opr == EditToken.DELETE then
372-
if self.panel.cur_file == v.cur_files[ai] then
373-
local file_list = self.panel:ordered_file_list()
374-
if file_list[1] == self.panel.cur_file then
375-
self.panel:set_cur_file(nil)
376-
else
377-
self.panel:set_cur_file(self.panel:prev_file())
378-
end
379-
end
339+
async.util.scheduler()
340+
341+
for _, v in ipairs(files) do
342+
---@param aa FileEntry
343+
---@param bb FileEntry
344+
local diff = Diff(v.cur_files, v.new_files, function(aa, bb)
345+
return aa.path == bb.path and aa.oldpath == bb.oldpath
346+
end)
347+
local script = diff:create_edit_script()
348+
349+
local ai = 1
350+
local bi = 1
351+
352+
for _, opr in ipairs(script) do
353+
if opr == EditToken.NOOP then
354+
-- Update status and stats
355+
local a_stats = v.cur_files[ai].stats
356+
local b_stats = v.new_files[bi].stats
380357

381-
v.cur_files[ai]:destroy()
382-
table.remove(v.cur_files, ai)
383-
384-
elseif opr == EditToken.INSERT then
385-
table.insert(v.cur_files, ai, v.new_files[bi])
386-
ai = ai + 1
387-
bi = bi + 1
388-
389-
elseif opr == EditToken.REPLACE then
390-
if self.panel.cur_file == v.cur_files[ai] then
391-
local file_list = self.panel:ordered_file_list()
392-
if file_list[1] == self.panel.cur_file then
393-
self.panel:set_cur_file(nil)
394-
else
395-
self.panel:set_cur_file(self.panel:prev_file())
396-
end
358+
if a_stats then
359+
v.cur_files[ai].stats = vim.tbl_extend("force", a_stats, b_stats or {})
360+
else
361+
v.cur_files[ai].stats = v.new_files[bi].stats
362+
end
363+
364+
v.cur_files[ai].status = v.new_files[bi].status
365+
v.cur_files[ai]:validate_stage_buffers(index_stat)
366+
367+
if new_head then
368+
v.cur_files[ai]:update_heads(new_head)
369+
end
370+
371+
ai = ai + 1
372+
bi = bi + 1
373+
374+
elseif opr == EditToken.DELETE then
375+
if self.panel.cur_file == v.cur_files[ai] then
376+
local file_list = self.panel:ordered_file_list()
377+
if file_list[1] == self.panel.cur_file then
378+
self.panel:set_cur_file(nil)
379+
else
380+
self.panel:set_cur_file(self.panel:prev_file())
397381
end
382+
end
383+
384+
v.cur_files[ai]:destroy()
385+
table.remove(v.cur_files, ai)
398386

399-
v.cur_files[ai]:destroy()
400-
table.remove(v.cur_files, ai)
401-
table.insert(v.cur_files, ai, v.new_files[bi])
402-
ai = ai + 1
403-
bi = bi + 1
387+
elseif opr == EditToken.INSERT then
388+
table.insert(v.cur_files, ai, v.new_files[bi])
389+
ai = ai + 1
390+
bi = bi + 1
391+
392+
elseif opr == EditToken.REPLACE then
393+
if self.panel.cur_file == v.cur_files[ai] then
394+
local file_list = self.panel:ordered_file_list()
395+
if file_list[1] == self.panel.cur_file then
396+
self.panel:set_cur_file(nil)
397+
else
398+
self.panel:set_cur_file(self.panel:prev_file())
399+
end
404400
end
401+
402+
v.cur_files[ai]:destroy()
403+
table.remove(v.cur_files, ai)
404+
table.insert(v.cur_files, ai, v.new_files[bi])
405+
ai = ai + 1
406+
bi = bi + 1
405407
end
406408
end
409+
end
407410

408-
perf:lap("updated file list")
409-
FileEntry.update_index_stat(self.adapter, index_stat)
410-
self.files:update_file_trees()
411-
self.panel:update_components()
412-
self.panel:render()
413-
self.panel:redraw()
414-
perf:lap("panel redrawn")
415-
self.panel:reconstrain_cursor()
411+
perf:lap("updated file list")
416412

417-
if utils.vec_indexof(self.panel:ordered_file_list(), self.panel.cur_file) == -1 then
418-
self.panel:set_cur_file(nil)
419-
end
413+
self.merge_ctx = next(new_files.conflicting) and self.adapter:get_merge_context() or nil
420414

421-
-- Set initially selected file
422-
if not self.initialized and self.options.selected_file then
423-
for _, file in self.files:ipairs() do
424-
if file.path == self.options.selected_file then
425-
self.panel:set_cur_file(file)
426-
break
427-
end
415+
for _, entry in ipairs(self.files.conflicting) do
416+
entry:update_merge_context(self.merge_ctx)
417+
end
418+
419+
FileEntry.update_index_stat(self.adapter, index_stat)
420+
self.files:update_file_trees()
421+
self.panel:update_components()
422+
self.panel:render()
423+
self.panel:redraw()
424+
perf:lap("panel redrawn")
425+
self.panel:reconstrain_cursor()
426+
427+
if utils.vec_indexof(self.panel:ordered_file_list(), self.panel.cur_file) == -1 then
428+
self.panel:set_cur_file(nil)
429+
end
430+
431+
-- Set initially selected file
432+
if not self.initialized and self.options.selected_file then
433+
for _, file in self.files:ipairs() do
434+
if file.path == self.options.selected_file then
435+
self.panel:set_cur_file(file)
436+
break
428437
end
429438
end
430-
self:set_file(self.panel.cur_file or self.panel:next_file(), false, not self.initialized)
439+
end
440+
self:set_file(self.panel.cur_file or self.panel:next_file(), false, not self.initialized)
431441

432-
if api.nvim_win_is_valid(last_winid) then
433-
api.nvim_set_current_win(last_winid)
434-
end
442+
if api.nvim_win_is_valid(last_winid) then
443+
api.nvim_set_current_win(last_winid)
444+
end
435445

436-
self.update_needed = false
437-
perf:time()
438-
logger.lvl(5).s_debug(perf)
439-
logger.s_info(
440-
("[%s] Completed update for %d files successfully (%.3f ms)")
441-
:format(self:class():name(), self.files:len(), perf.final_time)
442-
)
443-
self.emitter:emit("files_updated", self.files)
444-
if type(callback) == "function" then
445-
callback()
446-
end
446+
self.update_needed = false
447+
perf:time()
448+
logger.lvl(5).s_debug(perf)
449+
logger.s_info(
450+
("[%s] Completed update for %d files successfully (%.3f ms)")
451+
:format(self:class():name(), self.files:len(), perf.final_time)
452+
)
453+
self.emitter:emit("files_updated", self.files)
454+
if type(callback) == "function" then
455+
callback()
447456
end
448457
end)
449458
end)

0 commit comments

Comments
 (0)