Skip to content

Commit 28cf223

Browse files
committed
feat(config): add visual appearance options
1 parent f2dd45a commit 28cf223

8 files changed

Lines changed: 202 additions & 25 deletions

File tree

doc/diffview.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,15 @@ file_panel *diffview-config-file_panel*
785785
contain a single directory. Default: `true`
786786
• {folder_statuses} ("never"|"only_folded"|"always")
787787
Show folder statuses. Default: `"only_folded"`
788+
• {folder_count_style} ("grouped"|"simple"|"none")
789+
How to display file counts on collapsed folders.
790+
`"grouped"` shows per-status counts (e.g. "2M 1D"),
791+
`"simple"` shows a plain total (e.g. "3"),
792+
`"none"` hides the count entirely.
793+
Default: `"grouped"`
794+
• {folder_trailing_slash} (boolean)
795+
Append "/" to folder names in the file tree.
796+
Default: `true`
788797

789798
{win_config} (table|function)
790799
See |diffview-config-win_config|.
@@ -815,6 +824,12 @@ file_history_panel *diffview-config-file_history_pa
815824
"5, 3", `"bar"` shows e.g. "| 8 +++++---". Default:
816825
`"number"`
817826

827+
{subject_highlight} ("ref_aware"|"plain")
828+
How to highlight commit subjects. `"ref_aware"` uses
829+
different colours for pushed vs unpushed commits.
830+
`"plain"` uses a single colour for all.
831+
Default: `"ref_aware"`
832+
818833
{commit_format} (string[])
819834
Ordered list of components to show for each commit entry.
820835
Available components: `"status"`, `"files"`, `"stats"`,

doc/diffview_defaults.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ DEFAULT CONFIG *diffview.defaults*
8989
tree_options = { -- Only applies when listing_style is 'tree'
9090
flatten_dirs = true, -- Flatten dirs that only contain one single dir
9191
folder_statuses = "only_folded", -- One of 'never', 'only_folded' or 'always'.
92+
folder_count_style = "grouped", -- "grouped" (e.g. "2M 1D"), "simple" (e.g. "3"), or "none".
93+
folder_trailing_slash = true, -- Append "/" to folder names in the file tree.
9294
},
9395
win_config = { -- See |diffview-config-win_config|
9496
position = "left",
@@ -102,6 +104,7 @@ DEFAULT CONFIG *diffview.defaults*
102104
},
103105
file_history_panel = {
104106
stat_style = "number", -- "number" (e.g. "5, 3"), "bar" (e.g. "| 8 +++++---"), or "both".
107+
subject_highlight = "ref_aware", -- "ref_aware" (colour by pushed/unpushed) or "plain".
105108
-- Ordered list of components to show for each commit entry.
106109
-- Available: "status", "files", "stats", "hash", "reflog", "ref", "subject", "author", "date"
107110
commit_format = { "status", "files", "stats", "hash", "reflog", "ref", "subject", "author", "date" },

lua/diffview/config.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ M.defaults = {
152152
sort_file = nil, -- Custom file comparator: function(a_name, b_name, a_data, b_data) -> boolean
153153
tree_options = {
154154
flatten_dirs = true,
155-
folder_statuses = "only_folded"
155+
folder_statuses = "only_folded",
156+
folder_count_style = "grouped", -- "grouped" (e.g. "2M 1D"), "simple" (e.g. "3"), or "none".
157+
folder_trailing_slash = true, -- Append "/" to folder names in the file tree.
156158
},
157159
win_config = {
158160
position = "left",
@@ -166,6 +168,7 @@ M.defaults = {
166168
},
167169
file_history_panel = {
168170
stat_style = "number", -- "number" (e.g. "5, 3"), "bar" (e.g. "| 8 +++++---"), or "both".
171+
subject_highlight = "ref_aware", -- "ref_aware" (colour by pushed/unpushed) or "plain".
169172
-- Ordered list of components to show for each commit entry.
170173
-- Available: "status", "files", "stats", "hash", "reflog", "ref", "subject", "author", "date"
171174
commit_format = { "status", "files", "stats", "hash", "reflog", "ref", "subject", "author", "date" },

lua/diffview/scene/views/diff/file_panel.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ local M = {}
99
---@class TreeOptions
1010
---@field flatten_dirs boolean
1111
---@field folder_statuses "never"|"only_folded"|"always"
12+
---@field folder_count_style "grouped"|"simple"|"none"
13+
---@field folder_trailing_slash boolean
1214

1315
---@class FilePanel : Panel
1416
---@field adapter VCSAdapter

lua/diffview/scene/views/diff/render.lua

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,47 @@ local utils = require("diffview.utils")
44

55
local pl = utils.path
66

7+
---Format a folder name, optionally appending a trailing slash.
8+
---@param name string
9+
---@param tree_options table
10+
---@return string
11+
local function format_folder_name(name, tree_options)
12+
return name .. (tree_options.folder_trailing_slash and "/" or "")
13+
end
14+
15+
---Render the file count annotation for a collapsed folder.
16+
---@param comp table RenderComponent-like object supporting add_text().
17+
---@param node table Tree node whose leaves() will be counted.
18+
---@param tree_options table Config tree_options table.
19+
local function render_folder_count(comp, node, tree_options)
20+
if tree_options.folder_count_style == "none" then return end
21+
22+
if tree_options.folder_count_style == "grouped" then
23+
local leaves = node:leaves()
24+
local status_counts = {}
25+
for _, leaf in ipairs(leaves) do
26+
local s = leaf.data.status or "?"
27+
status_counts[s] = (status_counts[s] or 0) + 1
28+
end
29+
30+
-- Sort status letters for consistent display order.
31+
local statuses = vim.tbl_keys(status_counts)
32+
table.sort(statuses)
33+
34+
comp:add_text(" (", "DiffviewDim1")
35+
for i, s in ipairs(statuses) do
36+
if i > 1 then
37+
comp:add_text(" ", "DiffviewDim1")
38+
end
39+
comp:add_text(tostring(status_counts[s]) .. hl.get_status_icon(s), hl.get_git_hl(s))
40+
end
41+
comp:add_text(")", "DiffviewDim1")
42+
else
43+
local file_count = #node:leaves()
44+
comp:add_text(" (" .. file_count .. ")", "DiffviewDim1")
45+
end
46+
end
47+
748
---@param conf DiffviewConfig
849
---@param panel FilePanel
950
---@param comp RenderComponent
@@ -161,28 +202,11 @@ local function render_file_tree_recurse(conf, panel, depth, comp)
161202
"DiffviewFolderSign"
162203
)
163204

164-
dir:add_text(ctx.name .. "/", "DiffviewFolderName")
165-
-- Show file count grouped by status when folder is collapsed.
205+
local tree_options = conf.file_panel.tree_options
206+
dir:add_text(format_folder_name(ctx.name, tree_options), "DiffviewFolderName")
207+
-- Show file count when folder is collapsed.
166208
if ctx.collapsed and ctx._node then
167-
local leaves = ctx._node:leaves()
168-
local status_counts = {}
169-
for _, node in ipairs(leaves) do
170-
local s = node.data.status or "?"
171-
status_counts[s] = (status_counts[s] or 0) + 1
172-
end
173-
174-
-- Sort status letters for consistent display order.
175-
local statuses = vim.tbl_keys(status_counts)
176-
table.sort(statuses)
177-
178-
dir:add_text(" (", "DiffviewDim1")
179-
for i, s in ipairs(statuses) do
180-
if i > 1 then
181-
dir:add_text(" ", "DiffviewDim1")
182-
end
183-
dir:add_text(tostring(status_counts[s]) .. hl.get_status_icon(s), hl.get_git_hl(s))
184-
end
185-
dir:add_text(")", "DiffviewDim1")
209+
render_folder_count(dir, ctx._node, tree_options)
186210
end
187211
dir:ln()
188212

@@ -214,7 +238,7 @@ local function render_files(conf, panel, listing_style, comp)
214238
end
215239

216240
---@param panel FilePanel
217-
return function(panel)
241+
local function render_panel(panel)
218242
if not panel.render_data then
219243
return
220244
end
@@ -314,3 +338,13 @@ return function(panel)
314338
end
315339
end
316340
end
341+
342+
return setmetatable({
343+
-- Exposed for testing only.
344+
_test = {
345+
format_folder_name = format_folder_name,
346+
render_folder_count = render_folder_count,
347+
},
348+
}, {
349+
__call = function(_, panel) render_panel(panel) end,
350+
})

lua/diffview/scene/views/file_history/render.lua

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,12 @@ local formatters = {
180180
local subject_hl
181181
if ctx.panel.cur_item[1] == entry then
182182
subject_hl = "DiffviewFilePanelSelected"
183-
elseif entry.has_remote_ref then
183+
elseif ctx.conf.file_history_panel.subject_highlight == "ref_aware" and entry.has_remote_ref then
184184
subject_hl = "DiffviewCommitRemoteRef"
185-
else
185+
elseif ctx.conf.file_history_panel.subject_highlight == "ref_aware" then
186186
subject_hl = "DiffviewCommitLocalOnly"
187+
else
188+
subject_hl = "DiffviewFilePanelFileName"
187189
end
188190

189191
comp:add_text(" " .. subject, subject_hl)
@@ -422,4 +424,11 @@ return {
422424
clear_cache = function(panel)
423425
cache[panel] = nil
424426
end,
427+
-- Exposed for testing only.
428+
_test = {
429+
render_stat_bar = render_stat_bar,
430+
render_file_stats = render_file_stats,
431+
formatters = formatters,
432+
MAX_BAR_WIDTH = MAX_BAR_WIDTH,
433+
},
425434
}

lua/diffview/tests/functional/file_history_render_spec.lua

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
local helpers = require("diffview.tests.helpers")
22
local config = require("diffview.config")
3+
local render = require("diffview.scene.views.file_history.render")
34

45
local eq = helpers.eq
56

7+
local formatters = render._test.formatters
8+
69
-- ---------------------------------------------------------------------------
710
-- Mock RenderComponent
811
-- ---------------------------------------------------------------------------
@@ -340,6 +343,68 @@ describe("file_history_render", function()
340343
end)
341344
end)
342345

346+
-- -----------------------------------------------------------------------
347+
-- subject_highlight
348+
-- -----------------------------------------------------------------------
349+
350+
describe("subject_highlight", function()
351+
---Call the real formatters.subject and return the highlight group it used.
352+
---@param entry table
353+
---@param is_selected boolean
354+
---@return string
355+
local function render_subject_hl(entry, is_selected)
356+
entry.commit = entry.commit or { subject = "test" }
357+
local comp = make_comp()
358+
local ctx = {
359+
conf = config.get_config(),
360+
panel = { cur_item = { is_selected and entry or {} } },
361+
}
362+
formatters.subject(comp, entry, ctx)
363+
-- The subject is rendered as a single segment; return its hl group.
364+
return comp.lines[1][1].hl
365+
end
366+
367+
it("uses DiffviewFilePanelFileName for 'plain' mode", function()
368+
local conf = config.get_config()
369+
conf.file_history_panel.subject_highlight = "plain"
370+
config.setup(conf)
371+
372+
local entry = { has_remote_ref = true }
373+
eq("DiffviewFilePanelFileName", render_subject_hl(entry, false))
374+
end)
375+
376+
it("uses DiffviewCommitRemoteRef for 'ref_aware' with remote ref", function()
377+
local conf = config.get_config()
378+
conf.file_history_panel.subject_highlight = "ref_aware"
379+
config.setup(conf)
380+
381+
local entry = { has_remote_ref = true }
382+
eq("DiffviewCommitRemoteRef", render_subject_hl(entry, false))
383+
end)
384+
385+
it("uses DiffviewCommitLocalOnly for 'ref_aware' without remote ref", function()
386+
local conf = config.get_config()
387+
conf.file_history_panel.subject_highlight = "ref_aware"
388+
config.setup(conf)
389+
390+
local entry = { has_remote_ref = false }
391+
eq("DiffviewCommitLocalOnly", render_subject_hl(entry, false))
392+
end)
393+
394+
it("uses DiffviewFilePanelSelected when entry is selected", function()
395+
local conf = config.get_config()
396+
conf.file_history_panel.subject_highlight = "ref_aware"
397+
config.setup(conf)
398+
399+
local entry = { has_remote_ref = true }
400+
eq("DiffviewFilePanelSelected", render_subject_hl(entry, true))
401+
end)
402+
403+
it("defaults to 'ref_aware'", function()
404+
eq("ref_aware", config.get_config().file_history_panel.subject_highlight)
405+
end)
406+
end)
407+
343408
-- -----------------------------------------------------------------------
344409
-- date_format config default
345410
-- -----------------------------------------------------------------------

lua/diffview/tests/functional/panel_render_spec.lua

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ local config = require("diffview.config")
33
local Node = require("diffview.ui.models.file_tree.node").Node
44
local FileTree = require("diffview.ui.models.file_tree.file_tree").FileTree
55
local FileDict = require("diffview.vcs.file_dict").FileDict
6+
local panel_render = require("diffview.scene.views.diff.render")
67

78
local eq = helpers.eq
89

10+
local format_folder_name = panel_render._test.format_folder_name
11+
local render_folder_count = panel_render._test.render_folder_count
12+
913
-- ---------------------------------------------------------------------------
1014
-- Mock RenderComponent (same pattern as file_history_render_spec.lua)
1115
-- ---------------------------------------------------------------------------
@@ -248,6 +252,22 @@ describe("panel_render", function()
248252
eq(")", dim_segs[#dim_segs])
249253
end)
250254

255+
it("hides count entirely when folder_count_style is 'none'", function()
256+
local conf = config.get_config()
257+
conf.file_panel.tree_options.folder_count_style = "none"
258+
config.setup(conf)
259+
260+
local dir_node = Node("src", { name = "src", path = "src", kind = "working", collapsed = true, status = "M" })
261+
dir_node:add_child(Node("a.lua", { path = "src/a.lua", status = "M" }))
262+
dir_node:add_child(Node("b.lua", { path = "src/b.lua", status = "A" }))
263+
264+
-- Call the real render_folder_count; it should produce no output.
265+
local comp = make_comp()
266+
render_folder_count(comp, dir_node, config.get_config().file_panel.tree_options)
267+
eq(0, #comp:segments_by_hl("DiffviewDim1"))
268+
eq("", comp:flat_text())
269+
end)
270+
251271
it("does not show count when directory is expanded", function()
252272
-- When collapsed is false, the count section is skipped.
253273
local dir_node = Node("src", { name = "src", path = "src", kind = "working", collapsed = false, status = "M" })
@@ -269,6 +289,32 @@ describe("panel_render", function()
269289
end)
270290
end)
271291

292+
-- -----------------------------------------------------------------------
293+
-- folder_trailing_slash
294+
-- -----------------------------------------------------------------------
295+
296+
describe("folder_trailing_slash option", function()
297+
it("appends trailing slash when enabled", function()
298+
local conf = config.get_config()
299+
conf.file_panel.tree_options.folder_trailing_slash = true
300+
config.setup(conf)
301+
302+
eq("src/", format_folder_name("src", config.get_config().file_panel.tree_options))
303+
end)
304+
305+
it("omits trailing slash when disabled", function()
306+
local conf = config.get_config()
307+
conf.file_panel.tree_options.folder_trailing_slash = false
308+
config.setup(conf)
309+
310+
eq("src", format_folder_name("src", config.get_config().file_panel.tree_options))
311+
end)
312+
313+
it("defaults to true", function()
314+
eq(true, config.get_config().file_panel.tree_options.folder_trailing_slash)
315+
end)
316+
end)
317+
272318
-- -----------------------------------------------------------------------
273319
-- Loading indicator (commit 5f1603a)
274320
-- -----------------------------------------------------------------------

0 commit comments

Comments
 (0)