nvim-tree.lua/lua/nvim-tree/explorer/sorters.lua
Cyber Oliveira 04b2c1e08c
fix: sort_by "extension" falls back to name (#2306)
* fix(extension/sorter): fallbacks to C.name when both exts are the same or nil

* fix(nil): files with no extension

* fix(nil): files with no extension

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2023-07-09 13:12:49 +10:00

233 lines
4.9 KiB
Lua

local M = {}
local C = {}
--- Predefined comparator, defaulting to name
--- @param sort_by string as per options
--- @return function
local function get_comparator(sort_by)
return C[sort_by] or C.name
end
---Create a shallow copy of a portion of a list.
---@param t table
---@param first integer First index, inclusive
---@param last integer Last index, inclusive
---@return table
local function tbl_slice(t, first, last)
local slice = {}
for i = first, last or #t, 1 do
table.insert(slice, t[i])
end
return slice
end
local function merge(t, first, mid, last, comparator)
local n1 = mid - first + 1
local n2 = last - mid
local ls = tbl_slice(t, first, mid)
local rs = tbl_slice(t, mid + 1, last)
local i = 1
local j = 1
local k = first
while i <= n1 and j <= n2 do
if comparator(ls[i], rs[j]) then
t[k] = ls[i]
i = i + 1
else
t[k] = rs[j]
j = j + 1
end
k = k + 1
end
while i <= n1 do
t[k] = ls[i]
i = i + 1
k = k + 1
end
while j <= n2 do
t[k] = rs[j]
j = j + 1
k = k + 1
end
end
local function 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)
end
---Perform a merge sort using sort_by option.
---@param t table nodes
function M.sort(t)
if C.user 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,
})
table.insert(origin_index, n)
end
local predefined = C.user(t_user)
if predefined then
split_merge(t, 1, #t, get_comparator(predefined))
return
end
-- do merge sort for prevent memory exceed
local user_index = {}
for i, v in ipairs(t_user) do
if type(v.absolute_path) == "string" and user_index[v.absolute_path] == nil then
user_index[v.absolute_path] = i
end
end
-- if missing value found, then using origin_index
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]
if type(a_index) == "number" and type(b_index) == "number" then
return a_index <= b_index
end
return (a_index or 0) <= (b_index or 0)
end
split_merge(t, 1, #t, mini_comparator) -- sort by user order
else
split_merge(t, 1, #t, get_comparator(M.config.sort_by))
end
end
local function node_comparator_name_ignorecase_or_not(a, b, ignorecase)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
if ignorecase then
return a.name:lower() <= b.name:lower()
else
return a.name <= b.name
end
end
function C.case_sensitive(a, b)
return node_comparator_name_ignorecase_or_not(a, b, false)
end
function C.name(a, b)
return node_comparator_name_ignorecase_or_not(a, b, true)
end
function C.modification_time(a, b)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
local last_modified_a = 0
local last_modified_b = 0
if a.fs_stat ~= nil then
last_modified_a = a.fs_stat.mtime.sec
end
if b.fs_stat ~= nil then
last_modified_b = b.fs_stat.mtime.sec
end
return last_modified_b <= last_modified_a
end
function C.extension(a, b)
if not (a and b) then
return true
end
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
if a.extension and not b.extension then
return true
elseif not a.extension and b.extension then
return false
end
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)
end
return a_ext < b_ext
end
function C.filetype(a, b)
local a_ft = vim.filetype.match { filename = a.name }
local b_ft = vim.filetype.match { filename = b.name }
-- directories first
if a.nodes and not b.nodes then
return true
elseif not a.nodes and b.nodes then
return false
end
-- one is nil, the other wins
if a_ft and not b_ft then
return true
elseif not a_ft and b_ft then
return false
end
-- same filetype or both nil, sort by name
if a_ft == b_ft then
return C.name(a, b)
end
return a_ft < b_ft
end
function M.setup(opts)
M.config = {}
M.config.sort_by = opts.sort_by
if type(opts.sort_by) == "function" then
C.user = opts.sort_by
end
end
return M