Merge branch 'master' into chore/action_cb_help_poc

This commit is contained in:
Alexander Courtis
2022-09-04 13:09:50 +10:00
23 changed files with 517 additions and 148 deletions

View File

@@ -25,3 +25,9 @@ When adding new options, you should declare the defaults in the main `nvim-tree.
Once you did, you should run the `scripts/update-help.sh`. Once you did, you should run the `scripts/update-help.sh`.
Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file. Documentation for options should also be added, see how this is done after `nvim-tree.disable_netrw` in the `nvim-tree-lua.txt` file.
## Pull Request
Please reference any issues in the description e.g. "resolves #1234".
Please check "allow edits by maintainers" to allow nvim-tree developers to make small changes such as documentation tweaks.

View File

@@ -174,6 +174,8 @@ Subsequent calls to setup will replace the previous configuration.
sync_root_with_cwd = false, sync_root_with_cwd = false,
reload_on_bufenter = false, reload_on_bufenter = false,
respect_buf_cwd = false, respect_buf_cwd = false,
on_attach = "disable", -- function(bufnr). If nil, will use the deprecated mapping strategy
remove_keymaps = false, -- boolean (disable totally or not) or list of key (lhs)
view = { view = {
adaptive_size = false, adaptive_size = false,
centralize_selection = false, centralize_selection = false,
@@ -185,12 +187,24 @@ Subsequent calls to setup will replace the previous configuration.
number = false, number = false,
relativenumber = false, relativenumber = false,
signcolumn = "yes", signcolumn = "yes",
-- @deprecated
mappings = { mappings = {
custom_only = false, custom_only = false,
list = { list = {
-- user mappings go here -- user mappings go here
}, },
}, },
float = {
enable = false,
open_win_config = {
relative = "editor",
border = "rounded",
width = 30,
height = 30,
row = 1,
col = 1,
},
},
}, },
renderer = { renderer = {
add_trailing = false, add_trailing = false,
@@ -199,6 +213,7 @@ Subsequent calls to setup will replace the previous configuration.
full_name = false, full_name = false,
highlight_opened_files = "none", highlight_opened_files = "none",
root_folder_modifier = ":~", root_folder_modifier = ":~",
indent_width = 2,
indent_markers = { indent_markers = {
enable = false, enable = false,
inline_arrows = true, inline_arrows = true,
@@ -206,6 +221,7 @@ Subsequent calls to setup will replace the previous configuration.
corner = "└", corner = "└",
edge = "│", edge = "│",
item = "│", item = "│",
bottom = "─",
none = " ", none = " ",
}, },
}, },
@@ -299,6 +315,15 @@ Subsequent calls to setup will replace the previous configuration.
max_folder_discovery = 300, max_folder_discovery = 300,
exclude = {}, exclude = {},
}, },
file_popup = {
open_win_config = {
col = 1,
row = 1,
relative = "cursor",
border = "shadow",
style = "minimal",
},
},
open_file = { open_file = {
quit_on_open = false, quit_on_open = false,
resize_window = true, resize_window = true,
@@ -628,11 +653,33 @@ Window / buffer setup.
Configuration options for |nvim-tree-mappings| Configuration options for |nvim-tree-mappings|
*nvim-tree.view.mappings.custom_only* *nvim-tree.view.mappings.custom_only*
DEPRECATED: see |nvim-tree.remove_keymaps| Will use only the provided user mappings and not the default otherwise,
extends the default mappings with the provided user mappings.
Type: `boolean`, Default: `false`
*nvim-tree.view.mappings.list* *nvim-tree.view.mappings.list*
DEPRECATED: see |nvim-tree.on_attach| A list of keymaps that will extend or override the default keymaps.
Type: `table`
Default: see |nvim-tree-default-mappings|
*nvim-tree.view.float*
Configuration options for floating window
*nvim-tree.view.float.enable*
Display nvim-tree window as float (enforces |nvim-tree.actions.open_file.quit_on_open| if set).
Type: `boolean`, Default: `false`
*nvim-tree.view.float.open_win_config*
Floating window config. See |nvim_open_win| for more details.
Type: `table` or `function` that returns a table, Default:
`{`
`relative = "editor",`
`border = "rounded",`
`width = 30,`
`height = 30,`
`row = 1,`
`col = 1,`
`}`
*nvim-tree.renderer* *nvim-tree.renderer*
UI rendering setup UI rendering setup
@@ -664,6 +711,10 @@ UI rendering setup
available options. available options.
Type: `string`, Default: `":~"` Type: `string`, Default: `":~"`
*nvim-tree.renderer.indent_width*
Number of spaces for an each tree nesting level. Minimum 1.
Type: `number`, Default: `2`
*nvim-tree.renderer.indent_markers* *nvim-tree.renderer.indent_markers*
Configuration options for tree indent markers. Configuration options for tree indent markers.
@@ -677,8 +728,8 @@ UI rendering setup
Type: `boolean`, Default: `true` Type: `boolean`, Default: `true`
*nvim-tree.renderer.indent_markers.icons* *nvim-tree.renderer.indent_markers.icons*
Icons shown before the file/directory. Icons shown before the file/directory. Length 1.
Type: `table`, Default: `{ corner = "└", edge = "│", item = "│", none = " ", }` Type: `table`, Default: `{ corner = "└", edge = "│", item = "│", bottom = "─", none = " ", }`
*nvim-tree.renderer.icons* *nvim-tree.renderer.icons*
Configuration options for icons. Configuration options for icons.
@@ -833,6 +884,22 @@ Configuration for various actions.
E.g `{ ".git", "target", "build" }` etc. E.g `{ ".git", "target", "build" }` etc.
Type: `table`, Default: `{}` Type: `table`, Default: `{}`
*nvim-tree.actions.file_popup*
Configuration for file_popup behaviour.
*nvim-tree.actions.file_popup.open_win_config*
Floating window config for file_popup. See |nvim_open_win| for more details.
You shouldn't define `"width"` and `"height"` values here. They will be
overridden to fit the file_popup content.
Type: `table`, Default:
`{`
`col = 1,`
`row = 1,`
`relative = "cursor",`
`border = "shadow",`
`style = "minimal",`
`}`
*nvim-tree.actions.open_file* *nvim-tree.actions.open_file*
Configuration options for opening a file from nvim-tree. Configuration options for opening a file from nvim-tree.
@@ -1076,55 +1143,132 @@ exists.
============================================================================== ==============================================================================
6. MAPPINGS *nvim-tree-mappings* 6. MAPPINGS *nvim-tree-mappings*
Setting your own mapping in the configuration is deprecated, see |nvim-tree.on_attach| now. Setting your own mapping in the configuration will soon be deprecated, see |nvim-tree.on_attach| for experimental replacement.
You can remove default mappings with |nvim-tree.remove_keymaps|. The `list` option in `view.mappings.list` is a table of
`<CR>`, `o`, `<2-LeftMouse>` open a file or folder; root will cd to the above directory - `key` can be either a string or a table of string (lhs)
`<C-e>` edit the file in place, effectively replacing the tree explorer - `action` is the name of the action, set to `""` to remove default action
`O` same as (edit) with no window picker - `action_cb` is the function that will be called, it receives the node as a parameter. Optional for default actions
`<C-]>`, `<2-RightMouse>` cd in the directory under the cursor - `mode` is normal by default
`<C-v>` open the file in a vertical split >
`<C-x>` open the file in a horizontal split local tree_cb = require'nvim-tree.config'.nvim_tree_callback
`<C-t>` open the file in a new tab
`<` navigate to the previous sibling of current file/directory local function print_node_path(node) {
`>` navigate to the next sibling of current file/directory print(node.absolute_path)
`P` move cursor to the parent directory }
`<BS>` close current opened directory or parent
`<Tab>` open the file as a preview (keeps the cursor in the tree) local list = {
`K` navigate to the first sibling of current file/directory { key = {"<CR>", "o" }, action = "edit", mode = "n"},
`J` navigate to the last sibling of current file/directory { key = "p", action = "print_path", action_cb = print_node_path },
`I` toggle visibility of files/folders hidden via |git.ignore| option { key = "s", cb = tree_cb("vsplit") }, --tree_cb and the cb property are deprecated
`H` toggle visibility of dotfiles via |filters.dotfiles| option { key = "<2-RightMouse>", action = "" }, -- will remove default cd action
`U` toggle visibility of files/folders hidden via |filters.custom| option }
`R` refresh the tree <
`a` add a file; leaving a trailing `/` will add a directory Mouse support defined in |KeyBindings|
`d` delete a file (will prompt for confirmation)
`D` trash a file via |trash| option DEFAULT MAPPINGS *nvim-tree-default-mappings*
`r` rename a file
`<C-r>` rename a file and omit the filename on input `<CR>` edit open a file or folder; root will cd to the above directory
`x` add/remove file/directory to cut clipboard `o`
`c` add/remove file/directory to copy clipboard `<2-LeftMouse>`
`p` paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation `<C-e>` edit_in_place edit the file in place, effectively replacing the tree explorer
`y` copy name to system clipboard `O` edit_no_picker same as (edit) with no window picker
`Y` copy relative path to system clipboard `<C-]>` cd cd in the directory under the cursor
`gy` copy absolute path to system clipboard `<2-RightMouse>`
`[e` go to next diagnostic item `<C-v>` vsplit open the file in a vertical split
`[c` go to next git item `<C-x>` split open the file in a horizontal split
`]e` go to prev diagnostic item `<C-t>` tabnew open the file in a new tab
`]c` go to prev git item `<` prev_sibling navigate to the previous sibling of current file/directory
`-` navigate up to the parent directory of the current file/directory `>` next_sibling navigate to the next sibling of current file/directory
`s` open a file with default system application or a folder with default file manager, using |system_open| option `P` parent_node move cursor to the parent directory
`f` live filter nodes dynamically based on regex matching. `<BS>` close_node close current opened directory or parent
`F` clear live filter `<Tab>` preview open the file as a preview (keeps the cursor in the tree)
`q` close tree window `K` first_sibling navigate to the first sibling of current file/directory
`W` collapse the whole tree `J` last_sibling navigate to the last sibling of current file/directory
`E` expand the whole tree, stopping after expanding |actions.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder `I` toggle_git_ignored toggle visibility of files/folders hidden via |git.ignore| option
`S` prompt the user to enter a path and then expands the tree to match the path `H` toggle_dotfiles toggle visibility of dotfiles via |filters.dotfiles| option
`.` enter vim command mode with the file the cursor is on `U` toggle_custom toggle visibility of files/folders hidden via |filters.custom| option
`<C-k>` toggle a popup with file infos about the file under the cursor `R` refresh refresh the tree
`g?` toggle help `a` create add a file; leaving a trailing `/` will add a directory
`m` Toggle node in bookmarks `d` remove delete a file (will prompt for confirmation)
`D` trash trash a file via |trash| option
`r` rename rename a file
`<C-r>` full_rename rename a file and omit the filename on input
`x` cut add/remove file/directory to cut clipboard
`c` copy add/remove file/directory to copy clipboard
`p` paste paste from clipboard; cut clipboard has precedence over copy; will prompt for confirmation
`y` copy_name copy name to system clipboard
`Y` copy_path copy relative path to system clipboard
`gy` copy_absolute_path copy absolute path to system clipboard
`[e` prev_diag_item go to next diagnostic item
`[c` prev_git_item go to next git item
`]e` next_diag_item go to prev diagnostic item
`]c` next_git_item go to prev git item
`-` dir_up navigate up to the parent directory of the current file/directory
`s` system_open open a file with default system application or a folder with default file manager, using |system_open| option
`f` live_filter live filter nodes dynamically based on regex matching.
`F` clear_live_filter clear live filter
`q` close close tree window
`W` collapse_all collapse the whole tree
`E` expand_all expand the whole tree, stopping after expanding |actions.expand_all.max_folder_discovery| folders; this might hang neovim for a while if running on a big folder
`S` search_node prompt the user to enter a path and then expands the tree to match the path
`.` run_file_command enter vim command mode with the file the cursor is on
`<C-k>` toggle_file_info toggle a popup with file infos about the file under the cursor
`g?` toggle_help toggle help
`m` toggle_mark Toggle node in bookmarks
`bmv` bulk_move Move all bookmarked nodes into specified location
>
view.mappings.list = { -- BEGIN_DEFAULT_MAPPINGS
{ key = { "<CR>", "o", "<2-LeftMouse>" }, action = "edit" },
{ key = "<C-e>", action = "edit_in_place" },
{ key = "O", action = "edit_no_picker" },
{ key = { "<C-]>", "<2-RightMouse>" }, action = "cd" },
{ key = "<C-v>", action = "vsplit" },
{ key = "<C-x>", action = "split" },
{ key = "<C-t>", action = "tabnew" },
{ key = "<", action = "prev_sibling" },
{ key = ">", action = "next_sibling" },
{ key = "P", action = "parent_node" },
{ key = "<BS>", action = "close_node" },
{ key = "<Tab>", action = "preview" },
{ key = "K", action = "first_sibling" },
{ key = "J", action = "last_sibling" },
{ key = "I", action = "toggle_git_ignored" },
{ key = "H", action = "toggle_dotfiles" },
{ key = "U", action = "toggle_custom" },
{ key = "R", action = "refresh" },
{ key = "a", action = "create" },
{ key = "d", action = "remove" },
{ key = "D", action = "trash" },
{ key = "r", action = "rename" },
{ key = "<C-r>", action = "full_rename" },
{ key = "x", action = "cut" },
{ key = "c", action = "copy" },
{ key = "p", action = "paste" },
{ key = "y", action = "copy_name" },
{ key = "Y", action = "copy_path" },
{ key = "gy", action = "copy_absolute_path" },
{ key = "[e", action = "prev_diag_item" },
{ key = "[c", action = "prev_git_item" },
{ key = "]e", action = "next_diag_item" },
{ key = "]c", action = "next_git_item" },
{ key = "-", action = "dir_up" },
{ key = "s", action = "system_open" },
{ key = "f", action = "live_filter" },
{ key = "F", action = "clear_live_filter" },
{ key = "q", action = "close" },
{ key = "W", action = "collapse_all" },
{ key = "E", action = "expand_all" },
{ key = "S", action = "search_node" },
{ key = ".", action = "run_file_command" },
{ key = "<C-k>", action = "toggle_file_info" },
{ key = "g?", action = "toggle_help" },
{ key = "m", action = "toggle_mark" },
{ key = "bmv", action = "bulk_move" },
} -- END_DEFAULT_MAPPINGS
<
============================================================================== ==============================================================================
7. HIGHLIGHT GROUPS *nvim-tree-highlight* 7. HIGHLIGHT GROUPS *nvim-tree-highlight*

View File

@@ -414,6 +414,10 @@ local function setup_autocommands(opts)
end, end,
}) })
end end
if opts.view.float.enable then
create_nvim_tree_autocmd("WinLeave", { pattern = "NvimTree_*", callback = view.close })
end
end end
local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
@@ -447,13 +451,23 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
number = false, number = false,
relativenumber = false, relativenumber = false,
signcolumn = "yes", signcolumn = "yes",
-- @deprecated
mappings = { mappings = {
custom_only = false, custom_only = false,
list = { list = {
-- user mappings go here -- user mappings go here
}, },
}, },
float = {
enable = false,
open_win_config = {
relative = "editor",
border = "rounded",
width = 30,
height = 30,
row = 1,
col = 1,
},
},
}, },
renderer = { renderer = {
add_trailing = false, add_trailing = false,
@@ -462,6 +476,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
full_name = false, full_name = false,
highlight_opened_files = "none", highlight_opened_files = "none",
root_folder_modifier = ":~", root_folder_modifier = ":~",
indent_width = 2,
indent_markers = { indent_markers = {
enable = false, enable = false,
inline_arrows = true, inline_arrows = true,
@@ -469,6 +484,7 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
corner = "", corner = "",
edge = "", edge = "",
item = "", item = "",
bottom = "",
none = " ", none = " ",
}, },
}, },
@@ -562,6 +578,15 @@ local DEFAULT_OPTS = { -- BEGIN_DEFAULT_OPTS
max_folder_discovery = 300, max_folder_discovery = 300,
exclude = {}, exclude = {},
}, },
file_popup = {
open_win_config = {
col = 1,
row = 1,
relative = "cursor",
border = "shadow",
style = "minimal",
},
},
open_file = { open_file = {
quit_on_open = false, quit_on_open = false,
resize_window = true, resize_window = true,
@@ -606,6 +631,10 @@ local function merge_options(conf)
return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {}) return vim.tbl_deep_extend("force", DEFAULT_OPTS, conf or {})
end end
local FIELD_SKIP_VALIDATE = {
open_win_config = true,
}
local FIELD_OVERRIDE_TYPECHECK = { local FIELD_OVERRIDE_TYPECHECK = {
width = { string = true, ["function"] = true, number = true }, width = { string = true, ["function"] = true, number = true },
height = { string = true, ["function"] = true, number = true }, height = { string = true, ["function"] = true, number = true },
@@ -623,6 +652,7 @@ local function validate_options(conf)
end end
for k, v in pairs(user) do for k, v in pairs(user) do
if not FIELD_SKIP_VALIDATE[k] then
local invalid local invalid
local override_typecheck = FIELD_OVERRIDE_TYPECHECK[k] or {} local override_typecheck = FIELD_OVERRIDE_TYPECHECK[k] or {}
if def[k] == nil then if def[k] == nil then
@@ -645,6 +675,7 @@ local function validate_options(conf)
end end
end end
end end
end
validate(conf, DEFAULT_OPTS, "") validate(conf, DEFAULT_OPTS, "")

View File

@@ -27,12 +27,20 @@ function M.fn(fname)
local line = core.get_nodes_starting_line() local line = core.get_nodes_starting_line()
local absolute_paths_searched = {}
local found = Iterator.builder(core.get_explorer().nodes) local found = Iterator.builder(core.get_explorer().nodes)
:matcher(function(node) :matcher(function(node)
return node.absolute_path == fname_real or node.link_to == fname_real return node.absolute_path == fname_real or node.link_to == fname_real
end) end)
:applier(function(node) :applier(function(node)
line = line + 1 line = line + 1
if vim.tbl_contains(absolute_paths_searched, node.absolute_path) then
return
end
table.insert(absolute_paths_searched, node.absolute_path)
local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator) local abs_match = vim.startswith(fname_real, node.absolute_path .. utils.path_separator)
local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator) local link_match = node.link_to and vim.startswith(fname_real, node.link_to .. utils.path_separator)

View File

@@ -7,18 +7,27 @@ local find_file = require("nvim-tree.actions.finders.find-file").fn
local M = {} local M = {}
local function search(dir, input_path) local function search(search_dir, input_path)
local path, name, stat, handle, _ local realpaths_searched = {}
if not dir then if not search_dir then
return return
end end
local function iter(dir)
local realpath, path, name, stat, handle, _
handle, _ = uv.fs_scandir(dir) handle, _ = uv.fs_scandir(dir)
if not handle then if not handle then
return return
end end
realpath, _ = uv.fs_realpath(dir)
if not realpath or vim.tbl_contains(realpaths_searched, realpath) then
return
end
table.insert(realpaths_searched, realpath)
name, _ = uv.fs_scandir_next(handle) name, _ = uv.fs_scandir_next(handle)
while name do while name do
path = dir .. "/" .. name path = dir .. "/" .. name
@@ -34,7 +43,7 @@ local function search(dir, input_path)
end end
if stat.type == "directory" then if stat.type == "directory" then
path = search(path, input_path) path = iter(path)
if path then if path then
return path return path
end end
@@ -45,6 +54,9 @@ local function search(dir, input_path)
end end
end end
return iter(search_dir)
end
function M.fn() function M.fn()
if not core.get_explorer() then if not core.get_explorer() then
return return

View File

@@ -108,7 +108,7 @@ function M.fn(node)
end end
-- INFO: defer needed when reload is automatic (watchers) -- INFO: defer needed when reload is automatic (watchers)
vim.defer_fn(function() vim.defer_fn(function()
utils.focus_file(new_file_path) utils.focus_file(utils.path_remove_trailing(new_file_path))
end, 150) end, 150)
end) end)
end end

View File

@@ -3,10 +3,15 @@ local luv = vim.loop
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local events = require "nvim-tree.events" local events = require "nvim-tree.events"
local view = require "nvim-tree.view"
local M = {} local M = {}
local function close_windows(windows) local function close_windows(windows)
if view.View.float.enable and #a.nvim_list_wins() == 1 then
return
end
for _, window in ipairs(windows) do for _, window in ipairs(windows) do
if a.nvim_win_is_valid(window) then if a.nvim_win_is_valid(window) then
a.nvim_win_close(window, true) a.nvim_win_close(window, true)
@@ -18,12 +23,14 @@ local function clear_buffer(absolute_path)
local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 } local bufs = vim.fn.getbufinfo { bufloaded = 1, buflisted = 1 }
for _, buf in pairs(bufs) do for _, buf in pairs(bufs) do
if buf.name == absolute_path then if buf.name == absolute_path then
if buf.hidden == 0 and #bufs > 1 then if buf.hidden == 0 and (#bufs > 1 or view.View.float.enable) then
local winnr = a.nvim_get_current_win() local winnr = a.nvim_get_current_win()
a.nvim_set_current_win(buf.windows[1]) a.nvim_set_current_win(buf.windows[1])
vim.cmd ":bn" vim.cmd ":bn"
if not view.View.float.enable then
a.nvim_set_current_win(winnr) a.nvim_set_current_win(winnr)
end end
end
a.nvim_buf_delete(buf.bufnr, { force = true }) a.nvim_buf_delete(buf.bufnr, { force = true })
if M.close_window then if M.close_window then
close_windows(buf.windows) close_windows(buf.windows)

View File

@@ -390,6 +390,7 @@ local DEFAULT_MAPPING_CONFIG = {
function M.setup(opts) function M.setup(opts)
require("nvim-tree.actions.fs.trash").setup(opts) require("nvim-tree.actions.fs.trash").setup(opts)
require("nvim-tree.actions.node.system-open").setup(opts) require("nvim-tree.actions.node.system-open").setup(opts)
require("nvim-tree.actions.node.file-popup").setup(opts)
require("nvim-tree.actions.node.open-file").setup(opts) require("nvim-tree.actions.node.open-file").setup(opts)
require("nvim-tree.actions.root.change-dir").setup(opts) require("nvim-tree.actions.root.change-dir").setup(opts)
require("nvim-tree.actions.fs.create-file").setup(opts) require("nvim-tree.actions.fs.create-file").setup(opts)

View File

@@ -28,16 +28,13 @@ local function setup_window(node)
local max_width = vim.fn.max(vim.tbl_map(function(n) local max_width = vim.fn.max(vim.tbl_map(function(n)
return #n return #n
end, lines)) end, lines))
local winnr = a.nvim_open_win(0, false, { local open_win_config = vim.tbl_extend("force", M.open_win_config, {
col = 1,
row = 1,
relative = "cursor",
width = max_width + 1, width = max_width + 1,
height = #lines, height = #lines,
border = "shadow",
noautocmd = true, noautocmd = true,
style = "minimal", zindex = 60,
}) })
local winnr = a.nvim_open_win(0, false, open_win_config)
current_popup = { current_popup = {
winnr = winnr, winnr = winnr,
file_path = node.absolute_path, file_path = node.absolute_path,
@@ -78,4 +75,8 @@ function M.toggle_file_info(node)
}) })
end end
function M.setup(opts)
M.open_win_config = opts.actions.file_popup.open_win_config
end
return M return M

View File

@@ -280,7 +280,7 @@ function M.fn(mode, filename)
end end
function M.setup(opts) function M.setup(opts)
M.quit_on_open = opts.actions.open_file.quit_on_open M.quit_on_open = opts.actions.open_file.quit_on_open or opts.view.float.enable
M.resize_window = opts.actions.open_file.resize_window M.resize_window = opts.actions.open_file.resize_window
if opts.actions.open_file.window_picker.chars then if opts.actions.open_file.window_picker.chars then
opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper() opts.actions.open_file.window_picker.chars = tostring(opts.actions.open_file.window_picker.chars):upper()

View File

@@ -4,6 +4,7 @@ local watch = require "nvim-tree.explorer.watch"
local M = { local M = {
is_windows = vim.fn.has "win32" == 1, is_windows = vim.fn.has "win32" == 1,
is_wsl = vim.fn.has "wsl" == 1,
} }
function M.folder(parent, absolute_path, name) function M.folder(parent, absolute_path, name)
@@ -11,6 +12,7 @@ function M.folder(parent, absolute_path, name)
local has_children = handle and uv.fs_scandir_next(handle) ~= nil local has_children = handle and uv.fs_scandir_next(handle) ~= nil
return { return {
type = "directory",
absolute_path = absolute_path, absolute_path = absolute_path,
fs_stat = uv.fs_stat(absolute_path), fs_stat = uv.fs_stat(absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node group_next = nil, -- If node is grouped, this points to the next child dir/link node
@@ -23,9 +25,19 @@ function M.folder(parent, absolute_path, name)
} }
end end
function M.is_executable(absolute_path, ext) function M.is_executable(parent, absolute_path, ext)
if M.is_windows then if M.is_windows then
return utils.is_windows_exe(ext) return utils.is_windows_exe(ext)
elseif M.is_wsl then
if parent.is_wsl_windows_fs_path == nil then
-- Evaluate lazily when needed and do so only once for each parent
-- as 'wslpath' calls can get expensive in highly populated directories.
parent.is_wsl_windows_fs_path = utils.is_wsl_windows_fs_path(absolute_path)
end
if parent.is_wsl_windows_fs_path then
return utils.is_wsl_windows_fs_exe(ext)
end
end end
return uv.fs_access(absolute_path, "X") return uv.fs_access(absolute_path, "X")
end end
@@ -34,8 +46,9 @@ function M.file(parent, absolute_path, name)
local ext = string.match(name, ".?[^.]+%.(.*)") or "" local ext = string.match(name, ".?[^.]+%.(.*)") or ""
return { return {
type = "file",
absolute_path = absolute_path, absolute_path = absolute_path,
executable = M.is_executable(absolute_path, ext), executable = M.is_executable(parent, absolute_path, ext),
extension = ext, extension = ext,
fs_stat = uv.fs_stat(absolute_path), fs_stat = uv.fs_stat(absolute_path),
name = name, name = name,
@@ -61,6 +74,7 @@ function M.link(parent, absolute_path, name)
end end
return { return {
type = "link",
absolute_path = absolute_path, absolute_path = absolute_path,
fs_stat = uv.fs_stat(absolute_path), fs_stat = uv.fs_stat(absolute_path),
group_next = nil, -- If node is grouped, this points to the next child dir/link node group_next = nil, -- If node is grouped, this points to the next child dir/link node

View File

@@ -41,10 +41,32 @@ function M.reload(node, status)
break break
end end
local stat
local function fs_stat_cached(path)
if stat ~= nil then
return stat
end
stat = uv.fs_stat(path)
return stat
end
local abs = utils.path_join { cwd, name } local abs = utils.path_join { cwd, name }
t = t or (uv.fs_stat(abs) or {}).type t = t or (fs_stat_cached(abs) or {}).type
if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then if not filters.should_ignore(abs) and not filters.should_ignore_git(abs, status.files) then
child_names[abs] = true child_names[abs] = true
-- Recreate node if type changes.
if nodes_by_path[abs] then
local n = nodes_by_path[abs]
if n.type ~= t then
utils.array_remove(node.nodes, n)
common.node_destroy(n)
nodes_by_path[abs] = nil
end
end
if not nodes_by_path[abs] then if not nodes_by_path[abs] then
if t == "directory" and uv.fs_access(abs, "R") then if t == "directory" and uv.fs_access(abs, "R") then
local folder = builders.folder(node, abs, name) local folder = builders.folder(node, abs, name)
@@ -61,10 +83,12 @@ function M.reload(node, status)
table.insert(node.nodes, link) table.insert(node.nodes, link)
end end
end end
end else
local n = nodes_by_path[abs] local n = nodes_by_path[abs]
if n then if n then
n.executable = builders.is_executable(abs, n.extension or "") n.executable = builders.is_executable(n.parent, abs, n.extension or "")
n.fs_stat = fs_stat_cached(abs)
end
end end
end end
end end

View File

@@ -19,7 +19,7 @@ local function update_parent_statuses(node, project, root)
end end
local function is_git(path) local function is_git(path)
return path:match "%.git$" ~= nil or path:match(utils.path_add_trailing ".git") ~= nil return vim.fn.fnamemodify(path, ":t") == ".git"
end end
local IGNORED_PATHS = { local IGNORED_PATHS = {

View File

@@ -84,7 +84,7 @@ function Runner:_run_git_job()
local opts = self:_getopts(stdout, stderr) local opts = self:_getopts(stdout, stderr)
log.line("git", "running job with timeout %dms", self.timeout) log.line("git", "running job with timeout %dms", self.timeout)
log.line("git", "git %s", table.concat(opts.args, " ")) log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " "))
handle, pid = uv.spawn( handle, pid = uv.spawn(
"git", "git",

View File

@@ -14,7 +14,7 @@ function M.get_toplevel(cwd)
log.raw("git", toplevel) log.raw("git", toplevel)
log.profile_end(ps, "git toplevel %s", cwd) log.profile_end(ps, "git toplevel %s", cwd)
if not toplevel or #toplevel == 0 or toplevel:match "fatal" then if vim.v.shell_error ~= 0 or not toplevel or #toplevel == 0 or toplevel:match "fatal" then
return nil return nil
end end
@@ -23,6 +23,9 @@ function M.get_toplevel(cwd)
-- msys2 git support -- msys2 git support
if has_cygpath then if has_cygpath then
toplevel = vim.fn.system("cygpath -w " .. vim.fn.shellescape(toplevel)) toplevel = vim.fn.system("cygpath -w " .. vim.fn.shellescape(toplevel))
if vim.v.shell_error ~= 0 then
return nil
end
end end
toplevel = toplevel:gsub("/", "\\") toplevel = toplevel:gsub("/", "\\")
end end
@@ -38,7 +41,7 @@ function M.should_show_untracked(cwd)
return untracked[cwd] return untracked[cwd]
end end
local cmd = "git -C " .. cwd .. " config --type=bool status.showUntrackedFiles" local cmd = "git -C " .. cwd .. " config status.showUntrackedFiles"
local ps = log.profile_start("git untracked %s", cwd) local ps = log.profile_start("git untracked %s", cwd)
log.line("git", cmd) log.line("git", cmd)
@@ -48,7 +51,7 @@ function M.should_show_untracked(cwd)
log.raw("git", has_untracked) log.raw("git", has_untracked)
log.profile_end(ps, "git untracked %s", cwd) log.profile_end(ps, "git untracked %s", cwd)
untracked[cwd] = vim.trim(has_untracked) ~= "false" untracked[cwd] = vim.trim(has_untracked) ~= "no"
return untracked[cwd] return untracked[cwd]
end end

View File

@@ -238,7 +238,7 @@ local DEFAULT_KEYMAPS = {
}, },
}, },
{ {
key = "[e", key = "]e",
callback = Api.node.navigate.diagnostics.next, callback = Api.node.navigate.diagnostics.next,
desc = { desc = {
long = "Go to next diagnostic item.", long = "Go to next diagnostic item.",
@@ -246,7 +246,7 @@ local DEFAULT_KEYMAPS = {
}, },
}, },
{ {
key = "[c", key = "]c",
callback = Api.node.navigate.git.next, callback = Api.node.navigate.git.next,
desc = { desc = {
long = "Go to next git item.", long = "Go to next git item.",
@@ -254,7 +254,7 @@ local DEFAULT_KEYMAPS = {
}, },
}, },
{ {
key = "]e", key = "[e",
callback = Api.node.navigate.diagnostics.prev, callback = Api.node.navigate.diagnostics.prev,
desc = { desc = {
long = "Go to prev diagnostic item.", long = "Go to prev diagnostic item.",
@@ -262,7 +262,7 @@ local DEFAULT_KEYMAPS = {
}, },
}, },
{ {
key = "]c", key = "[c",
callback = Api.node.navigate.git.prev, callback = Api.node.navigate.git.prev,
desc = { desc = {
long = "Go to prev git item.", long = "Go to prev git item.",

View File

@@ -25,6 +25,15 @@ local overlay_bufnr = nil
local overlay_winnr = nil local overlay_winnr = nil
local function remove_overlay() local function remove_overlay()
if view.View.float.enable then
-- return to normal nvim-tree float behaviour when filter window is closed
a.nvim_create_autocmd("WinLeave", {
pattern = "NvimTree_*",
group = a.nvim_create_augroup("NvimTree", { clear = false }),
callback = view.close,
})
end
a.nvim_win_close(overlay_winnr, { force = true }) a.nvim_win_close(overlay_winnr, { force = true })
overlay_bufnr = nil overlay_bufnr = nil
overlay_winnr = nil overlay_winnr = nil
@@ -92,12 +101,24 @@ local function configure_buffer_overlay()
end end
local function create_overlay() local function create_overlay()
local min_width = 20
if view.View.float.enable then
-- don't close nvim-tree float when focus is changed to filter window
a.nvim_clear_autocmds {
event = "WinLeave",
pattern = "NvimTree_*",
group = a.nvim_create_augroup("NvimTree", { clear = false }),
}
min_width = min_width - 2
end
configure_buffer_overlay() configure_buffer_overlay()
overlay_winnr = a.nvim_open_win(overlay_bufnr, true, { overlay_winnr = a.nvim_open_win(overlay_bufnr, true, {
col = 1, col = 1,
row = 0, row = 0,
relative = "cursor", relative = "cursor",
width = math.max(20, a.nvim_win_get_width(view.get_winnr()) - #M.prefix - 2), width = math.max(min_width, a.nvim_win_get_width(view.get_winnr()) - #M.prefix - 2),
height = 1, height = 1,
border = "none", border = "none",
style = "minimal", style = "minimal",

View File

@@ -17,6 +17,10 @@ local function remove_mark(node)
end end
function M.toggle_mark(node) function M.toggle_mark(node)
if node.absolute_path == nil then
return
end
if M.get_mark(node) then if M.get_mark(node) then
remove_mark(node) remove_mark(node)
else else

View File

@@ -1,4 +1,5 @@
local utils = require "nvim-tree.utils" local utils = require "nvim-tree.utils"
local core = require "nvim-tree.core"
local git = require "nvim-tree.renderer.components.git" local git = require "nvim-tree.renderer.components.git"
local pad = require "nvim-tree.renderer.components.padding" local pad = require "nvim-tree.renderer.components.padding"
@@ -114,7 +115,8 @@ function Builder:_build_folder(node, padding, git_hl, git_icons_tbl)
local foldername = name .. self.trailing_slash local foldername = name .. self.trailing_slash
if node.link_to and self.symlink_destination then if node.link_to and self.symlink_destination then
local arrow = icons.i.symlink_arrow local arrow = icons.i.symlink_arrow
foldername = foldername .. arrow .. node.link_to local link_to = utils.path_relative(node.link_to, core.get_cwd())
foldername = foldername .. arrow .. link_to
end end
local git_icons = self:_unwrap_git_data(git_icons_tbl, offset + #icon + (self.is_git_after and #foldername + 1 or 0)) local git_icons = self:_unwrap_git_data(git_icons_tbl, offset + #icon + (self.is_git_after and #foldername + 1 or 0))
@@ -160,7 +162,8 @@ function Builder:_build_symlink(node, padding, git_highlight, git_icons_tbl)
local arrow = icons.i.symlink_arrow local arrow = icons.i.symlink_arrow
local symlink_formatted = node.name local symlink_formatted = node.name
if self.symlink_destination then if self.symlink_destination then
symlink_formatted = symlink_formatted .. arrow .. node.link_to local link_to = utils.path_relative(node.link_to, core.get_cwd())
symlink_formatted = symlink_formatted .. arrow .. link_to
end end
local link_highlight = git_highlight or "NvimTreeSymlink" local link_highlight = git_highlight or "NvimTreeSymlink"
@@ -258,9 +261,9 @@ function Builder:_build_line(node, idx, num_children)
self.index = self.index + 1 self.index = self.index + 1
if node.open then if node.open then
self.depth = self.depth + 2 self.depth = self.depth + 1
self:build(node) self:build(node)
self.depth = self.depth - 2 self.depth = self.depth - 1
end end
end end

View File

@@ -99,6 +99,7 @@ local git_hl = {
["AD"] = "NvimTreeFileStaged", ["AD"] = "NvimTreeFileStaged",
["MD"] = "NvimTreeFileStaged", ["MD"] = "NvimTreeFileStaged",
["T "] = "NvimTreeFileStaged", ["T "] = "NvimTreeFileStaged",
["TT"] = "NvimTreeFileStaged",
[" M"] = "NvimTreeFileDirty", [" M"] = "NvimTreeFileDirty",
["CM"] = "NvimTreeFileDirty", ["CM"] = "NvimTreeFileDirty",
[" C"] = "NvimTreeFileDirty", [" C"] = "NvimTreeFileDirty",

View File

@@ -25,30 +25,33 @@ local function get_padding_indent_markers(depth, idx, nodes_number, markers, wit
if depth > 0 then if depth > 0 then
local has_folder_sibling = check_siblings_for_folder(node, with_arrows) local has_folder_sibling = check_siblings_for_folder(node, with_arrows)
local rdepth = depth / 2 local indent = string.rep(" ", M.config.indent_width - 1)
markers[rdepth] = idx ~= nodes_number markers[depth] = idx ~= nodes_number
for i = 1, rdepth do for i = 1, depth do
local glyph local glyph
if idx == nodes_number and i == rdepth then if idx == nodes_number and i == depth then
local bottom_width = M.config.indent_width
- 2
+ (with_arrows and not inline_arrows and has_folder_sibling and 2 or 0)
glyph = M.config.indent_markers.icons.corner glyph = M.config.indent_markers.icons.corner
elseif markers[i] and i == rdepth then .. string.rep(M.config.indent_markers.icons.bottom, bottom_width)
glyph = M.config.indent_markers.icons.item .. (M.config.indent_width > 1 and " " or "")
elseif markers[i] and i == depth then
glyph = M.config.indent_markers.icons.item .. indent
elseif markers[i] then elseif markers[i] then
glyph = M.config.indent_markers.icons.edge glyph = M.config.indent_markers.icons.edge .. indent
else else
glyph = M.config.indent_markers.icons.none glyph = M.config.indent_markers.icons.none .. indent
end end
if not with_arrows or (inline_arrows and (rdepth ~= i or not node.nodes)) then if not with_arrows or (inline_arrows and (depth ~= i or not node.nodes)) then
padding = padding .. glyph .. " " padding = padding .. glyph
elseif inline_arrows then elseif inline_arrows then
padding = padding padding = padding
elseif idx == nodes_number and i == rdepth and has_folder_sibling then elseif idx ~= nodes_number and depth == i and not node.nodes and has_folder_sibling then
padding = padding .. base_padding .. glyph .. "── " padding = padding .. base_padding .. glyph .. base_padding
elseif rdepth == i and not node.nodes and has_folder_sibling then
padding = padding .. base_padding .. glyph .. " " .. base_padding
else else
padding = padding .. base_padding .. glyph .. " " padding = padding .. base_padding .. glyph
end end
end end
end end
@@ -71,11 +74,12 @@ function M.get_padding(depth, idx, nodes_number, node, markers)
local show_arrows = M.config.icons.show.folder_arrow local show_arrows = M.config.icons.show.folder_arrow
local show_markers = M.config.indent_markers.enable local show_markers = M.config.indent_markers.enable
local inline_arrows = M.config.indent_markers.inline_arrows local inline_arrows = M.config.indent_markers.inline_arrows
local indent_width = M.config.indent_width
if show_markers then if show_markers then
padding = padding .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node) padding = padding .. get_padding_indent_markers(depth, idx, nodes_number, markers, show_arrows, inline_arrows, node)
else else
padding = padding .. string.rep(" ", depth) padding = padding .. string.rep(" ", depth * indent_width)
end end
if show_arrows then if show_arrows then
@@ -87,6 +91,22 @@ end
function M.setup(opts) function M.setup(opts)
M.config = opts.renderer M.config = opts.renderer
if M.config.indent_width < 1 then
M.config.indent_width = 1
end
local function check_marker(symbol)
if #symbol == 0 then
return " "
end
-- return the first character from the UTF-8 encoded string; we may use utf8.codes from Lua 5.3 when available
return symbol:match "[%z\1-\127\194-\244][\128-\191]*"
end
for k, v in pairs(M.config.indent_markers.icons) do
M.config.indent_markers.icons[k] = check_marker(v)
end
end end
return M return M

View File

@@ -10,6 +10,7 @@ local M = {
} }
M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1 M.is_windows = vim.fn.has "win32" == 1 or vim.fn.has "win32unix" == 1
M.is_wsl = vim.fn.has "wsl" == 1
function M.path_to_matching_str(path) function M.path_to_matching_str(path)
return path:gsub("(%-)", "(%%-)"):gsub("(%.)", "(%%.)"):gsub("(%_)", "(%%_)") return path:gsub("(%-)", "(%%-)"):gsub("(%.)", "(%%.)"):gsub("(%_)", "(%%_)")
@@ -171,8 +172,11 @@ end
---@return boolean ---@return boolean
function M.is_windows_exe(ext) function M.is_windows_exe(ext)
if not M.pathexts then if not M.pathexts then
local PATHEXT = vim.env.PATHEXT or "" if not vim.env.PATHEXT then
local wexe = vim.split(PATHEXT:gsub("%.", ""), ";") return false
end
local wexe = vim.split(vim.env.PATHEXT:gsub("%.", ""), ";")
M.pathexts = {} M.pathexts = {}
for _, v in pairs(wexe) do for _, v in pairs(wexe) do
M.pathexts[v] = true M.pathexts[v] = true
@@ -182,6 +186,44 @@ function M.is_windows_exe(ext)
return M.pathexts[ext:upper()] return M.pathexts[ext:upper()]
end end
--- Check whether path maps to Windows filesystem mounted by WSL
-- @param path string
-- @return boolean
function M.is_wsl_windows_fs_path(path)
-- Run 'wslpath' command to try translating WSL path to Windows path.
-- Consume stderr output as well because 'wslpath' can produce permission
-- errors on some files (e.g. temporary files in root of system drive).
local handle = io.popen('wslpath -w "' .. path .. '" 2>/dev/null')
if handle then
local output = handle:read "*a"
handle:close()
return string.find(output, "^\\\\wsl$\\") == nil
end
return false
end
--- Check whether extension is Windows executable under WSL
-- @param ext string
-- @return boolean
function M.is_wsl_windows_fs_exe(ext)
if not vim.env.PATHEXT then
-- Extract executable extensions from within WSL.
-- Redirect stderr to null to silence warnings when
-- Windows command is executed from Linux filesystem:
-- > CMD.EXE was started with the above path as the current directory.
-- > UNC paths are not supported. Defaulting to Windows directory.
local handle = io.popen 'cmd.exe /c "echo %PATHEXT%" 2>/dev/null'
if handle then
vim.env.PATHEXT = handle:read "*a"
handle:close()
end
end
return M.is_windows_exe(ext)
end
function M.rename_loaded_buffers(old_path, new_path) function M.rename_loaded_buffers(old_path, new_path)
for _, buf in pairs(a.nvim_list_bufs()) do for _, buf in pairs(a.nvim_list_bufs()) do
if a.nvim_buf_is_loaded(buf) then if a.nvim_buf_is_loaded(buf) then
@@ -408,6 +450,12 @@ function M.array_remove(array, item)
end end
end end
function M.array_remove_nils(array)
return vim.tbl_filter(function(v)
return v ~= nil
end, array)
end
function M.inject_node(f) function M.inject_node(f)
return function() return function()
f(require("nvim-tree.lib").get_node_at_cursor()) f(require("nvim-tree.lib").get_node_at_cursor())

View File

@@ -133,9 +133,21 @@ local function set_window_options_and_buffer()
end end
end end
local function open_win_config()
if type(M.View.float.open_win_config) == "function" then
return M.View.float.open_win_config()
else
return M.View.float.open_win_config
end
end
local function open_window() local function open_window()
if M.View.float.enable then
a.nvim_open_win(0, true, open_win_config())
else
a.nvim_command "vsp" a.nvim_command "vsp"
M.reposition_window() M.reposition_window()
end
setup_tabpage(a.nvim_get_current_tabpage()) setup_tabpage(a.nvim_get_current_tabpage())
set_window_options_and_buffer() set_window_options_and_buffer()
end end
@@ -184,11 +196,13 @@ function M.close()
local current_win = a.nvim_get_current_win() local current_win = a.nvim_get_current_win()
for _, win in pairs(a.nvim_list_wins()) do for _, win in pairs(a.nvim_list_wins()) do
if tree_win ~= win and a.nvim_win_get_config(win).relative == "" then if tree_win ~= win and a.nvim_win_get_config(win).relative == "" then
a.nvim_win_close(tree_win, true)
local prev_win = vim.fn.winnr "#" -- this tab only local prev_win = vim.fn.winnr "#" -- this tab only
if tree_win == current_win and prev_win > 0 then if tree_win == current_win and prev_win > 0 then
a.nvim_set_current_win(vim.fn.win_getid(prev_win)) a.nvim_set_current_win(vim.fn.win_getid(prev_win))
end end
if a.nvim_win_is_valid(tree_win) then
a.nvim_win_close(tree_win, true)
end
events._dispatch_on_tree_close() events._dispatch_on_tree_close()
return return
end end
@@ -232,6 +246,12 @@ function M.grow_from_content()
end end
function M.resize(size) function M.resize(size)
if M.View.float.enable and not M.View.adaptive_size then
-- if the floating windows's adaptive size is not desired, then the
-- float size should be defined in view.float.open_win_config
return
end
if type(size) == "string" then if type(size) == "string" then
size = vim.trim(size) size = vim.trim(size)
local first_char = size:sub(1, 1) local first_char = size:sub(1, 1)
@@ -431,6 +451,7 @@ function M.setup(opts)
M.View.winopts.number = options.number M.View.winopts.number = options.number
M.View.winopts.relativenumber = options.relativenumber M.View.winopts.relativenumber = options.relativenumber
M.View.winopts.signcolumn = options.signcolumn M.View.winopts.signcolumn = options.signcolumn
M.View.float = options.float
M.on_attach = opts.on_attach M.on_attach = opts.on_attach
end end