nvim-neo-tree / neo-tree.nvim

Neovim plugin to manage the file system and other tree like structures.
MIT License
3.85k stars 224 forks source link

FEATURE: Shared neo-tree window across tabs #1235

Open sedmicha opened 11 months ago

sedmicha commented 11 months ago

Did you check the docs?

Is your feature request related to a problem? Please describe.

The README states this as one of the features:

While this might be the optimal behavior for many people, it doesn't really work for me, and I would like a configuration option to do the opposite - have one Neotree instance shared across all my tabs.

Describe the solution you'd like.

The easiest solution that comes to mind would be a true/false flag in the config options, e.g. something like

separate_windows_across_tabs = false

Describe alternatives you've considered.

Another option which I think would be cool and more flexible would be the ability to provide an something like an "instance ID" to the Neotree command as an optional argument. So for example doing

:Neotree float id=1

would always bring up the same Neotree instance as long as the same id is provided. This would default to the tabid if not provided, as is now.

Additional Context

I hope I'm not missing something obvious, but I spent a lot of time searching the internet, reading the docs, and even looking through the source code to find if there's a way to do this already, with no success.

pysan3 commented 11 months ago

I also open one neovim instance per directory, so even in different tabs, the tree will be identical and I actually wanted this kind of feature as well.

I think the cleanest solution would be to hijack vim.api.nvim_get_current_tabpage() and replace all occurrence with a function like below.

local function get_current_tabpage(tabid)
  if require("neo-tree").config.shared_tree_across_tabs then
    return 0 -- always return a fake constant
  else
    return tabid or vim.api.nvim_get_current_tabpage()
  end
end

I need to take care of few places where to_tabnr is called to not break it but this should not be a big deal.

I honestly think this is somewhat in the region of clever solution rather than a nasty hack, but @cseickel would you agree on this approach? Or even this idea in the first place?

If it is ok, I'll definitely test it thoroughly and will make a more detailed PR, but I'd love to hear your opinion before start working on it.

cseickel commented 11 months ago

@pysan3 First off, I think it's likely that this is something where there are going to be a lot of unexpected bugs that pop up that you won't find until it gets released. I say that because one tree per tab was such a core assumption in the original design.

I think if you are going to go through the trouble of changing this, the best way to handle it is to allow for a named instance like @sedmicha suggested. That would support other use cases besides just sharing an instance across tabs. I think internally we already have an id field for states which is based on the tab or window id. For a user provided id, we should prefix it with user_ or custom_ or something like that to disambiguate it in case they use a number which would conflict with those IDs.

So, I will merge it but it will be a long review with a lot of revisions because this is an extremely sensitive area. You would definitely need to add a suite of tests that cover per tab, per window, and named instances. I won't accept a quick fix that just gets the job done.

lougreenwood commented 11 months ago

❤️ - This is my biggest pain-point with neo-tree.

I've been thinking of hacking this in my local config, something like:

I'm not sure if this is possible with the current API, but it's the rough idea I've has floating around my head for a while.

But it would be amazing to see this natively be supported in neo-tree, good luck! 🤞

pysan3 commented 11 months ago

Thanks for the response @cseickel

I agree that we should take the biggest care to not bring any bugs and I like the idea to name the instances. We might even be able to save the state with that name and resume on a new vim session as well (but that will be a whole different story).

I will be looking forward to implementing this but due to real life issues, (as I mentioned in other issues as well) I'd be able to start working on this in February at the earliest.

I'd be happy if someone could start working on it, and if so, please let me know. Otherwise, plz wait till next year, and I'm pretty sure it will take some amount of time.

lougreenwood commented 11 months ago

I seem to have managed to hack something together (excpet for one issue with neo-tree events).

So far I've got:

However, a key piece is missing, saving the state of the neo-tree open state into the plugin. I tried to use the neo-tree events to save this, but it seems that they aren't triggered.

{
    "nvim-neo-tree/neo-tree.nvim",
    opts = function(_, opts)
      return {
        default_component_configs = {
          indent = {
            with_markers = false,
            with_expanders = true,
            expander_collapsed = "",
            expander_expanded = "",
            -- expander_collapsed = "",
            -- expander_expanded = "",
            padding = 2,
          },
        },
        window = {
          auto_expand_width = true,
          width = 50,
          mappings = {
            ["<cr>"] = "open_tab_drop",
            ["<C-b>"] = "open",
            ["<C-_>"] = "open_split",
            ["<C-\\>"] = "open_vsplit",
          },
        },
        filesystem = {
          filtered_items = {
            visible = true,
            hide_dotfiles = false,
            hide_gitignored = false,
            hide_hidden = false,
          },
          follow_current_file = {
            enabled = true,
            leave_dirs_open = true,
          },
        },
        event_handlers = {
          {
            event = "neo_tree_window_after_open",
            handler = function()
              local notify = require("astronvim.utils").notify
              notify "neotree-did-something"
              print("neotree opened: `mplugin.neotree_open` before save: " .. vim.inspect(myplugin.neotree_open))
              require("myplugin").neotree_open = true
              print("neotree opened: `mplugin.neotree_open` after save: " .. vim.inspect(myplugin.neotree_open))
            end,
          },
          {
            event = "neo_tree_window_after_close",
            handler = function()
              print("neotree closed: `myplugin.neotree_open` before save: " .. vim.inspect(myplugin.neotree_open))
              require("myplugin").neotree_open = false
              print("neotree close: `myplugin.neotree_open` after save: " .. vim.inspect(myplugin.neotree_open))
            end,
          },
        },
      }
    end,
  }

I know that generally the neo-tree config is working because all of my updated bindings are set. But I never see any prints or notifications from the event handlers when I open or close neo-tree... Maybe I'm mis-understanding how the events work?

If anyone has any ideas this is the missing piece and I could then share my hacky solution publicly....

cseickel commented 11 months ago

@lougreenwood The events are correct and they work for me if I copy your config. Maybe the plugin manager is just not updating as you make changes.

lougreenwood commented 11 months ago

Thanks @cseickel, I can confirm it was a bug on my end, or rather the issue is that the nvim distro that I use (AstroNvim) has a community packages repo. One of the packages in that repo which I am using was clobbering the event_handlers that I set. 🤦

I've got a working prototype that is able to remember a "global" open state of neotree in a given tab and when moving to another tab either open or close neotree in the destination tab depending on the global open state of neo tree.

Opening new files in new tabs (tabdrop etc) will auto open/close neotree in that tab.

Also, I'm integrating with resession so that the open state is remembered between sessions.

Generally, it's working well, the only issue I notice is that when the "global" neotree open state is true and if I either open a new tab or move to a tab which doesn't have neo-tree opened (e.g if I last had that tab last open when neo-tree was closed) then there there is a sudden re-draw and jumping of the nvim window content as the neo-tree is opened... but this only happens for the first time when visiting tabs whose neotree state differs from the global state.

Ideally, if there were a TabEnterPre and NeoTree also allowed executing an action against a given NeoTree instance by id then I would be able to toggle the open/close state before a user enters a given tab.

An alternate solution might be to still add a feature to NeoTree allowing a use of command.execute to specify a specific tabpage or neo-tree instance ID so that when neo-tree is toggled in one tab, I could asyncronously trigger the open/closing of NeoTree in all the other tabs in the background. It might also be possible to use this with TabNew depending on whether TabNew is triggered before we see the new tabpage on screen....

@cseickel WDYT of the above, could you see the command.execute api being expanded to allow targeting specific neo-tree instances or tab pages?

Hopefully I'll create some plugin or snippet soon so that others can also make use of this.

Here are a few videos for anyone who's interested:

Synchronised NeoTree state across tabs

https://github.com/nvim-neo-tree/neo-tree.nvim/assets/12051736/958f76a3-8a15-4459-b2a3-2d2b5bf1da34

Restoring NeoTree state between nvim sessions

https://github.com/nvim-neo-tree/neo-tree.nvim/assets/12051736/eca3d8f4-6d30-491c-b89e-de36693157ff

pysan3 commented 11 months ago

@lougreenwood I think this is a great solution and I'll pass it to @cseickel to decide about the API change.

However, let me note that what I meant as a true solution with the same named instance will,

lougreenwood commented 11 months ago

Thanks @pysan3! A few comments below

share the open / close state of the folders as well on new tabs

Yep, for me reveal the current file, so it's implicitly handled for me in my current setup... however if the appropriate events exist for tracking changes to the current file in a given neotree instance exist as well as api to update the current file for any neotree instance (by id etc) exists, I could also see it being possible to add this to my hacky fix (for now).

when you toggle folder in one tab, neo-tree on other tabs will also reflect that

This is already handled in my version, the only issue at the moment is that the update only happens when you enter that tab. As I mentioned above, adding to the API for command.execute to allow executing against a neotree instance in another tab would allow me to handle this in the background. Or maybe there's another way (which I didn't consider yet), which would be to get a list of all NT instances and iterate and toggle them when the current tab's NT instance is opened/closed.

However... with all of that said. A proper native solution like you suggest would be my preference too. But maybe my hack can be used as a reference implementation for that native one (assuming I finish polishing mine before the work on the proper native one begins)...

jondkinney commented 10 months ago

I'm very interested in this coming from using NERDTree Tabs for many years.

From the README

Just one NERDTree, always and ever. It will always look the same in all tabs, including expanded/collapsed nodes, scroll position etc.

jondkinney commented 10 months ago

@lougreenwood I'm very interested in what you're doing here

CleanShot 2023-12-15 at 00 14 57 Since in AstroNVim it's very difficult for me to open more than 2-3 files in a given tab with how things constantly replace each other in the splits I have in a window with the current more buffer focused but tab-looking heirline. And the actual tabs are a bit harder to work with since they're sort of hidden in the upper right of the UI.

Anyway, slightly off topic from this issue, but since we're talking bout syncing neotree across tabs it seemed somewhat relevant.