Skip to content

Restore tree state in session restore #128

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
cseickel opened this issue Feb 6, 2022 Discussed in #126 · 16 comments
Open

Restore tree state in session restore #128

cseickel opened this issue Feb 6, 2022 Discussed in #126 · 16 comments
Labels
enhancement New feature or request

Comments

@cseickel
Copy link
Contributor

cseickel commented Feb 6, 2022

Discussed in #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 cseickel added the enhancement New feature or request label Feb 6, 2022
@cseickel
Copy link
Contributor Author

cseickel commented Feb 8, 2022

@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
Copy link

aiecee commented Feb 24, 2022

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
Copy link

bennypowers commented Aug 2, 2022

This is my current config:
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
Copy link

nmsobri commented Mar 24, 2023

so what is the status of this?

@cseickel
Copy link
Contributor Author

@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
Copy link

nmsobri commented Mar 25, 2023

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

@cseickel
Copy link
Contributor Author

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
Copy link

nmsobri commented Mar 26, 2023

nvm, i figure it out using shada

@stevenxxiu
Copy link

stevenxxiu commented Sep 29, 2023

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:

  • The root directory.
  • The list of expanded folders.
  • Whether hidden files are shown.

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
Copy link
Contributor Author

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
Copy link

Hm the only place I see in the code that reads explicitly_opened_directories is

if not state.explicitly_opened_directories[id] then
. 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
Copy link
Contributor Author

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:

-- create a new tree for this window
local state = manager.get_state("filesystem", nil, current_winid)
state.path = old_state.path
state.current_position = "current"
local renderer = require("neo-tree.ui.renderer")
state.force_open_folders = renderer.get_expanded_nodes(old_state.tree)
require("neo-tree.sources.filesystem")._navigate_internal(state, nil, nil, nil, false)

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
Copy link

stevenxxiu commented Oct 2, 2023

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
Copy link

axelhj commented Jan 7, 2024

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
Copy link

PixsaOJ commented Feb 26, 2024

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

#126 this is from their project

@axelhj
Copy link

axelhj commented Mar 28, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants