Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(#3119): Expose filter functions to the api#3121

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
gracepetryk wants to merge3 commits intonvim-tree:master
base:master
Choose a base branch
Loading
fromgracepetryk:filter-api
Draft
Show file tree
Hide file tree
Changes fromall 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
4 changes: 2 additions & 2 deletionslua/nvim-tree/actions/finders/search-node.lua
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,7 @@ local function search(search_dir, input_path)
local function iter(dir)
local realpath, path, name, stat, handle, _

local filter_status =explorer.filters:prepare()
explorer.filters:prepare()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Great clean up! All the state is in filters now.


handle, _ = vim.loop.fs_scandir(dir)
if not handle then
Expand All@@ -46,7 +46,7 @@ local function search(search_dir, input_path)
break
end

if not explorer.filters:should_filter(path, stat, filter_status) then
if not explorer.filters:should_filter(path) then
if string.find(path, "/" .. input_path .. "$") then
return path
end
Expand Down
20 changes: 20 additions & 0 deletionslua/nvim-tree/api.lua
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,7 @@ local Api = {
commands = {},
diagnostics = {},
decorator = {},
filters = {},
}

---Print error when setup not called.
Expand DownExpand Up@@ -108,6 +109,17 @@ local function wrap_explorer_member_args(explorer_member, member_method, ...)
end)
end

---@param filter_api_method string
---@return fun(path: string): boolean
local function wrap_explorer_filter_function(filter_api_method)
return wrap(function(path)
local explorer = core.get_explorer()
if explorer then
return explorer.filters.api[filter_api_method](explorer.filters, path)
end
end)
end

---Invoke a member's method on the singleton explorer.
---Print error when setup not called.
---@param explorer_member string explorer member name
Expand DownExpand Up@@ -356,4 +368,12 @@ end)
---@type nvim_tree.api.decorator.UserDecorator
Api.decorator.UserDecorator = UserDecorator --[[@as nvim_tree.api.decorator.UserDecorator]]

Api.filters.custom = wrap_explorer_filter_function("custom")
Api.filters.dotfile = wrap_explorer_filter_function("dotfile")
Api.filters.git_ignored = wrap_explorer_filter_function("git_ignored")
Api.filters.git_clean = wrap_explorer_filter_function("git_clean")
Api.filters.no_buffer = wrap_explorer_filter_function("no_buffer")
Api.filters.no_bookmark = wrap_explorer_filter_function("no_bookmark")
Api.filters.filter_reason = wrap_explorer_filter_function("filter_reason")

return Api
3 changes: 2 additions & 1 deletionlua/nvim-tree/enum.lua
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,11 +4,12 @@ local M = {}
---@enum FILTER_REASON
M.FILTER_REASON = {
none = 0, -- It's not filtered
git = 1,
git_clean = 1,
buf = 2,
dotfile = 4,
custom = 8,
bookmark = 16,
git_ignore = 32
}

return M
176 changes: 125 additions & 51 deletionslua/nvim-tree/explorer/filters.lua
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,14 +4,23 @@ local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
local Class = require("nvim-tree.classic")

---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark"
---@alias GitFilterType "git_clean" | "git_ignored"

---@class FilterStatus
---@field project GitProject | nil
---@field bufinfo table
---@field bookmarks table

---@class (exact) Filters: Class
---@field enabled boolean
---@field state table<FilterType, boolean>
---@field api table<string, fun(self: Filters, path: string): boolean|nil>
---@field private explorer Explorer
---@field private exclude_list string[] filters.exclude
---@field private ignore_list table<string, boolean> filters.custom string table
---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function
---@field protected status FilterStatus
---@field private filter_cache table<string, FILTER_REASON>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
---@fieldprivatefilter_cache table<string,FILTER_REASON>
---@fieldprivatefilter_cache table<string,FILTER_REASON>
---@fieldprivatecustom fun(self:Filters,path:string):boolean
---@fieldprivatedotfile fun(self:Filters,path:string):boolean
---@fieldprivategit_ignored fun(self:Filters,path:string):boolean
---@fieldprivategit_clean fun(self:Filters,path:string):boolean
---@fieldprivatebuf fun(self:Filters,path:string):boolean
---@fieldprivatebookmark fun(self:Filters,path:string):boolean

This is annoying, but it will keep the language server from complaining about inject-field

You could even wrap them in the constructor, although I'm not sure how clean that would look.

Copy link
Member

@alex-courtisalex-courtisMay 16, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
---@fieldprivatefilter_cache table<string,FILTER_REASON>
---@fieldprivatefilter_cache table<FILTER_REASON,table<string,boolean>>

local Filters = Class:extend()

---@class Filters
Expand All@@ -38,6 +47,8 @@ function Filters:new(args)
no_bookmark = self.explorer.opts.filters.no_bookmark,
}

self.filter_cache = {}

