-
-
Notifications
You must be signed in to change notification settings - Fork 625
Recipes
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', 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 lib = require("nvim-tree.lib")
local view = require("nvim-tree.view")
local function collapse_all()
require("nvim-tree.actions.tree-modifiers.collapse-all").fn()
end
local function edit_or_open()
-- open as vsplit on current node
local action = "edit"
local node = lib.get_node_at_cursor()
-- Just copy what's done normally with vsplit
if node.link_to and not node.nodes then
require('nvim-tree.actions.node.open-file').fn(action, node.link_to)
view.close() -- Close the tree if file was opened
elseif node.nodes ~= nil then
lib.expand_or_collapse(node)
else
require('nvim-tree.actions.node.open-file').fn(action, node.absolute_path)
view.close() -- Close the tree if file was opened
end
end
local function vsplit_preview()
-- open as vsplit on current node
local action = "vsplit"
local node = lib.get_node_at_cursor()
-- Just copy what's done normally with vsplit
if node.link_to and not node.nodes then
require('nvim-tree.actions.node.open-file').fn(action, node.link_to)
elseif node.nodes ~= nil then
lib.expand_or_collapse(node)
else
require('nvim-tree.actions.node.open-file').fn(action, node.absolute_path)
end
-- Finally refocus on tree if it was lost
view.focus()
end
You can stage and unstage files directly in the tree view with this config:
local lib = require("nvim-tree.lib")
local git_add = function()
local node = lib.get_node_at_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
lib.refresh_tree()
end
require("nvim-tree").setup {
view = {
mappings = {
list = {
{ key = "ga", action = "git_add", action_cb = 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 lib = require'nvim-tree.lib'
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 lib_status_ok, lib = pcall(require, "nvim-tree.lib")
if not lib_status_ok then
return
end
local node = lib.get_node_at_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
And then to use it, add something like this depending on your configuration structure
local function custom_callback(callback_name)
return string.format(":lua require('treeutils').%s()<CR>", callback_name)
end
-- this is passed in the call to setup
require 'nvim-tree'.setup {
view = {
mappings = {
list = {
{ key = "<c-f>", cb = custom_callback "launch_find_files" },
{ key = "<c-g>", cb = custom_callback "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 node = require('nvim-tree.lib').get_node_at_cursor()
vim.cmd("wincmd l")
require('nvim-tree.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,
},
})
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
Add a keymap for the created function in your on_attach
:
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
---
mappings = {
list = {
{ key = "<C-C>", action = "global_cwd", action_cb = 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
---
require("nvim-tree").setup({
disable_netrw = false,
view = {
mappings = {
list = {
{ key = "T", action = "open_tab_silent", action_cb = 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 " .. file.fname)
end)
nvim-tree can behave like vinegar. To allow this, you will need to configure it in a specific way:
Use require"nvim-tree".open_replacing_current_buffer()
instead of the default open command. You can easily implement a toggle using this too:
local function toggle_replace()
local view = require"nvim-tree.view"
local api = require"nvim-tree.api"
if view.is_visible() then
api.tree.close()
else
require"nvim-tree".open_replacing_current_buffer()
end
end
Use the edit_in_place
action to edit files. It's bound to <C-e>
by default, vinegar uses <CR>
. You can override this with:
require"nvim-tree".setup {
view = {
mappings = {
list = {
{ key = "<CR>", action = "edit_in_place" }
}
}
}
}
Going up a dir is bound to -
by default in nvim-tree which is identical to vinegar, no change is needed here.
You'll also need to set nvim-tree.hijack_netrw
to true
during setup. A good functionality to enable is nvim-tree.hijack_directories
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
require('nvim-tree').setup {
view = {
mappings = {
list = {
{ key = "c", action = "copy_file_to", action_cb = 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 view = require('nvim-tree.view')
local is_visible = view.is_visible()
local api = require('nvim-tree.api')
if not 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.
Withouth nvim-tree when a buffer in a single window is deleted with :bdelete neovim goes to the most recent entry in the jump list that points into a loaded buffer (i.e. some hidden buffer). When nvim-tree is open you end up with a single window with nvim-tree instead. The following script tries to fix this.
vim.api.nvim_create_autocmd("BufEnter", {
nested = true,
callback = function()
-- Only 1 window with nvim-tree left: we probably closed a file buffer
if #vim.api.nvim_list_wins() == 1 and require("nvim-tree.utils").is_nvim_tree_buf() then
local api = require('nvim-tree.api')
-- Required to let the close event complete. An error is thrown without this.
vim.defer_fn(function()
-- close nvim-tree: will go to the last hidden buffer used before closing
api.tree.toggle({find_file = true, focus = true})
-- re-open nivm-tree
api.tree.toggle({find_file = true, focus = true})
-- nvim-tree is still the active window. Go to the previous window.
vim.cmd("wincmd p")
end, 0)
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,
---
})
Define a mapping as per :help nvim-tree-mappings
vim.keymap.set('n', 'T', cycle_sort, opts('Cycle Sort'))