Skip to content

feat: Mixin Sorter (#1565) Self Solved #1566

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 18, 2022
Merged

Conversation

p4r4d0xb0x
Copy link
Contributor

@p4r4d0xb0x p4r4d0xb0x commented Aug 31, 2022

resolves #1490 #1565

adding mixin sort options for rust like package systems


package.rs
package/
  __inside__

lib.rs
lib/
  _inside_

a.rs
b.rs
module.rs

adding `mixin` sort options for `rust` like package systems

```

package.rs
package/
  __inside__

lib.rs
lib/
  _inside_

a.rs
b.rs
module.rs

```
@p4r4d0xb0x p4r4d0xb0x mentioned this pull request Aug 31, 2022
Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

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

This additional sorting functionality is very useful, however mixin is very specific.

It would be more useful for the user to provide their own sorting function:

*nvim-tree.sort_by*
Changes how files within the same directory are sorted.
Can be one of 'name', 'case_sensitive', 'modification_time',
'extension' or a function.
  Type: `string` or `function`, Default: `"name"`

sort_by = function(nodes)
  insert a simple example that sorts, say, file name length
end

@p4r4d0xb0x
Copy link
Contributor Author

Thanks For Review 👍🏻

that will be more better!

i think it will be better not to use default merge sort function when user provide function
and provide user can handle which one to use
default merge sort func?
user specified func?
sorry for my bad explain..

(MEMO)

todo ] 기본적인 정렬을 포함하여, 함수를 인자로 받아 해당 함수로 정렬 함수를 대체하도록 소스수정

@alex-courtis
Copy link
Member

i think it will be better not to use default merge sort function when user provide function and provide user can handle which one to use default merge sort func? user specified func? sorry for my bad explain..

Do you mean the user provides a comparator to pass to merge_sort? That would be very easy.

sort_by = function(a, b)
  -- return a <= b
end

Yes, please build that.

@alex-courtis
Copy link
Member

Please grant me push access to your repo so that I can help you with the documentation.

@p4r4d0xb0x
Copy link
Contributor Author

Please grant me push access to your repo so that I can help you with the documentation.

sure! invited!

@alex-courtis
Copy link
Member

Please grant me push access to your repo so that I can help you with the documentation.

sure! invited!

Thanks. I'll wait for your changes then write the help doc.

```
*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

```
@p4r4d0xb0x
Copy link
Contributor Author

@alex-courtis i've changed something!
Can you review it again..?, I appreciate it.

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

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

Questions I have:

  • Do we need to provide a comparator and a merge sort function?
  • The input to a comparator can be sanitised by passing in a cut-down copy of the nodes. The input to after_sort cannot be sanitised in this way, and would allow users to directly modify the nodes.

Let me consider...

Type: `string | function(a, b)`, Default: `"name"`

*nvim-tree.after_sort*
Related to nvim-tree.sort_by, this function runs without mergesort.
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this? Is it because mixin sort cannot be achieved via a comparator?

@@ -73,6 +73,9 @@ function M.merge_sort(t, comparator)
end

