-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
feat(treesitter): add ability to reload parser #28715
base: master
Are you sure you want to change the base?
feat(treesitter): add ability to reload parser #28715
Conversation
Do we really need a new method for this? Can't we instead add a (Also needs tests.) |
Not sure about I would love to add tests, but is there an example how to test function that depends on |
This comment was marked as outdated.
This comment was marked as outdated.
Yes, that is exactly what I was thinking of. What I don't like about it is that it's specific to injections, but you (sometimes) want to reload the main queries. Even if that is the actual effect, the API is misleading then, hurting discoverability.
Neovim already ships with queries in runtimepath, so, yes, creating a temporary file and adding that directory to runtimepath could work. The best thing would be to look through the |
Yeah, more general "reload parser" is what I was looking for initially. So I would do it this way. But there is a lot happening with callbacks, that I don't fully grasp. Do I need to do any specific clean up for this: api.nvim_buf_attach(
source,
false,
{ on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true }
) |
I don't think so; tests will show. |
Problem: There is no way to reload current parser for buffer. This is needed when `vim.opt.rtp` changes and new paths have treesitter queries. For example when loading plugins lazily. Solution: Add options `reload` to `vim.treesitter.create_parser()`, that will remove previous parser and create new one, effectively loading new queries from `runtimepath`
404ec64
to
f74018c
Compare
parsers[bufnr]:destroy() | ||
parsers[bufnr] = M._create_parser(bufnr, lang, opts) | ||
|
||
if M.highlighter.active[bufnr] then | ||
M.highlighter.active[bufnr]:destroy() | ||
M.highlighter.new(parsers[bufnr]) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this different than LanguageTree:invalidate()
? in fact we have something called _on_reload
that currently calls invalidate():
neovim/runtime/lua/vim/treesitter/languagetree.lua
Lines 1042 to 1044 in c1396af
function LanguageTree:_on_reload() | |
self:invalidate(true) | |
end |
Would be very helpful to determine which semantics are correct and tighten things up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invalidate()
does not reload LanguageTree._inject_query
which is the reason I want to reload the parser. If there is different way it should be done, I am happy to do it.
What happens there is basically what is going to happen if :edit
executed. When :edit
executed fires event "detach" for buffer, which triggers detach_cb
and this deletes parser. Then vim.treesitter.start()
called by ft plugin and creates new parser.
neovim/runtime/lua/vim/treesitter.lua
Lines 47 to 52 in 4e5c633
local function detach_cb(_, ...) | |
if parsers[bufnr] == self then | |
parsers[bufnr] = nil | |
end | |
self:_on_detach(...) | |
end |
Right now my goal is to make parser read queries from filesystem again, because I change vim.opt.rtp
where I add more queries from plugin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking, maybe a better idea would be refreshing queries in invalidate
? Something like this
function LanguageTree:invalidate(reload, refresh_queries)
self._valid = false
if refresh_queries then
if not self._opts.injections then
query._reset_cache(self:lang(), 'injections')
self._injection_query = query.get(self:lang(), 'injections')
end
self:_do_callback('changedquery', self:lang())
end
-- ...
and then highlighter.lua would register callback
-- ...
on_changedquery = function(lang)
-- just remove queires, they are loaded later on demand
query._reset_cache(lang, 'highlights')
self._queries[lang] = nil
end,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invalidate
is only supposed to be called when the tracked state of the Languagetree is not valid against the parse tree in tree-sitter, and is called fairly often. I expect clearing file system caches will kill performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invalidate
is only supposed to be called when the tracked state of the Languagetree is not valid against the parse tree in tree-sitter, and is called fairly often
I attempted to mention this in the docstring of invalidate() #28747
Which plugins are we talking about? Is the lazy loading being done meaningfully or just blindly? And why can't the plugin be improved so it doesn't need to be lazy loaded (assuming it does). It's still up in the air whether we want to support |
This one https://github.com/S1M0N38/love2d.nvim , I actually added the queries there and thats when I hit the problem with rtp changes.
In my case I load this plugin only in specific projects, so it is not about speed, but more about per project configuration. There is similar idea in another PR #27392 that may solve my problem. |
love2d.nvim creates two user commands: https://github.com/S1M0N38/love2d.nvim/blob/main/plugin/love2d.lua Is having this enabled only for specific projects really gaining you anything? |
It adds love2d library to lua_ls path as well. But sure, this plugin can be loaded eagerly without a problem. I will have some irrelevant functions in autocomplete, but it is not a big deal. Before creating PR I tried to solve this with available apis, but had to use private field local parser = vim.treesitter.get_parser()
parser._injection_query = vim.treesitter.query.get("lua", "injections")
parser:invalidate(true)
parser:parse(true) Weirdly never hit cache with this call, so it worked, but using private field bothers me :) |
The whole parser already getting dropped and new one created when executing neovim/runtime/lua/vim/treesitter.lua Lines 47 to 52 in 6a264e0
neovim/runtime/lua/vim/treesitter.lua Lines 106 to 109 in 6a264e0
I am doing the same, when calling |
If explicitly dropping query cache is a problem (or could become a problem in the future) how about just having an api to remove parser from a buffer? function M.drop_parser(bufnr)
parsers[bufnr]:destroy()
parsers[bufnr] = nil
end Or making Or even adding parameter to This will enable me to write something like this (probably in a loop for all lua buffers) vim.treesitter.stop(bufnr, {drop = true})
collectgarbage() -- maybe clear query cache
vim.treesitter.start(bufnr, "lua") -- create new parser with fresh queries |
Remember that the highlighter is not the only "module" that is using treesitter parsers (which are shared!) and queries. |
Yeah, but they already have to handle buffer detaching neovim/runtime/lua/vim/treesitter.lua Lines 47 to 52 in 6a264e0
So calling function M.drop_parser(bufnr)
if parsers[bufnr] then
parsers[bufnr]:_on_detach()
parsers[bufnr] = nil
end
end Regarding queries, query cache already drops if no one is referencing given query. So by removing parser this way I try to create situation where no one references |
@lewis6991 please, let me know if this is a good idea. This is the only needed change. Not touching query cache. Just an ability to optionally destroy a parser. -- vim/treesitter.lua
function M.stop(bufnr, opts)
opts = opts or {}
-- ...
if opts.destroy and parsers[bufnr] then
parsers[bufnr]:_on_detach()
parsers[bufnr]:destroy()
parsers[bufnr] = nil
end
-- ...
end |
Problem:
There is no way to reload current parser for buffer. This is needed when
vim.opt.rtp
changes and new paths have treesitter queries. For examplewhen loading plugins lazily.
Solution:
Add options
reload
tovim.treesitter.get_parser()
, that willremove previous parser and create new one, effectively loading new
queries from
runtimepath