nvim-neo-tree / neo-tree.nvim

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

Restore tree state in session restore #128

Open cseickel opened 2 years ago

cseickel commented 2 years ago

Discussed in https://github.com/nvim-neo-tree/neo-tree.nvim/discussions/126

Originally posted by **bennypowers** February 6, 2022 Hello! I'm using neo-tree with `auto-session` and `bufferline.nvim`, and notice the following: - WHEN neo-tree is open and I quit vim with buffers open in session via `quitall` - THEN when I reopen the session from the shell prompt, - The left pane (where the tree should be) is blank - A new, empty buffer appears in the buffer line labeled `neo-tree filesystem [1]` - the bufferline, normally offset via bufferline config, is flush to the left of the screen Screen Shot 2022-02-06 at 11 06 34 Screen Shot 2022-02-06 at 11 06 40 I'd like instead for the session to restore as is, i.e. - WHEN neo-tree is open and I quit vim with buffers open in session via `quitall` - THEN when I reopen the session from the shell prompt, - The left pane (where the tree should be) contains a neo-tree with the same state as at quit - the bufferline offsets from the tree as it did before quit - the state of the bufferline persists Should I hook into auto-session to prevent saving buffers with type `neo-tree` and to focus the active buffer on startup?
cseickel commented 2 years ago

@bennypowers I am trying out auto-session and I really like it. Unfortunately I can get this or my old session manager to restore the tree windows at all, not even as empty buffers. It is just completely omitted from the session restore.

Is it just me or does that happen to you as well with the latest updates?

aiecee commented 2 years ago

The way I got around this with auto-session was to use the command hooks. pre_save_cmds to do tabdo NeoTreeClose post_save_cmds to do tabdo NeoTreeReveal post_restore_cmds to do tabdo NeoTreeReveal Sorry I can't provide an example setup I switched to neovim-session-manager yesterday

bennypowers commented 1 year ago
This is my current config: ```lua local function close_neo_tree() require 'neo-tree.sources.manager'.close_all() end local function open_neo_tree() require 'neo-tree.sources.manager'.show('filesystem') end require 'auto-session'.setup { auto_session_create_enabled = false, auto_save_enabled = true, auto_restore_enabled = true, auto_session_use_git_branch = true, bypass_session_save_file_types = { "neo-tree", "tsplayground", "query", }, pre_save_cmds = { close_neo_tree, }, post_restore_cmds = { open_neo_tree, } } ```

It closes the neo-tree before saving the session, then opens it again whenever it finds a session

this is ok. I'd rather it only open a session IFF the tree was open when it saved the session

edit: made a little progress i suppose, but my tree state is always false. In other words I'm missing a handy way to determine whether or not any filesystem tree is currently open.

local TREE_STATE = '__AS_NT_FS_Open'

