Skip to content
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

Emacs treesit experiments #109

Open
dvzubarev opened this issue Dec 31, 2023 · 5 comments
Open

Emacs treesit experiments #109

dvzubarev opened this issue Dec 31, 2023 · 5 comments

Comments

@dvzubarev
Copy link
Contributor

Hi, I wanted to share my experiments with the Emacs builtin treesit library.
Recently, there were added some functions for creating arbitrary thing at points from the parse tree nodes.
I had an idea to implement text objects based on these things.
The basic idea is to search current thing at point.
Then extend or shrink its bounds according to current modifier (inner, outer, etc.).

There are pros and cons for this approach in comparison with the query based approach.
On the good side, It is more efficient approach, since it does not require to query the whole buffer with bunch of queries.
Its working by local traversing of the tree nodes, that are near the point.
So performance of this approach should not be affected by the size of the buffer.
Also, it allows more fine-grained control of text object ranges.
For example, for parameter outer, its possible to include surrounding spaces.
There are also possibilities to make fancier text objects.

The main downside is the lack of language support.
Since we cannot use community queries, we have to create settings for each language from the scratch.

I ended up crafting a package out of this experiments for my personal use - https://github.com/dvzubarev/evil-ts-obj.
Please feel free to check it out, it would be great to hear some feedback.

@meain
Copy link
Owner

meain commented Jan 3, 2024

This looks interesting. Relying on the named fields seems like a good idea(not sure about the support for every language though). FYI, there is a package which does something vaguely similar foxfriday/evil-ts.

It is more efficient approach, since it does not require to query the whole buffer with bunch of queries.

Hmm, that sound useful. That said, I've not really had run into any issues with perf even on relatively large buffers.

Also, it allows more fine-grained control of text object ranges.
For example, for parameter outer, its possible to include surrounding spaces.

The elisp-tree-sitter variant lets you do similar things. The reason why we can't support this is due to missing functionality to get matches.

The main downside is the lack of language support.

Yeah, my plan from the start was to just copy the "homework" done by neovim folks. It has not always been that smooth because of neovim using a lot of custom operators.

@dvzubarev
Copy link
Contributor Author

The elisp-tree-sitter variant lets you do similar things. The reason why we can't support this is due to missing functionality to get matches.

No it's not. There are a lot of edge cases in situations when cursor is not on a node, but somewhere between. Like on spaces or on a separator etc. See nvim-treesitter/nvim-treesitter-textobjects#69
Another problem is extending text object to the next sibling and including surrounding spaces (for parameter text objects).
It seems they eventually fixed this problem by writing custom lua code (see nvim-treesitter/nvim-treesitter-textobjects#235), not via using custom predicates.

@meain
Copy link
Owner

meain commented Jan 3, 2024

I see what you mean. I misunderstood your initial comment. We don't really do that as of now.

For what its worth, the current behavior here is a bit different compared to what is explained in nvim-treesitter/nvim-treesitter-textobjects#69.

For the first example:

print( "a" , "b" , "c" )

... if you were to do parameter.inner, it would select the "b" as we select the very next one if we are not on one. I copied the behavior as I got used to this for selecting brackets in vim. parameter.outer will also technically work in the expected way. This however has downsides in that if there are no immediate next "parameter" we end up selecting the next one anywhere in the buffer which also seems to be the behavior can be kinda jarring.

As for the second case:

def foo(bar):
    print(bar)
  ^

... similar to the first case doing a function.inner will do the right thing here as well just because we select the next entry.

@dvzubarev
Copy link
Contributor Author

if you were to do parameter.inner, it would select the "b" as we select the very next one if we are not on one.

Yes, but this approach does not work if there are nested text objects. For example print( call(a,| b, c) ) will select call(a,| b, c) as parameter.inner. In most cases, it is not what I want to do.

@meain
Copy link
Owner

meain commented Jan 10, 2024

Ahh, I see. That would definitely need custom handling.

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

No branches or pull requests

2 participants