split_merge(t, 1, #t, comparator)
if M.after_sort then
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to run the comparator and split_merge first?

We should call the user's after_sort instead of M.merge_sort.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OMZ,,, I miss understood..
I thought 'sort_by' comparator must be run through merge sort function

Copy link
Member

Choose a reason for hiding this comment

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

OMZ,,, I miss understood.. I thought 'sort_by' comparator must be run through merge sort function

We would need to try it.

Please try explorer.lua and reload.lua calling after_sort directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i will try this too☺️

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,

```
@p4r4d0xb0x
Copy link
Contributor Author

Questions I have:

  • Do we need to provide a comparator and a merge sort function?
  • The input to a comparator can be sanitised by passing in a cut-down copy of the nodes. The input to after_sort cannot be sanitised in this way, and would allow users to directly modify the nodes.

Reply to Answer:

  • Do we need to provide a comparator and a merge sort function?
    • I thought provide well-made this repo's merge sort function will bring benefits to reduce users error caused by mistake 😄
    • I've changed function split_merge's local scope to global but still in question, is it better to provide new access point like M.merge_sort function for safety...?

    explorer/sorters.lua
    local function split_merge(t, first, last, comparator) > function M.split_merge(t, first, last, comparator)

  • The input to a comparator can be sanitised by passing in a cut-down copy of the nodes. The input to after_sort cannot be sanitised in this way, and would allow users to directly modify the nodes.
    • after_sort have to be deleted!!!!!

@alex-courtis
Copy link
Member

alex-courtis commented Sep 6, 2022

I thought provide well-made this repo's merge sort function will bring benefits to reduce users error caused by mistake

That is thoughtful, however it adds complexity and exposes non-API functionality.

If the user is to write their own sort, they will just have to do everything themselves. table.sort can be used.

The doc can contain a simple example that use table.sort.

is it better to provide new access point like M.merge_sort function for safety...?

passing in a cut-down copy of the nodes

Existing merge_sort can simply call the user's sort_by with sanitised nodes.

Something like this. The TODO is for you ;)

function M.merge_sort(t, comparator)

  if type(M.sort_by) == "function" then
    local t_user = {}
    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,
      })
    end

    -- returns an array of absolute_path that represents the order
    local user_order = M.sort_by(t_user)

    -- TODO reorder t based on user_order

  else
    if not comparator then
      comparator = function(left, right)
        return left < right
      end
    end
    M.split_merge(t, 1, #t, comparator)
  end
end

@p4r4d0xb0x
Copy link
Contributor Author

I thought provide well-made this repo's merge sort function will bring benefits to reduce users error caused by mistake

That is thoughtful, however it adds complexity and exposes non-API functionality.

If the user is to write their own sort, they will just have to do everything themselves. table.sort can be used.

The doc can contain a simple example that use table.sort.

is it better to provide new access point like M.merge_sort function for safety...?

passing in a cut-down copy of the nodes

Existing merge_sort can simply call the user's sort_by with sanitised nodes.

Something like this. The TODO is for you ;)

function M.merge_sort(t, comparator)

  if type(M.sort_by) == "function" then
    local t_user = {}
    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,
      })
    end

    -- returns an array of absolute_path that represents the order
    local user_order = M.sort_by(t_user)

    -- TODO reorder t based on user_order

  else
    if not comparator then
      comparator = function(left, right)
        return left < right
      end
    end
    M.split_merge(t, 1, #t, comparator)
  end
end

ohhhhhh thx for make it simple good first issue for me :)

I've been using this extension so much usefully, so I really wanted to participate as a contributor.

god of lua alex-courtis!
god of lua ! god of lua ! 👍🏻

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

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

Tested:

  • empty sort_by
  • nil sort_by - FAIL
  • partial sort_by
  • complete sort_by
  • duplicates in sort_by (garbage in garbage out)
  • nonexistent absolute paths in sort_by
  • huge (double size) sort_by - FAIL

Pushed update to doc for function params/return; add more complex example.

@p4r4d0xb0x
Copy link
Contributor Author

p4r4d0xb0x commented Sep 16, 2022

dear @alex-courtis,
can i request one more review :)

  • there are have exclude settings. so if user function makes missing value, then just try original position to place
  • changed re-ordering mechanism to use built-in merge_sort to solving large-data sorting problems
  • fetch current head of repo
  • add complex (?) examples to doc.. but sincerely request to review my docs..

Best regards, 👍🏻

@alex-courtis
Copy link
Member

sort_by = nil and sort_by = "extension" were failing. Pushed fda6d4c

@alex-courtis
Copy link
Member

  • there are have exclude settings. so if user function makes missing value, then just try original position to place
  • changed re-ordering mechanism to use built-in merge_sort to solving large-data sorting problems

Fantastic! That works well.

  • add complex (?) examples to doc.. but sincerely request to review my docs..

Sorry, we will only provide a trivial example. Users can devise their own solutions. I have reverted 26e4066

@alex-courtis
Copy link
Member

After considering, I realise I made a huge mistake by asking the user to return a table of absolute path.

The user now simply sorts t_user and merge_sort will directly use it. Pushed dfbfec6

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

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

All tested OK.

I'm happy; if you're happy I will merge.

@p4r4d0xb0x
Copy link
Contributor Author

All tested OK.

I'm happy; if you're happy I will merge.

excited to die, hhh

@p4r4d0xb0x
Copy link
Contributor Author

All tested OK.

I'm happy; if you're happy I will merge.

thanksssss

have a nice day!!!!

@alex-courtis
Copy link
Member

All tested OK.
I'm happy; if you're happy I will merge.

thanksssss

have a nice day!!!!

Thank you so much for all your work!

@alex-courtis alex-courtis merged commit 3676e0b into nvim-tree:master Sep 18, 2022
@petobens
Copy link

petobens commented Nov 9, 2022

Hi sorry to chime in here: is possible to dynamically sort the nodes once a tree is opened? i.e I would like to have a map to "toggle"sorting between name, time etc. AFAICT sort_by currently is only available as part of setup()

@alex-courtis
Copy link
Member

Hi sorry to chime in here: is possible to dynamically sort the nodes once a tree is opened? i.e I would like to have a map to "toggle"sorting between name, time etc. AFAICT sort_by currently is only available as part of setup()

Your sort_by function will always be applied when sorting the tree.

You could change the sorting by storing your own state. Hacky example:

local sort_by = function(nodes)
  table.sort(nodes, function(a, b)
    if vim.g.my_sort_by == "length" then
      -- sort by name length
      return #a.name < #b.name
    else
      -- sort by name alphanumerically
      return a.name <= b.name
    end
  end)
end

@petobens
Copy link

You could change the sorting by storing your own state. Hacky example:

I'm afraid I don't quite follow. Is it possible to place that function behind a mapping to actually toggle sorting? My ideal use is to define something like the following map:

{ key = 'T', cb = ':lua NvimTreeConfig.toggle_sort("time")<CR>' },

where by pressing T i get the tree sorted by modification time (rather than the default alphanumeric sort).

@alex-courtis
Copy link
Member

alex-courtis commented Nov 15, 2022

The callback needs to be a lua function.

:help nvim-tree-mappings

Something like this to set your sort then refresh the tree:

{
  key = "T",
  action = "sort_by_time",
  action_cb = function()
    vim.g.my_sort_by = "time"
    require("nvim-tree.api").tree.reload()
  end
},

It doesn't have to be an nvim-tree mapping, however it is more convenient than defining a global mapping.

@petobens
Copy link

I tried doing the following:

local tree_api = require('nvim-tree.api').tree

vim.g.my_sort_by = 'name'

require('nvim-tree').setup({
    sort_by = vim.g.my_sort_by,
    .....

local map_list = {
    {
        key = 'T',
        action = 'sort_by_time',
        action_cb = function()
            vim.g.my_sort_by = 'modification_time'
            tree_api.reload()
        end,
    },
}

However when I open a nvim tree and press T nothing happens (i.e the vim.g_sort_by variable changes but the tree is not updated/sorted accordingly):

sort_nvimtree

@alex-courtis
Copy link
Member

sort_by = vim.g.my_sort_by,

You need to set sort_by to your function, not your own state variable.

Complete working example to run with nvim -nu ~/nvt-min.lua

nvt-min.lua.txt

@petobens
Copy link

Hi. Thanks for the reply. I tried:

local sorters = require('nvim-tree.explorer.sorters')
local my_sort_by_mode = 'name'

local my_sort_by_function = function(nodes)
    print('sorting by ' .. my_sort_by_mode)
    table.sort(nodes, function(a, b)
        if my_sort_by_mode == 'time' then
            return sorters.node_comparator_modification_time(a, b)
        else
            return sorters.node_comparator_name_ignorecase(a, b)
        end
    end)
end

local my_sort_by_action_callback = function()
    if my_sort_by_mode == 'time' then
        my_sort_by_mode = 'name'
    else
        my_sort_by_mode = 'time'
    end
    require('nvim-tree.api').tree.reload()
end

But:

  1. The initial name sort is not the same as the "default" name sort: in the default name sort directories appear at the top whereas with my implementation directories get sorted along files.
  2. Whenever I press T I get:
E5108: Error executing lua: ...pedro/.config/nvim/lua/plugin-config/nvimtree_config.lua:99: invalid order function for sorting
stack traceback:
        [C]: in function 'sort'
        ...pedro/.config/nvim/lua/plugin-config/nvimtree_config.lua:99: in function 'sort_by'
        ...r/start/nvim-tree.lua/lua/nvim-tree/explorer/sorters.lua:85: in function 'merge_sort'
        ...er/start/nvim-tree.lua/lua/nvim-tree/explorer/reload.lua:136: in function 'reload'
        ...m-tree.lua/lua/nvim-tree/actions/reloaders/reloaders.lua:12: in function 'refresh_nodes'
        ...m-tree.lua/lua/nvim-tree/actions/reloaders/reloaders.lua:43: in function 'reload'
        ...pedro/.config/nvim/lua/plugin-config/nvimtree_config.lua:114: in function 'handle_tree_actions'
        ...r/start/nvim-tree.lua/lua/nvim-tree/actions/dispatch.lua:120: in function 'dispatch'
        ...acker/start/nvim-tree.lua/lua/nvim-tree/actions/init.lua:258: in function <...acker/start/nvim-tree.lua/lua/nvim-tree/actions/init.lua:257>

At the end of the day my request is: can you add some api function to easily toggle sorting? I imagine something like require('nvim-tree.api').tree.toggle_sort('time').

Thanks for the help and patience.

@alex-courtis
Copy link
Member

2. Whenever I press T I get:

sorters.node_comparator_modification_time is not API. It relies on extra information present in the node that is not available to sort_by.

At the end of the day my request is: can you add some api function to easily toggle sorting?

That is problematic in that sort states and the available methods need to be configured and tracked.

How about this:

*nvim-tree.sort_by*
Changes how files within the same directory are sorted.
Can be a method: `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"`
  
  Function should return a table with the nodes sorted or one of the method
  strings defined above.

  Example: sort by name length: >
    local my_sort_by = function(nodes)
      table.sort(nodes, function(a, b)
        return #a.name < #b.name
      end)
    end
<
  Example: toggle between `name` and `extension` >
    local my_sort_method = "name"
    local my_sort_by = function(nodes)
      if sort_method == "name" then
        sort_method = "extension"
      else
        sort_method = "name"
      end
      return my_sort_method
    end
<

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow folders to not be sorted before files
3 participants