-
-
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 mappings with your own action_cb
:
local config = {
view = {
mappings = {
custom_only = false,
list = {
{ key = "l", action = "edit", action_cb = edit_or_open },
{ key = "L", action = "vsplit_preview", action_cb = vsplit_preview },
{ key = "h", action = "close_node" },
{ key = "H", action = "collapse_all", action_cb = collapse_all }
}
},
},
actions = {
open_file = {
quit_on_open = false
}
}
}
vim.api.nvim_set_keymap("n", "<C-h>", ":NvimTreeToggle<cr>" ,{silent = true, noremap = true})
require('nvim-tree').setup(config)
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
-- 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 = lib.get_node_at_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,
},
})
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
Finally, you will need to add a keymap for the created function:
view.mapping:
view = {
mappings = {
list = {
{ key = "<C-Space>", action = "tree actions", action_cb = tree_actions_menu }
---
or
on_attach:
on_attach = function(buffer)
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.
local function natural_cmp(left, right)
left = left.name:lower()
right = right.name:lower()
for i = 1, string.len(left), 1 do
local l = string.sub(left, i, -1)
local r = string.sub(right, i, -1)
if l == r then
return false
elseif type(tonumber(string.sub(l, 1, 1))) == "number" and type(tonumber(string.sub(r, 1, 1))) == "number" then
return tonumber(string.match(l, "^[0-9]+")) < tonumber(string.match(r, "^[0-9]+"))
elseif l[1] == r[1] then
goto continue
else
return l < r
end
::continue::
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
nvim-lsp-file-operations is an extension to nvim-tree.lua
that adds support for file operations using built-in LSP support. It automatically applies
any necessary workspace edits when performing file operations. So something like imports and package names would be fixed automatically when you rename or move files if your lsp server supports it.