diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 436baf3591b..a224f4f61cc 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -592,7 +592,7 @@ Git integration with icons and colors. Type: `boolean`, Default: `true` *nvim-tree.git.show_on_open_dirs* - Show status icons on directories that are open. + Show status icons of children on directories that are open. Only relevant when `git.show_on_dirs` is `true`. Type: `boolean`, Default: `true` diff --git a/lua/nvim-tree/actions/moves/item.lua b/lua/nvim-tree/actions/moves/item.lua index b5646193346..556fd071720 100644 --- a/lua/nvim-tree/actions/moves/item.lua +++ b/lua/nvim-tree/actions/moves/item.lua @@ -2,7 +2,7 @@ local utils = require "nvim-tree.utils" local view = require "nvim-tree.view" local core = require "nvim-tree.core" local lib = require "nvim-tree.lib" -local explorer_common = require "nvim-tree.explorer.common" +local explorer_node = require "nvim-tree.explorer.node" local M = {} @@ -15,7 +15,7 @@ function M.fn(where, what) for line, node in pairs(nodes_by_line) do local valid = false if what == "git" then - valid = explorer_common.shows_git_status(node) + valid = explorer_node.get_git_status(node) ~= nil elseif what == "diag" then valid = node.diag_status ~= nil end diff --git a/lua/nvim-tree/actions/reloaders/reloaders.lua b/lua/nvim-tree/actions/reloaders/reloaders.lua index 7fca29068d1..b6005ae87ad 100644 --- a/lua/nvim-tree/actions/reloaders/reloaders.lua +++ b/lua/nvim-tree/actions/reloaders/reloaders.lua @@ -3,6 +3,7 @@ local view = require "nvim-tree.view" local renderer = require "nvim-tree.renderer" local explorer_module = require "nvim-tree.explorer" local core = require "nvim-tree.core" +local explorer_node = require "nvim-tree.explorer.node" local M = {} @@ -21,11 +22,7 @@ function M.reload_node_status(parent_node, projects) local project_root = git.get_project_root(parent_node.absolute_path) local status = projects[project_root] or {} for _, node in ipairs(parent_node.nodes) do - if node.nodes then - node.git_status = status.dirs and status.dirs[node.absolute_path] - else - node.git_status = status.files and status.files[node.absolute_path] - end + explorer_node.update_git_status(node, explorer_node.is_git_ignored(parent_node), status) if node.nodes and #node.nodes > 0 then M.reload_node_status(node, projects) end @@ -50,7 +47,7 @@ function M.reload_explorer(_, unloaded_bufnr) end function M.reload_git() - if not core.get_explorer() or not git.config.enable or event_running then + if not core.get_explorer() or not git.config.git.enable or event_running then return end event_running = true diff --git a/lua/nvim-tree/explorer/common.lua b/lua/nvim-tree/explorer/common.lua deleted file mode 100644 index 71eae8af608..00000000000 --- a/lua/nvim-tree/explorer/common.lua +++ /dev/null @@ -1,74 +0,0 @@ -local M = {} - -local function get_dir_git_status(parent_ignored, status, absolute_path) - if parent_ignored then - return "!!" - end - - local file_status = status.files and status.files[absolute_path] - if file_status then - return file_status - end - - return status.dirs and status.dirs[absolute_path] -end - -local function get_git_status(parent_ignored, status, absolute_path) - return parent_ignored and "!!" or status.files and status.files[absolute_path] -end - -function M.has_one_child_folder(node) - return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R") -end - -function M.update_git_status(node, parent_ignored, status) - -- status of the node's absolute path - if node.nodes then - node.git_status = get_dir_git_status(parent_ignored, status, node.absolute_path) - else - node.git_status = get_git_status(parent_ignored, status, node.absolute_path) - end - - -- status of the link target, if the link itself is not dirty - if node.link_to and not node.git_status then - if node.nodes then - node.git_status = get_dir_git_status(parent_ignored, status, node.link_to) - else - node.git_status = get_git_status(parent_ignored, status, node.link_to) - end - end -end - -function M.shows_git_status(node) - if not node.git_status then - -- status doesn't exist - return false - elseif not node.nodes then - -- status exist and is a file - return true - elseif not node.open then - -- status exist, is a closed dir - return M.config.git.show_on_dirs - else - -- status exist, is a open dir - return M.config.git.show_on_dirs and M.config.git.show_on_open_dirs - end -end - -function M.node_destroy(node) - if not node then - return - end - - if node.watcher then - node.watcher:destroy() - end -end - -function M.setup(opts) - M.config = { - git = opts.git, - } -end - -return M diff --git a/lua/nvim-tree/explorer/explore.lua b/lua/nvim-tree/explorer/explore.lua index da0803c8419..7a83980ea05 100644 --- a/lua/nvim-tree/explorer/explore.lua +++ b/lua/nvim-tree/explorer/explore.lua @@ -1,6 +1,6 @@ local utils = require "nvim-tree.utils" local builders = require "nvim-tree.explorer.node-builders" -local common = require "nvim-tree.explorer.common" +local explorer_node = require "nvim-tree.explorer.node" local sorters = require "nvim-tree.explorer.sorters" local filters = require "nvim-tree.explorer.filters" local live_filter = require "nvim-tree.live-filter" @@ -13,7 +13,7 @@ local function get_type_from(type_, cwd) end local function populate_children(handle, cwd, node, git_status) - local node_ignored = node.git_status == "!!" + local node_ignored = explorer_node.is_git_ignored(node) local nodes_by_path = utils.bool_record(node.nodes, "absolute_path") local filter_status = filters.prepare(git_status) while true do @@ -39,7 +39,7 @@ local function populate_children(handle, cwd, node, git_status) if child then table.insert(node.nodes, child) nodes_by_path[child.absolute_path] = true - common.update_git_status(child, node_ignored, git_status) + explorer_node.update_git_status(child, node_ignored, git_status) end end end @@ -64,7 +64,7 @@ function M.explore(node, status) populate_children(handle, cwd, node, status) local is_root = not node.parent - local child_folder_only = common.has_one_child_folder(node) and node.nodes[1] + local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1] if M.config.group_empty and not is_root and child_folder_only then node.group_next = child_folder_only local ns = M.explore(child_folder_only, status) diff --git a/lua/nvim-tree/explorer/filters.lua b/lua/nvim-tree/explorer/filters.lua index c5701deaacd..8af47e1fe16 100644 --- a/lua/nvim-tree/explorer/filters.lua +++ b/lua/nvim-tree/explorer/filters.lua @@ -24,7 +24,9 @@ local function git(path, git_status) end -- default status to clean - local status = git_status.files[path] or git_status.dirs[path] or " " + local status = git_status.files[path] + status = status or git_status.dirs.direct[path] and git_status.dirs.direct[path][1] + status = status or git_status.dirs.indirect[path] and git_status.dirs.indirect[path][1] -- filter ignored; overrides clean as they are effectively dirty if M.config.filter_git_ignored and status == "!!" then @@ -32,7 +34,7 @@ local function git(path, git_status) end -- filter clean - if M.config.filter_git_clean and status == " " then + if M.config.filter_git_clean and not status then return true end diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index 8f34f9d7b38..a47ac4d7423 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -1,6 +1,6 @@ local git = require "nvim-tree.git" local watch = require "nvim-tree.explorer.watch" -local common = require "nvim-tree.explorer.common" +local explorer_node = require "nvim-tree.explorer.node" local M = {} @@ -24,8 +24,8 @@ end function Explorer:_load(node) local cwd = node.link_to or node.absolute_path - local git_statuses = git.load_project_status(cwd) - M.explore(node, git_statuses) + local git_status = git.load_project_status(cwd) + M.explore(node, git_status) end function Explorer:expand(node) @@ -34,7 +34,7 @@ end function Explorer:destroy() local function iterate(node) - common.node_destroy(node) + explorer_node.node_destroy(node) if node.nodes then for _, child in pairs(node.nodes) do iterate(child) @@ -45,7 +45,7 @@ function Explorer:destroy() end function M.setup(opts) - require("nvim-tree.explorer.common").setup(opts) + require("nvim-tree.explorer.node").setup(opts) require("nvim-tree.explorer.explore").setup(opts) require("nvim-tree.explorer.filters").setup(opts) require("nvim-tree.explorer.sorters").setup(opts) diff --git a/lua/nvim-tree/explorer/node.lua b/lua/nvim-tree/explorer/node.lua new file mode 100644 index 00000000000..0a3b9f3f941 --- /dev/null +++ b/lua/nvim-tree/explorer/node.lua @@ -0,0 +1,133 @@ +local M = {} + +-- node.git_status structure: +-- { +-- file = string | nil, +-- dir = { +-- direct = { string } | nil, +-- indirect = { string } | nil, +-- } | nil, +-- } + +local function get_dir_git_status(parent_ignored, status, absolute_path) + if parent_ignored then + return { file = "!!" } + end + + return { + file = status.files and status.files[absolute_path], + dir = status.dirs and { + direct = status.dirs.direct[absolute_path], + indirect = status.dirs.indirect[absolute_path], + }, + } +end + +local function get_git_status(parent_ignored, status, absolute_path) + local file_status = parent_ignored and "!!" or status.files and status.files[absolute_path] + return { file = file_status } +end + +function M.has_one_child_folder(node) + return #node.nodes == 1 and node.nodes[1].nodes and vim.loop.fs_access(node.nodes[1].absolute_path, "R") +end + +function M.update_git_status(node, parent_ignored, status) + local get_status + if node.nodes then + get_status = get_dir_git_status + else + get_status = get_git_status + end + + -- status of the node's absolute path + node.git_status = get_status(parent_ignored, status, node.absolute_path) + + -- status of the link target, if the link itself is not dirty + if node.link_to and not node.git_status then + node.git_status = get_status(parent_ignored, status, node.link_to) + end +end + +function M.get_git_status(node) + local git_status = node.git_status + if not git_status then + -- status doesn't exist + return nil + end + + if not node.nodes then + -- file + return git_status.file and { git_status.file } + end + + -- dir + if not M.config.git.show_on_dirs then + return nil + end + + local status = {} + if not node.open or M.config.git.show_on_open_dirs then + -- dir is closed or we should show on open_dirs + if git_status.file ~= nil then + table.insert(status, git_status.file) + end + if git_status.dir ~= nil then + if git_status.dir.direct ~= nil then + for _, s in pairs(node.git_status.dir.direct) do + table.insert(status, s) + end + end + if git_status.dir.indirect ~= nil then + for _, s in pairs(node.git_status.dir.indirect) do + table.insert(status, s) + end + end + end + else + -- dir is open and we shouldn't show on open_dirs + if git_status.file ~= nil then + table.insert(status, git_status.file) + end + if git_status.dir ~= nil and git_status.dir.direct ~= nil then + local deleted = { + [" D"] = true, + ["D "] = true, + ["RD"] = true, + ["DD"] = true, + } + for _, s in pairs(node.git_status.dir.direct) do + if deleted[s] then + table.insert(status, s) + end + end + end + end + if #status == 0 then + return nil + else + return status + end +end + +function M.is_git_ignored(node) + return node.git_status and node.git_status.file == "!!" +end + +function M.node_destroy(node) + if not node then + return + end + + if node.watcher then + node.watcher:destroy() + end +end + +function M.setup(opts) + M.config = { + git = opts.git, + } +end + +return M diff --git a/lua/nvim-tree/explorer/reload.lua b/lua/nvim-tree/explorer/reload.lua index 8f1a3384153..da727925466 100644 --- a/lua/nvim-tree/explorer/reload.lua +++ b/lua/nvim-tree/explorer/reload.lua @@ -1,6 +1,6 @@ local utils = require "nvim-tree.utils" local builders = require "nvim-tree.explorer.node-builders" -local common = require "nvim-tree.explorer.common" +local explorer_node = require "nvim-tree.explorer.node" local filters = require "nvim-tree.explorer.filters" local sorters = require "nvim-tree.explorer.sorters" local live_filter = require "nvim-tree.live-filter" @@ -15,7 +15,7 @@ local M = {} local function update_status(nodes_by_path, node_ignored, status) return function(node) if nodes_by_path[node.absolute_path] then - common.update_git_status(node, node_ignored, status) + explorer_node.update_git_status(node, node_ignored, status) end return node end @@ -29,7 +29,7 @@ end local function update_parent_statuses(node, project, root) while project and node and node.absolute_path ~= root do - common.update_git_status(node, false, project) + explorer_node.update_git_status(node, false, project) node = node.parent end end @@ -53,7 +53,7 @@ function M.reload(node, git_status, unloaded_bufnr) local child_names = {} - local node_ignored = node.git_status == "!!" + local node_ignored = explorer_node.is_git_ignored(node) local nodes_by_path = utils.key_by(node.nodes, "absolute_path") while true do local ok, name, t = pcall(vim.loop.fs_scandir_next, handle) @@ -82,7 +82,7 @@ function M.reload(node, git_status, unloaded_bufnr) if n.type ~= t then utils.array_remove(node.nodes, n) - common.node_destroy(n) + explorer_node.node_destroy(n) nodes_by_path[abs] = nil end end @@ -119,14 +119,14 @@ function M.reload(node, git_status, unloaded_bufnr) if child_names[n.absolute_path] then return child_names[n.absolute_path] else - common.node_destroy(n) + explorer_node.node_destroy(n) return nil end end, node.nodes) ) local is_root = not node.parent - local child_folder_only = common.has_one_child_folder(node) and node.nodes[1] + local child_folder_only = explorer_node.has_one_child_folder(node) and node.nodes[1] if M.config.group_empty and not is_root and child_folder_only then node.group_next = child_folder_only local ns = M.reload(child_folder_only, git_status) diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index e16169503fb..308838750e7 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -4,6 +4,7 @@ local git_utils = require "nvim-tree.git.utils" local Runner = require "nvim-tree.git.runner" local Watcher = require("nvim-tree.watcher").Watcher local Iterator = require "nvim-tree.iterators.node-iterator" +local explorer_node = require "nvim-tree.explorer.node" local M = { config = {}, @@ -103,19 +104,13 @@ local function reload_tree_at(project_root) end M.reload_project(project_root) - local project = M.get_project(project_root) - - local project_files = project.files and project.files or {} - local project_dirs = project.dirs and project.dirs or {} + local git_status = M.get_project(project_root) Iterator.builder(root_node.nodes) :hidden() :applier(function(node) - local parent_ignored = node.parent.git_status == "!!" - node.git_status = project_dirs[node.absolute_path] or project_files[node.absolute_path] - if not node.git_status and parent_ignored then - node.git_status = "!!" - end + local parent_ignored = explorer_node.is_git_ignored(node.parent) + explorer_node.update_git_status(node, parent_ignored, git_status) end) :recursor(function(node) return node.nodes and #node.nodes > 0 and node.nodes diff --git a/lua/nvim-tree/git/utils.lua b/lua/nvim-tree/git/utils.lua index 48074c08f6e..e14cdafca40 100644 --- a/lua/nvim-tree/git/utils.lua +++ b/lua/nvim-tree/git/utils.lua @@ -55,24 +55,43 @@ function M.should_show_untracked(cwd) return untracked[cwd] end +local function nil_insert(t, k) + t = t or {} + t[k] = true + return t +end + function M.file_status_to_dir_status(status, cwd) - local dirs = {} + local direct = {} for p, s in pairs(status) do if s ~= "!!" then local modified = vim.fn.fnamemodify(p, ":h") - dirs[modified] = s + direct[modified] = nil_insert(direct[modified], s) end end - for dirname, s in pairs(dirs) do - local modified = dirname - while modified ~= cwd and modified ~= "/" do - modified = vim.fn.fnamemodify(modified, ":h") - dirs[modified] = s + local indirect = {} + for dirname, statuses in pairs(direct) do + for s, _ in pairs(statuses) do + local modified = dirname + while modified ~= cwd and modified ~= "/" do + modified = vim.fn.fnamemodify(modified, ":h") + indirect[modified] = nil_insert(indirect[modified], s) + end end end - return dirs + local r = { indirect = indirect, direct = direct } + for _, d in pairs(r) do + for dirname, statuses in pairs(d) do + local new_statuses = {} + for s, _ in pairs(statuses) do + table.insert(new_statuses, s) + end + d[dirname] = new_statuses + end + end + return r end return M diff --git a/lua/nvim-tree/renderer/builder.lua b/lua/nvim-tree/renderer/builder.lua index 1a63900e989..52657bab810 100644 --- a/lua/nvim-tree/renderer/builder.lua +++ b/lua/nvim-tree/renderer/builder.lua @@ -248,10 +248,17 @@ function Builder:_build_line(node, idx, num_children) local git_highlight = git.get_highlight(node) local git_icons_tbl = git.get_icons(node) - if self.is_git_sign and git_icons_tbl and #git_icons_tbl > 0 then - local git_info = git_icons_tbl[1] - table.insert(self.signs, { sign = git_info.hl, lnum = self.index + 1 }) - git_icons_tbl = {} + if git_icons_tbl and #git_icons_tbl > 0 then + if self.is_git_sign then + local git_info = git_icons_tbl[1] + table.insert(self.signs, { sign = git_info.hl, lnum = self.index + 1 }) + git_icons_tbl = {} + else + -- sort icons so it looks slightly better + table.sort(git_icons_tbl, function(a, b) + return a.ord < b.ord + end) + end end local is_folder = node.nodes ~= nil diff --git a/lua/nvim-tree/renderer/components/git.lua b/lua/nvim-tree/renderer/components/git.lua index fae22803f30..1c51eb1d3ea 100644 --- a/lua/nvim-tree/renderer/components/git.lua +++ b/lua/nvim-tree/renderer/components/git.lua @@ -1,68 +1,51 @@ local notify = require "nvim-tree.notify" -local explorer_common = require "nvim-tree.explorer.common" +local explorer_node = require "nvim-tree.explorer.node" local M = { SIGN_GROUP = "NvimTreeGitSigns", } local function build_icons_table(i) + local icons = { + staged = { icon = i.staged, hl = "NvimTreeGitStaged", ord = 1 }, + unstaged = { icon = i.unstaged, hl = "NvimTreeGitDirty", ord = 2 }, + renamed = { icon = i.renamed, hl = "NvimTreeGitRenamed", ord = 3 }, + deleted = { icon = i.deleted, hl = "NvimTreeGitDeleted", ord = 4 }, + unmerged = { icon = i.unmerged, hl = "NvimTreeGitMerge", ord = 5 }, + untracked = { icon = i.untracked, hl = "NvimTreeGitNew", ord = 6 }, + ignored = { icon = i.ignored, hl = "NvimTreeGitIgnored", ord = 7 }, + } return { - ["M "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } }, - [" M"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } }, - ["C "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } }, - [" C"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } }, - ["CM"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } }, - [" T"] = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } }, - ["T "] = { { icon = i.staged, hl = "NvimTreeGitStaged" } }, - ["MM"] = { - { icon = i.staged, hl = "NvimTreeGitStaged" }, - { icon = i.unstaged, hl = "NvimTreeGitDirty" }, - }, - ["MD"] = { - { icon = i.staged, hl = "NvimTreeGitStaged" }, - }, - ["A "] = { - { icon = i.staged, hl = "NvimTreeGitStaged" }, - }, - ["AD"] = { - { icon = i.staged, hl = "NvimTreeGitStaged" }, - }, - [" A"] = { - { icon = i.untracked, hl = "NvimTreeGitNew" }, - }, + ["M "] = { icons.staged }, + [" M"] = { icons.unstaged }, + ["C "] = { icons.staged }, + [" C"] = { icons.unstaged }, + ["CM"] = { icons.unstaged }, + [" T"] = { icons.unstaged }, + ["T "] = { icons.staged }, + ["MM"] = { icons.staged, icons.unstaged }, + ["MD"] = { icons.staged }, + ["A "] = { icons.staged }, + ["AD"] = { icons.staged }, + [" A"] = { icons.untracked }, -- not sure about this one - ["AA"] = { - { icon = i.unmerged, hl = "NvimTreeGitMerge" }, - { icon = i.untracked, hl = "NvimTreeGitNew" }, - }, - ["AU"] = { - { icon = i.unmerged, hl = "NvimTreeGitMerge" }, - { icon = i.untracked, hl = "NvimTreeGitNew" }, - }, - ["AM"] = { - { icon = i.staged, hl = "NvimTreeGitStaged" }, - { icon = i.unstaged, hl = "NvimTreeGitDirty" }, - }, - ["??"] = { { icon = i.untracked, hl = "NvimTreeGitNew" } }, - ["R "] = { { icon = i.renamed, hl = "NvimTreeGitRenamed" } }, - [" R"] = { { icon = i.renamed, hl = "NvimTreeGitRenamed" } }, - ["RM"] = { - { icon = i.unstaged, hl = "NvimTreeGitDirty" }, - { icon = i.renamed, hl = "NvimTreeGitRenamed" }, - }, - ["UU"] = { { icon = i.unmerged, hl = "NvimTreeGitMerge" } }, - ["UD"] = { { icon = i.unmerged, hl = "NvimTreeGitMerge" } }, - ["UA"] = { { icon = i.unmerged, hl = "NvimTreeGitMerge" } }, - [" D"] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } }, - ["D "] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } }, - ["RD"] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } }, - ["DD"] = { { icon = i.deleted, hl = "NvimTreeGitDeleted" } }, - ["DU"] = { - { icon = i.deleted, hl = "NvimTreeGitDeleted" }, - { icon = i.unmerged, hl = "NvimTreeGitMerge" }, - }, - ["!!"] = { { icon = i.ignored, hl = "NvimTreeGitIgnored" } }, - dirty = { { icon = i.unstaged, hl = "NvimTreeGitDirty" } }, + ["AA"] = { icons.unmerged, icons.untracked }, + ["AU"] = { icons.unmerged, icons.untracked }, + ["AM"] = { icons.staged, icons.unstaged }, + ["??"] = { icons.untracked }, + ["R "] = { icons.renamed }, + [" R"] = { icons.renamed }, + ["RM"] = { icons.unstaged, icons.renamed }, + ["UU"] = { icons.unmerged }, + ["UD"] = { icons.unmerged }, + ["UA"] = { icons.unmerged }, + [" D"] = { icons.deleted }, + ["D "] = { icons.deleted }, + ["RD"] = { icons.deleted }, + ["DD"] = { icons.deleted }, + ["DU"] = { icons.deleted, icons.unmerged }, + ["!!"] = { icons.ignored }, + dirty = { icons.unstaged }, } end @@ -77,20 +60,32 @@ local function warn_status(git_status) end local function get_icons_(node) - if not explorer_common.shows_git_status(node) then + local git_status = explorer_node.get_git_status(node) + if git_status == nil then return nil end - local git_status = node.git_status - local icons = M.git_icons[git_status] - if not icons then - if not M.config.highlight_git then - warn_status(git_status) + local inserted = {} + local iconss = {} + + for _, s in pairs(git_status) do + local icons = M.git_icons[s] + if not icons then + if not M.config.highlight_git then + warn_status(s) + end + return nil + end + + for _, icon in pairs(icons) do + if not inserted[icon] then + table.insert(iconss, icon) + inserted[icon] = true + end end - return nil end - return icons + return iconss end local git_hl = { @@ -137,12 +132,12 @@ function M.setup_signs(i) end local function get_highlight_(node) - local git_status = node.git_status - if not explorer_common.shows_git_status(node) then + local git_status = explorer_node.get_git_status(node) + if git_status == nil then return end - return git_hl[git_status] + return git_hl[git_status[1]] end function M.setup(opts)