local custom_filter = self.explorer.opts.filters.custom
if type(custom_filter) == "function" then
self.custom_function = custom_filter
Expand All@@ -50,6 +61,35 @@ function Filters:new(args)
end
end

--- Cache filter function results so subsequent calls to the same path in a loop
--- iteration are as fast as possible
---@private
---@param fn fun(self: Filters, path: string): boolean
---@param reason FILTER_REASON
---@return fun(self: Filters, path: string): boolean
local function cache_wrapper(fn, reason)

---@param self Filters
---@param path string
---@return FILTER_REASON
---@diagnostic disable:invisible
Copy link
Member

@alex-courtisalex-courtisMay 16, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've had no success in resolving it. I'll keep at it...

Edit: we can change it to a method so that we may "legally" reference self. This keeps the language server happy:

--- Cache filter function results so subsequent calls to the same path in a loop--- iteration are as fast as possible---@private---@paramfnfun(self:Filters,path:string):boolean---@paramreasonFILTER_REASON---@returnfun(self:Filters,path:string):booleanfunctionFilters:cache_wrapper(fn,reason)---@parampathstring---@returnbooleanlocalfunctioninner(_,path)ifself.filter_cache[reason]==nilthenself.filter_cache[reason]= {}endifself.filter_cache[reason][path]==nilthenself.filter_cache[reason][path]=fn(self,path)endreturnself.filter_cache[reason][path]endreturninnerend

We'll need to move the assignments into the constructor:

self.bookmark=self:cache_wrapper(Filters.bookmark,FILTER_REASON.bookmark)self.custom=self:cache_wrapper(Filters.custom,FILTER_REASON.custom)self.dotfile=self:cache_wrapper(Filters.dotfile,FILTER_REASON.dotfile)self.git_ignored=self:cache_wrapper(Filters.git_ignored,FILTER_REASON.git_ignore)self.git_clean=self:cache_wrapper(Filters.git_clean,FILTER_REASON.git_clean)self.buf=self:cache_wrapper(Filters.buf,FILTER_REASON.buf)self.bookmark=self:cache_wrapper(Filters.bookmark,FILTER_REASON.bookmark)

local function inner(self, path)

if self.filter_cache[reason] == nil then
self.filter_cache[reason] = {}
end

if self.filter_cache[reason][path] == nil then
self.filter_cache[reason][path] = fn(self, path)
end

return self.filter_cache[reason][path]
end
---@diagnostic enable:invisible

return inner
end

---@private
---@param path string
---@return boolean
Expand All@@ -64,10 +104,11 @@ end

---Check if the given path is git clean/ignored
---@private
---@param filter_type GitFilterType
---@param path string Absolute path
---@param project GitProject from prepare
---@return boolean
function Filters:git(path, project)
function Filters:git(filter_type,path, project)
if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then
return false
end
Expand All@@ -77,31 +118,44 @@ function Filters:git(path, project)
xy = xy or project.dirs.direct[path] and project.dirs.direct[path][1]
xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1]

-- filter ignored; overrides clean as they are effectively dirty
if self.state.git_ignored and xy == "!!" then
if filter_type == "git_ignored" and xy == "!!" then
return true
end

-- filter clean
if self.state.git_clean and not xy then
if filter_type == "git_clean" and not xy then
return true
end

return false
end

---@param path string
---@return boolean
function Filters:git_clean(path)
-- filter ignored; overrides clean as they are effectively dirty
if self.state.git_ignored and self:git_ignored("path") then
return true
else
return self:git("git_clean", path, self.status.project)
end
end

---@param path string
---@return boolean
function Filters:git_ignored(path)
return self:git("git_ignored", path, self.status.project)
end

---Check if the given path has no listed buffer
---@private
---@param path string Absolute path
---@param bufinfo table vim.fn.getbufinfo { buflisted = 1 }
---@return boolean
function Filters:buf(path, bufinfo)
ifnotself.state.no_buffer or type(bufinfo) ~= "table" then
function Filters:buf(path)
iftype(self.status.bufinfo) ~= "table" then
return false
end

-- filter files with no open buffer and directories containing no open buffers
for _, b in ipairs(bufinfo) do
for _, b in ipairs(self.status.bufinfo) do
if b.name == path or b.name:find(path .. "/", 1, true) then
return false
end
Expand All@@ -110,28 +164,26 @@ function Filters:buf(path, bufinfo)
return true
end

---@private
---@param path string
---@return boolean
function Filters:dotfile(path)
returnself.state.dotfiles andutils.path_basename(path):sub(1, 1) == "."
return utils.path_basename(path):sub(1, 1) == "."
end

---Bookmark is present
---@private
---@param path string
---@param path_type string|nil filetype of path
---@param bookmarks table<string, string|nil> path, filetype table of bookmarked files
---@return boolean
function Filters:bookmark(path, path_type, bookmarks)
if not self.state.no_bookmark then
return false
end
function Filters:bookmark(path)
local bookmarks = self.status.bookmarks

