chore: migrate to classic (#2991)
* add classic, migrating nodes classes
* add mixins to classic
* typechecked optargs constructors for nodes
* typechecked optargs constructors for watcher and event
* luacheck
* typechecked optargs constructors for GitRunner
* typechecked optargs constructors for Sorter
* typechecked optargs constructors for decorators, WIP
* typechecked optargs constructors for decorators, WIP
* typechecked optargs constructors for decorators
* remove class
* replace enums with named maps
* Renderer and Builder use classic, tidy opts
* LiveFilter uses classic, tidy opts
* Filter uses classic, tidy opts
* add FilterTypes named map
* move toggles into filters
* Marks uses classic, tidy opts
* Sorter uses classic, tidy opts
* Clipboard uses classic, tidy opts
* use supers for node methods
* HighlightDisplay uses classic
* protected :new
* Watcher tidy
* Revert "use supers for node methods"
This reverts commit 9fc7a866ec.
* Watcher tidy
* format
* format
* Filters private methods
* format
* Sorter type safety
* Sorter type safety
* Sorter type safety
* Sorter type safety
* Sorter type safety
* Sorter type safety
* tidy Runner
* tidy hi-test name
This commit is contained in:
committed by
GitHub
parent
610a1c189b
commit
3fc8de198c
@@ -1,52 +1,59 @@
|
||||
local utils = require("nvim-tree.utils")
|
||||
local FILTER_REASON = require("nvim-tree.enum").FILTER_REASON
|
||||
|
||||
---@class Filters to handle all opts.filters and related API
|
||||
---@field config table hydrated user opts.filters
|
||||
local Class = require("nvim-tree.classic")
|
||||
|
||||
---@alias FilterType "custom" | "dotfiles" | "git_ignored" | "git_clean" | "no_buffer" | "no_bookmark"
|
||||
|
||||
---@class (exact) Filters: Class
|
||||
---@field enabled boolean
|
||||
---@field state table<FilterType, boolean>
|
||||
---@field private explorer Explorer
|
||||
---@field private exclude_list string[] filters.exclude
|
||||
---@field private ignore_list string[] filters.custom string table
|
||||
---@field private ignore_list table<string, boolean> filters.custom string table
|
||||
---@field private custom_function (fun(absolute_path: string): boolean)|nil filters.custom function
|
||||
local Filters = {}
|
||||
local Filters = Class:extend()
|
||||
|
||||
---@param opts table user options
|
||||
---@param explorer Explorer
|
||||
---@return Filters
|
||||
function Filters:new(opts, explorer)
|
||||
local o = {
|
||||
explorer = explorer,
|
||||
ignore_list = {},
|
||||
exclude_list = opts.filters.exclude,
|
||||
custom_function = nil,
|
||||
config = {
|
||||
enable = opts.filters.enable,
|
||||
filter_custom = true,
|
||||
filter_dotfiles = opts.filters.dotfiles,
|
||||
filter_git_ignored = opts.filters.git_ignored,
|
||||
filter_git_clean = opts.filters.git_clean,
|
||||
filter_no_buffer = opts.filters.no_buffer,
|
||||
filter_no_bookmark = opts.filters.no_bookmark,
|
||||
},
|
||||
---@class Filters
|
||||
---@overload fun(args: FiltersArgs): Filters
|
||||
|
||||
---@class (exact) FiltersArgs
|
||||
---@field explorer Explorer
|
||||
|
||||
---@protected
|
||||
---@param args FiltersArgs
|
||||
function Filters:new(args)
|
||||
self.explorer = args.explorer
|
||||
self.ignore_list = {}
|
||||
self.exclude_list = self.explorer.opts.filters.exclude
|
||||
self.custom_function = nil
|
||||
|
||||
self.enabled = self.explorer.opts.filters.enable
|
||||
self.state = {
|
||||
custom = true,
|
||||
dotfiles = self.explorer.opts.filters.dotfiles,
|
||||
git_ignored = self.explorer.opts.filters.git_ignored,
|
||||
git_clean = self.explorer.opts.filters.git_clean,
|
||||
no_buffer = self.explorer.opts.filters.no_buffer,
|
||||
no_bookmark = self.explorer.opts.filters.no_bookmark,
|
||||
}
|
||||
|
||||
local custom_filter = opts.filters.custom
|
||||
local custom_filter = self.explorer.opts.filters.custom
|
||||
if type(custom_filter) == "function" then
|
||||
o.custom_function = custom_filter
|
||||
self.custom_function = custom_filter
|
||||
else
|
||||
if custom_filter and #custom_filter > 0 then
|
||||
for _, filter_name in pairs(custom_filter) do
|
||||
o.ignore_list[filter_name] = true
|
||||
self.ignore_list[filter_name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param path string
|
||||
---@return boolean
|
||||
local function is_excluded(self, path)
|
||||
function Filters:is_excluded(path)
|
||||
for _, node in ipairs(self.exclude_list) do
|
||||
if path:match(node) then
|
||||
return true
|
||||
@@ -56,10 +63,11 @@ local function is_excluded(self, path)
|
||||
end
|
||||
|
||||
---Check if the given path is git clean/ignored
|
||||
---@private
|
||||
---@param path string Absolute path
|
||||
---@param project GitProject from prepare
|
||||
---@return boolean
|
||||
local function git(self, path, project)
|
||||
function Filters:git(path, project)
|
||||
if type(project) ~= "table" or type(project.files) ~= "table" or type(project.dirs) ~= "table" then
|
||||
return false
|
||||
end
|
||||
@@ -70,12 +78,12 @@ local function git(self, path, project)
|
||||
xy = xy or project.dirs.indirect[path] and project.dirs.indirect[path][1]
|
||||
|
||||
-- filter ignored; overrides clean as they are effectively dirty
|
||||
if self.config.filter_git_ignored and xy == "!!" then
|
||||
if self.state.git_ignored and xy == "!!" then
|
||||
return true
|
||||
end
|
||||
|
||||
-- filter clean
|
||||
if self.config.filter_git_clean and not xy then
|
||||
if self.state.git_clean and not xy then
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -83,11 +91,12 @@ local function git(self, path, 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
|
||||
local function buf(self, path, bufinfo)
|
||||
if not self.config.filter_no_buffer or type(bufinfo) ~= "table" then
|
||||
function Filters:buf(path, bufinfo)
|
||||
if not self.state.no_buffer or type(bufinfo) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -101,19 +110,21 @@ local function buf(self, path, bufinfo)
|
||||
return true
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param path string
|
||||
---@return boolean
|
||||
local function dotfile(self, path)
|
||||
return self.config.filter_dotfiles and utils.path_basename(path):sub(1, 1) == "."
|
||||
function Filters:dotfile(path)
|
||||
return self.state.dotfiles and 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
|
||||
local function bookmark(self, path, path_type, bookmarks)
|
||||
if not self.config.filter_no_bookmark then
|
||||
function Filters:bookmark(path, path_type, bookmarks)
|
||||
if not self.state.no_bookmark then
|
||||
return false
|
||||
end
|
||||
-- if bookmark is empty, we should see a empty filetree
|
||||
@@ -145,10 +156,11 @@ local function bookmark(self, path, path_type, bookmarks)
|
||||
return true
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param path string
|
||||
---@return boolean
|
||||
local function custom(self, path)
|
||||
if not self.config.filter_custom then
|
||||
function Filters:custom(path)
|
||||
if not self.state.custom then
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -190,7 +202,7 @@ function Filters:prepare(project)
|
||||
bookmarks = {},
|
||||
}
|
||||
|
||||
if self.config.filter_no_buffer then
|
||||
if self.state.no_buffer then
|
||||
status.bufinfo = vim.fn.getbufinfo({ buflisted = 1 })
|
||||
end
|
||||
|
||||
@@ -210,20 +222,20 @@ end
|
||||
---@param status table from prepare
|
||||
---@return boolean
|
||||
function Filters:should_filter(path, fs_stat, status)
|
||||
if not self.config.enable then
|
||||
if not self.enabled then
|
||||
return false
|
||||
end
|
||||
|
||||
-- exclusions override all filters
|
||||
if is_excluded(self, path) then
|
||||
if self:is_excluded(path) then
|
||||
return false
|
||||
end
|
||||
|
||||
return git(self, path, status.project)
|
||||
or buf(self, path, status.bufinfo)
|
||||
or dotfile(self, path)
|
||||
or custom(self, path)
|
||||
or bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks)
|
||||
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)
|
||||
end
|
||||
|
||||
--- Check if the given path should be filtered, and provide the reason why it was
|
||||
@@ -232,27 +244,44 @@ end
|
||||
---@param status table from prepare
|
||||
---@return FILTER_REASON
|
||||
function Filters:should_filter_as_reason(path, fs_stat, status)
|
||||
if not self.config.enable then
|
||||
if not self.enabled then
|
||||
return FILTER_REASON.none
|
||||
end
|
||||
|
||||
if is_excluded(self, path) then
|
||||
if self:is_excluded(path) then
|
||||
return FILTER_REASON.none
|
||||
end
|
||||
|
||||
if git(self, path, status.project) then
|
||||
if self:git(path, status.project) then
|
||||
return FILTER_REASON.git
|
||||
elseif buf(self, path, status.bufinfo) then
|
||||
elseif self:buf(path, status.bufinfo) then
|
||||
return FILTER_REASON.buf
|
||||
elseif dotfile(self, path) then
|
||||
elseif self:dotfile(path) then
|
||||
return FILTER_REASON.dotfile
|
||||
elseif custom(self, path) then
|
||||
elseif self:custom(path) then
|
||||
return FILTER_REASON.custom
|
||||
elseif bookmark(self, path, fs_stat and fs_stat.type, status.bookmarks) then
|
||||
elseif self:bookmark(path, fs_stat and fs_stat.type, status.bookmarks) then
|
||||
return FILTER_REASON.bookmark
|
||||
else
|
||||
return FILTER_REASON.none
|
||||
end
|
||||
end
|
||||
|
||||
---Toggle a type and refresh
|
||||
---@private
|
||||
---@param type FilterType? nil to disable all
|
||||
function Filters:toggle(type)
|
||||
if not type or self.state[type] == nil then
|
||||
self.enabled = not self.enabled
|
||||
else
|
||||
self.state[type] = not self.state[type]
|
||||
end
|
||||
|
||||
local node = self.explorer:get_node_at_cursor()
|
||||
self.explorer:reload_explorer()
|
||||
if node then
|
||||
utils.focus_node_or_parent(node)
|
||||
end
|
||||
end
|
||||
|
||||
return Filters
|
||||
|
||||
@@ -3,7 +3,6 @@ local buffers = require("nvim-tree.buffers")
|
||||
local core = require("nvim-tree.core")
|
||||
local git = require("nvim-tree.git")
|
||||
local log = require("nvim-tree.log")
|
||||
local notify = require("nvim-tree.notify")
|
||||
local utils = require("nvim-tree.utils")
|
||||
local view = require("nvim-tree.view")
|
||||
local node_factory = require("nvim-tree.node.factory")
|
||||
@@ -18,7 +17,7 @@ local NodeIterator = require("nvim-tree.iterators.node-iterator")
|
||||
local Filters = require("nvim-tree.explorer.filters")
|
||||
local Marks = require("nvim-tree.marks")
|
||||
local LiveFilter = require("nvim-tree.explorer.live-filter")
|
||||
local Sorters = require("nvim-tree.explorer.sorters")
|
||||
local Sorter = require("nvim-tree.explorer.sorter")
|
||||
local Clipboard = require("nvim-tree.actions.fs.clipboard")
|
||||
local Renderer = require("nvim-tree.renderer")
|
||||
|
||||
@@ -36,51 +35,39 @@ local config
|
||||
---@field sorters Sorter
|
||||
---@field marks Marks
|
||||
---@field clipboard Clipboard
|
||||
local Explorer = RootNode:new()
|
||||
local Explorer = RootNode:extend()
|
||||
|
||||
---Static factory method
|
||||
---@param path string?
|
||||
---@return Explorer?
|
||||
function Explorer:create(path)
|
||||
local err
|
||||
---@class Explorer
|
||||
---@overload fun(args: ExplorerArgs): Explorer
|
||||
|
||||
if path then
|
||||
path, err = vim.loop.fs_realpath(path)
|
||||
else
|
||||
path, err = vim.loop.cwd()
|
||||
end
|
||||
if not path then
|
||||
notify.error(err)
|
||||
return nil
|
||||
end
|
||||
---@class (exact) ExplorerArgs
|
||||
---@field path string
|
||||
|
||||
---@type Explorer
|
||||
local explorer_placeholder = nil
|
||||
---@protected
|
||||
---@param args ExplorerArgs
|
||||
function Explorer:new(args)
|
||||
Explorer.super.new(self, {
|
||||
explorer = self,
|
||||
absolute_path = args.path,
|
||||
name = "..",
|
||||
})
|
||||
|
||||
local o = RootNode:create(explorer_placeholder, path, "..", nil)
|
||||
self.uid_explorer = vim.loop.hrtime()
|
||||
self.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. self.uid_explorer, {})
|
||||
|
||||
o = self:new(o)
|
||||
self.open = true
|
||||
self.opts = config
|
||||
|
||||
o.explorer = o
|
||||
self.sorters = Sorter({ explorer = self })
|
||||
self.renderer = Renderer({ explorer = self })
|
||||
self.filters = Filters({ explorer = self })
|
||||
self.live_filter = LiveFilter({ explorer = self })
|
||||
self.marks = Marks({ explorer = self })
|
||||
self.clipboard = Clipboard({ explorer = self })
|
||||
|
||||
o.uid_explorer = vim.loop.hrtime()
|
||||
o.augroup_id = vim.api.nvim_create_augroup("NvimTree_Explorer_" .. o.uid_explorer, {})
|
||||
self:create_autocmds()
|
||||
|
||||
o.open = true
|
||||
o.opts = config
|
||||
|
||||
o.sorters = Sorters:create(config)
|
||||
o.renderer = Renderer:new(config, o)
|
||||
o.filters = Filters:new(config, o)
|
||||
o.live_filter = LiveFilter:new(config, o)
|
||||
o.marks = Marks:new(config, o)
|
||||
o.clipboard = Clipboard:new(config, o)
|
||||
|
||||
o:create_autocmds()
|
||||
|
||||
o:_load(o)
|
||||
|
||||
return o
|
||||
self:_load(self)
|
||||
end
|
||||
|
||||
function Explorer:destroy()
|
||||
@@ -114,7 +101,7 @@ function Explorer:create_autocmds()
|
||||
vim.api.nvim_create_autocmd("BufReadPost", {
|
||||
group = self.augroup_id,
|
||||
callback = function(data)
|
||||
if (self.filters.config.filter_no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
|
||||
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
|
||||
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
|
||||
self:reload_explorer()
|
||||
end)
|
||||
@@ -126,7 +113,7 @@ function Explorer:create_autocmds()
|
||||
vim.api.nvim_create_autocmd("BufUnload", {
|
||||
group = self.augroup_id,
|
||||
callback = function(data)
|
||||
if (self.filters.config.filter_no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
|
||||
if (self.filters.state.no_buffer or self.opts.highlight_opened_files ~= "none") and vim.bo[data.buf].buftype == "" then
|
||||
utils.debounce("Buf:filter_buffer_" .. self.uid_explorer, self.opts.view.debounce_delay, function()
|
||||
self:reload_explorer()
|
||||
end)
|
||||
@@ -213,10 +200,10 @@ function Explorer:reload(node, project)
|
||||
|
||||
-- To reset we must 'zero' everything that we use
|
||||
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
|
||||
git = 0,
|
||||
buf = 0,
|
||||
dotfile = 0,
|
||||
custom = 0,
|
||||
git = 0,
|
||||
buf = 0,
|
||||
dotfile = 0,
|
||||
custom = 0,
|
||||
bookmark = 0,
|
||||
})
|
||||
|
||||
@@ -246,7 +233,13 @@ function Explorer:reload(node, project)
|
||||
end
|
||||
|
||||
if not nodes_by_path[abs] then
|
||||
local new_child = node_factory.create_node(self, node, abs, stat, name)
|
||||
local new_child = node_factory.create({
|
||||
explorer = self,
|
||||
parent = node,
|
||||
absolute_path = abs,
|
||||
name = name,
|
||||
fs_stat = stat
|
||||
})
|
||||
if new_child then
|
||||
table.insert(node.nodes, new_child)
|
||||
nodes_by_path[abs] = new_child
|
||||
@@ -362,10 +355,10 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
|
||||
local filter_status = parent.filters:prepare(project)
|
||||
|
||||
node.hidden_stats = vim.tbl_deep_extend("force", node.hidden_stats or {}, {
|
||||
git = 0,
|
||||
buf = 0,
|
||||
dotfile = 0,
|
||||
custom = 0,
|
||||
git = 0,
|
||||
buf = 0,
|
||||
dotfile = 0,
|
||||
custom = 0,
|
||||
bookmark = 0,
|
||||
})
|
||||
|
||||
@@ -384,7 +377,13 @@ function Explorer:populate_children(handle, cwd, node, project, parent)
|
||||
local stat = vim.loop.fs_lstat(abs)
|
||||
local filter_reason = parent.filters:should_filter_as_reason(abs, stat, filter_status)
|
||||
if filter_reason == FILTER_REASON.none and not nodes_by_path[abs] then
|
||||
local child = node_factory.create_node(self, node, abs, stat, name)
|
||||
local child = node_factory.create({
|
||||
explorer = self,
|
||||
parent = node,
|
||||
absolute_path = abs,
|
||||
name = name,
|
||||
fs_stat = stat
|
||||
})
|
||||
if child then
|
||||
table.insert(node.nodes, child)
|
||||
nodes_by_path[child.absolute_path] = true
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
local view = require("nvim-tree.view")
|
||||
local utils = require("nvim-tree.utils")
|
||||
|
||||
local Class = require("nvim-tree.classic")
|
||||
local Iterator = require("nvim-tree.iterators.node-iterator")
|
||||
local DirectoryNode = require("nvim-tree.node.directory")
|
||||
|
||||
---@class LiveFilter
|
||||
---@class (exact) LiveFilter: Class
|
||||
---@field explorer Explorer
|
||||
---@field prefix string
|
||||
---@field always_show_folders boolean
|
||||
---@field filter string
|
||||
local LiveFilter = {}
|
||||
local LiveFilter = Class:extend()
|
||||
|
||||
---@param opts table
|
||||
---@param explorer Explorer
|
||||
---@return LiveFilter
|
||||
function LiveFilter:new(opts, explorer)
|
||||
local o = {
|
||||
explorer = explorer,
|
||||
prefix = opts.live_filter.prefix,
|
||||
always_show_folders = opts.live_filter.always_show_folders,
|
||||
filter = nil,
|
||||
}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
---@class LiveFilter
|
||||
---@overload fun(args: LiveFilterArgs): LiveFilter
|
||||
|
||||
---@class (exact) LiveFilterArgs
|
||||
---@field explorer Explorer
|
||||
|
||||
---@protected
|
||||
---@param args LiveFilterArgs
|
||||
function LiveFilter:new(args)
|
||||
self.explorer = args.explorer
|
||||
self.prefix = self.explorer.opts.live_filter.prefix
|
||||
self.always_show_folders = self.explorer.opts.live_filter.always_show_folders
|
||||
self.filter = nil
|
||||
end
|
||||
|
||||
---@param node_ Node?
|
||||
@@ -81,7 +82,7 @@ end
|
||||
---@param node Node
|
||||
---@return boolean
|
||||
local function matches(self, node)
|
||||
if not self.explorer.filters.config.enable then
|
||||
if not self.explorer.filters.enabled then
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -168,21 +169,21 @@ local function create_overlay(self)
|
||||
if view.View.float.enable then
|
||||
-- don't close nvim-tree float when focus is changed to filter window
|
||||
vim.api.nvim_clear_autocmds({
|
||||
event = "WinLeave",
|
||||
event = "WinLeave",
|
||||
pattern = "NvimTree_*",
|
||||
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||
group = vim.api.nvim_create_augroup("NvimTree", { clear = false }),
|
||||
})
|
||||
end
|
||||
|
||||
configure_buffer_overlay(self)
|
||||
overlay_winnr = vim.api.nvim_open_win(overlay_bufnr, true, {
|
||||
col = 1,
|
||||
row = 0,
|
||||
col = 1,
|
||||
row = 0,
|
||||
relative = "cursor",
|
||||
width = calculate_overlay_win_width(self),
|
||||
height = 1,
|
||||
border = "none",
|
||||
style = "minimal",
|
||||
width = calculate_overlay_win_width(self),
|
||||
height = 1,
|
||||
border = "none",
|
||||
style = "minimal",
|
||||
})
|
||||
|
||||
if vim.fn.has("nvim-0.10") == 1 then
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
local Class = require("nvim-tree.class")
|
||||
local Class = require("nvim-tree.classic")
|
||||
local DirectoryNode = require("nvim-tree.node.directory")
|
||||
|
||||
local C = {}
|
||||
---@alias SorterType "name" | "case_sensitive" | "modification_time" | "extension" | "suffix" | "filetype"
|
||||
---@alias SorterComparator fun(self: Sorter, a: Node, b: Node): boolean?
|
||||
|
||||
---@class (exact) SorterCfg
|
||||
---@field sorter string|fun(nodes: Node[])
|
||||
---@field folders_first boolean
|
||||
---@field files_first boolean
|
||||
---@alias SorterUser fun(nodes: Node[]): SorterType?
|
||||
|
||||
---@class (exact) Sorter: Class
|
||||
---@field cfg SorterCfg
|
||||
---@field user fun(nodes: Node[])?
|
||||
---@field pre string?
|
||||
local Sorter = Class:new()
|
||||
---@field private explorer Explorer
|
||||
local Sorter = Class:extend()
|
||||
|
||||
---@param opts table user options
|
||||
---@return Sorter
|
||||
function Sorter:create(opts)
|
||||
---@type Sorter
|
||||
local o = {
|
||||
cfg = vim.deepcopy(opts.sort),
|
||||
}
|
||||
o = self:new(o)
|
||||
---@class Sorter
|
||||
---@overload fun(args: SorterArgs): Sorter
|
||||
|
||||
if type(o.cfg.sorter) == "function" then
|
||||
o.user = o.cfg.sorter --[[@as fun(nodes: Node[])]]
|
||||
elseif type(o.cfg.sorter) == "string" then
|
||||
o.pre = o.cfg.sorter --[[@as string]]
|
||||
end
|
||||
return o
|
||||
end
|
||||
---@class (exact) SorterArgs
|
||||
---@field explorer Explorer
|
||||
|
||||
--- Predefined comparator, defaulting to name
|
||||
---@param sorter string as per options
|
||||
---@return fun(a: Node, b: Node): boolean
|
||||
function Sorter:get_comparator(sorter)
|
||||
return function(a, b)
|
||||
return (C[sorter] or C.name)(a, b, self.cfg)
|
||||
end
|
||||
---@protected
|
||||
---@param args SorterArgs
|
||||
function Sorter:new(args)
|
||||
self.explorer = args.explorer
|
||||
end
|
||||
|
||||
---Create a shallow copy of a portion of a list.
|
||||
@@ -54,31 +36,32 @@ local function tbl_slice(t, first, last)
|
||||
return slice
|
||||
end
|
||||
|
||||
---Evaluate `sort.folders_first` and `sort.files_first`
|
||||
---@param a Node
|
||||
---@param b Node
|
||||
---@param cfg SorterCfg
|
||||
---@return boolean|nil
|
||||
local function folders_or_files_first(a, b, cfg)
|
||||
if not (cfg.folders_first or cfg.files_first) then
|
||||
return
|
||||
---Evaluate folders_first and sort.files_first returning nil when no order is necessary
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:folders_or_files_first(a, b)
|
||||
if not (self.explorer.opts.sort.folders_first or self.explorer.opts.sort.files_first) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if not a:is(DirectoryNode) and b:is(DirectoryNode) then
|
||||
-- file <> folder
|
||||
return cfg.files_first
|
||||
return self.explorer.opts.sort.files_first
|
||||
elseif a:is(DirectoryNode) and not b:is(DirectoryNode) then
|
||||
-- folder <> file
|
||||
return not cfg.files_first
|
||||
return not self.explorer.opts.sort.files_first
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@private
|
||||
---@param t Node[]
|
||||
---@param first number
|
||||
---@param mid number
|
||||
---@param last number
|
||||
---@param comparator fun(a: Node, b: Node): boolean
|
||||
local function merge(t, first, mid, last, comparator)
|
||||
---@param comparator SorterComparator
|
||||
function Sorter:merge(t, first, mid, last, comparator)
|
||||
local n1 = mid - first + 1
|
||||
local n2 = last - mid
|
||||
local ls = tbl_slice(t, first, mid)
|
||||
@@ -88,7 +71,7 @@ local function merge(t, first, mid, last, comparator)
|
||||
local k = first
|
||||
|
||||
while i <= n1 and j <= n2 do
|
||||
if comparator(ls[i], rs[j]) then
|
||||
if comparator(self, ls[i], rs[j]) then
|
||||
t[k] = ls[i]
|
||||
i = i + 1
|
||||
else
|
||||
@@ -111,45 +94,49 @@ local function merge(t, first, mid, last, comparator)
|
||||
end
|
||||
end
|
||||
|
||||
---@param t table
|
||||
---@private
|
||||
---@param t Node[]
|
||||
---@param first number
|
||||
---@param last number
|
||||
---@param comparator fun(a: Node, b: Node): boolean
|
||||
local function split_merge(t, first, last, comparator)
|
||||
---@param comparator SorterComparator
|
||||
function Sorter:split_merge(t, first, last, comparator)
|
||||
if (last - first) < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local mid = math.floor((first + last) / 2)
|
||||
|
||||
split_merge(t, first, mid, comparator)
|
||||
split_merge(t, mid + 1, last, comparator)
|
||||
merge(t, first, mid, last, comparator)
|
||||
self:split_merge(t, first, mid, comparator)
|
||||
self:split_merge(t, mid + 1, last, comparator)
|
||||
self:merge(t, first, mid, last, comparator)
|
||||
end
|
||||
|
||||
---Perform a merge sort using sorter option.
|
||||
---@param t Node[]
|
||||
function Sorter:sort(t)
|
||||
if self.user then
|
||||
if self[self.explorer.opts.sort.sorter] then
|
||||
self:split_merge(t, 1, #t, self[self.explorer.opts.sort.sorter])
|
||||
elseif type(self.explorer.opts.sort.sorter) == "function" then
|
||||
local t_user = {}
|
||||
local origin_index = {}
|
||||
|
||||
for _, n in ipairs(t) do
|
||||
table.insert(t_user, {
|
||||
absolute_path = n.absolute_path,
|
||||
executable = n.executable,
|
||||
extension = n.extension,
|
||||
filetype = vim.filetype.match({ filename = n.name }),
|
||||
link_to = n.link_to,
|
||||
name = n.name,
|
||||
type = n.type,
|
||||
executable = n.executable,
|
||||
extension = n.extension,
|
||||
filetype = vim.filetype.match({ filename = n.name }),
|
||||
link_to = n.link_to,
|
||||
name = n.name,
|
||||
type = n.type,
|
||||
})
|
||||
table.insert(origin_index, n)
|
||||
end
|
||||
|
||||
local predefined = self.user(t_user)
|
||||
if predefined then
|
||||
split_merge(t, 1, #t, self:get_comparator(predefined))
|
||||
-- user may return a SorterType
|
||||
local ret = self.explorer.opts.sort.sorter(t_user)
|
||||
if self[ret] then
|
||||
self:split_merge(t, 1, #t, self[ret])
|
||||
return
|
||||
end
|
||||
|
||||
@@ -162,7 +149,7 @@ function Sorter:sort(t)
|
||||
end
|
||||
|
||||
-- if missing value found, then using origin_index
|
||||
local mini_comparator = function(a, b)
|
||||
local mini_comparator = function(_, a, b)
|
||||
local a_index = user_index[a.absolute_path] or origin_index[a.absolute_path]
|
||||
local b_index = user_index[b.absolute_path] or origin_index[b.absolute_path]
|
||||
|
||||
@@ -172,48 +159,52 @@ function Sorter:sort(t)
|
||||
return (a_index or 0) <= (b_index or 0)
|
||||
end
|
||||
|
||||
split_merge(t, 1, #t, mini_comparator) -- sort by user order
|
||||
elseif self.pre then
|
||||
split_merge(t, 1, #t, self:get_comparator(self.pre))
|
||||
self:split_merge(t, 1, #t, mini_comparator) -- sort by user order
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param a Node
|
||||
---@param b Node
|
||||
---@param ignorecase boolean|nil
|
||||
---@param cfg SorterCfg
|
||||
---@param ignore_case boolean
|
||||
---@return boolean
|
||||
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase, cfg)
|
||||
function Sorter:name_case(a, b, ignore_case)
|
||||
if not (a and b) then
|
||||
return true
|
||||
end
|
||||
|
||||
local early_return = folders_or_files_first(a, b, cfg)
|
||||
local early_return = self:folders_or_files_first(a, b)
|
||||
if early_return ~= nil then
|
||||
return early_return
|
||||
end
|
||||
|
||||
if ignorecase then
|
||||
if ignore_case then
|
||||
return a.name:lower() <= b.name:lower()
|
||||
else
|
||||
return a.name <= b.name
|
||||
end
|
||||
end
|
||||
|
||||
function C.case_sensitive(a, b, cfg)
|
||||
return node_comparator_name_ignorecase_or_not(a, b, false, cfg)
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:case_sensitive(a, b)
|
||||
return self:name_case(a, b, false)
|
||||
end
|
||||
|
||||
function C.name(a, b, cfg)
|
||||
return node_comparator_name_ignorecase_or_not(a, b, true, cfg)
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:name(a, b)
|
||||
return self:name_case(a, b, true)
|
||||
end
|
||||
|
||||
function C.modification_time(a, b, cfg)
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:modification_time(a, b)
|
||||
if not (a and b) then
|
||||
return true
|
||||
end
|
||||
|
||||
local early_return = folders_or_files_first(a, b, cfg)
|
||||
local early_return = self:folders_or_files_first(a, b)
|
||||
if early_return ~= nil then
|
||||
return early_return
|
||||
end
|
||||
@@ -232,17 +223,19 @@ function C.modification_time(a, b, cfg)
|
||||
return last_modified_b <= last_modified_a
|
||||
end
|
||||
|
||||
function C.suffix(a, b, cfg)
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:suffix(a, b)
|
||||
if not (a and b) then
|
||||
return true
|
||||
end
|
||||
|
||||
-- directories go first
|
||||
local early_return = folders_or_files_first(a, b, cfg)
|
||||
local early_return = self:folders_or_files_first(a, b)
|
||||
if early_return ~= nil then
|
||||
return early_return
|
||||
elseif a.nodes and b.nodes then
|
||||
return C.name(a, b, cfg)
|
||||
return self:name(a, b)
|
||||
end
|
||||
|
||||
-- dotfiles go second
|
||||
@@ -251,7 +244,7 @@ function C.suffix(a, b, cfg)
|
||||
elseif a.name:sub(1, 1) ~= "." and b.name:sub(1, 1) == "." then
|
||||
return false
|
||||
elseif a.name:sub(1, 1) == "." and b.name:sub(1, 1) == "." then
|
||||
return C.name(a, b, cfg)
|
||||
return self:name(a, b)
|
||||
end
|
||||
|
||||
-- unsuffixed go third
|
||||
@@ -263,7 +256,7 @@ function C.suffix(a, b, cfg)
|
||||
elseif a_suffix_ndx and not b_suffix_ndx then
|
||||
return false
|
||||
elseif not (a_suffix_ndx and b_suffix_ndx) then
|
||||
return C.name(a, b, cfg)
|
||||
return self:name(a, b)
|
||||
end
|
||||
|
||||
-- finally, compare by suffixes
|
||||
@@ -275,18 +268,20 @@ function C.suffix(a, b, cfg)
|
||||
elseif not a_suffix and b_suffix then
|
||||
return false
|
||||
elseif a_suffix:lower() == b_suffix:lower() then
|
||||
return C.name(a, b, cfg)
|
||||
return self:name(a, b)
|
||||
end
|
||||
|
||||
return a_suffix:lower() < b_suffix:lower()
|
||||
end
|
||||
|
||||
function C.extension(a, b, cfg)
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:extension(a, b)
|
||||
if not (a and b) then
|
||||
return true
|
||||
end
|
||||
|
||||
local early_return = folders_or_files_first(a, b, cfg)
|
||||
local early_return = self:folders_or_files_first(a, b)
|
||||
if early_return ~= nil then
|
||||
return early_return
|
||||
end
|
||||
@@ -300,18 +295,20 @@ function C.extension(a, b, cfg)
|
||||
local a_ext = (a.extension or ""):lower()
|
||||
local b_ext = (b.extension or ""):lower()
|
||||
if a_ext == b_ext then
|
||||
return C.name(a, b, cfg)
|
||||
return self:name(a, b)
|
||||
end
|
||||
|
||||
return a_ext < b_ext
|
||||
end
|
||||
|
||||
function C.filetype(a, b, cfg)
|
||||
---@private
|
||||
---@type SorterComparator
|
||||
function Sorter:filetype(a, b)
|
||||
local a_ft = vim.filetype.match({ filename = a.name })
|
||||
local b_ft = vim.filetype.match({ filename = b.name })
|
||||
|
||||
-- directories first
|
||||
local early_return = folders_or_files_first(a, b, cfg)
|
||||
local early_return = self:folders_or_files_first(a, b)
|
||||
if early_return ~= nil then
|
||||
return early_return
|
||||
end
|
||||
@@ -325,7 +322,7 @@ function C.filetype(a, b, cfg)
|
||||
|
||||
-- same filetype or both nil, sort by name
|
||||
if a_ft == b_ft then
|
||||
return C.name(a, b, cfg)
|
||||
return self:name(a, b)
|
||||
end
|
||||
|
||||
return a_ft < b_ft
|
||||
@@ -83,8 +83,12 @@ function M.create_watcher(node)
|
||||
end
|
||||
|
||||
M.uid = M.uid + 1
|
||||
return Watcher:create(path, nil, callback, {
|
||||
context = "explorer:watch:" .. path .. ":" .. M.uid,
|
||||
return Watcher:create({
|
||||
path = path,
|
||||
callback = callback,
|
||||
data = {
|
||||
context = "explorer:watch:" .. path .. ":" .. M.uid
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user