local function has_tree()
  local manager = require 'neo-tree.sources.manager'
  local states = manager.get_state 'filesystem'
  vim.api.nvim_set_var(TREE_STATE, #states > 0)
end

local function close_neo_tree()
  local manager = require 'neo-tree.sources.manager'
  has_tree()
  manager.close_all()
end

local function open_neo_tree()
  if vim.api.nvim_get_var(TREE_STATE) then
    require 'neo-tree.sources.manager'.show('filesystem')
  end
end

require 'auto-session'.setup {
  auto_session_create_enabled = false,
  auto_save_enabled = true,
  auto_restore_enabled = true,
  auto_session_use_git_branch = true,
  bypass_session_save_file_types = {
    "neo-tree",
    "tsplayground",
    "query",
  },
  pre_save_cmds = {
    close_neo_tree,
  },
  post_restore_cmds = {
    open_neo_tree,
  }
}
nmsobri commented 1 year ago

so what is the status of this?

cseickel commented 1 year ago

@nmsobri It's not something that I'm looking at, and it's not something I'm interested in because I don't want the tree saved as part of the session. Someone else would have to volunteer to do work in this area for it to ever get done.

nmsobri commented 1 year ago

@cseickel alright.. can you point me to the direction on how I can achieve this?

cseickel commented 1 year ago

I think the most important thing to know with regards to sessions is that neo-tree will create several buffer local variables that you can use to save and restore the state:

    vim.api.nvim_buf_set_var(state.bufnr, "neo_tree_source", state.name)
    vim.api.nvim_buf_set_var(state.bufnr, "neo_tree_tabnr", state.tabnr)
    vim.api.nvim_buf_set_var(state.bufnr, "neo_tree_position", state.current_position)
    vim.api.nvim_buf_set_var(state.bufnr, "neo_tree_winid", state.winid)

You can use this information in a pre-session save function that iterates over all open buffers that are displayed in a window to save the state.

If your session creation method includes saving buffer local variables, then you can either create your own autocmd or submit a PR here that reads in those variables and opens a new neo-tree instance to replace it.

nmsobri commented 1 year ago

nvm, i figure it out using shada

stevenxxiu commented 9 months ago

I've just started out using Neovim, and encountered this issue too. I'm using bufferline, so I just have a single neo-tree buffer. This means in my case, there's no need to store the information @cseickel suggested in the previous post. However I did want to save:

Through looking at the neo-tree source and really helpful comments above, I did manage to achieve this. I had to use the possession plugin, which offers saving user-specified custom data in the session file.

My config is as follows:

--[[ *possession* ]]
neo_expand_dirs = function(dir_paths, dir_i)
  if dir_i > #dir_paths then
    return
  end
  local cur_dir_path = dir_paths[dir_i]
  vim.loop.fs_opendir(cur_dir_path, function(err, dir)
    if err then
      print(cur_dir_path, ': ', err)
      neo_expand_dirs(dir_paths, dir_i + 1)
      return
    end
    vim.loop.fs_readdir(dir, function(err, entries)
      if entries[1] ~= nil then
        vim.schedule(function()
          local utils = require('neo-tree.utils')
          local manager = require('neo-tree.sources.manager')
          local filesystem = require('neo-tree.sources.filesystem')

          local state = manager.get_state('filesystem')
          local child_path = utils.path_join(cur_dir_path, entries[1].name)
          filesystem._navigate_internal(state, state.path, child_path, function()
            state.explicitly_opened_directories = state.explicitly_opened_directories or {}
            state.explicitly_opened_directories[cur_dir_path] = true
            neo_expand_dirs(dir_paths, dir_i + 1)
          end)
        end)
      end
    end)
  end)
end

neo_get_state = function()
  for _, buf_i in ipairs(vim.api.nvim_list_bufs()) do
    if vim.api.nvim_buf_get_option(buf_i, 'filetype') == 'neo-tree' and next(vim.fn.win_findbuf(buf_i)) then
      local utils = require('neo-tree.utils')
      local manager = require('neo-tree.sources.manager')
      local filesystem = require('neo-tree.sources.filesystem')

      local state = manager.get_state('filesystem')
      local expanded_dirs = {}
      if state.explicitly_opened_directories ~= nil then
        for cur_dir, is_expanded in pairs(state.explicitly_opened_directories) do
          if is_expanded then
            table.insert(expanded_dirs, cur_dir)
          end
        end
      end
      return { path = state.path, expanded_dirs = expanded_dirs, show_hidden = state.filtered_items.visible }
    end
  end
end

neo_set_state = function(data)
  require('neo-tree.command').execute({
    action = 'show',
    dir = data['path'],
  })
  if data['show_hidden'] then
    local manager = require('neo-tree.sources.manager')
    local state = manager.get_state('filesystem')
    state.filtered_items.visible = true
  end
  neo_expand_dirs(data['expanded_dirs'], 1)
end

require('possession').setup({
  autosave = {
    current = true,
  },
  commands = {
    save = 'SSave',
    load = 'SLoad',
    rename = 'SRename',
    close = 'SClose',
    delete = 'SDelete',
    show = 'SShow',
    list = 'SList',
    migrate = 'SMigrate',
  },
  hooks = {
    before_save = function(name)
      local res = {}
      local neo_state = neo_get_state()
      if neo_state ~= nil then
        res['neo_tree'] = neo_state
      end
      return res
    end,
    after_save = function(name, user_data, aborted) end,
    before_load = function(name, user_data) return user_data end,
    after_load = function(name, user_data)
      if user_data['neo_tree'] ~= nil then
        neo_set_state(user_data['neo_tree'])
      end
    end,
  },
  plugins = {
    delete_hidden_buffers = false, -- For *bufferline*
  },
})

@cseickel I wonder if you're open to a PR to include the neo_expand_dirs() function? It just expands a list of user-specified directories. I think it's quite a useful function for config files.

Even better if there's a better way to do what I wrote.

cseickel commented 9 months ago

I would accept a PR that adds an api method to save and restore this state, but I don't think that most of the work you are doing in neo_expand_dirs() is necessary. I'm pretty sure this could be done just by setting the entire explicitly_opened_directories object as-is and doing one navigate.

stevenxxiu commented 9 months ago

Hm the only place I see in the code that reads explicitly_opened_directories is https://github.com/nvim-neo-tree/neo-tree.nvim/blob/7e2a3caf999e2028abb643eb0472f351b2777591/lua/neo-tree/sources/filesystem/init.lua#L71. I think it depends on expanded_nodes being set.

I tried the following, which does nothing:

neo_expand_dirs = function(dir_paths, dir_i)
  local utils = require('neo-tree.utils')
  local manager = require('neo-tree.sources.manager')
  local filesystem = require('neo-tree.sources.filesystem')

  local state = manager.get_state('filesystem')
  for _, cur_dir_path in ipairs(dir_paths) do
    state.explicitly_opened_directories = state.explicitly_opened_directories or {}
    state.explicitly_opened_directories[cur_dir_path] = true
  end
end
cseickel commented 9 months ago

You're right, that property is actually the entirely wrong thing to use. See this code that actually clones the state of the tree into a new window:

https://github.com/nvim-neo-tree/neo-tree.nvim/blob/7e2a3caf999e2028abb643eb0472f351b2777591/lua/neo-tree/setup/init.lua#L340-L346

What you really want to do is to get the opened directories like this:

renderer.get_expanded_nodes(old_state.tree)

and restore that output to

state.force_open_folders
stevenxxiu commented 9 months ago

Ah thanks a lot for that. That simplified my code heaps. Now it's just:

--[[ *possession* ]]
neo_is_open = function()
  for _, buf_i in ipairs(vim.api.nvim_list_bufs()) do
    if vim.api.nvim_buf_get_option(buf_i, 'filetype') == 'neo-tree' and next(vim.fn.win_findbuf(buf_i)) then
      return true
    end
  end
end

neo_get_state = function()
  if not neo_is_open() then
    return
  end
  local manager = require('neo-tree.sources.manager')
  local renderer = require('neo-tree.ui.renderer')

  local state = manager.get_state('filesystem')
  local expanded_nodes = renderer.get_expanded_nodes(state.tree)
  return { path = state.path, expanded_nodes = expanded_nodes, show_hidden = state.filtered_items.visible }
end

neo_set_state = function(data)
  local command = require('neo-tree.command')
  local manager = require('neo-tree.sources.manager')
  local state = manager.get_state('filesystem')

  command.execute({
    action = 'show',
    dir = data['path'],
  })
  state.filtered_items.visible = data['show_hidden']
  state.force_open_folders = data['expanded_nodes']
end
axelhj commented 5 months ago

Hi, I found this thread useful as I'm currently exploring session management with rmagatti/auto-session and neo-tree.

I decided to try and persist the open/closed state of Neo-tree when exiting vim. I use a global variable to track whether neo-tree was opened on last SessionSave. I needed to call vim.cmd":wshada" in the pre_save_cmds hook in order for the variable to be saved to the shada file. Similarly, I lazy-load auto-session on the "UIEnter" event to make sure that the global variable is restored when I reopen Neo-tree.

It is good to know that the full tree-state can be persisted & restored efficiently with Possession which seem more straightforward than using the global variable.

And it is good to know that there is a proposed enhancement to have Neo-tree restore itself on session load.

PixsaOJ commented 4 months ago

Could somebody include full instructions for this please? maybe using rmagatti/auto-session ?

https://github.com/nvim-neo-tree/neo-tree.nvim/discussions/126 this is from their project

axelhj commented 3 months ago

@PixsaOJ The possession can be configured to use some Neotree hooks on save/restore to save its state as steven showed. But I didn't find a session-plugin that does this automatically yet. Like this .

Edit: I published my session-config as a plugin. It depends on Neotree + Bufferline but it's all in the readme. It's not very configurable but it does restore buffers & the Neotree buffer on restarts. Make sure that the $XDG_CONFIG_HOME/nvim-data/sessions folder exists.