-- if bookmark is empty, we should see a empty filetree
if next(bookmarks) == nil then
return true
end

local stat, _ = vim.loop.fs_stat(path)
local path_type = stat and stat.type

local mark_parent = utils.path_add_trailing(path)
for mark, mark_type in pairs(bookmarks) do
if path == mark then
Expand All@@ -156,21 +208,16 @@ function Filters:bookmark(path, path_type, bookmarks)
return true
end

---@private
---@param path string
---@return boolean
function Filters:custom(path)
if not self.state.custom then
return false
-- filter user's custom function
if type(self.custom_function) == "function" then
return self.custom_function(path)
end

local basename = utils.path_basename(path)

-- filter user's custom function
if self.custom_function and self.custom_function(path) then
return true
end

-- filter custom regexes
local relpath = utils.path_relative(path, vim.loop.cwd())
for pat, _ in pairs(self.ignore_list) do
Expand All@@ -196,32 +243,30 @@ end
--- bufinfo: empty unless no_buffer set: vim.fn.getbufinfo { buflisted = 1 }
--- bookmarks: absolute paths to boolean
function Filters:prepare(project)
localstatus = {
self.status = {
project = project or {},
bufinfo = {},
bookmarks = {},
}

ifself.state.no_buffer then
status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })
end
self.filter_cache = {}

self.status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })

local explorer = require("nvim-tree.core").get_explorer()
if explorer then
for _, node in pairs(explorer.marks:list()) do
status.bookmarks[node.absolute_path] = node.type
self.status.bookmarks[node.absolute_path] = node.type
end
end

return status
returnself.status
end

---Check if the given path should be filtered.
---@param path string Absolute path
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
---@param status table from prepare
---@return boolean
function Filters:should_filter(path, fs_stat, status)
function Filters:should_filter(path)
if not self.enabled then
return false
end
Expand All@@ -231,19 +276,18 @@ function Filters:should_filter(path, fs_stat, status)
return false
end

return self:git(path, status.project)
or self:buf(path, status.bufinfo)
or self:dotfile(path)
or self:custom(path)
or self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks)
return (self.state.custom and self:custom(path))
or (self.state.git_clean and self:git_clean(path))
or (self.state.git_ignored and self:git_ignored(path))
or (self.state.no_buffer and self:buf(path))
or (self.state.dotfiles and self:dotfile(path))
or (self.state.no_bookmark and self:bookmark(path))
end

--- Check if the given path should be filtered, and provide the reason why it was
---@param path string Absolute path
---@param fs_stat uv.fs_stat.result|nil fs_stat of file
---@param status table from prepare
---@return FILTER_REASON
function Filters:should_filter_as_reason(path, fs_stat, status)
function Filters:should_filter_as_reason(path)
if not self.enabled then
return FILTER_REASON.none
end
Expand All@@ -252,16 +296,28 @@ function Filters:should_filter_as_reason(path, fs_stat, status)
return FILTER_REASON.none
end

if self:git(path, status.project) then
return FILTER_REASON.git
elseif self:buf(path, status.bufinfo) then
if not self:should_filter(path) then
return FILTER_REASON.none
end

if self.state.custom and self:custom(path) then
return FILTER_REASON.custom

elseif self.state.git_clean and self:git_clean(path) then
return FILTER_REASON.git_clean

elseif self.state.git_ignored and self:git_ignored(path) then
return FILTER_REASON.git_ignore

elseif self.state.no_buffer and self:buf(path) then
return FILTER_REASON.buf
elseif self:dotfile(path) then

elseif self.state.dotfiles and self:dotfile(path) then
return FILTER_REASON.dotfile
elseif self:custom(path) then
return FILTER_REASON.custom
elseif self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) then

elseif self.state.no_bookmark and self:bookmark(path) then
return FILTER_REASON.bookmark

else
return FILTER_REASON.none
end
Expand All@@ -284,4 +340,22 @@ function Filters:toggle(type)
end
end

---@diagnostic disable:inject-field
Filters.custom = cache_wrapper(Filters.custom, FILTER_REASON.custom)
Filters.dotfile = cache_wrapper(Filters.dotfile, FILTER_REASON.dotfile)
Filters.git_ignored = cache_wrapper(Filters.git_ignored, FILTER_REASON.git_ignore)
Filters.git_clean = cache_wrapper(Filters.git_clean, FILTER_REASON.git_clean)
Filters.buf = cache_wrapper(Filters.buf, FILTER_REASON.buf)
Filters.bookmark = cache_wrapper(Filters.bookmark, FILTER_REASON.bookmark)
---@diagnostic enable:inject-field

Filters.api = {
custom = Filters.custom,
dotfile = Filters.dotfile,
git_ignored = Filters.git_ignored,
git_clean = Filters.git_clean,
no_buffer = Filters.buf,
no_bookmark = Filters.bookmark,
}

return Filters
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp