feat/chore: rewrite git with job and some other fixes (#743)

* feat/chore: rewrite git with job and some other fixes

* fix: fs clear window, rename echo_warning -> warn

also fix renaming and add an event blocker to avoid running many events
at the same time
This commit is contained in:
Kiyan 2021-11-27 16:02:54 +01:00 committed by GitHub
parent b853e1083c
commit 6662b60a2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 453 additions and 354 deletions

View File

@ -23,7 +23,9 @@ Install with [packer](https://github.com/wbthomason/packer.nvim):
```lua
use {
'kyazdani42/nvim-tree.lua',
requires = 'kyazdani42/nvim-web-devicons',
requires = {
'kyazdani42/nvim-web-devicons', -- optional, for file icon
},
config = function() require'nvim-tree'.setup {} end
}
```
@ -72,6 +74,11 @@ require'nvim-tree'.setup {
dotfiles = false,
custom = {}
},
git = {
enable = true,
ignore = true,
timeout = 500,
},
view = {
width = 30,
height = 30,
@ -89,7 +96,6 @@ require'nvim-tree'.setup {
These additional options must be set **BEFORE** calling `require'nvim-tree'` or calling setup.
They are being migrated to the setup function bit by bit, check [this issue](https://github.com/kyazdani42/nvim-tree.lua/issues/674) if you encounter any problems related to configs not working after update.
```vim
let g:nvim_tree_gitignore = 1 "0 by default
let g:nvim_tree_quit_on_open = 1 "0 by default, closes the tree when you open a file
let g:nvim_tree_indent_markers = 1 "0 by default, this option shows indent markers when folders are open
let g:nvim_tree_git_hl = 1 "0 by default, will enable file highlight for git attributes (can be used without the icons).

View File

@ -101,6 +101,10 @@ function.
cmd = nil,
args = {}
},
git = {
enable = true,
ignore = true,
},
view = {
width = 30,
height = 30,
@ -231,6 +235,31 @@ Here is a list of the options available in the setup call:
- `NvimTreeLspDiagnosticsInformation`
- `NvimTreeLspDiagnosticsHint`
*nvim-tree.git*
- |git|: git integration with icons and colors
- |git.enable|: enable / disable the feature
type: `boolean`
default: `true`
- |git.ignore|: ignore files based on `.gitignore`.
will add `ignored=matching` to the integration when `true`. Otherwise will
add `ignored=no` to the integration which can lead to better performance.
- |git.timeout|: kills the git process after some time if it takes too long
type: `number`
default: `400` (ms)
You will still need to configure `g:nvim_tree_show_icons.git` or
`g:nvim_tree_git_hl` to be able to see things in the tree. This will be
changed in the future versions.
The configurable timeout will kill the current process and so disable the
git integration for the project that takes too long.
The git integration is blocking, so if your timeout is too long (like not in
milliseconds but a few seconds), it will not render anything until the git
process returned the data.
*nvim-tree.view*
- |view|: window / buffer setup
@ -296,16 +325,6 @@ width of the window, can be *width_in_columns* or *'width_in_percent%'*
where the window will open (default to 'left')
- 'left' or 'right'
|g:nvim_tree_gitignore| *g:nvim_tree_gitignore*
Determines whether to include in g:nvim_tree_ignore
files ignored by git.
Must be:
0: not ignored
1: ignored files from `git ls-files --others --ignored --exclude-standard --directory`
>
|g:nvim_tree_show_icons| *g:nvim_tree_show_icons*
Dictionary, if your terminal or font doesn't support certain unicode

View File

@ -114,7 +114,7 @@ local keypress_funcs = {
elseif _config.is_unix then
_config.system_open.cmd = 'xdg-open'
else
require'nvim-tree.utils'.echo_warning("Cannot open file with system application. Unrecognized platform.")
require'nvim-tree.utils'.warn("Cannot open file with system application. Unrecognized platform.")
return
end
end
@ -173,16 +173,12 @@ function M.on_keypress(mode)
if node.link_to and not node.entries then
lib.open_file(mode, node.link_to)
elseif node.entries ~= nil then
lib.unroll_dir(node)
lib.expand_or_collapse(node)
else
lib.open_file(mode, node.absolute_path)
end
end
function M.refresh()
lib.refresh_tree()
end
function M.print_clipboard()
fs.print_clipboard()
end
@ -227,7 +223,7 @@ function M.on_enter(opts)
M.hijack_current_window()
end
lib.init(should_open, should_open)
lib.init(should_open)
end
local function is_file_readable(fname)
@ -242,7 +238,7 @@ local function update_base_dir_with_filepath(filepath, bufnr)
local ft = api.nvim_buf_get_option(bufnr, 'filetype') or ""
for _, value in pairs(_config.update_focused_file.ignore_list) do
if vim.fn.stridx(filepath, value) ~= -1 or vim.fn.stridx(ft, value) ~= -1 then
if utils.str_find(filepath, value) or utils.str_find(ft, value) then
return
end
end
@ -359,7 +355,7 @@ local function setup_vim_commands()
command! NvimTreeClose lua require'nvim-tree'.close()
command! NvimTreeToggle lua require'nvim-tree'.toggle(false)
command! NvimTreeFocus lua require'nvim-tree'.focus()
command! NvimTreeRefresh lua require'nvim-tree'.refresh()
command! NvimTreeRefresh lua require'nvim-tree.lib'.refresh_tree()
command! NvimTreeClipboard lua require'nvim-tree'.print_clipboard()
command! NvimTreeFindFile lua require'nvim-tree'.find_file(true)
command! NvimTreeFindFileToggle lua require'nvim-tree'.toggle(true)
@ -381,8 +377,8 @@ local function setup_autocommands(opts)
""" reset highlights when colorscheme is changed
au ColorScheme * lua require'nvim-tree'.reset_highlight()
au BufWritePost * lua require'nvim-tree'.refresh()
au User FugitiveChanged,NeogitStatusRefreshed lua require'nvim-tree'.refresh()
au BufWritePost * lua require'nvim-tree.lib'.refresh_tree()
au User FugitiveChanged,NeogitStatusRefreshed lua require'nvim-tree.lib'.reload_git()
]]
if opts.auto_close then
@ -400,6 +396,7 @@ local function setup_autocommands(opts)
if opts.update_focused_file.enable then
vim.cmd "au BufEnter * lua require'nvim-tree'.find_file(false)"
end
vim.cmd "au BufUnload NvimTree lua require'nvim-tree.view'.View.tabpages = {}"
vim.cmd "augroup end"
end
@ -439,6 +436,11 @@ local DEFAULT_OPTS = {
filters = {
dotfiles = false,
custom_filter = {}
},
git = {
enable = true,
ignore = true,
timeout = 400,
}
}
@ -452,7 +454,7 @@ function M.setup(conf)
_config.open_on_setup = opts.open_on_setup
_config.ignore_ft_on_setup = opts.ignore_ft_on_setup
if type(opts.update_to_buf_dir) == "boolean" then
utils.echo_warning("update_to_buf_dir is now a table, see :help nvim-tree.update_to_buf_dir")
utils.warn("update_to_buf_dir is now a table, see :help nvim-tree.update_to_buf_dir")
_config.update_to_buf_dir = {
enable = opts.update_to_buf_dir,
auto_open = opts.update_to_buf_dir,
@ -462,13 +464,14 @@ function M.setup(conf)
end
if opts.lsp_diagnostics ~= nil then
utils.echo_warning("setup.lsp_diagnostics has been removed, see :help nvim-tree.diagnostics")
utils.warn("setup.lsp_diagnostics has been removed, see :help nvim-tree.diagnostics")
end
require'nvim-tree.colors'.setup()
require'nvim-tree.view'.setup(opts.view or {})
require'nvim-tree.diagnostics'.setup(opts)
require'nvim-tree.populate'.setup(opts)
require'nvim-tree.git'.setup(opts)
setup_autocommands(opts)
setup_vim_commands()

View File

@ -58,12 +58,6 @@ function M.get_icon_state()
}
end
function M.use_git()
return M.get_icon_state().show_git_icon
or vim.g.nvim_tree_git_hl == 1
or vim.g.nvim_tree_gitignore == 1
end
function M.nvim_tree_callback(callback_name)
return string.format(":lua require'nvim-tree'.on_keypress('%s')<CR>", callback_name)
end

View File

@ -34,7 +34,7 @@ local function create_file(file)
else
luv.fs_close(fd)
events._dispatch_file_created(file)
lib.refresh_tree(true)
lib.refresh_tree()
focus_file(file)
end
end))
@ -98,7 +98,7 @@ function M.create(node)
end
api.nvim_out_write(ans..' was properly created\n')
events._dispatch_folder_created(ans)
lib.refresh_tree(true)
lib.refresh_tree()
focus_file(ans)
end
@ -113,6 +113,9 @@ local function clear_buffer(absolute_path)
api.nvim_set_current_win(winnr)
end
vim.api.nvim_buf_delete(buf.bufnr, {})
if buf.windows[1] then
vim.api.nvim_win_close(buf.windows[1], true)
end
return
end
end
@ -239,7 +242,7 @@ local function do_paste(node, action_type, action_fn)
end
clipboard[action_type] = {}
return lib.refresh_tree(true)
return lib.refresh_tree()
end
local function add_to_clipboard(node, clip)
@ -276,7 +279,7 @@ function M.remove(node)
events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path)
end
lib.refresh_tree(true)
lib.refresh_tree()
end
end
@ -289,7 +292,13 @@ function M.rename(with_sub)
local abs_path = with_sub and node.absolute_path:sub(0, namelen * (-1) -1) or node.absolute_path
local new_name = vim.fn.input("Rename " ..node.name.. " to ", abs_path)
utils.clear_prompt()
if not new_name or #new_name == 0 then return end
if not new_name or #new_name == 0 then
return
end
if luv.fs_access(new_name, 'R') then
utils.warn("Cannot rename: file already exists")
return
end
local success = luv.fs_rename(node.absolute_path, new_name)
if not success then
@ -298,7 +307,7 @@ function M.rename(with_sub)
api.nvim_out_write(node.absolute_path..''..new_name..'\n')
rename_loaded_buffers(node.absolute_path, new_name)
events._dispatch_node_renamed(abs_path, new_name)
lib.refresh_tree(true)
lib.refresh_tree()
end
end

View File

@ -1,168 +0,0 @@
local utils = require'nvim-tree.utils'
local M = {}
local roots = {}
---A map from git roots to a list of ignored paths
local gitignore_map = {}
local not_git = 'not a git repo'
local is_win = vim.api.nvim_call_function("has", {"win32"}) == 1
local function update_root_status(root)
local e_root = vim.fn.shellescape(root)
local untracked = ' -u'
local cmd = "git -C " .. e_root .. " config --type=bool status.showUntrackedFiles"
if vim.trim(vim.fn.system(cmd)) == 'false' then
untracked = ''
end
cmd = "git -C " .. e_root .. " status --porcelain=v1 --ignored=matching" .. untracked
local status = vim.fn.systemlist(cmd)
roots[root] = {}
gitignore_map[root] = {}
for _, v in pairs(status) do
local head = v:sub(0, 2)
local body = v:sub(4, -1)
if body:match('%->') ~= nil then
body = body:gsub('^.* %-> ', '')
end
--- Git returns paths with a forward slash wherever you run it, thats why i have to replace it only on windows
if is_win then
body = body:gsub("/", "\\")
end
roots[root][body] = head
if head == "!!" then
gitignore_map[root][utils.path_remove_trailing(utils.path_join({root, body}))] = true
end
end
end
function M.reload_roots()
for root, status in pairs(roots) do
if status ~= not_git then
update_root_status(root)
end
end
end
local function get_git_root(path)
if roots[path] then
return path, roots[path]
end
for name, status in pairs(roots) do
if status ~= not_git then
if path:match(utils.path_to_matching_str(name)) then
return name, status
end
end
end
end
local function create_root(cwd)
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel"
local git_root = vim.fn.system(cmd)
if not git_root or #git_root == 0 or git_root:match('fatal') then
roots[cwd] = not_git
return false
end
if is_win then
git_root = git_root:gsub("/", "\\")
end
update_root_status(git_root:sub(0, -2))
return true
end
---Get the root of the git dir containing the given path or `nil` if it's not a
---git dir.
---@param path string
---@return string|nil
function M.git_root(path)
local git_root, git_status = get_git_root(path)
if not git_root then
if not create_root(path) then
return
end
git_root, git_status = get_git_root(path)
end
if git_status == not_git then
return
end
return git_root
end
function M.update_status(entries, cwd, parent_node, with_redraw)
local git_root, git_status = get_git_root(cwd)
if not git_root then
if not create_root(cwd) then
return
end
git_root, git_status = get_git_root(cwd)
elseif git_status == not_git then
return
end
if not git_root then
return
end
if not parent_node then parent_node = {} end
local matching_cwd = utils.path_to_matching_str( utils.path_add_trailing(git_root) )
for _, node in pairs(entries) do
if parent_node.git_status == "!!" then
node.git_status = "!!"
else
local relpath = node.absolute_path:gsub(matching_cwd, '')
if node.entries ~= nil then
relpath = utils.path_add_trailing(relpath)
node.git_status = nil
end
local status = git_status[relpath]
if status then
node.git_status = status
elseif node.entries ~= nil then
local matcher = '^'..utils.path_to_matching_str(relpath)
for key, entry_status in pairs(git_status) do
if entry_status ~= "!!" and key:match(matcher) then
node.git_status = entry_status
break
end
end
else
node.git_status = nil
end
end
end
if with_redraw then
require'nvim-tree.lib'.redraw()
end
end
---Check if the given path is ignored by git.
---@param path string Absolute path
---@return boolean
function M.should_gitignore(path)
for _, paths in pairs(gitignore_map) do
if paths[path] == true then
return true
end
end
return false
end
return M

View File

@ -0,0 +1,86 @@
local git_utils = require'nvim-tree.git.utils'
local Runner = require'nvim-tree.git.runner'
local M = {
config = nil,
projects = {},
cwd_to_project_root = {}
}
function M.reload(callback)
local num_projects = vim.tbl_count(M.projects)
if not M.config.enable or num_projects == 0 then
return callback({})
end
local done = 0
for project_root in pairs(M.projects) do
M.projects[project_root] = {}
Runner.run {
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = M.config.ignore,
timeout = M.config.timeout,
on_end = function(git_status)
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root)
}
done = done + 1
if done == num_projects then
callback(M.projects)
end
end
}
end
end
function M.get_project_root(cwd)
if M.cwd_to_project_root[cwd] then
return M.cwd_to_project_root[cwd]
end
if M.cwd_to_project_root[cwd] == false then
return nil
end
local project_root = git_utils.get_toplevel(cwd)
return project_root
end
function M.load_project_status(cwd, callback)
if not M.config.enable then
return callback({})
end
local project_root = M.get_project_root(cwd)
if not project_root then
M.cwd_to_project_root[cwd] = false
return callback({})
end
local status = M.projects[project_root]
if status then
return callback(status)
end
Runner.run {
project_root = project_root,
list_untracked = git_utils.should_show_untracked(project_root),
list_ignored = M.config.ignore,
timeout = M.config.timeout,
on_end = function(git_status)
M.projects[project_root] = {
files = git_status,
dirs = git_utils.file_status_to_dir_status(git_status, project_root)
}
callback(M.projects[project_root])
end
}
end
function M.setup(opts)
M.config = opts.git
end
return M

View File

@ -0,0 +1,99 @@
local uv = vim.loop
local utils = require'nvim-tree.utils'
local Runner = {}
Runner.__index = Runner
function Runner:_parse_status_output(line)
local status = line:sub(1, 2)
-- removing `"` when git is returning special file status containing spaces
local path = line:sub(4, -2):gsub('^"', ''):gsub('"$', '')
if #status > 0 and #path > 0 then
self.output[utils.path_remove_trailing(utils.path_join({self.project_root,path}))] = status
end
return #line
end
function Runner:_handle_incoming_data(prev_output, incoming)
if incoming and utils.str_find(incoming, '\n') then
local prev = prev_output..incoming
local i = 1
for line in prev:gmatch('[^\n]*\n') do
i = i + self:_parse_status_output(line)
end
return prev:sub(i, -1)
end
if incoming then
return prev_output..incoming
end
for line in prev_output:gmatch('[^\n]*\n') do
self._parse_status_output(line)
end
return nil
end
function Runner:_getopts(stdout_handle)
local untracked = self.list_untracked and '-u' or nil
local ignored = self.list_ignored and '--ignored=matching' or '--ignored=no'
return {
args = {"status", "--porcelain=v1", ignored, untracked},
cwd = self.project_root,
stdio = { nil, stdout_handle, nil },
}
end
function Runner:_run_git_job()
local handle, pid
local stdout = uv.new_pipe(false)
local timer = uv.new_timer()
local function on_finish(output)
if timer:is_closing() or stdout:is_closing() or handle:is_closing() then
return
end
timer:stop()
timer:close()
stdout:read_stop()
stdout:close()
handle:close()
pcall(uv.kill, pid)
self.on_end(output or self.output)
end
handle, pid = uv.spawn(
"git",
self:_getopts(stdout),
vim.schedule_wrap(function() on_finish() end)
)
timer:start(self.timeout, 0, vim.schedule_wrap(function() on_finish({}) end))
local output_leftover = ''
local function manage_output(err, data)
if err then return end
output_leftover = self:_handle_incoming_data(output_leftover, data)
end
uv.read_start(stdout, vim.schedule_wrap(manage_output))
end
-- This module runs a git process, which will be killed if it takes more than timeout which defaults to 400ms
function Runner.run(opts)
local self = setmetatable({
project_root = opts.project_root,
list_untracked = opts.list_untracked,
list_ignored = opts.list_ignored,
timeout = opts.timeout or 400,
output = {},
on_end = opts.on_end,
}, Runner)
self:_run_git_job()
end
return Runner

View File

@ -0,0 +1,54 @@
local M = {}
function M.get_toplevel(cwd)
local cmd = "git -C " .. vim.fn.shellescape(cwd) .. " rev-parse --show-toplevel"
local toplevel = vim.fn.system(cmd)
if not toplevel or #toplevel == 0 or toplevel:match('fatal') then
return nil
end
-- git always returns path with forward slashes
if vim.fn.has('win32') == 1 then
toplevel = toplevel:gsub("/", "\\")
end
-- remove newline
return toplevel:sub(0, -2)
end
local untracked = {}
function M.should_show_untracked(cwd)
if untracked[cwd] ~= nil then
return untracked[cwd]
end
local cmd = "git -C "..cwd.." config --type=bool status.showUntrackedFiles"
local has_untracked = vim.fn.system(cmd)
untracked[cwd] = vim.trim(has_untracked) ~= 'false'
return untracked[cwd]
end
function M.file_status_to_dir_status(status, cwd)
local dirs = {}
for p, s in pairs(status) do
if s ~= '!!' then
local modified = vim.fn.fnamemodify(p, ':h')
dirs[modified] = 'dirty'
end
end
for dirname, _ in pairs(dirs) do
local modified = dirname
while modified ~= cwd and modified ~= '/' do
modified = vim.fn.fnamemodify(modified, ':h')
dirs[modified] = 'dirty'
end
end
return dirs
end
return M

View File

@ -3,12 +3,12 @@ local luv = vim.loop
local renderer = require'nvim-tree.renderer'
local config = require'nvim-tree.config'
local git = require'nvim-tree.git'
local diagnostics = require'nvim-tree.diagnostics'
local pops = require'nvim-tree.populate'
local utils = require'nvim-tree.utils'
local view = require'nvim-tree.view'
local events = require'nvim-tree.events'
local git = require'nvim-tree.git'
local populate = pops.populate
local refresh_entries = pops.refresh_entries
@ -19,33 +19,25 @@ local M = {}
M.Tree = {
entries = {},
cwd = nil,
loaded = false,
target_winid = nil,
}
function M.init(with_open, with_reload)
M.Tree.entries = {}
if not M.Tree.cwd then
M.Tree.cwd = luv.cwd()
end
if config.use_git() then
git.git_root(M.Tree.cwd)
end
populate(M.Tree.entries, M.Tree.cwd)
local function load_children(cwd, children, parent)
git.load_project_status(cwd, function(git_statuses)
populate(children, cwd, parent, git_statuses)
M.redraw()
end)
end
local stat = luv.fs_stat(M.Tree.cwd)
M.Tree.last_modified = stat.mtime.sec
function M.init(with_open, foldername)
M.Tree.entries = {}
M.Tree.cwd = foldername or luv.cwd()
if with_open then
M.open()
elseif view.win_open() then
M.refresh_tree()
end
if with_reload then
renderer.draw(M.Tree, true)
M.Tree.loaded = true
end
load_children(M.Tree.cwd, M.Tree.entries)
if not first_init_done then
events._dispatch_ready()
@ -85,7 +77,7 @@ local function get_line_from_node(node, find_parent)
local function iter(entries, recursive)
for _, entry in ipairs(entries) do
local n = M.get_last_group_node(entry)
if node_path:match('^'..n.match_path..'$') ~= nil then
if node_path == n.absolute_path then
return line, entry
end
@ -132,73 +124,75 @@ function M.get_last_group_node(node)
return next
end
function M.unroll_dir(node)
function M.expand_or_collapse(node)
node.open = not node.open
if node.has_children then node.has_children = false end
if #node.entries > 0 then
renderer.draw(M.Tree, true)
if #node.entries == 0 then
load_children(
node.link_to or node.absolute_path,
node.entries,
node
)
else
if config.use_git() then
git.git_root(node.absolute_path)
end
populate(node.entries, node.link_to or node.absolute_path, node)
renderer.draw(M.Tree, true)
M.redraw()
end
diagnostics.update()
end
local function refresh_git(node)
if not node then node = M.Tree end
git.update_status(node.entries, node.absolute_path or node.cwd, node, false)
for _, entry in pairs(node.entries) do
if entry.entries and #entry.entries > 0 then
refresh_git(entry)
end
end
end
-- TODO update only entries where directory has changed
local function refresh_nodes(node)
refresh_entries(node.entries, node.absolute_path or node.cwd, node)
local function refresh_nodes(node, projects)
local project_root = git.get_project_root(node.absolute_path or node.cwd)
refresh_entries(node.entries, node.absolute_path or node.cwd, node, projects[project_root] or {})
for _, entry in ipairs(node.entries) do
if entry.entries and entry.open then
refresh_nodes(entry)
refresh_nodes(entry, projects)
end
end
end
-- this variable is used to bufferize the refresh actions
-- so only one happens every second at most
local refreshing = false
function M.refresh_tree(disable_clock)
if not M.Tree.cwd or (not disable_clock and refreshing) or vim.v.exiting ~= vim.NIL then
local event_running = false
function M.refresh_tree()
if event_running or not M.Tree.cwd or vim.v.exiting ~= vim.NIL then
return
end
refreshing = true
refresh_nodes(M.Tree)
local use_git = config.use_git()
if use_git then
vim.schedule(function()
git.reload_roots()
refresh_git(M.Tree)
M.redraw()
end)
end
vim.schedule(diagnostics.update)
event_running = true
git.reload(function(projects)
refresh_nodes(M.Tree, projects)
if view.win_open() then
renderer.draw(M.Tree, true)
else
M.Tree.loaded = false
M.redraw()
end
diagnostics.update()
event_running = false
end)
end
vim.defer_fn(function() refreshing = false end, vim.g.nvim_tree_refresh_wait or 1000)
local function reload_node_status(parent_node, projects)
local project_root = git.get_project_root(parent_node.absolute_path or parent_node.cwd)
local status = projects[project_root] or {}
for _, node in ipairs(parent_node.entries) do
if node.entries then
node.git_status = status.dirs and status.dirs[node.absolute_path]
else
node.git_status = status.files and status.files[node.absolute_path]
end
if node.entries and #node.entries > 0 then
reload_node_status(node, projects)
end
end
end
function M.reload_git()
if not git.config.enable or event_running then
return
end
event_running = true
git.reload(function(projects)
reload_node_status(M.Tree, projects)
M.redraw()
event_running = false
end)
end
function M.set_index_and_redraw(fname)
@ -209,40 +203,46 @@ function M.set_index_and_redraw(fname)
else
i = 1
end
local reload = false
local function iter(entries)
for _, entry in ipairs(entries) do
local tree_altered = false
local function iterate_nodes(nodes)
for _, node in ipairs(nodes) do
i = i + 1
if entry.absolute_path == fname then
if node.absolute_path == fname then
return i
end
if fname:match(entry.match_path..utils.path_separator) ~= nil then
if #entry.entries == 0 then
reload = true
populate(entry.entries, entry.absolute_path, entry)
local path_matches = utils.str_find(fname, node.absolute_path..utils.path_separator)
if path_matches then
if #node.entries == 0 then
node.open = true
populate(node.entries, node.absolute_path, node, {})
git.load_project_status(node.absolute_path, function(status)
if status.dirs or status.files then
reload_node_status(node, git.projects)
M.redraw()
end
if entry.open == false then
reload = true
entry.open = true
end)
end
if iter(entry.entries) ~= nil then
if node.open == false then
node.open = true
tree_altered = true
end
if iterate_nodes(node.entries) ~= nil then
return i
end
elseif entry.open == true then
iter(entry.entries)
elseif node.open == true then
iterate_nodes(node.entries)
end
end
end
local index = iter(M.Tree.entries)
if not view.win_open() then
M.Tree.loaded = false
return
local index = iterate_nodes(M.Tree.entries)
if tree_altered then
M.redraw()
end
renderer.draw(M.Tree, reload)
if index then
if index and view.win_open() then
view.set_cursor({index, 0})
end
end
@ -405,8 +405,6 @@ function M.open_file(mode, filename)
if vim.g.nvim_tree_quit_on_open == 1 then
view.close()
end
renderer.draw(M.Tree, true)
end
function M.open_file_in_tab(filename)
@ -467,8 +465,7 @@ function M.change_dir(name)
end
vim.cmd('lcd '..vim.fn.fnameescape(foldername))
M.Tree.cwd = foldername
M.init(false, true)
M.init(false, foldername)
end
function M.set_target_win()
@ -486,18 +483,19 @@ function M.open()
M.set_target_win()
local cwd = vim.fn.getcwd()
view.open()
local should_redraw = view.open()
local respect_buf_cwd = vim.g.nvim_tree_respect_buf_cwd or 0
if M.Tree.loaded and (respect_buf_cwd == 1 and cwd ~= M.Tree.cwd) then
if respect_buf_cwd == 1 and cwd ~= M.Tree.cwd then
M.change_dir(cwd)
end
renderer.draw(M.Tree, not M.Tree.loaded)
M.Tree.loaded = true
if should_redraw then
M.redraw()
end
end
function M.sibling(node, direction)
if not direction then return end
if node.name == '..' or not direction then return end
local iter = get_line_from_node(node, true)
local node_path = node.absolute_path
@ -507,7 +505,7 @@ function M.sibling(node, direction)
-- Check if current node is already at root entries
for index, entry in ipairs(M.Tree.entries) do
if node_path:match('^'..entry.match_path..'$') ~= nil then
if node_path == entry.absolute_path then
line = index
end
end
@ -534,7 +532,6 @@ function M.sibling(node, direction)
line, _ = get_line_from_node(target_node)(M.Tree.entries, true)
view.set_cursor({line, 0})
renderer.draw(M.Tree, true)
end
function M.close_node(node)
@ -543,11 +540,14 @@ end
function M.parent_node(node, should_close)
if node.name == '..' then return end
should_close = should_close or false
local altered_tree = false
local iter = get_line_from_node(node, true)
if node.open == true and should_close then
node.open = false
altered_tree = true
else
local line, parent = iter(M.Tree.entries, true)
if parent == nil then
@ -555,9 +555,12 @@ function M.parent_node(node, should_close)
elseif should_close then
parent.open = false
end
api.nvim_win_set_cursor(view.get_winnr(), {line, 0})
view.set_cursor({line, 0})
end
if altered_tree then
M.redraw()
end
renderer.draw(M.Tree, true)
end
function M.toggle_ignored()

View File

@ -1,17 +1,13 @@
local config = require'nvim-tree.config'
local git = require'nvim-tree.git'
local api = vim.api
local luv = vim.loop
local utils = require'nvim-tree.utils'
local M = {
ignore_list = {}
}
local utils = require'nvim-tree.utils'
local path_to_matching_str = utils.path_to_matching_str
local function dir_new(cwd, name)
local function dir_new(cwd, name, status, parent_ignored)
local absolute_path = utils.path_join({cwd, name})
local stat = luv.fs_stat(absolute_path)
local handle = luv.fs_scandir(absolute_path)
@ -28,16 +24,15 @@ local function dir_new(cwd, name)
absolute_path = absolute_path,
-- TODO: last modified could also involve atime and ctime
last_modified = last_modified,
match_name = path_to_matching_str(name),
match_path = path_to_matching_str(absolute_path),
open = false,
group_next = nil, -- If node is grouped, this points to the next child dir/link node
has_children = has_children,
entries = {}
entries = {},
git_status = parent_ignored and '!!' or (status.dirs and status.dirs[absolute_path]) or (status.files and status.files[absolute_path]),
}
end
local function file_new(cwd, name)
local function file_new(cwd, name, status, parent_ignored)
local absolute_path = utils.path_join({cwd, name})
local is_exec = luv.fs_access(absolute_path, 'X')
return {
@ -45,8 +40,7 @@ local function file_new(cwd, name)
absolute_path = absolute_path,
executable = is_exec,
extension = string.match(name, ".?[^.]+%.(.*)") or "",
match_name = path_to_matching_str(name),
match_path = path_to_matching_str(absolute_path),
git_status = parent_ignored and '!!' or status.files and status.files[absolute_path],
}
end
@ -55,8 +49,7 @@ end
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails
-- when it has no real reason to. Maybe there is a reason, but errno is definitely wrong.
-- So we need to check for link_to ~= nil when adding new links to the main tree
local function link_new(cwd, name)
local function link_new(cwd, name, status, parent_ignored)
--- I dont know if this is needed, because in my understanding, there isnt hard links in windows, but just to be sure i changed it.
local absolute_path = utils.path_join({ cwd, name })
local link_to = luv.fs_realpath(absolute_path)
@ -80,8 +73,7 @@ local function link_new(cwd, name)
open = open,
group_next = nil, -- If node is grouped, this points to the next child dir/link node
entries = entries,
match_name = path_to_matching_str(name),
match_path = path_to_matching_str(absolute_path),
git_status = parent_ignored and '!!' or status.files and status.files[absolute_path],
}
end
@ -105,6 +97,9 @@ local function should_group(cwd, dirs, files, links)
end
local function node_comparator(a, b)
if not (a and b) then
return true
end
if a.entries and not b.entries then
return true
elseif not a.entries and b.entries then
@ -130,12 +125,6 @@ local function should_ignore(path)
return false
end
if vim.g.nvim_tree_gitignore == 1 then
if git.should_gitignore(path) then
return true
end
end
local relpath = utils.path_relative(path, vim.loop.cwd())
if M.ignore_list[relpath] == true or M.ignore_list[basename] == true then
return true
@ -151,7 +140,11 @@ local function should_ignore(path)
return false
end
function M.refresh_entries(entries, cwd, parent_node)
local function should_ignore_git(path, status)
return M.config.filter_ignored and (status and status[path] == '!!')
end
function M.refresh_entries(entries, cwd, parent_node, status)
local handle = luv.fs_scandir(cwd)
if type(handle) == 'string' then
api.nvim_err_writeln(handle)
@ -162,6 +155,9 @@ function M.refresh_entries(entries, cwd, parent_node)
local cached_entries = {}
local entries_idx = {}
for i, node in ipairs(entries) do
node.git_status = (parent_node and parent_node.git_status == '!!' and '!!')
or (status.files and status.files[node.absolute_path])
or (status.dirs and status.dirs[node.absolute_path])
cached_entries[i] = node.name
entries_idx[node.name] = i
named_entries[node.name] = node
@ -179,7 +175,7 @@ function M.refresh_entries(entries, cwd, parent_node)
num_new_entries = num_new_entries + 1
local abs = utils.path_join({cwd, name})
if not should_ignore(abs) then
if not should_ignore(abs) and not should_ignore_git(abs, status.files) then
if not t then
local stat = luv.fs_stat(abs)
t = stat and stat.type
@ -208,7 +204,7 @@ function M.refresh_entries(entries, cwd, parent_node)
parent_node.group_next = nil
named_entries[next_node.name] = next_node
else
M.refresh_entries(entries, next_node.absolute_path, next_node)
M.refresh_entries(entries, next_node.absolute_path, next_node, status)
return
end
end
@ -245,7 +241,7 @@ function M.refresh_entries(entries, cwd, parent_node)
for _, name in ipairs(e.entries) do
change_prev = true
if not named_entries[name] then
local n = e.fn(cwd, name)
local n = e.fn(cwd, name, status)
if e.check(n.link_to, n.absolute_path) then
new_nodes_added = true
idx = 1
@ -274,7 +270,7 @@ function M.refresh_entries(entries, cwd, parent_node)
end
end
function M.populate(entries, cwd, parent_node)
function M.populate(entries, cwd, parent_node, status)
local handle = luv.fs_scandir(cwd)
if type(handle) == 'string' then
api.nvim_err_writeln(handle)
@ -290,7 +286,7 @@ function M.populate(entries, cwd, parent_node)
if not name then break end
local abs = utils.path_join({cwd, name})
if not should_ignore(abs) then
if not should_ignore(abs) and not should_ignore_git(abs, status.files) then
if not t then
local stat = luv.fs_stat(abs)
t = stat and stat.type
@ -306,52 +302,42 @@ function M.populate(entries, cwd, parent_node)
end
end
-- Create Nodes --
local parent_node_ignored = parent_node and parent_node.git_status == '!!'
-- Group empty dirs
if parent_node and vim.g.nvim_tree_group_empty == 1 then
if should_group(cwd, dirs, files, links) then
local child_node
if dirs[1] then child_node = dir_new(cwd, dirs[1]) end
if links[1] then child_node = link_new(cwd, links[1]) end
if dirs[1] then child_node = dir_new(cwd, dirs[1], status, parent_node_ignored) end
if links[1] then child_node = link_new(cwd, links[1], status, parent_node_ignored) end
if luv.fs_access(child_node.absolute_path, 'R') then
parent_node.group_next = child_node
child_node.git_status = parent_node.git_status
M.populate(entries, child_node.absolute_path, child_node)
M.populate(entries, child_node.absolute_path, child_node, status)
return
end
end
end
for _, dirname in ipairs(dirs) do
local dir = dir_new(cwd, dirname)
local dir = dir_new(cwd, dirname, status, parent_node_ignored)
if luv.fs_access(dir.absolute_path, 'R') then
table.insert(entries, dir)
end
end
for _, linkname in ipairs(links) do
local link = link_new(cwd, linkname)
local link = link_new(cwd, linkname, status, parent_node_ignored)
if link.link_to ~= nil then
table.insert(entries, link)
end
end
for _, filename in ipairs(files) do
local file = file_new(cwd, filename)
local file = file_new(cwd, filename, status, parent_node_ignored)
table.insert(entries, file)
end
utils.merge_sort(entries, node_comparator)
local icon_config = config.get_icon_state()
if (not icon_config.show_git_icon) and vim.g.nvim_tree_git_hl ~= 1 then
return
end
if config.use_git() then
vim.schedule(function() git.update_status(entries, cwd, parent_node, true) end)
end
end
function M.setup(opts)

View File

@ -163,7 +163,7 @@ if vim.g.nvim_tree_git_hl == 1 then
local icons = git_hl[git_status]
if icons == nil then
utils.echo_warning('Unrecognized git state "'..git_status..'". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.')
utils.warn('Unrecognized git state "'..git_status..'". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.')
icons = git_hl.dirty
end
@ -239,7 +239,7 @@ if icon_state.show_git_icon then
local icons = git_icon_state[git_status]
if not icons then
if vim.g.nvim_tree_git_hl ~= 1 then
utils.echo_warning('Unrecognized git state "'..git_status..'". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.')
utils.warn('Unrecognized git state "'..git_status..'". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.')
end
icons = git_icon_state.dirty
end

View File

@ -6,12 +6,16 @@ function M.path_to_matching_str(path)
return path:gsub('(%-)', '(%%-)'):gsub('(%.)', '(%%.)'):gsub('(%_)', '(%%_)')
end
function M.echo_warning(msg)
function M.warn(msg)
api.nvim_command('echohl WarningMsg')
api.nvim_command("echom '[NvimTree] "..msg:gsub("'", "''").."'")
api.nvim_command('echohl None')
end
function M.str_find(haystack, needle)
return vim.fn.stridx(haystack, needle) ~= -1
end
function M.read_file(path)
local fd = uv.fs_open(path, "r", 438)
if not fd then return '' end

View File

@ -304,7 +304,9 @@ local function is_buf_valid(bufnr)
end
function M.open(options)
local should_redraw = false
if not is_buf_valid(M.View.bufnr) then
should_redraw = true
create_buffer()
end
@ -322,6 +324,7 @@ function M.open(options)
if not opts.focus_tree then
vim.cmd("wincmd p")
end
return should_redraw
end
local function get_existing_buffers()

View File

@ -24,7 +24,8 @@ local out_config = {
"nvim_tree_disable_keybindings",
"nvim_tree_disable_default_keybindings",
"nvim_tree_hide_dotfiles",
"nvim_tree_ignore"
"nvim_tree_ignore",
"nvim_tree_gitignore"
}
local x = vim.tbl_filter(function(v)
@ -33,5 +34,5 @@ end, out_config)
if #x > 0 then
local msg = "Following options were moved to setup, see git.io/JPhyt: "
require'nvim-tree.utils'.echo_warning(msg..table.concat(x, ", "))
require'nvim-tree.utils'.warn(msg..table.concat(x, ", "))
end