Skip to content

Commit 72485bf

Browse files
authored
feat(filesystem): support .neotreeignore, .ignore, etc. (#1868)
1 parent e345ad8 commit 72485bf

File tree

12 files changed

+884
-50
lines changed

12 files changed

+884
-50
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,13 @@ require("neo-tree").setup({
462462
visible = false, -- when true, they will just be displayed differently than normal items
463463
hide_dotfiles = true,
464464
hide_gitignored = true,
465+
hide_ignored = true, -- hide files that are ignored by other gitignore-like files
466+
-- other gitignore-like files, in descending order of precedence.
467+
ignore_files = {
468+
".neotreeignore",
469+
-- ".ignore"
470+
-- ".rgignore"
471+
},
465472
hide_hidden = true, -- only works on Windows for hidden files/directories
466473
hide_by_name = {
467474
--"node_modules"

lua/neo-tree/defaults.lua

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ local config = {
279279
text_format = " ➛ %s", -- %s will be replaced with the symlink target's path.
280280
},
281281
},
282+
-- The renderer section provides the renderers that will be used to render the tree.
283+
-- The first level is the node type.
284+
-- For each node type, you can specify a list of components to render.
285+
-- Components are rendered in the order they are specified.
286+
-- The first field in each component is the name of the function to call.
287+
-- The rest of the fields are passed to the function as the "config" argument.
282288
renderers = {
283289
directory = {
284290
{ "indent" },
@@ -503,22 +509,24 @@ local config = {
503509
sidebar = "tab", -- sidebar is when position = left or right
504510
current = "window" -- current is when position = current
505511
},
506-
check_gitignore_in_search = true, -- check gitignore status for files/directories when searching
507-
-- setting this to false will speed up searches, but gitignored
508-
-- items won't be marked if they are visible.
509-
-- The renderer section provides the renderers that will be used to render the tree.
510-
-- The first level is the node type.
511-
-- For each node type, you can specify a list of components to render.
512-
-- Components are rendered in the order they are specified.
513-
-- The first field in each component is the name of the function to call.
514-
-- The rest of the fields are passed to the function as the "config" argument.
512+
-- check gitignore status for files/directories when searching.
513+
-- setting this to false will speed up searches, but gitignored
514+
-- items won't be marked if they are visible.
515+
check_gitignore_in_search = true,
515516
filtered_items = {
516517
visible = false, -- when true, they will just be displayed differently than normal items
517518
force_visible_in_empty_folder = false, -- when true, hidden files will be shown if the root folder is otherwise empty
518519
children_inherit_highlights = true, -- whether children of filtered parents should inherit their parent's highlight group
519520
show_hidden_count = true, -- when true, the number of hidden items in each folder will be shown as the last entry
520521
hide_dotfiles = true,
521522
hide_gitignored = true,
523+
hide_ignored = true, -- hide files that are ignored by other gitignore-like files
524+
-- other gitignore-like files, in descending order of precedence.
525+
ignore_files = {
526+
".neotreeignore",
527+
".ignore"
528+
-- ".rgignore"
529+
},
522530
hide_hidden = true, -- only works on Windows for hidden files/directories
523531
hide_by_name = {
524532
".DS_Store",

lua/neo-tree/git/ignored.lua

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ end
4646

4747
---@param state neotree.State
4848
---@param items neotree.FileItem[]
49+
---@param callback fun(results: string[])
50+
---@overload fun(state: neotree.State, items: neotree.FileItem[]):string[]
4951
M.mark_ignored = function(state, items, callback)
5052
local folders = {}
5153
log.trace("================================================================================")
@@ -54,50 +56,50 @@ M.mark_ignored = function(state, items, callback)
5456
for _, item in ipairs(items) do
5557
local folder = utils.split_path(item.path)
5658
if folder then
57-
if not folders[folder] then
58-
folders[folder] = {}
59-
end
59+
folders[folder] = folders[folder] or {}
6060
table.insert(folders[folder], item.path)
6161
end
6262
end
6363

64-
local function process_result(result)
64+
---@param results string[]
65+
local function process_results(results)
6566
if utils.is_windows then
6667
--on Windows, git seems to return quotes and double backslash "path\\directory"
67-
result = vim.tbl_map(function(item)
68+
---@param item string
69+
results = vim.tbl_map(function(item)
6870
item = item:gsub("\\\\", "\\")
6971
return item
70-
end, result)
72+
end, results)
7173
else
7274
--check-ignore does not indicate directories the same as 'status' so we need to
7375
--add the trailing slash to the path manually if not on Windows.
74-
log.trace("IGNORED: Checking types of", #result, "items to see which ones are directories")
75-
for i, item in ipairs(result) do
76+
log.trace("IGNORED: Checking types of", #results, "items to see which ones are directories")
77+
for i, item in ipairs(results) do
7678
local stat = uv.fs_stat(item)
7779
if stat and stat.type == "directory" then
78-
result[i] = item .. sep
80+
results[i] = item .. sep
7981
end
8082
end
8183
end
82-
result = vim.tbl_map(function(item)
84+
---@param item string
85+
results = vim.tbl_map(function(item)
86+
item = item:gsub("\\\\", "\\")
8387
-- remove leading and trailing " from git output
8488
item = item:gsub('^"', ""):gsub('"$', "")
8589
-- convert octal encoded lines to utf-8
8690
item = git_utils.octal_to_utf8(item)
8791
return item
88-
end, result)
89-
return result
92+
end, results)
93+
return results
9094
end
9195

96+
---@param all_results string[]
9297
local function finalize(all_results)
93-
local show_gitignored = state.filtered_items and state.filtered_items.hide_gitignored == false
94-
log.trace("IGNORED: Comparing results to mark items as ignored:", show_gitignored)
9598
local ignored, not_ignored = 0, 0
9699
for _, item in ipairs(items) do
97100
if M.is_ignored(all_results, item.path, item.type) then
98101
item.filtered_by = item.filtered_by or {}
99102
item.filtered_by.gitignored = true
100-
item.filtered_by.show_gitignored = show_gitignored
101103
ignored = ignored + 1
102104
else
103105
not_ignored = not_ignored + 1
@@ -107,6 +109,7 @@ M.mark_ignored = function(state, items, callback)
107109
log.trace("================================================================================")
108110
end
109111

112+
---@type string[]
110113
local all_results = {}
111114
if type(callback) == "function" then
112115
local jobs = {}
@@ -145,14 +148,14 @@ M.mark_ignored = function(state, items, callback)
145148
log.trace("IGNORED: Running async git with args: ", args)
146149
end,
147150
on_exit = function(self, code, _)
148-
local result
151+
local results
149152
if code ~= 0 then
150153
log.debug("Failed to load ignored files for", folder, ":", self:stderr_result())
151-
result = {}
154+
results = {}
152155
else
153-
result = self:result()
156+
results = self:result()
154157
end
155-
vim.list_extend(all_results, process_result(result))
158+
vim.list_extend(all_results, process_results(results))
156159

157160
running_jobs = running_jobs - 1
158161
completed_jobs = completed_jobs + 1
@@ -173,7 +176,7 @@ M.mark_ignored = function(state, items, callback)
173176
log.debug("Failed to load ignored files for", state.path, ":", result)
174177
result = {}
175178
end
176-
vim.list_extend(all_results, process_result(result))
179+
vim.list_extend(all_results, process_results(result))
177180
end
178181
finalize(all_results)
179182
return all_results

lua/neo-tree/health/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ function M.check_config(config)
234234
validate("show_hidden_count", f.show_hidden_count, "boolean")
235235
validate("hide_dotfiles", f.hide_dotfiles, "boolean")
236236
validate("hide_gitignored", f.hide_gitignored, "boolean")
237+
validate("hide_ignored", f.hide_ignored, "boolean")
237238
validate("hide_hidden", f.hide_hidden, "boolean")
238239
validate("hide_by_name", f.hide_by_name, v.array("string"))
239240
validate("hide_by_pattern", f.hide_by_pattern, v.array("string"))

lua/neo-tree/sources/common/components.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ end
337337

338338
---@class neotree.Component.Common.FilteredBy
339339
---@field [1] "filtered_by"?
340+
---@param node neotree.FileNode
340341
M.filtered_by = function(_, node, state)
341342
local fby = node.filtered_by
342343
if not state.filtered_items or type(fby) ~= "table" then
@@ -363,6 +364,11 @@ M.filtered_by = function(_, node, state)
363364
text = "(dotfile)",
364365
highlight = highlights.DOTFILE,
365366
}
367+
elseif fby.ignored then
368+
return {
369+
text = ("(ignored by )"):format(fby.ignore_file),
370+
highlight = highlights.IGNORED,
371+
}
366372
elseif fby.hidden then
367373
return {
368374
text = "(hidden)",

lua/neo-tree/sources/common/file-items.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,11 @@ local create_item, set_parents
115115
---@field dotfiles boolean?
116116
---@field hidden boolean?
117117
---@field gitignored boolean?
118+
---@field ignore_file string?
119+
---@field ignored boolean?
118120
---@field parent neotree.FileItemFilters?
119121
---@field show_gitignored boolean?
122+
---@field show_ignored boolean?
120123

121124
---@class (exact) neotree.FileItemExtra
122125
---@field status string? Git status
@@ -318,7 +321,7 @@ end
318321
---@field folders table<string, neotree.FileItem.Directory|neotree.FileItem.Link?>
319322
---@field nesting neotree.FileItem[]
320323
---@field item_exists table<string, boolean?>
321-
---@field all_items table<string, neotree.FileItem?>
324+
---@field all_items neotree.FileItem[]?
322325
---@field path_to_reveal string?
323326

324327
---Create context to be used in other file-items functions.

lua/neo-tree/sources/filesystem/init.lua

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ end
292292
---@field show_hidden_count boolean?
293293
---@field hide_dotfiles boolean?
294294
---@field hide_gitignored boolean?
295+
---@field hide_ignored boolean?
296+
---@field ignore_files string[]?
295297
---@field hide_hidden boolean?
296298
---@field hide_by_name string[]?
297299
---@field hide_by_pattern string[]?
@@ -351,19 +353,22 @@ M.setup = function(config, global_config)
351353
config.filtered_items = config.filtered_items or {}
352354
config.enable_git_status = config.enable_git_status or global_config.enable_git_status
353355

354-
for _, key in ipairs({ "hide_by_pattern", "always_show_by_pattern", "never_show_by_pattern" }) do
355-
local list = config.filtered_items[key]
356-
if type(list) == "table" then
356+
local filtered = config.filtered_items
357+
if filtered then
358+
for _, list in ipairs({
359+
filtered.hide_by_pattern or {},
360+
filtered.always_show_by_pattern or {},
361+
filtered.never_show_by_pattern or {},
362+
}) do
357363
for i, pattern in ipairs(list) do
358364
list[i] = glob.globtopattern(pattern)
359365
end
360366
end
361-
end
362-
363-
for _, key in ipairs({ "hide_by_name", "always_show", "never_show" }) do
364-
local list = config.filtered_items[key]
365-
if type(list) == "table" then
366-
config.filtered_items[key] = utils.list_to_dict(list)
367+
for _, key in ipairs({ "hide_by_name", "always_show", "never_show" }) do
368+
local list = filtered[key]
369+
if type(list) == "table" then
370+
filtered[key] = utils.list_to_dict(list)
371+
end
367372
end
368373
end
369374

lua/neo-tree/sources/filesystem/lib/fs_scan.lua

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch")
1111
local git = require("neo-tree.git")
1212
local events = require("neo-tree.events")
1313
local async = require("plenary.async")
14+
local ignored = require("neo-tree.sources.filesystem.lib.ignored")
1415

1516
local M = {}
1617

@@ -117,7 +118,7 @@ end
117118
local should_check_gitignore = function(context)
118119
local state = context.state
119120
if #context.all_items == 0 then
120-
log.info("No items, skipping git ignored/status lookups")
121+
log.debug("No items, skipping git ignored/status lookups")
121122
return false
122123
end
123124
if state.search_pattern and state.check_gitignore_in_search == false then
@@ -132,6 +133,7 @@ local should_check_gitignore = function(context)
132133
return true
133134
end
134135

136+
---@param context neotree.sources.filesystem.Context
135137
local job_complete_async = function(context)
136138
local state = context.state
137139
local parent_id = context.parent_id
@@ -144,6 +146,8 @@ local job_complete_async = function(context)
144146
-- end
145147
if should_check_gitignore(context) then
146148
local mark_ignored_async = async.wrap(function(_state, _all_items, _callback)
149+
---lua-ls can't narrow this properly
150+
---@diagnostic disable-next-line: redundant-parameter
147151
git.mark_ignored(_state, _all_items, _callback)
148152
end, 3)
149153
local all_items = mark_ignored_async(state, context.all_items)
@@ -154,17 +158,23 @@ local job_complete_async = function(context)
154158
state.git_ignored = all_items
155159
end
156160
end
161+
162+
ignored.mark_ignored(state, context.all_items)
157163
return context
158164
end
159165

166+
---@param context neotree.sources.filesystem.Context
160167
local job_complete = function(context)
161168
local state = context.state
162169
local parent_id = context.parent_id
163170

164171
file_nesting.nest_items(context)
165172

173+
ignored.mark_ignored(state, context.all_items)
166174
if should_check_gitignore(context) then
167175
if require("neo-tree").config.git_status_async then
176+
---lua-ls can't narrow this properly
177+
---@diagnostic disable-next-line: redundant-parameter
168178
git.mark_ignored(state, context.all_items, function(all_items)
169179
if parent_id then
170180
vim.list_extend(state.git_ignored, all_items)
@@ -352,14 +362,14 @@ local function async_scan(context, path)
352362

353363
-- from https://github.com/nvim-lua/plenary.nvim/blob/master/lua/plenary/scandir.lua
354364
local function read_dir(current_dir, ctx)
355-
uv.fs_opendir(current_dir, function(err, dir)
356-
if err then
357-
log.error(current_dir, ": ", err)
365+
uv.fs_opendir(current_dir, function(openerr, dir)
366+
if openerr then
367+
log.error(current_dir, ": ", openerr)
358368
return
359369
end
360-
local function on_fs_readdir(err, entries)
361-
if err then
362-
log.error(current_dir, ": ", err)
370+
local function on_fs_readdir(readerr, entries)
371+
if readerr then
372+
log.error(current_dir, ": ", readerr)
363373
return
364374
end
365375
if entries then
@@ -605,6 +615,7 @@ end
605615
---@field is_a_never_show_file fun(filename: string?):boolean
606616

607617
---@class neotree.sources.filesystem.State : neotree.StateWithTree, neotree.Config.Filesystem
618+
---@field git_ignored neotree.FileItem[]
608619
---@field path string
609620

610621
---@param state neotree.sources.filesystem.State

0 commit comments

Comments
 (0)