Skip to content
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

Sync closing of nvim-tree across tabs #1698

Merged
merged 17 commits into from
Nov 19, 2022

Conversation

rwblokzijl
Copy link
Contributor

@rwblokzijl rwblokzijl commented Oct 28, 2022

Resolves #1493

This PR syncs nvim tree across tabs if open_on_tab is set.

Previously:

  • On tab change this would open nvim-tree
  • When having closed nvim-tree it would still open nvim tree on tab change

Now:

  • when having closed nvim-tree it will sync this on tab change
  • if nvim-tree is closed (using :quit) this is also synced across tabs
  • if nvim-tree is the final window in a tab, the tab is closed (if this is the last tab, vim is closed)

@rwblokzijl
Copy link
Contributor Author

Continuation of #1662, which had the following review comments:

@alex-courtis
Copy link
Member

I think I'm missing something fundamental here. Pressing q on a node results in it being opened rather than the tree closing.

nvim -nu nvt-dev.lua

Pressed q on .luacheckrc:
asciicast

It looks like handle_tree_actions is falling through to open_file.

local view = require "nvim-tree.view"
local lib = require "nvim-tree.lib"

local M = {}

local Actions = {
close = view.close,
close = api.close,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you're referencing the vim api instead of nvim-tree

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Oct 29, 2022

First of all, thanks for looking at this!

I think I'm missing something fundamental here. Pressing q on a node results in it being opened rather than the tree closing.

nvim -nu nvt-dev.lua

Pressed q on .luacheckrc: ...

It looks like handle_tree_actions is falling through to open_file.

This is something i broke accidentally, and i think i need some advice on how not to do that.

I'll explain my goal with this PR in more detail.

Nvim-tree implements the open_on_tab flag. What this does is ensure Nvim tree is open on all tabs IFF there is already a tree open somewhere. However, once nvim tree is open there is no way to properly close it again. This PR closes all instances of Nvim-tree on close. This is only done when open_on_tab is set. This goes pretty far in making nvim-tree "persistent" across tabs.

In order to only trigger the new behaviour if open_on_tab is set. close needs to know whether the opt is set. Thus i create nvim-tree.lua close to be the new general interfact instead of view.close.

This also means api.tree.close is now. nvim-tree.close instead of view.close.

I replaced all other instances of view.close with api.close, which should have been api.tree.close, but when i do that i get some errors, i now see. I think nvim-tree.close should be used here instead. But i wanted some advice here first before i change it.

@alex-courtis
Copy link
Member

alex-courtis commented Oct 30, 2022

I replaced all other instances of view.close with api.close, which should have been api.tree.close, but when i do that i get some errors, i now see. I think nvim-tree.close should be used here instead. But i wanted some advice here first before i change it.

It looks like we have some inconsistency around vim.api:

:; ag --nogroup "local a .*vim.api"
lua/nvim-tree/actions/fs/remove-file.lua:1:local a = vim.api
lua/nvim-tree/actions/init.lua:3:local a = vim.api
lua/nvim-tree/actions/node/file-popup.lua:2:local a = vim.api
lua/nvim-tree/actions/fs/trash.lua:1:local a = vim.api
lua/nvim-tree/actions/root/change-dir.lua:1:local a = vim.api
lua/nvim-tree/diagnostics.lua:1:local a = vim.api
lua/nvim-tree/live-filter.lua:1:local a = vim.api
lua/nvim-tree/utils.lua:3:local a = vim.api
lua/nvim-tree/view.lua:1:local a = vim.api
:; ag --nogroup "local api.*vim.api"
lua/nvim-tree/actions/finders/search-node.lua:1:local api = vim.api
lua/nvim-tree/actions/node/open-file.lua:2:local api = vim.api
lua/nvim-tree/lib.lua:1:local api = vim.api
lua/nvim-tree/renderer/components/full-name.lua:3:local api = vim.api
lua/nvim-tree/renderer/init.lua:15:local api = vim.api
lua/nvim-tree.lua:2:local api = vim.api
lua/nvim-tree/actions/dispatch.lua:1:local api = vim.api
lua/nvim-tree/colors.lua:1:local api = vim.api

Let's tidy things up and use a for vim.api and api for nvim-tree API:
local a = vim.api
local api = require"nvim-tree.api"

Edit: let's just explicitly invoke vim.api to avoid any confusion. PR incoming.

@alex-courtis
Copy link
Member

PR to remove the vim.* "require"s: #1701

chore-remove-vim-quote-requires-quote

I'm going to soak that for a few days of regular usage before merging it.

That should give you a good base to work from. You could rebase to that branch, merge it or create a new branch from it.

@alex-courtis
Copy link
Member

Nvim-tree implements the open_on_tab flag. What this does is ensure Nvim tree is open on all tabs IFF there is already a tree open somewhere. However, once nvim tree is open there is no way to properly close it again. This PR closes all instances of Nvim-tree on close. This is only done when open_on_tab is set. This goes pretty far in making nvim-tree "persistent" across tabs.

That is the right course of action. It is implicitly consistent with :help nvim-tree.open_on_tab. We can add to that description to specify this closing behaviour.

@alex-courtis
Copy link
Member

In order to only trigger the new behaviour if open_on_tab is set. close needs to know whether the opt is set. Thus i create nvim-tree.lua close to be the new general interfact instead of view.close.

This also means api.tree.close is now. nvim-tree.close instead of view.close.

Will this be an issue when not using open_on_tab? We need to test to ensure that the user can still close the tree on just one tab.

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • use chore-remove-vim-quote-requires-quote branch
  • test behaviour unchanged when not open_on_tab
  • add to open_on_tab help (I'll make that change later)

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Oct 30, 2022

  • use chore-remove-vim-quote-requires-quote branch
  • test behaviour unchanged when not open_on_tab
  • add to open_on_tab help (I'll make that change later)

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Oct 30, 2022

Im in doubt about what to do with the following usaged of the old view.close. These need sync_on_tabs as input, but don't have easy access to the config. And using nvim-tree.api.close leads to import loops.

@alex-courtis
Copy link
Member

Created dummy PR #1704 to aid in reviewing against the unmerged branch.

@alex-courtis
Copy link
Member

Im in doubt about what to do with the following usaged of the old view.close. These need sync_on_tabs as input, but don't have easy access to the config. And using nvim-tree.api.close leads to import loops.

I see. That is a problem.

Summary, please correct me:

  • close_on_tab only needs to be obeyed on:
    • Api.tree.close
    • nvim-tree.lua WinLeave autocommand
    • nvim-tree.lua toggle action
  • other calls to view.close do not obey close_on_tab
  • view.lua can be made aware of close_on_tab in its setup

@alex-courtis
Copy link
Member

Idea:

  • remove nvim-tree.lua close
  • revert API to Api.tree.close = require("nvim-tree.view").close
  • Revent view.close signature change and have it always obey close_on_tab

Refactor view to something like:

local function close(all_tabpages)
  ---
end

function M.close_this_tab_only()
  close(false)
end

function M.close()
  close(M.View.open_on_tab)
end

API remains unchanged. view.close(false) calls will be changed to view.close_this_tab_only.

@alex-courtis
Copy link
Member

alex-courtis commented Oct 31, 2022

Let's build it and experiment. We can change these to call view.close or view.close_this_tab_only after testing.

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Nov 5, 2022

Idea:

  • remove nvim-tree.lua close
  • revert API to Api.tree.close = require("nvim-tree.view").close
  • Revent view.close signature change and have it always obey close_on_tab

Refactor view to something like:

local function close(all_tabpages)
  ---
end

function M.close_this_tab_only()
  close(false)
end

function M.close()
  close(M.View.open_on_tab)
end

API remains unchanged. view.close(false) calls will be changed to view.close_this_tab_only.

I Implemented this.

I also did some experimentation and found the following regarding the usages of close:

Usage location My choice reasoning
lua/nvim-tree/actions/node/open-file.lua:146: view.close() Here the close behavior is dependent on the setting actions.open_file.quit_on_open. If we closed only the current tab, it would reopen again if the user switches tabs and back. This is an artifact of how open_on_tab is/(was already) implemented: "if any nvim-tree is open, then open on tab". Thus i think the correct setting here is closing in all tabs. I think this would be expected behavior when open_on_tab and quit_on_open are both set anyway.
lua/nvim-tree/actions/node/open-file.lua:310: view.close() idem
lua/nvim-tree/lib.lua:120: view.close_this_tab_only() Here it seems to be about the setting hijack_unnamed_buffer_when_opening. In this case existing buffers in the tab are closed and reopened in the unnamed buffer. This means only the current tab should be closed.
lua/nvim-tree/live-filter.lua:34: view.close() This is only triggered if opts.view.float.quit_on_focus_loss is set. This means only 1 instance of nvim-tree can exist at a time anyway, making behaviour identical.
lua/nvim-tree.lua:73: view.close() User triggered toggle
lua/nvim-tree.lua:301: view.close() User triggered close
lua/nvim-tree.lua:442: view.close() Idem live-filter
lua/nvim-tree.lua:778: view.close() Close all on load?
lua/nvim-tree/actions/dispatch.lua:7: Need advice. I cant understand what this is trying to do. It seems to be triggered by view.close itself through events._dispatch_on_tree_close.

@rwblokzijl
Copy link
Contributor Author

I also think sync_across_tabs is a better name for the opt than open_on_tab as it describes the behavior better. What would be the least destructive way of updating this? Or should i just leave it as open_on_tab.

@alex-courtis
Copy link
Member

I also think sync_across_tabs is a better name for the opt than open_on_tab as it describes the behavior better. What would be the least destructive way of updating this? Or should i just leave it as open_on_tab.

That is a good idea. I'll make/document that change as a silent refactor.

@alex-courtis
Copy link
Member

Usage location My choice Alex Choice Tested open_on_tab Tested no open_on_tab
lua/nvim-tree/actions/node/open-file.lua:146 view.close() Y Y Y
lua/nvim-tree/actions/node/open-file.lua:310 view.close() Y Y Y
lua/nvim-tree/lib.lua:120 view.close_this_tab_only() Y Y Y
lua/nvim-tree/live-filter.lua:34 view.close() Y Y Y
lua/nvim-tree.lua:73 view.close() Y Y Y
lua/nvim-tree.lua:301 view.close() Y Y Y
lua/nvim-tree.lua:442 view.close() Y Y Y
lua/nvim-tree.lua:778 view.close() Yes. We will need an explicit close_all_tabs here, to remove any remnants of the previous "nvim-tree session". We should also add that to the API for users that don't use open_on_tab. . .
lua/nvim-tree/actions/dispatch.lua:7 Need advice. view.close is appropriate. It's only used by the "close" action. Changed to the function pointer close = view.close, and tested OK. Y Y

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is working nicely.

@rwblokzijl

  • add close_all_tabs
  • fix lua/nvim-tree/actions/dispatch.lua:7 to use function pointer, test q
  • remove TODOs

@alex-courtis

  • rename or split option
  • silent option migration
  • doc

I'll make my changes after yours.

@rwblokzijl
Copy link
Contributor Author

  • add close_all_tabs
  • fix lua/nvim-tree/actions/dispatch.lua:7 to use function pointer, test q
  • remove TODOs

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

if view.is_visible { any_tabpage = true } then
local bufname = vim.api.nvim_buf_get_name(0)
local ft = vim.api.nvim_buf_get_option(0, "ft")
for _, filter in ipairs(M.config.ignore_buf_on_tab_change) do
for _, filter in ipairs(M.config.tab.sync.ignore) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work very well, however that is for another day.

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added doc and new silently migrated options:

  tab = {
    sync = {
      open = false,
      close = false,
      ignore = {},
    },
  },

Please:

  • test migration of open_on_tab to tab.sync.open
  • test migration of ignore_buf_on_tab_change to tab.sync.ignore
  • test all of the changes from the table (thanks for putting that together, it was very useful)
  • fix close_in_all_tabs failure when no sync

vim.api.nvim_win_close(v.winnr, true)
end
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is failing when the tree is not open in the current tab:

    tab = {
      sync = {
        open = false,
        close = false,
        ignore = {},
      },
    },

Fail:

:NvimTreeOpen
:tabnew
:lua require"nvim-tree.api".tree.close_in_all_tabs()

Pass:

:NvimTreeOpen
:tabnew
:NvimTreeOpen
:lua require"nvim-tree.api".tree.close_in_all_tabs()

It looks like we will need to move the all-tabs-closing bit out of the nvim_list_wins loop.

@alex-courtis
Copy link
Member

<C-w>c on the nvim-tree window does not result in expected behaviour however that goes into the "no auto close" Too Bad bucket.

We could also enhance the tab_enter event to obey tab.sync.close however that may be equally problematic.

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Nov 12, 2022

<C-w>c on the nvim-tree window does not result in expected behaviour however that goes into the "no auto close" Too Bad bucket.

We could also enhance the tab_enter event to obey tab.sync.close however that may be equally problematic.

This falls in the same category as :q. In my personal config i use the following snippet to solve it (among other things):

local function tab_win_closed(winnr)
  local tabnr = vim.api.nvim_win_get_tabpage(winnr)
  local bufnr = vim.api.nvim_win_get_buf(winnr)
  local buf_info = vim.fn.getbufinfo(bufnr)[1]
  local tab_wins = vim.tbl_filter(function(w) return w~=winnr end, vim.api.nvim_tabpage_list_wins(tabnr))
  local tab_bufs = vim.tbl_map(vim.api.nvim_win_get_buf, tab_wins)
  if buf_info.name:match(".*NvimTree_%d*$") then            -- close buffer was nvim tree
    -- Close all nvim tree on :q
    if not vim.tbl_isempty(tab_bufs) then                      -- and was not the last window (not closed automatically by code below)
      api.tree.close()
    end
  else                                                      -- else closed buffer was normal buffer
    if #tab_bufs == 1 then                                    -- if there is only 1 buffer left in the tab
      local last_buf_info = vim.fn.getbufinfo(tab_bufs[1])[1]
      if last_buf_info.name:match(".*NvimTree_%d*$") then       -- and that buffer is nvim tree
        vim.schedule(function ()
          if #vim.api.nvim_list_wins() == 1 then                -- if its the last buffer in vim
            vim.cmd "quit"                                        -- then close all of vim
          else                                                  -- else there are more tabs open
            vim.api.nvim_win_close(tab_wins[1], true)             -- then close only the tab
          end
        end)
      end
    end
  end
end

vim.api.nvim_create_autocmd("WinClosed", {
  callback = function ()
    local winnr = tonumber(vim.fn.expand("<amatch>"))
    vim.schedule_wrap(tab_win_closed(winnr))
  end,
  nested = true
})

Perhaps it could go in the tips and tricks section.

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Nov 12, 2022

Please:

  • test migration of open_on_tab to tab.sync.open
  • test migration of ignore_buf_on_tab_change to tab.sync.ignore
  • test all of the changes from the table (thanks for putting that together, it was very useful)
Usage sync=false sync=true
lua/nvim-tree/actions/node/open-file.lua:146: x x
lua/nvim-tree/actions/node/open-file.lua:310: x x
lua/nvim-tree/lib.lua:120: x x
lua/nvim-tree/live-filter.lua:34: x x
lua/nvim-tree.lua:73: x x
lua/nvim-tree.lua:301: x x
lua/nvim-tree.lua:442: x x
lua/nvim-tree.lua:778: ~ ~
lua/nvim-tree/actions/dispatch.lua:7: ~ ~

I cant figure out how to properly test the last 2.

I did notice that when hijack_unnamed_buffer_when_opening is set the new buffer created on new tab is not hijacked. But this was already present before. The fix would be to use M.open in lua/nvim-tree.lua:128. However this doesn't allow the use of focus_tree = false, so it would take a refactor to fix.

  • fix close_in_all_tabs failure when no sync

I looked into the way i integrated close all tabs into view.close and noticed that i was skipping a lot of things related to saving state. As a result the cursor position isnt saved when nvim-tree is reopened in that tab. I refactored local close to work on (any) single tab. This fixes the save issue, as well as the "failure when no sync".

@alex-courtis
Copy link
Member

alex-courtis commented Nov 12, 2022

Perhaps it could go in the tips and tricks section.

Yes.

There's a reference to #1115 in there but that's not very explicit. There are several solutions people have used for this.

A RECIPE wiki page collecting all the solutions would be good. I'll look into that.

Edit: wiki page

I'd be grateful if you could add your solution.

@alex-courtis
Copy link
Member

alex-courtis commented Nov 13, 2022

I cant figure out how to properly test the last 2.

lua/nvim-tree.lua:778:

This is for the case of the user invoking setup a second time and should remove the tree views/windows.

This is actually currently broken, as only the view/window in the current tab is removed.

Added abandon_all_windows and removed the view visibility check.

Please test this one. You can do it using the minimal config template and :lua _G.setup()

Edit: see wiki development

@alex-courtis
Copy link
Member

I cant figure out how to properly test the last 2.

lua/nvim-tree/actions/dispatch.lua:7:

This one is simply the close action q.

Working nicely for me when tab.sync.close = true.

@alex-courtis
Copy link
Member

I did notice that when hijack_unnamed_buffer_when_opening is set the new buffer created on new tab is not hijacked. But this was already present before. The fix would be to use M.open in lua/nvim-tree.lua:128. However this doesn't allow the use of focus_tree = false, so it would take a refactor to fix.

I'm happy to let that one go, however if you'd like to put together a fix I would be most grateful.

Copy link
Member

@alex-courtis alex-courtis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested OK

@rwblokzijl @alex-courtis

  • test this for a week's regular usage

@rwblokzijl

@rwblokzijl
Copy link
Contributor Author

rwblokzijl commented Nov 13, 2022

Tested OK

@rwblokzijl @alex-courtis

  • test this for a week's regular usage

Will do.

@rwblokzijl

It works (with and without sync).

minimal config
vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.cmd [[set packpath=/tmp/nvt-min/site]]
local package_root = "/tmp/nvt-min/site/pack"
local install_path = package_root .. "/packer/start/packer.nvim"
local function load_plugins()
require("packer").startup {
  {
    "wbthomason/packer.nvim",
    {"rwblokzijl/nvim-tree.lua", branch="sync-close-through-tabs"},
    "nvim-tree/nvim-web-devicons",
    -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
  },
  config = {
    package_root = package_root,
    compile_path = install_path .. "/plugin/packer_compiled.lua",
    display = { non_interactive = true },
  },
}
end
if vim.fn.isdirectory(install_path) == 0 then
print "Installing nvim-tree and dependencies."
vim.fn.system { "git", "clone", "--depth=1", "https://github.com/wbthomason/packer.nvim", install_path }
end
load_plugins()
require("packer").sync()
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua setup()]]
vim.opt.termguicolors = true
vim.opt.cursorline = true

-- MODIFY NVIM-TREE SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
_G.setup = function()
require("nvim-tree").setup {
  tab = {
    sync = {
      open = true,
      close = true,
      ignore = {},
    }
  },
}
end
  • optionally fix hijack_unnamed_buffer_when_opening

I'll skip this for now. I might open a PR for this at some point.

@alex-courtis alex-courtis merged commit c494994 into nvim-tree:master Nov 19, 2022
@alex-courtis
Copy link
Member

Many thanks for all your work @rwblokzijl

@erikw
Copy link

erikw commented Nov 21, 2022

Many thanks from me too! @rwblokzijl

So far this works great when I've tried it

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.

:NvimTreeToggle - toggle file explorer in all tabs
3 participants