Skip to content

feat: Add TreePreOpen and TreePreClose events #3105

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

devansh08
Copy link
Collaborator

This adds 2 new events TreePreOpen and TreePreClose, which get triggered before TreeOpen and TreeClose events. They work similar to TreeOpen and TreeClose, in terms of the handler function.

This should help running logic before the NvimTree window is created or removed.

Let me know your thoughts.
Thanks!

@gegoune
Copy link
Collaborator

gegoune commented Apr 12, 2025

Could you please give us some examples why is it needed?

@devansh08
Copy link
Collaborator Author

So in my personal setup, I wanted a way to auto adjust split sizes in a tabpage, but the opening/closing of the NvimTree window would usually reset the layout. Also, none of the builtin Win events in NeoVim get triggered before a window is created or removed; so I wasn't able to use those either.

When I saw the TreeOpen event, I thought to add this TreePreOpen event, which is what I needed.

Frankly I don't currently have a use of TreePreClose, but just added it for symmetry :)

@alex-courtis
Copy link
Member

  1. Closes do not fire when window is closed via, say, :q. They only fire when :NvimTreeClose or API close is called. This is existing behaviour.

  2. Open events are sometimes called twice: once in lib.open and again in view.open. This is consistent with existing behaviour.

Test Case:

-- subscribe to events
api.events.subscribe(api.events.Event.TreePreOpen, function(data)
  log.line("dev", "Event.TreePreOpen %s", vim.inspect(data))
end)

api.events.subscribe(api.events.Event.TreeOpen, function(data)
  log.line("dev", "Event.TreeOpen %s", vim.inspect(data))
end)

api.events.subscribe(api.events.Event.TreePreClose, function(data)
  log.line("dev", "Event.TreePreClose %s", vim.inspect(data))
end)

api.events.subscribe(api.events.Event.TreeClose, function(data)
  log.line("dev", "Event.TreeClose %s", vim.inspect(data))
end)

-- map 
vim.keymap.set("n", "<leader>c", function() api.tree.open({ current_window = true }) end,  { noremap = true })
vim.keymap.set("n", "<leader>n", function() api.tree.open({ current_window = false }) end, { noremap = true })

Open in current then :NvimTreeClose, expected:

[2025-04-13 09:28:22] [dev] Event.TreePreOpen nil
[2025-04-13 09:28:22] [dev] Event.TreeOpen nil
[2025-04-13 09:28:31] [dev] Event.TreePreClose nil
[2025-04-13 09:28:31] [dev] Event.TreeClose nil

Open in new then :NvimTreeClose, double opens, demonstrates 2

[2025-04-13 09:29:08] [dev] Event.TreePreOpen nil
[2025-04-13 09:29:08] [dev] Event.TreePreOpen nil
[2025-04-13 09:29:08] [dev] Event.TreeOpen nil
[2025-04-13 09:29:08] [dev] Event.TreeOpen nil
[2025-04-13 09:29:13] [dev] Event.TreePreClose nil
[2025-04-13 09:29:13] [dev] Event.TreeClose nil

Open two windows, open in current, then :q. No close event, demonstrates 1

[2025-04-13 09:30:53] [dev] Event.TreePreOpen nil
[2025-04-13 09:30:53] [dev] Event.TreeOpen nil
NOTHING HERE!

@alex-courtis
Copy link
Member

At a minimum we need to fix 2. This is a bug and will be more of a problem for PreOpen events than the Open event, as users are more likely to be executing non-idempotent actions during PreOpen.

How? I reckon we could move it down into view:
There are two entry points open_in_win and open. The latter fires the event, the former does not.
We could move the event dispatch from lib to view. I think that's enough as they are the only places that actually creates the tree buffer.

@alex-courtis
Copy link
Member

I'd also like to fix 1. Events not firing is not acceptable and we're compounding the problem with a new event. It should be achievable via a vim event listener.

What are your thoughts on all of this @gegoune ?

@alex-courtis
Copy link
Member

I've invited you to write access to this project @devansh08 ; you can push directly instead of having to fork every time.

@devansh08
Copy link
Collaborator Author

Sure @alex-courtis, will take a look at and try to implement separately what you have suggested for these cases. Open to thoughts from others as well.

@devansh08
Copy link
Collaborator Author

There are two entry points open_in_win and open. The latter fires the event, the former does not. We could move the event dispatch from lib to view. I think that's enough as they are the only places that actually creates the tree buffer.

So in lib, the open event is fired from lib.open(), which calls either view.open_in_win() or view.open(). I think it would be better to remove the event dispatch from view and leave it in lib, so that it fires once for both open_in_win and open. Unless there is some reason to not fire the event when opening in current window ?


Events not firing is not acceptable and we're compounding the problem with a new event. It should be achievable via a vim event listener.

For this, I see that NvimTreeClose triggers WinClosed event, via vim.api.nvim_win_close. So I'm thinking we can make autocmds for WinClosed which can dispatch TreeClose event; and QuitPre for TreePreClose event.
QuitPre fires before WinClosed and it seems like the buffer still exists while its fired. So it looks to me like the logical place to keep the PreClose dispatch.

vim.api.nvim_create_autocmd("WinClosed", { pattern = "*", callback = function(e) print("WinClosed: " .. vim.inspect(e)) end })
vim.api.nvim_create_autocmd("QuitPre", { pattern = "*", callback = function(e) print("QuitPre: " .. vim.inspect(e)) end })

-- Running `:q` in the NvimTree buffer (with current_window true or false)
--[[ Output:
QuitPre: {
  buf = 9,
  event = "QuitPre",
  file = "/home/devansh/NvimTree_1",
  id = 146,
  match = "/home/devansh/NvimTree_1"
}
WinClosed: {
  buf = 9,
  event = "WinClosed",
  file = "1011",
  id = 145,
  match = "1011"
}
]]

Thoughts ?

@alex-courtis @gegoune

@alex-courtis
Copy link
Member

So in lib, the open event is fired from lib.open(), which calls either view.open_in_win() or view.open(). I think it would be better to remove the event dispatch from view and leave it in lib, so that it fires once for both open_in_win and open. Unless there is some reason to not fire the event when opening in current window ?

Unfortunately lib.open is only one place that will open the tree; there are many other calls into view that will open the tree. create_buffer is the one place that all the open paths will reach, hence it should stay down there.

@alex-courtis
Copy link
Member

For this, I see that NvimTreeClose triggers WinClosed event, via vim.api.nvim_win_close. So I'm thinking we can make autocmds for WinClosed which can dispatch TreeClose event; and QuitPre for TreePreClose event.
QuitPre fires before WinClosed and it seems like the buffer still exists while its fired. So it looks to me like the logical place to keep the PreClose dispatch.

Sounds great... it will be a pleasure to have complete and reliable events!

@devansh08
Copy link
Collaborator Author

So in lib, the open event is fired from lib.open(), which calls either view.open_in_win() or view.open(). I think it would be better to remove the event dispatch from view and leave it in lib, so that it fires once for both open_in_win and open. Unless there is some reason to not fire the event when opening in current window ?

Unfortunately lib.open is only one place that will open the tree; there are many other calls into view that will open the tree. create_buffer is the one place that all the open paths will reach, hence it should stay down there.

Ah, I see... Alright, will remove the event dispatch from lib.open and add it to view.open_in_win.

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.

3 participants