-
-
Notifications
You must be signed in to change notification settings - Fork 609
Recipes
Module nvim-tree.api
is public API that will not change in a breaking manner and can be relied on.
Other modules such as nvim-tree.view
are internal and will change without notice.
Please raise a feature request to add API that you may need.
Hotkeys that keep your fingers using h, j, k, l for navigation and opening.
- Ctrl + h | Open tree
- h | Collapse current containing folder
- H | Collapse Tree
- l | Open node if it is a folder, else edit the file and close tree
- L | Open node if it is a folder, else create vsplit of file and keep cursor focus on tree
In particular, L is quite nice for opening a few files in quick succession without losing focus of the tree while using l for just editing a file in the current buffer.
To do so, you'll want to add some global and nvim-tree mappings:
-- global
vim.api.nvim_set_keymap("n", "<C-h>", ":NvimTreeToggle<cr>", {silent = true, noremap = true})
-- on_attach
vim.keymap.set("n", "l", edit_or_open, opts("Edit Or Open"))
vim.keymap.set("n", "L", vsplit_preview, opts("Vsplit Preview"))
vim.keymap.set("n", "h", api.tree.close, opts("Close"))
vim.keymap.set("n", "H", api.tree.collapse_all, opts("Collapse All"))
You'll also need to define these callbacks that nvim-tree
will use, they'll need to be defined above the snippet above:
local api = require("nvim-tree.api")
local function edit_or_open()
local node = api.tree.get_node_under_cursor()
if node.nodes ~= nil then
-- expand or collapse folder
api.node.open.edit()
else
-- open file
api.node.open.edit()
-- Close the tree if file was opened
api.tree.close()
end
end
-- open as vsplit on current node
local function vsplit_preview()
local node = api.tree.get_node_under_cursor()
if node.nodes ~= nil then
-- expand or collapse folder
api.node.open.edit()
else
-- open file as vsplit
api.node.open.vertical()
end
-- Finally refocus on tree if it was lost
api.tree.focus()
end
You can stage and unstage files directly in the tree view with this config:
local api = require("nvim-tree.api")
local git_add = function()
local node = api.tree.get_node_under_cursor()
local gs = node.git_status.file
-- If the current node is a directory get children status
if gs == nil then
gs = (node.git_status.dir.direct ~= nil and node.git_status.dir.direct[1])
or (node.git_status.dir.indirect ~= nil and node.git_status.dir.indirect[1])
end
-- If the file is untracked, unstaged or partially staged, we stage it
if gs == "??" or gs == "MM" or gs == "AM" or gs == " M" then
vim.cmd("silent !git add " .. node.absolute_path)
-- If the file is staged, we unstage
elseif gs == "M " or gs == "A " then
vim.cmd("silent !git restore --staged " .. node.absolute_path)
end
api.tree.reload()
end
vim.keymap.set('n', 'ga', git_add, opts('Git Add'))
If you target a file or directory in the tree and press ga
, it will be git staged. If it's already staged, it will instead be unstaged.
Here is a small snippet of code to open the file under the cursor in telescope
File named 'treeutils.lua'
local api = require("nvim-tree.api")
local openfile = require'nvim-tree.actions.node.open-file'
local actions = require'telescope.actions'
local action_state = require'telescope.actions.state'
local M = {}
local view_selection = function(prompt_bufnr, map)
actions.select_default:replace(function()
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
local filename = selection.filename
if (filename == nil) then
filename = selection[1]
end
openfile.fn('preview', filename)
end)
return true
end
function M.launch_live_grep(opts)
return M.launch_telescope("live_grep", opts)
end
function M.launch_find_files(opts)
return M.launch_telescope("find_files", opts)
end
function M.launch_telescope(func_name, opts)
local telescope_status_ok, _ = pcall(require, "telescope")
if not telescope_status_ok then
return
end
local node = api.tree.get_node_under_cursor()
local is_folder = node.fs_stat and node.fs_stat.type == 'directory' or false
local basedir = is_folder and node.absolute_path or vim.fn.fnamemodify(node.absolute_path, ":h")
if (node.name == '..' and TreeExplorer ~= nil) then
basedir = TreeExplorer.cwd
end
opts = opts or {}
opts.cwd = basedir
opts.search_dirs = { basedir }
opts.attach_mappings = view_selection
return require("telescope.builtin")[func_name](opts)
end
return M
local treeutils = require("treeutils")
vim.keymap.set('n', '<c-f>', treeutils.launch_find_files, opts('Launch Find Files'))
vim.keymap.set('n', '<c-g>', treeutils.launch_live_grep, opts('Launch Live Grep'))
By default, the live filter
doesn't filter the directory names but it's so painful if you have many folders in your project.
Put this code to your init.lua.
require("nvim-tree").setup {
live_filter = {
prefix = "[FILTER]: ",
always_show_folders = false, -- Turn into false from true by default
}
}
By default, opening a file in a new tab leaves NvimTree_1 as the text in the tab bar. This function will run <C-w>l
before opening a new tab to set the tab title to the split adjacent to the tree.
local swap_then_open_tab = function()
local api = require("nvim-tree.api")
local node = api.tree.get_node_under_cursor()
vim.cmd("wincmd l")
api.node.open.tab(node)
end
-- { key = "t", action = "swap_then_open_tab", action_cb = swap_then_open_tab },
For nvim-tree to be a centered floating window, add this to your config:
local HEIGHT_RATIO = 0.8 -- You can change this
local WIDTH_RATIO = 0.5 -- You can change this too
require('nvim-tree').setup({
view = {
float = {
enable = true,
open_win_config = function()
local screen_w = vim.opt.columns:get()
local screen_h = vim.opt.lines:get() - vim.opt.cmdheight:get()
local window_w = screen_w * WIDTH_RATIO
local window_h = screen_h * HEIGHT_RATIO
local window_w_int = math.floor(window_w)
local window_h_int = math.floor(window_h)
local center_x = (screen_w - window_w) / 2
local center_y = ((vim.opt.lines:get() - window_h) / 2)
- vim.opt.cmdheight:get()
return {
border = 'rounded',
relative = 'editor',
row = center_y,
col = center_x,
width = window_w_int,
height = window_h_int,
}
end,
},
width = function()
return math.floor(vim.opt.columns:get() * WIDTH_RATIO)
end,
},
})
If you want to automatically resize the floating window when neovim's window size changes, add this to your config:
local tree_api = require("nvim-tree")
local tree_view = require("nvim-tree.view")
vim.api.nvim_create_augroup("NvimTreeResize", {
clear = true,
})
vim.api.nvim_create_autocmd({ "VimResized" }, {
group = "NvimTreeResize",
callback = function()
if tree_view.is_visible() then
tree_view.close()
tree_api.open()
end
end
})
Original solution by @davidsierradz and @alex-courtis (see #1538). Recipe written by @Kryzar.
It is possible to use Telescope as a menu, listing custom items and executing a callback on item selection. Following is a minimal example.
First of all, you will need a list of labels to show in the menu, along with their associated callbacks.
Note that nvim-tree actions are directly requirable from nvim-tree.api
.
local tree_actions = {
{
name = "Create node",
handler = require("nvim-tree.api").fs.create,
},
{
name = "Remove node",
handler = require("nvim-tree.api").fs.remove,
},
{
name = "Trash node",
handler = require("nvim-tree.api").fs.trash,
},
{
name = "Rename node",
handler = require("nvim-tree.api").fs.rename,
},
{
name = "Fully rename node",
handler = require("nvim-tree.api").fs.rename_sub,
},
{
name = "Copy",
handler = require("nvim-tree.api").fs.copy.node,
},
-- ... other custom actions you may want to display in the menu
}
You will then need a function used to display the actions in a Telescope picker.
The function will be keymapped inside on_attach
nvim-tree config option and as such it will receive the highlighted node.
local function tree_actions_menu(node)
local entry_maker = function(menu_item)
return {
value = menu_item,
ordinal = menu_item.name,
display = menu_item.name,
}
end
local finder = require("telescope.finders").new_table({
results = tree_actions,
entry_maker = entry_maker,
})
local sorter = require("telescope.sorters").get_generic_fuzzy_sorter()
local default_options = {
finder = finder,
sorter = sorter,
attach_mappings = function(prompt_buffer_number)
local actions = require("telescope.actions")
-- On item select
actions.select_default:replace(function()
local state = require("telescope.actions.state")
local selection = state.get_selected_entry()
-- Closing the picker
actions.close(prompt_buffer_number)
-- Executing the callback
selection.value.handler(node)
end)
-- The following actions are disabled in this example
-- You may want to map them too depending on your needs though
actions.add_selection:replace(function() end)
actions.remove_selection:replace(function() end)
actions.toggle_selection:replace(function() end)
actions.select_all:replace(function() end)
actions.drop_all:replace(function() end)
actions.toggle_all:replace(function() end)
return true
end,
}
-- Opening the menu
require("telescope.pickers").new({ prompt_title = "Tree menu" }, default_options):find()
end
vim.keymap.set("n", "<C-Space>", tree_actions_menu, { buffer = buffer, noremap = true, silent = true })
When not using update_focused_file.update_root
the tree's current working directory may differ from the global.
You can manually change the root to the global cwd:
local function change_root_to_global_cwd()
local api = require("nvim-tree.api")
local global_cwd = vim.fn.getcwd(-1, -1)
api.tree.change_root(global_cwd)
end
vim.keymap.set('n', '<C-c>', change_root_to_global_cwd, opts('Change Root To Global CWD'))
Sometimes, we only want to open a tab, but don't want to jump to that tab immediately.
local function open_tab_silent(node)
local api = require("nvim-tree.api")
api.node.open.tab(node)
vim.cmd.tabprev()
end
vim.keymap.set('n', 'T', open_tab_silent, opts('Open Tab Silent'))
The current available sorting by name algorithm just sorts files names by comparing every character in them. This leads to a strange and undesirable arrangement when you have numbered files names, since it will sort numbers as any normal letter.
For example files will be sorted as 1 foo
, 20 foo
, 3 foo
rather then 1 foo
, 3 foo
, 20 foo
. The code bellow should solve that.
This is it, the fixed and improved version:
local function natural_cmp(left, right)
left = left.name:lower()
right = right.name:lower()
if left == right then
return false
end
for i = 1, math.max(string.len(left), string.len(right)), 1 do
local l = string.sub(left, i, -1)
local r = string.sub(right, i, -1)
if type(tonumber(string.sub(l, 1, 1))) == "number" and type(tonumber(string.sub(r, 1, 1))) == "number" then
local l_number = tonumber(string.match(l, "^[0-9]+"))
local r_number = tonumber(string.match(r, "^[0-9]+"))
if l_number ~= r_number then
return l_number < r_number
end
elseif string.sub(l, 1, 1) ~= string.sub(r, 1, 1) then
return l < r
end
end
end
require("nvim-tree").setup({
sort_by = function(nodes)
table.sort(nodes, natural_cmp)
end,
})
Related discussion: #1896
References: lifthrasiir/rust-natord/lib.rs#L142, lifthrasiir/rust-natord/lib.rs#L37
local api = require("nvim-tree.api")
api.events.subscribe(api.events.Event.FileCreated, function(file)
vim.cmd("edit " .. vim.fn.fnameescape(file.fname))
end)
NvimTree.Drop.selection.demo.mov
Note
REF: PR2161
require("nvim-tree").setup({
on_attach = function(bufnr)
local function opts(desc)
return { desc = 'nvim-tree: ' .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
end
local ok, api = pcall(require, "nvim-tree.api")
assert(ok, "api module is not found")
vim.keymap.set("n", "<CR>", api.node.open.tab_drop, opts("Tab drop"))
end
})
nvim-tree can behave like vinegar. To allow this, you will need to configure it in a specific way:
Use api.node.open.replace_tree_buffer
instead of the default open command. You can easily implement a toggle:
local function toggle_replace()
local api = require("nvim-tree.api")
if api.tree.is_visible() then
api.tree.close()
else
api.node.open.replace_tree_buffer()
end
end
Use "Open: In Place" to edit files. It's bound to <C-e>
by default, vinegar uses <CR>
. You can override this:
vim.keymap.set('n', '<CR>', api.node.open.replace_tree_buffer, opts('Open: In Place'))
Going up a dir is bound to -
by default in nvim-tree which is identical to vinegar, no change is needed here.
The NERDTree way of copying files works like this:
- Select the node in the tree
- Activate copy file to
- Edit the path to be the destination path of the copied file, you can use file completion to to aid entering in the new path.
- It copies the file to the new path, and creates any parent dirs as required.
Below the copy_file_to
function is bound to c
, if you wish to use it in additional to the existing c
function, you will have to map it to some other hotkey
local function copy_file_to(node)
local file_src = node['absolute_path']
-- The args of input are {prompt}, {default}, {completion}
-- Read in the new file path using the existing file's path as the baseline.
local file_out = vim.fn.input("COPY TO: ", file_src, "file")
-- Create any parent dirs as required
local dir = vim.fn.fnamemodify(file_out, ":h")
vim.fn.system { 'mkdir', '-p', dir }
-- Copy the file
vim.fn.system { 'cp', '-R', file_src, file_out }
end
vim.keymap.set('n', 'c', copy_file_to, opts('Copy File To'))
Show dynamic Hydra popup with file actions that user can perform on files, when NvimTree window is focused. Requires hydra.nvim - this plugin can draw menu popups and execute custom, user defined, commands. nvim-tree_hydra.webm
-- auto show hydra on nvimtree focus
local function change_root_to_global_cwd()
local global_cwd = vim.fn.getcwd()
-- local global_cwd = vim.fn.getcwd(-1, -1)
api.tree.change_root(global_cwd)
end
local hint = [[
_w_: cd CWD _c_: Path yank _/_: Filter
_y_: Copy _x_: Cut _p_: Paste
_r_: Rename _d_: Remove _n_: New
_h_: Hidden _?_: Help
^
]]
-- ^ ^ _q_: exit
local nvim_tree_hydra = nil
local nt_au_group = vim.api.nvim_create_augroup("NvimTreeHydraAu", { clear = true })
local Hydra = require "hydra"
local function spawn_nvim_tree_hydra()
nvim_tree_hydra = Hydra {
name = "NvimTree",
hint = hint,
config = {
color = "pink",
invoke_on_body = true,
buffer = 0, -- only for active buffer
hint = {
position = "bottom",
border = "rounded",
},
},
mode = "n",
body = "H",
heads = {
{ "w", change_root_to_global_cwd, { silent = true } },
{ "c", api.fs.copy.absolute_path, { silent = true } },
{ "/", api.live_filter.start, { silent = true } },
{ "y", api.fs.copy.node, { silent = true } },
{ "x", api.fs.cut, { exit = true, silent = true } },
{ "p", api.fs.paste, { exit = true, silent = true } },
{ "r", api.fs.rename, { silent = true } },
{ "d", api.fs.remove, { silent = true } },
{ "n", api.fs.create, { silent = true } },
{ "h", api.tree.toggle_hidden_filter, { silent = true } },
{ "?", api.tree.toggle_help, { silent = true } },
-- { "q", nil, { exit = true, nowait = true } },
},
}
nvim_tree_hydra:activate()
end
vim.api.nvim_create_autocmd({ "BufEnter" }, {
pattern = "*",
callback = function(opts)
if vim.bo[opts.buf].filetype == "NvimTree" then
spawn_nvim_tree_hydra()
else
if nvim_tree_hydra then
nvim_tree_hydra:exit()
end
end
end,
group = nt_au_group,
})
There is currently an issue with restoring nvim-tree fully when using rmagatti/auto-session. Upon restoring the session, nvim-tree buffer will be empty, sometimes positioned in strange places with random dimensions. This issue only happens when saving session with nvim-tree open. To prevent this from happening you can use the following autocmd:
vim.api.nvim_create_autocmd({ 'BufEnter' }, {
pattern = 'NvimTree*',
callback = function()
local api = require('nvim-tree.api')
local view = require('nvim-tree.view')
if not view.is_visible() then
api.tree.open()
end
end,
})
This autocmd will check whether nvim-tree is open when the session is restored and refresh it.
When closing the last window besides nvim-tree with :q
or :bd
you end up with a single window with nvim-tree.
The following script makes the two commands behave as if the tree was closed:
- :q will quit vim if only a single window (besides the tree) is open (unless there are unsaved changes)
- :bdelete will show the most recent entry in the jump list that points into a loaded buffer (i.e. some hidden buffer) or an empty buffer in the current window
-- Make :bd and :q behave as usual when tree is visible
vim.api.nvim_create_autocmd({'BufEnter', 'QuitPre'}, {
nested = false,
callback = function(e)
local tree = require('nvim-tree.api').tree
-- Nothing to do if tree is not opened
if not tree.is_visible() then
return
end
-- How many focusable windows do we have? (excluding e.g. incline status window)
local winCount = 0
for _,winId in ipairs(vim.api.nvim_list_wins()) do
if vim.api.nvim_win_get_config(winId).focusable then
winCount = winCount + 1
end
end
-- We want to quit and only one window besides tree is left
if e.event == 'QuitPre' and winCount == 2 then
vim.api.nvim_cmd({cmd = 'qall'}, {})
end
-- :bd was probably issued an only tree window is left
-- Behave as if tree was closed (see `:h :bd`)
if e.event == 'BufEnter' and winCount == 1 then
-- Required to avoid "Vim:E444: Cannot close last window"
vim.defer_fn(function()
-- close nvim-tree: will go to the last buffer used before closing
tree.toggle({find_file = true, focus = true})
-- re-open nivm-tree
tree.toggle({find_file = true, focus = false})
end, 10)
end
end
})
Predefined sorts may be cycled. See :help nvim-tree.sort_by
local SORT_METHODS = {
"name",
"case_sensitive",
"modification_time",
"extension",
}
local sort_current = 1
local cycle_sort = function()
if sort_current >= #SORT_METHODS then
sort_current = 1
else
sort_current = sort_current + 1
end
api.tree.reload()
end
local sort_by = function()
return SORT_METHODS[sort_current]
end
Define your sort_by
in your configuration:
require("nvim-tree").setup({
sort_by = sort_by,
---
})
vim.keymap.set('n', 'T', cycle_sort, opts('Cycle Sort'))
Use the apis provided by neovim-tree to implement the following config:
{
ui = {
confirm = {
remove = true,
trash = false,
},
},
on_attach = function(bufnr)
local api = require("nvim-tree.api")
local opts = function(desc)
return { desc = "nvim-tree: " .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
end
-- mark operation
local mark_move_j = function()
api.marks.toggle()
vim.cmd("norm j")
end
local mark_move_k = function()
api.marks.toggle()
vim.cmd("norm k")
end
-- marked files operation
local mark_trash = function()
local marks = api.marks.list()
if #marks == 0 then
table.insert(marks, api.tree.get_node_under_cursor())
end
vim.ui.input({ prompt = string.format("Trash %s files? [y/n] ", #marks) }, function(input)
if input == "y" then
for _, node in ipairs(marks) do
api.fs.trash(node)
end
api.marks.clear()
api.tree.reload()
end
end)
end
local mark_remove = function()
local marks = api.marks.list()
if #marks == 0 then
table.insert(marks, api.tree.get_node_under_cursor())
end
vim.ui.input({ prompt = string.format("Remove/Delete %s files? [y/n] ", #marks) }, function(input)
if input == "y" then
for _, node in ipairs(marks) do
api.fs.remove(node)
end
api.marks.clear()
api.tree.reload()
end
end)
end
local mark_copy = function()
local marks = api.marks.list()
if #marks == 0 then
table.insert(marks, api.tree.get_node_under_cursor())
end
for _, node in pairs(marks) do
api.fs.copy.node(node)
end
api.marks.clear()
api.tree.reload()
end
local mark_cut = function()
local marks = api.marks.list()
if #marks == 0 then
table.insert(marks, api.tree.get_node_under_cursor())
end
for _, node in pairs(marks) do
api.fs.cut(node)
end
api.marks.clear()
api.tree.reload()
end
vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
vim.keymap.set("n", "J", mark_move_j, opts("Toggle Bookmark Down"))
vim.keymap.set("n", "K", mark_move_k, opts("Toggle Bookmark Up"))
vim.keymap.set("n", "dd", mark_cut, opts("Cut File(s)"))
vim.keymap.set("n", "df", mark_trash, opts("Trash File(s)"))
vim.keymap.set("n", "dF", mark_remove, opts("Remove File(s)"))
vim.keymap.set("n", "yy", mark_copy, opts("Copy File(s)"))
vim.keymap.set("n", "mv", api.marks.bulk.move, opts("Move Bookmarked"))
end,
}
Opens up a telescope search dialog of all the directories in your project. If a selection is chosen it will then open nvim-tree and focus on that directory allowing you to quickly add/change files in any directory.
function find_directory_and_focus()
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
local function open_nvim_tree(prompt_bufnr, _)
actions.select_default:replace(function()
local api = require("nvim-tree.api")
actions.close(prompt_bufnr)
local selection = action_state.get_selected_entry()
api.tree.open()
api.tree.find_file(selection.cwd .. "/" .. selection.value)
end)
return true
end
require("telescope.builtin").find_files({
find_command = { "fd", "--type", "directory", "--hidden", "--exclude", ".git/*" },
attach_mappings = open_nvim_tree,
})
end
vim.keymap.set("n", "fd", find_directory_and_focus)
This is a different approach to how the code generated by the temporary on_attach
function looks like.
- Before
-- BEGIN_DEFAULT_ON_ATTACH
vim.keymap.set("n", "<C-]>", api.tree.change_root_to_node, opts "CD")
vim.keymap.set("n", "<C-e>", api.node.open.replace_tree_buffer, opts "Open: In Place")
vim.keymap.set("n", "<C-k>", api.node.show_info_popup, opts "Info")
vim.keymap.set("n", "<C-r>", api.fs.rename_sub, opts "Rename: Omit Filename")
vim.keymap.set("n", "<C-t>", api.node.open.tab, opts "Open: New Tab")
vim.keymap.set("n", "<C-v>", api.node.open.vertical, opts "Open: Vertical Split")
vim.keymap.set("n", "<C-x>", api.node.open.horizontal, opts "Open: Horizontal Split")
vim.keymap.set("n", "<BS>", api.node.navigate.parent_close, opts "Close Directory")
vim.keymap.set("n", "<CR>", api.node.open.edit, opts "Open")
vim.keymap.set("n", "<Tab>", api.node.open.preview, opts "Open Preview")
vim.keymap.set("n", ">", api.node.navigate.sibling.next, opts "Next Sibling")
vim.keymap.set("n", "<", api.node.navigate.sibling.prev, opts "Previous Sibling")
vim.keymap.set("n", ".", api.node.run.cmd, opts "Run Command")
vim.keymap.set("n", "-", api.tree.change_root_to_parent, opts "Up")
vim.keymap.set("n", "a", api.fs.create, opts "Create")
vim.keymap.set("n", "bmv", api.marks.bulk.move, opts "Move Bookmarked")
vim.keymap.set("n", "B", api.tree.toggle_no_buffer_filter, opts "Toggle No Buffer")
vim.keymap.set("n", "c", api.fs.copy.node, opts "Copy")
vim.keymap.set("n", "C", api.tree.toggle_git_clean_filter, opts "Toggle Git Clean")
vim.keymap.set("n", "[c", api.node.navigate.git.prev, opts "Prev Git")
vim.keymap.set("n", "]c", api.node.navigate.git.next, opts "Next Git")
vim.keymap.set("n", "d", api.fs.remove, opts "Delete")
vim.keymap.set("n", "D", api.fs.trash, opts "Trash")
vim.keymap.set("n", "E", api.tree.expand_all, opts "Expand All")
vim.keymap.set("n", "e", api.fs.rename_basename, opts "Rename: Basename")
vim.keymap.set("n", "]e", api.node.navigate.diagnostics.next, opts "Next Diagnostic")
vim.keymap.set("n", "[e", api.node.navigate.diagnostics.prev, opts "Prev Diagnostic")
vim.keymap.set("n", "F", api.live_filter.clear, opts "Clean Filter")
vim.keymap.set("n", "f", api.live_filter.start, opts "Filter")
vim.keymap.set("n", "g?", api.tree.toggle_help, opts "Help")
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts "Copy Absolute Path")
vim.keymap.set("n", "H", api.tree.toggle_hidden_filter, opts "Toggle Dotfiles")
vim.keymap.set("n", "I", api.tree.toggle_gitignore_filter, opts "Toggle Git Ignore")
vim.keymap.set("n", "J", api.node.navigate.sibling.last, opts "Last Sibling")
vim.keymap.set("n", "K", api.node.navigate.sibling.first, opts "First Sibling")
vim.keymap.set("n", "m", api.marks.toggle, opts "Toggle Bookmark")
vim.keymap.set("n", "o", api.node.open.edit, opts "Open")
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts "Open: No Window Picker")
vim.keymap.set("n", "p", api.fs.paste, opts "Paste")
vim.keymap.set("n", "P", api.node.navigate.parent, opts "Parent Directory")
vim.keymap.set("n", "q", api.tree.close, opts "Close")
vim.keymap.set("n", "r", api.fs.rename, opts "Rename")
vim.keymap.set("n", "R", api.tree.reload, opts "Refresh")
vim.keymap.set("n", "s", api.node.run.system, opts "Run System")
vim.keymap.set("n", "S", api.tree.search_node, opts "Search")
vim.keymap.set("n", "U", api.tree.toggle_custom_filter, opts "Toggle Hidden")
vim.keymap.set("n", "W", api.tree.collapse_all, opts "Collapse")
vim.keymap.set("n", "x", api.fs.cut, opts "Cut")
vim.keymap.set("n", "y", api.fs.copy.filename, opts "Copy Name")
vim.keymap.set("n", "Y", api.fs.copy.relative_path, opts "Copy Relative Path")
vim.keymap.set("n", "<2-LeftMouse>", api.node.open.edit, opts "Open")
vim.keymap.set("n", "<2-RightMouse>", api.tree.change_root_to_node, opts "CD")
-- END_DEFAULT_ON_ATTACH
-- Mappings migrated from view.mappings.list
vim.keymap.set("n", "l", api.node.open.edit, opts "Open")
vim.keymap.set("n", "<CR>", api.node.open.edit, opts "Open")
vim.keymap.set("n", "o", api.node.open.edit, opts "Open")
vim.keymap.set("n", "h", api.node.navigate.parent_close, opts "Close Directory")
vim.keymap.set("n", "v", api.node.open.vertical, opts "Open: Vertical Split")
vim.keymap.set("n", "C", api.tree.change_root_to_node, opts "CD")
- After
local mappings = {
-- BEGIN_DEFAULT_ON_ATTACH
["<C-]>"] = { api.tree.change_root_to_node, "CD" },
["<C-e>"] = { api.node.open.replace_tree_buffer, "Open: In Place" },
["<C-k>"] = { api.node.show_info_popup, "Info" },
["<C-r>"] = { api.fs.rename_sub, "Rename: Omit Filename" },
["<C-t>"] = { api.node.open.tab, "Open: New Tab" },
["<C-v>"] = { api.node.open.vertical, "Open: Vertical Split" },
["<C-x>"] = { api.node.open.horizontal, "Open: Horizontal Split" },
["<BS>"] = { api.node.navigate.parent_close, "Close Directory" },
["<CR>"] = { api.node.open.edit, "Open" },
["<Tab>"] = { api.node.open.preview, "Open Preview" },
[">"] = { api.node.navigate.sibling.next, "Next Sibling" },
["<"] = { api.node.navigate.sibling.prev, "Previous Sibling" },
["."] = { api.node.run.cmd, "Run Command" },
["-"] = { api.tree.change_root_to_parent, "Up" },
["a"] = { api.fs.create, "Create" },
["bmv"] = { api.marks.bulk.move, "Move Bookmarked" },
["B"] = { api.tree.toggle_no_buffer_filter, "Toggle No Buffer" },
["c"] = { api.fs.copy.node, "Copy" },
["C"] = { api.tree.toggle_git_clean_filter, "Toggle Git Clean" },
["[c"] = { api.node.navigate.git.prev, "Prev Git" },
["]c"] = { api.node.navigate.git.next, "Next Git" },
["d"] = { api.fs.remove, "Delete" },
["D"] = { api.fs.trash, "Trash" },
["E"] = { api.tree.expand_all, "Expand All" },
["e"] = { api.fs.rename_basename, "Rename: Basename" },
["]e"] = { api.node.navigate.diagnostics.next, "Next Diagnostic" },
["[e"] = { api.node.navigate.diagnostics.prev, "Prev Diagnostic" },
["F"] = { api.live_filter.clear, "Clean Filter" },
["f"] = { api.live_filter.start, "Filter" },
["g?"] = { api.tree.toggle_help, "Help" },
["gy"] = { api.fs.copy.absolute_path, "Copy Absolute Path" },
["H"] = { api.tree.toggle_hidden_filter, "Toggle Dotfiles" },
["I"] = { api.tree.toggle_gitignore_filter, "Toggle Git Ignore" },
["J"] = { api.node.navigate.sibling.last, "Last Sibling" },
["K"] = { api.node.navigate.sibling.first, "First Sibling" },
["m"] = { api.marks.toggle, "Toggle Bookmark" },
["o"] = { api.node.open.edit, "Open" },
["O"] = { api.node.open.no_window_picker, "Open: No Window Picker" },
["p"] = { api.fs.paste, "Paste" },
["P"] = { api.node.navigate.parent, "Parent Directory" },
["q"] = { api.tree.close, "Close" },
["r"] = { api.fs.rename, "Rename" },
["R"] = { api.tree.reload, "Refresh" },
["s"] = { api.node.run.system, "Run System" },
["S"] = { api.tree.search_node, "Search" },
["U"] = { api.tree.toggle_custom_filter, "Toggle Hidden" },
["W"] = { api.tree.collapse_all, "Collapse" },
["x"] = { api.fs.cut, "Cut" },
["y"] = { api.fs.copy.filename, "Copy Name" },
["Y"] = { api.fs.copy.relative_path, "Copy Relative Path" },
["<2-LeftMouse>"] = { api.node.open.edit, "Open" },
["<2-RightMouse>"] = { api.tree.change_root_to_node, "CD" },
-- END_DEFAULT_ON_ATTACH
-- Mappings migrated from view.mappings.list
["l"] = { api.node.open.edit, "Open" },
["<CR>"] = { api.node.open.edit, "Open" },
["o"] = { api.node.open.edit, "Open" },
["h"] = { api.node.navigate.parent_close, "Close Directory" },
["v"] = { api.node.open.vertical, "Open: Vertical Split" },
["C"] = { api.tree.change_root_to_node, "CD" },
}
for keys, mapping in pairs(mappings) do
vim.keymap.set("n", keys, mapping[1], opts(mapping[2]))
end
This refactor keeps the escense of the original code, but centralize the key mappings in a table.
By default, NvimTree clipboard is "additive", meaning you can copy or cut multiple files by invoking c
or x
on them, then paste them all at once using p
.
This snippet will change the default behavior to only allow copying & cutting single files at a time. It works by rebinding the c
and x
commands to a new function which first clears the clipboard before adding the new file to it, as explained in this discussion.
Add the following to your on_attach
function:
local function clear_and_copy()
api.fs.clear_clipboard()
api.fs.copy.node()
end
local function clear_and_cut()
api.fs.clear_clipboard()
api.fs.cut()
end
vim.keymap.set('n', 'c', clear_and_copy, opts('Copy'))
vim.keymap.set('n', 'x', clear_and_cut, opts('Cut'))
nvimTreeFocusOrToggle.mov
Use single hotkeys to toggle/focus nvim-tree:
If nvim-tree is focused - close it
Or always focus nvim-tree otherwise
-- init.lua
local nvimTreeFocusOrToggle = function ()
local nvimTree=require("nvim-tree.api")
local currentBuf = vim.api.nvim_get_current_buf()
local currentBufFt = vim.api.nvim_get_option_value("filetype", { buf = currentBuf })
if currentBufFt == "NvimTree" then
nvimTree.tree.toggle()
else
nvimTree.tree.focus()
end
end
vim.keymap.set("n", "<A-1>", nvimTreeFocusOrToggle)
-- or legendary
legendary.setup({
-- ..
keymaps = {
{ '<A-1>', nvimTreeFocusOrToggle, description = "nvim-tree: focus/toggle nvim-tree" }
}
})
Simplest approach is to bind your nvimtree exit keybind to <Cmd>bwipeout<CR>
. That method closes the current buffer without saving it to your jumps list nor anywhere else.
If you have a quick closing keybind, you can use regexes to choose the method for closing:
local function close()
local name = vim.api.nvim_buf_get_name(0)
if
name:find('.git/', 7, true)
-- or name:match 'node_modules'
or name:match 'NvimTree_[0-9]+$'
or not vim.loop.fs_stat(name)
then
vim.cmd.bwipeout()
else
vim.cmd.bdelete()
end
end
Alternatively you can add a hook for closing with the nvim-tree.on_attach
config function:
vim.api.nvim_create_autocmd('BufDelete', {callback = function(state)
vim.opt.shada:append(state.file)
end})
Turn long paths into something short yet understandable:
/home/user/Documents/PG/proj-name/src/DirName
→
~/D/PG/p-n/s/DName
local function label(path)
path = path:gsub(os.getenv 'HOME', '~', 1)
return path:gsub('([a-zA-Z])[a-z0-9]+', '%1') ..
(path:match '[a-zA-Z]([a-z0-9]*)$' or '')
end
local api = require 'nvim-tree.api'
local nt = require 'nvim-tree'
nt.setup { renderer = { root_folder_label = label, group_empty = label } }
Mapping to toggle width between fixed and adaptive - width to fit largest file or folder.
Define your state and toggle function:
local VIEW_WIDTH_FIXED = 30
local view_width_max = VIEW_WIDTH_FIXED -- fixed to start
-- toggle the width and redraw
local function toggle_width_adaptive()
if view_width_max == -1 then
view_width_max = VIEW_WIDTH_FIXED
else
view_width_max = -1
end
require("nvim-tree.api").tree.reload()
end
Use the state:
-- get current view width
local function get_view_width_max()
return view_width_max
end
require("nvim-tree").setup({
view = {
width = {
min = 30,
max = get_view_width_max,
}
}
})
Map in your on_attach
, could also be global:
vim.keymap.set('n', 'A', toggle_width_adaptive, opts('Toggle Adaptive Width'))
Often, a rich statusline does not behave well in a very narrow vertical split window, like nvim-tree normally uses. See #424 for example.
A different, simpler statusline for nvim-tree windows can be configured like this:
local nt_api = require'nvim-tree.api'
nt_api.events.subscribe(nt_api.events.Event.TreeOpen, function()
local tree_winid = nt_api.tree.winid()
if tree_winid ~= nil then
vim.api.nvim_set_option_value('statusline', '%t', {win = tree_winid})
end
end)
Statusline value of '%t'
will make nvim-tree windows display just the title NvimTree_#
. See :h 'statusline
for explanation of how statusline strings work.
To make nvim-tree have no visible statusline at all, you can use a value of a single space ' '
. Note, that using a value of empty string ''
will make Neovim fall back to global statusline, which is the opposite of what we want.
@jimafisk
Add the following script to ~/.config/nvim/init.vim
to hide/show nvim-tree by typing nt in quick succession:
function! NvimTreeToggleAll()
let current_tab = tabpagenr()
if g:nvim_tree_open
tabdo NvimTreeClose
let g:nvim_tree_open = 0
else
tabdo NvimTreeOpen
let g:nvim_tree_open = 1
endif
execute 'tabnext' current_tab
endfunction
let g:nvim_tree_open = 0
if isdirectory(argv(0))
let g:nvim_tree_open = 1
endif
nnoremap nt :call NvimTreeToggleAll()<CR>
Nvim-tree window size is reset when you close and reopen the tree.
This script remember the last window width for each tab, and restore the window size when nvim-tree is opened
require('nvim-tree').setup({
actions = {
open_file = {
resize_window = false, -- don't resize window when opening file
},
},
})
local view = require('nvim-tree.view')
local api = require('nvim-tree.api')
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd
-- save nvim-tree window width on WinResized event
augroup('save_nvim_tree_width', { clear = true })
autocmd('WinResized', {
group = 'save_nvim_tree_width',
pattern = '*',
callback = function()
local filetree_winnr = view.get_winnr()
if filetree_winnr ~= nil and vim.tbl_contains(vim.v.event['windows'], filetree_winnr) then
vim.t['filetree_width'] = vim.api.nvim_win_get_width(filetree_winnr)
end
end,
})
-- restore window size when openning nvim-tree
api.events.subscribe(api.events.Event.TreeOpen, function()
if vim.t['filetree_width'] ~= nil then
view.resize(vim.t['filetree_width'])
end
end)
vim.keymap.set('n', '<leader>e', function()
api.tree.find_file({ open = true, focus = true })
end)
Got a workaround for pasting all marked files:
local tree_api = require('nvim-tree.api')
local function opts(desc)
return { desc = 'Tree: ' .. desc, buffer = bufnr , noremap = true, silent = true }
end
local bulk_paste = function()
local marked_nodes = tree_api.marks.list()
if next(marked_nodes) == nil then
vim.api.nvim_echo({{"No nodes are marked to paste","None"}},false,{})
else
for _, node in ipairs(marked_nodes) do
tree_api.fs.copy.node(node)
tree_api.fs.paste(tree_api.tree.get_node_under_cursor())
end
tree_api.fs.clear_clipboard()
end
end
keymap('n', 'p',bulk_paste, opts('Paste marked files'))
This on_attach
function replaces the change_root
keybinds to call local functions that also change nvim's working directory.
local function nvim_tree_on_attach(bufnr)
local api = require "nvim-tree.api"
local function opts(desc)
return { desc = 'nvim-tree: ' .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
end
-- default mappings
api.config.mappings.default_on_attach(bufnr)
-- custom mappings
local function change_root_to_node(node)
if node == nil then
node = api.tree.get_node_under_cursor()
end
if node ~= nil and node.type == "directory" then
vim.api.nvim_set_current_dir(node.absolute_path)
end
api.tree.change_root_to_node(node)
end
local function change_root_to_parent(node)
local abs_path
if node == nil then
abs_path = api.tree.get_nodes().absolute_path
else
abs_path = node.absolute_path
end
local parent_path = vim.fs.dirname(abs_path)
vim.api.nvim_set_current_dir(parent_path)
api.tree.change_root(parent_path)
end
vim.keymap.set('n', '<C-]>', change_root_to_node, opts('CD'))
vim.keymap.set('n', '<2-RightMouse>', change_root_to_node, opts('CD'))
vim.keymap.set('n', '-', change_root_to_parent, opts('Up'))
end
In ~/.config/nvim/lua/plugins/spec_functions/nvim_tree-functions.lua
:
local M = {}
local tree_api = require("nvim-tree.api")
-- *********************************************
-- * Format the root folder name to uppercase *
-- *********************************************
M.format_root_folder = function(path)
-- Get the last component of the path (the folder name)
local folder_name = vim.fn.fnamemodify(path, ":t")
-- Convert the folder name to uppercase and return it
return string.upper(folder_name)
end
return M
In ~/.config/nvim/lua/plugins/nvim-tree.lua
:
local nvim_tree_functions = require('plugins.spec_functions.nvim_tree_functions')
nv_tree = require("nvim-tree")
nv_tree.setup({
renderer = { root_folder_label = nvim_tree_functions.format_root_folder, },
})
Set up for navigation with arrows (or h
, j
, k
, l
) and Enter, as found in several systems - turns out it has a name:
ARIA
Right arrow |
|
Left arrow |
|
Down Arrow | Moves focus to the next node that is focusable without opening or closing a node. |
Up Arrow | Moves focus to the previous node that is focusable without opening or closing a node. |
Enter | Performs the default action of the currently focused node. For parent nodes, it opens or closes the node. In single-select trees, if the node has no children, selects the current node if not already selected (which is the default action). |
Up/k, Down/j, and Enter are already nvim-tree defaults; right/l from open node to first child is not implemented here (struck-through in the table above)
local api = require("nvim-tree.api")
require"nvim-tree".setup {
on_attach = function (bufnr)
local opts = { buffer = bufnr }
api.config.mappings.default_on_attach(bufnr)
-- function for left to assign to keybindings
local lefty = function ()
local node_at_cursor = api.tree.get_node_under_cursor()
-- if it's a node and it's open, close
if node_at_cursor.nodes and node_at_cursor.open then
api.node.open.edit()
-- else left jumps up to parent
else
api.node.navigate.parent()
end
end
-- function for right to assign to keybindings
local righty = function ()
local node_at_cursor = api.tree.get_node_under_cursor()
-- if it's a closed node, open it
if node_at_cursor.nodes and not node_at_cursor.open then
api.node.open.edit()
end
end
vim.keymap.set("n", "h", lefty , opts )
vim.keymap.set("n", "<Left>", lefty , opts )
vim.keymap.set("n", "<Right>", righty , opts )
vim.keymap.set("n", "l", righty , opts )
end,
-- actions, view, etc.
}