Skip to content

Add status.tree_view option #1720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ neogit.setup {
HEAD_padding = 10,
HEAD_folded = false,
mode_padding = 3,
-- group changes by folder
tree_view = false,
mode_text = {
M = "modified",
N = "new file",
Expand Down
96 changes: 93 additions & 3 deletions lua/neogit/buffers/status/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,84 @@ local Section = Component.new(function(props)
})
end)

local TreeSection = Component.new(function(props)
local count
if props.count then
count = { text(" ("), text.highlight("NeogitSectionHeaderCount")(#props.items), text(")") }
end

local function appendRows(items)
return a.void(function(this, ui)
this.options.on_open = nil
this.options.folded = false

ui.buf:with_locked_viewport(function()
for _, item in ipairs(items) do
if item.type == "group" then
local groupPath = item.name
local indent = string.rep("│ ", item.indent_level - 1)
item.name = vim.fn.fnamemodify(item.name, ":t")
this:append(col.tag("Item")({
row {
text.highlight("NeogitSubtleText")(indent),
text.highlight("NeogitFolderPath")(groupPath),
},
}, {
foldable = true,
folded = true,
on_open = appendRows(item.children),
context = true,
id = groupPath,
yankable = groupPath,
filename = groupPath,
item = nil,
}))
else
this:append(props.render(item.content))
end
end
ui:update()
end)
end)
end

local sections = {}
local groups = util.groupByFilePath(props.items)
for _, item in ipairs(groups) do
local groupPath = item.name
local section
if item.type == "group" then
section = col.tag("Item")({
row {
text.highlight("NeogitFolderPath")(groupPath),
},
}, {
foldable = true,
folded = true,
on_open = appendRows(item.children),
context = true,
id = groupPath,
yankable = groupPath,
filename = groupPath,
item = nil,
})
else
section = props.render(item.content)
end
table.insert(sections, section)
end
return col.tag("Section")({
row(util.merge(props.title, count or {})),
col(sections),
EmptyLine(),
}, {
foldable = true,
folded = props.folded,
section = props.name,
id = props.name,
})
end)

local SequencerSection = Component.new(function(props)
return col.tag("Section")({
row(util.merge(props.title)),
Expand Down Expand Up @@ -242,6 +320,13 @@ local SectionItemFile = function(section, config)
end)
end

local indent = ""
if config.status.tree_view then
local indent_level = #vim.split(item.name, "/", { plain = true }) - 1
indent = string.rep("│ ", indent_level)
item.name = vim.fn.fnamemodify(item.name, ":t")
end

local mode = config.status.mode_text[item.mode]
local mode_text
if mode == "" then
Expand Down Expand Up @@ -291,6 +376,7 @@ local SectionItemFile = function(section, config)

return col.tag("Item")({
row {
text.highlight("NeogitSubtleText")(indent),
text.highlight(highlight)(mode_text),
text(name),
text.highlight("NeogitSubtleText")(unmerged_types[item.mode] or ""),
Expand Down Expand Up @@ -518,6 +604,10 @@ function M.Status(state, config)
local show_recent = #state.recent.items > 0
and not config.sections.recent.hidden

local ChangesSection = config.status.tree_view
and TreeSection
or Section

return {
List {
items = {
Expand Down Expand Up @@ -612,23 +702,23 @@ function M.Status(state, config)
folded = config.sections.bisect.folded,
name = "bisect",
},
show_untracked and Section {
show_untracked and ChangesSection {
title = SectionTitle { title = "Untracked files", highlight = "NeogitUntrackedfiles" },
count = true,
render = SectionItemFile("untracked", config),
items = state.untracked.items,
folded = config.sections.untracked.folded,
name = "untracked",
},
show_unstaged and Section {
show_unstaged and ChangesSection {
title = SectionTitle { title = "Unstaged changes", highlight = "NeogitUnstagedchanges" },
count = true,
render = SectionItemFile("unstaged", config),
items = state.unstaged.items,
folded = config.sections.unstaged.folded,
name = "unstaged",
},
show_staged and Section {
show_staged and ChangesSection {
title = SectionTitle { title = "Staged changes", highlight = "NeogitStagedchanges" },
count = true,
render = SectionItemFile("staged", config),
Expand Down
2 changes: 2 additions & 0 deletions lua/neogit/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ end
---@field HEAD_folded? boolean Whether or not this section should be open or closed by default
---@field mode_text? { [string]: string } The text to display for each mode
---@field show_head_commit_hash? boolean Show the commit hash for HEADs in the status buffer
---@field tree_view? boolean use a nested directory representation for changes (untracked, unstaged, staged)

---@class NeogitConfigMappings Consult the config file or documentation for values
---@field finder? { [string]: NeogitConfigMappingsFinder } A dictionary that uses finder commands to set multiple keybinds
Expand Down Expand Up @@ -428,6 +429,7 @@ function M.get_default_values()
HEAD_padding = 10,
HEAD_folded = false,
mode_padding = 3,
tree_view = false,
mode_text = {
M = "modified",
N = "new file",
Expand Down
1 change: 1 addition & 0 deletions lua/neogit/lib/hl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ function M.setup(config)
NeogitPopupOptionDisabled = { link = "NeogitSubtleText" },
NeogitPopupConfigKey = { fg = palette.purple },
NeogitPopupConfigEnabled = { link = "SpecialChar" },
NeogitFolderPath = { bold = palette.bold },
NeogitPopupConfigDisabled = { link = "NeogitSubtleText" },
NeogitPopupActionKey = { fg = palette.purple },
NeogitPopupActionDisabled = { link = "NeogitSubtleText" },
Expand Down
71 changes: 71 additions & 0 deletions lua/neogit/lib/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,77 @@ function M.remove_ansi_escape_codes(s)
return s
end

--- Organizes a list of items by their file path, compressing elements with single children into groups.
---@param items table[] A list of items, each having a 'name' field which is a string representing the file path.
---@return table[] A hierarchical table structure representing the compressed file path groups and files.
function M.groupByFilePath(items)
local root = {}
for _, item in ipairs(items) do
local parts = {}
for part in string.gmatch(item.name, "[^/]+") do
table.insert(parts, part)
end

local node = root
for i = 1, #parts do
local part = parts[i]
node.children = node.children or {}
node.children[part] = node.children[part] or { name = part, indent_level = i }

node = node.children[part]
end
node.is_file = true
node.content = item
end
local function compress(node, path_prefix)
local children = node.children
if not children then
return {
name = node.name,
type = "file",
content = node.content,
indent_level = node.indent_level,
}
end

local keys = {}
for k in pairs(children) do
table.insert(keys, k)
end
table.sort(keys)
while #keys == 1 and not children[keys[1]].is_file do
local only = keys[1]
path_prefix = path_prefix .. "/" .. only
node = children[only]
children = node.children
keys = {}
for k in pairs(children or {}) do
table.insert(keys, k)
end
table.sort(keys)
end

local result = {
name = path_prefix,
type = "group",
children = {},
indent_level = node.indent_level or 0,
}

for _, key in ipairs(keys) do
table.insert(result.children, compress(children[key], key))
end

return result
end
local result = {}
for name, child in pairs(root.children or {}) do
table.insert(result, compress(child, name))
end

return result
end

--- Safely close a window
---@param winid integer
---@param force boolean
Expand Down
Loading