mtrajano / tssorter.nvim

Sort almost anything in neovim using treesitter
MIT License
47 stars 0 forks source link

project plans #4

Closed uwla closed 1 month ago

uwla commented 1 month ago

This project looks awesome! I've been looking for something like this for a while.

In order to keep files organized I usually sort HTML attributes, CSS properties, JSON keys, etc, by splitting them into multiple lines (using splitjoin.vim) them sort them apply vim_sort_motion using inner indentation textobj, them join them again. But this is too much manual work... So, I loved this project!

I saw there is a project roadmap. Do you plan to keep improving the project?

mtrajano commented 1 month ago

Hey @uwla glad you liked the project! Yes that workflow you explained is a great example of tssorters use case, and reducing keystrokes in (n)vim is always great 😄

To answer your question: Yes I do plan on continuing to support the plugin, specially since I use it in my day to day as well! A few things of the things I'm working on:

  1. Cleaning up a lot of the stuff that got left over since pushing the plugin out
  2. Refactoring and adding tests so that it's easier to do future contributions
  3. Adding the ability to sort by "ordinals." Essentially this will allow us to pick any nested node to do the sorting by rather than just the node's text. This is what I initially had in mind when creating the plugin and is going to allow us to do some really cool things such as:
    • Sorting functions by their scopes (add all the private methods at the top, and all the public methods at the bottom for example)
    • Sorting norg headers by a date tag, a priority tag, etc.. (which will eventually allow us to do sorting for something like agenda view - in case you are familiar with emac's org mode)
    • Markdown tasks by their todo status Etc.. Essentially this is going to give the plugin a lot more power and unlock a lot of really cool things.

I also created the plugin as a proof of concept and wanted to let the plugin "simmer" to see how people were using it and also seeing if there were any crazy bugs I didn't take into account. Once I get these things out of the way it should make things much smoother, so I do appreciate the patience!

I saw you posted the issue in the other thread which I pushed a fix for. I'm closing this issue for now but feel free to reopen it or create another issue in case something else comes up, thanks!

uwla commented 1 month ago

The third point is great!

Is it possible to make the plugin accept a lua function to compare the nodes to be sorted? The function would take two treesitter nodes, have read access to all their properties/attributes, and then return -1,0,1 (-1 means less, 0 equality, 1 greater).

Something like:

cmp_fn = function(nodeA, nodeB)
  return calcVal(nodeA.attrX) - calcVal(nodeB.attrX)
end

That would allow, for example

The possibilities are endless... Basically, it would be possible to create rules to sort not based on a number or string, but upon relevant rules from some coding standard agreed upon developers within a team

uwla commented 1 month ago

I took a brief look into the source code and found out exactly what I was looking for.

You use the function in opts or a default one as the order_by sorting function:

--- Returns the retrieved lines in a sorted order
---@param nodes TSNode[]
---@param opts SorterOpts | SortableOpts
---@return string[]
local function get_sorted_lines(nodes, opts)
  local order_by = opts.order_by or default_sort

The format of the function is as I expected:

--- Default sort function simply sorts the text alphabetically
---@param node1 TSNode
---@param node2 TSNode
---@return boolean # Return true if the node1 comes before node2
local function default_sort(node1, node2)
  local line1 = tshelper.get_text(node1)
  local line2 = tshelper.get_text(node2)

  return line1 < line2
end

So, @mtrajano , I would suggest add some option like cmp_fn to be passed to setup({}):

require("tssorter").setup({
    ---@param node1 TSNode
    ---@param node2 TSNode
    ---@param tshelper TSHelper
    cmp_fn = function(a, b, tshelper)
        val_1 = tshelper.get_someval(a)
        val_2 = tshelper.get_someval(b)
        return val_1 < val_2
        -- or maybe
        return val_1 - val_2
    end
})

The tshelper is passed as third argument in order for the plugin user to be able to get relevant information from the nodes without importing tshelper in their lua config.

I could make a PR but I would instructions because I have no clue about how to develop lua plugins or how they work... and I only code in lua to rice nvim and yazi

mtrajano commented 1 month ago

@uwla You should already be able to do this with the current implementation. The opts for a sortable does take in an order_by key with 2 params (node1, node2) as you mentioned: https://github.com/mtrajano/tssorter.nvim/blob/main/lua/tssorter/sorter.lua#L42. It's possible the docs around this feature aren't super clear so if you have an idea on how to improve that I'm open to it.

So your config for a sortable could be something like:

{
  node = 'some_int_node',
  order_by = function(node1, node2)
      local num1 = tshelper.get_text(node1)
      local num1 = tshelper.get_text(node2)

      -- randomly sort number nodes
      return tonumber(num1) * math.random() < tonumber(num2) * math.random()
  end
}

Let me know if that's what you meant or if you had something else in mind. node1 and node2 are just TSNodes so you could theoretically do anything with it, including looking for nested nodes and sorting based on that. The ordinal stuff I'm working on will hopefully make that a lot easier though through configuration.

mtrajano commented 3 weeks ago

@uwla Just pushed some changes adding the ability to sort by inner nodes, here's a more complex example making use of that order by function to sort norg tasks by a specific order: https://github.com/mtrajano/tssorter.nvim/blob/main/lua/tssorter/config.lua#L73

uwla commented 3 weeks ago

Hi, thanks for the answer and sorry for the delay.

Your example was very helpful and it is clear now what can be done and how.

I want to write custom sorters, I just hadn't the time yet, but with your examples it will be easy I guess!

This plugin can be very powerful since we can use custom sorters for any treesitter nodes!