From 3676e0b124c2a132857e2bbcf7f48f05228f1052 Mon Sep 17 00:00:00 2001 From: KuuWang Date: Sun, 18 Sep 2022 15:00:49 +0900 Subject: [PATCH] feat(sorters): allow user sort_by * feat: Mixin Sorter (#1565) Self Solved adding `mixin` sort options for `rust` like package systems ``` package.rs package/ __inside__ lib.rs lib/ _inside_ a.rs b.rs module.rs ``` * feat: sort_by, after_sort options for more convinient using ``` *nvim-tree.sort_by* Changes how files within the same directory are sorted. Can be one of 'name', 'case_sensitive', 'modification_time' or 'extension', 'function'. > sort_by = function(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 return a.name:lower() <= b.name:lower() end end Type: `string | function(a, b)`, Default: `"name"` *nvim-tree.after_sort* Related to nvim-tree.sort_by, this function runs without mergesort. Can be defined by your own after-sort works. Type: `function(table)`, Default: `disable` > after_sort = function(t) local i = 1 while i <= #t do if t[i] and t[i].nodes then local j = i + 1 while j <= #t do if t[j] and not t[j].nodes and t[i].name:lower() == t[j].name:lower():match "(.+)%..+$" then local change_target = t[j] table.remove(t, j) table.insert(t, i, change_target) break end j = j + 1 end end i = i + 1 end end ``` * remove: after_sort ( misunderstood feature ) sort_by parameter can be function. ``` lua sort_by = function(t) local sorters = require "nvim-tree.explorer.sorters" local comparator = sorters.retrieve_comparator("name") sorters.split_merge(t, 1, #t, comparator) -- run default merge_sort local i = 1 while i <= #t do if t[i] and t[i].nodes then local j = i + 1 while j <= #t do if t[j] and not t[j].nodes and t[i].name:lower() == t[j].name:lower():match "(.+)%..+$" then local change_target = t[j] table.remove(t, j) table.insert(t, i, change_target) break end j = j + 1 end end i = i + 1 end end, ``` * try-fix: change existing merge_sort function, call user's sort_by hope.. like it...? * doc: explain function parameter and return, add more complex example * fix: reorder with user-comparator exceed memory limit apply merge_sort check nil & type for senitize * fix: user_index based sorting ( create index ) for performance, create index once, using index to re-ordering * fix: fence problems * doc & fix: merge_sort problem fix & nil sorting add complex example * fix: sort_by detect and use string and nil * doc: revert sort_by to simple * fix: sort_by does not return anything Co-authored-by: Alexander Courtis --- doc/nvim-tree-lua.txt | 21 ++++++++++-- lua/nvim-tree.lua | 1 + lua/nvim-tree/explorer/sorters.lua | 55 ++++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index be998c6d..9dc36cad 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -191,7 +191,6 @@ Subsequent calls to setup will replace the previous configuration. number = false, relativenumber = false, signcolumn = "yes", - -- @deprecated mappings = { custom_only = false, list = { @@ -426,9 +425,25 @@ if the tree was previously open. *nvim-tree.sort_by* Changes how files within the same directory are sorted. -Can be one of 'name', 'case_sensitive', 'modification_time' or 'extension'. - Type: `string`, Default: `"name"` +Can be one of `name`, `case_sensitive`, `modification_time`, `extension` or a +function. + Type: `string` | `function(nodes)`, Default: `"name"` + Function is passed a table of nodes to be sorted, each node containing: + - `absolute_path`: `string` + - `executable`: `boolean` + - `extension`: `string` + - `link_to`: `string` + - `name`: `string` + - `type`: `"directory"` | `"file"` | `"link"` + + Example: sort by name length: > + local sort_by = function(nodes) + table.sort(nodes, function(a, b) + return #a.name < #b.name + end) + end +< *nvim-tree.hijack_unnamed_buffer_when_opening* Opens in place of the unnamed buffer if it's empty. Type: `boolean`, Default: `false` diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index a1a81b5a..0af27560 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -640,6 +640,7 @@ local FIELD_OVERRIDE_TYPECHECK = { height = { string = true, ["function"] = true, number = true }, remove_keymaps = { boolean = true, table = true }, on_attach = { ["function"] = true, string = true }, + sort_by = { ["function"] = true, string = true }, } local function validate_options(conf) diff --git a/lua/nvim-tree/explorer/sorters.lua b/lua/nvim-tree/explorer/sorters.lua index 4168a931..be0d7fcf 100644 --- a/lua/nvim-tree/explorer/sorters.lua +++ b/lua/nvim-tree/explorer/sorters.lua @@ -66,13 +66,52 @@ end ---@param t any[] ---@param comparator function|nil function M.merge_sort(t, comparator) - if not comparator then - comparator = function(left, right) - return left < right - end - end + if type(M.sort_by) == "function" then + local t_user = {} + local origin_index = {} - split_merge(t, 1, #t, comparator) + for _, n in ipairs(t) do + table.insert(t_user, { + absolute_path = n.absolute_path, + executable = n.executable, + extension = n.extension, + link_to = n.link_to, + name = n.name, + type = n.type, + }) + table.insert(origin_index, n) + end + + M.sort_by(t_user) + + -- 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 + if not comparator then + comparator = function(left, right) + return left < right + end + end + split_merge(t, 1, #t, comparator) + end end local function node_comparator_name_ignorecase_or_not(a, b, ignorecase) @@ -150,7 +189,9 @@ end function M.setup(opts) M.sort_by = opts.sort_by - if M.sort_by == "modification_time" then + if M.sort_by and type(M.sort_by) == "function" then + M.node_comparator = M.sort_by + elseif M.sort_by == "modification_time" then M.node_comparator = M.node_comparator_modification_time elseif M.sort_by == "case_sensitive" then M.node_comparator = M.node_comparator_name_case_sensisive