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 ```lua
use { use {
'kyazdani42/nvim-tree.lua', '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 config = function() require'nvim-tree'.setup {} end
} }
``` ```
@ -72,6 +74,11 @@ require'nvim-tree'.setup {
dotfiles = false, dotfiles = false,
custom = {} custom = {}
}, },
git = {
enable = true,
ignore = true,
timeout = 500,
},
view = { view = {
width = 30, width = 30,
height = 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. 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. 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 ```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_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_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). 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, cmd = nil,
args = {} args = {}
}, },
git = {
enable = true,
ignore = true,
},
view = { view = {
width = 30, width = 30,
height = 30, height = 30,
@ -231,6 +235,31 @@ Here is a list of the options available in the setup call:
- `NvimTreeLspDiagnosticsInformation` - `NvimTreeLspDiagnosticsInformation`
- `NvimTreeLspDiagnosticsHint` - `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* *nvim-tree.view*
- |view|: window / buffer setup - |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') where the window will open (default to 'left')
- 'left' or 'right' - '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* |g:nvim_tree_show_icons| *g:nvim_tree_show_icons*
Dictionary, if your terminal or font doesn't support certain unicode 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 elseif _config.is_unix then
_config.system_open.cmd = 'xdg-open' _config.system_open.cmd = 'xdg-open'
else 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 return
end end
end end
@ -173,16 +173,12 @@ function M.on_keypress(mode)
if node.link_to and not node.entries then if node.link_to and not node.entries then
lib.open_file(mode, node.link_to) lib.open_file(mode, node.link_to)
elseif node.entries ~= nil then elseif node.entries ~= nil then
lib.unroll_dir(node) lib.expand_or_collapse(node)
else else
lib.open_file(mode, node.absolute_path) lib.open_file(mode, node.absolute_path)
end end
end end
function M.refresh()
lib.refresh_tree()
end
function M.print_clipboard() function M.print_clipboard()
fs.print_clipboard() fs.print_clipboard()
end end
@ -227,7 +223,7 @@ function M.on_enter(opts)
M.hijack_current_window() M.hijack_current_window()
end end
lib.init(should_open, should_open) lib.init(should_open)
end end
local function is_file_readable(fname) 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 "" local ft = api.nvim_buf_get_option(bufnr, 'filetype') or ""
for _, value in pairs(_config.update_focused_file.ignore_list) do 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 return
end end
end end
@ -359,7 +355,7 @@ local function setup_vim_commands()
command! NvimTreeClose lua require'nvim-tree'.close() command! NvimTreeClose lua require'nvim-tree'.close()
command! NvimTreeToggle lua require'nvim-tree'.toggle(false) command! NvimTreeToggle lua require'nvim-tree'.toggle(false)
command! NvimTreeFocus lua require'nvim-tree'.focus() 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! NvimTreeClipboard lua require'nvim-tree'.print_clipboard()
command! NvimTreeFindFile lua require'nvim-tree'.find_file(true) command! NvimTreeFindFile lua require'nvim-tree'.find_file(true)
command! NvimTreeFindFileToggle lua require'nvim-tree'.toggle(true) command! NvimTreeFindFileToggle lua require'nvim-tree'.toggle(true)
@ -381,8 +377,8 @@ local function setup_autocommands(opts)
""" reset highlights when colorscheme is changed """ reset highlights when colorscheme is changed
au ColorScheme * lua require'nvim-tree'.reset_highlight() au ColorScheme * lua require'nvim-tree'.reset_highlight()
au BufWritePost * lua require'nvim-tree'.refresh() au BufWritePost * lua require'nvim-tree.lib'.refresh_tree()
au User FugitiveChanged,NeogitStatusRefreshed lua require'nvim-tree'.refresh() au User FugitiveChanged,NeogitStatusRefreshed lua require'nvim-tree.lib'.reload_git()
]] ]]
if opts.auto_close then if opts.auto_close then
@ -400,6 +396,7 @@ local function setup_autocommands(opts)
if opts.update_focused_file.enable then if opts.update_focused_file.enable then
vim.cmd "au BufEnter * lua require'nvim-tree'.find_file(false)" vim.cmd "au BufEnter * lua require'nvim-tree'.find_file(false)"
end end
vim.cmd "au BufUnload NvimTree lua require'nvim-tree.view'.View.tabpages = {}"
vim.cmd "augroup end" vim.cmd "augroup end"
end end
@ -439,6 +436,11 @@ local DEFAULT_OPTS = {
filters = { filters = {
dotfiles = false, dotfiles = false,
custom_filter = {} 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.open_on_setup = opts.open_on_setup
_config.ignore_ft_on_setup = opts.ignore_ft_on_setup _config.ignore_ft_on_setup = opts.ignore_ft_on_setup
if type(opts.update_to_buf_dir) == "boolean" then 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 = { _config.update_to_buf_dir = {
enable = opts.update_to_buf_dir, enable = opts.update_to_buf_dir,
auto_open = opts.update_to_buf_dir, auto_open = opts.update_to_buf_dir,
@ -462,13 +464,14 @@ function M.setup(conf)
end end
if opts.lsp_diagnostics ~= nil then 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 end
require'nvim-tree.colors'.setup() require'nvim-tree.colors'.setup()
require'nvim-tree.view'.setup(opts.view or {}) require'nvim-tree.view'.setup(opts.view or {})
require'nvim-tree.diagnostics'.setup(opts) require'nvim-tree.diagnostics'.setup(opts)
require'nvim-tree.populate'.setup(opts) require'nvim-tree.populate'.setup(opts)
require'nvim-tree.git'.setup(opts)
setup_autocommands(opts) setup_autocommands(opts)
setup_vim_commands() setup_vim_commands()

View File

@ -58,12 +58,6 @@ function M.get_icon_state()
} }
end 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) function M.nvim_tree_callback(callback_name)
return string.format(":lua require'nvim-tree'.on_keypress('%s')<CR>", callback_name) return string.format(":lua require'nvim-tree'.on_keypress('%s')<CR>", callback_name)
end end

View File

@ -34,7 +34,7 @@ local function create_file(file)
else else
luv.fs_close(fd) luv.fs_close(fd)
events._dispatch_file_created(file) events._dispatch_file_created(file)
lib.refresh_tree(true) lib.refresh_tree()
focus_file(file) focus_file(file)
end end
end)) end))
@ -98,7 +98,7 @@ function M.create(node)
end end
api.nvim_out_write(ans..' was properly created\n') api.nvim_out_write(ans..' was properly created\n')
events._dispatch_folder_created(ans) events._dispatch_folder_created(ans)
lib.refresh_tree(true) lib.refresh_tree()
focus_file(ans) focus_file(ans)
end end
@ -113,6 +113,9 @@ local function clear_buffer(absolute_path)
api.nvim_set_current_win(winnr) api.nvim_set_current_win(winnr)
end end
vim.api.nvim_buf_delete(buf.bufnr, {}) vim.api.nvim_buf_delete(buf.bufnr, {})
if buf.windows[1] then
vim.api.nvim_win_close(buf.windows[1], true)
end
return return
end end
end end
@ -239,7 +242,7 @@ local function do_paste(node, action_type, action_fn)
end end
clipboard[action_type] = {} clipboard[action_type] = {}
return lib.refresh_tree(true) return lib.refresh_tree()
end end
local function add_to_clipboard(node, clip) local function add_to_clipboard(node, clip)
@ -276,7 +279,7 @@ function M.remove(node)
events._dispatch_file_removed(node.absolute_path) events._dispatch_file_removed(node.absolute_path)
clear_buffer(node.absolute_path) clear_buffer(node.absolute_path)
end end
lib.refresh_tree(true) lib.refresh_tree()
end end
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 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) local new_name = vim.fn.input("Rename " ..node.name.. " to ", abs_path)
utils.clear_prompt() 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) local success = luv.fs_rename(node.absolute_path, new_name)
if not success then if not success then
@ -298,7 +307,7 @@ function M.rename(with_sub)
api.nvim_out_write(node.absolute_path..''..new_name..'\n') api.nvim_out_write(node.absolute_path..''..new_name..'\n')
rename_loaded_buffers(node.absolute_path, new_name) rename_loaded_buffers(node.absolute_path, new_name)
events._dispatch_node_renamed(abs_path, new_name) events._dispatch_node_renamed(abs_path, new_name)
lib.refresh_tree(true) lib.refresh_tree()
end end
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 renderer = require'nvim-tree.renderer'
local config = require'nvim-tree.config' local config = require'nvim-tree.config'
local git = require'nvim-tree.git'
local diagnostics = require'nvim-tree.diagnostics' local diagnostics = require'nvim-tree.diagnostics'
local pops = require'nvim-tree.populate' local pops = require'nvim-tree.populate'
local utils = require'nvim-tree.utils' local utils = require'nvim-tree.utils'
local view = require'nvim-tree.view' local view = require'nvim-tree.view'
local events = require'nvim-tree.events' local events = require'nvim-tree.events'
local git = require'nvim-tree.git'
local populate = pops.populate local populate = pops.populate
local refresh_entries = pops.refresh_entries local refresh_entries = pops.refresh_entries
@ -19,33 +19,25 @@ local M = {}
M.Tree = { M.Tree = {
entries = {}, entries = {},
cwd = nil, cwd = nil,
loaded = false,
target_winid = nil, target_winid = nil,
} }
function M.init(with_open, with_reload) local function load_children(cwd, children, parent)
M.Tree.entries = {} git.load_project_status(cwd, function(git_statuses)
if not M.Tree.cwd then populate(children, cwd, parent, git_statuses)
M.Tree.cwd = luv.cwd() M.redraw()
end end)
if config.use_git() then end
git.git_root(M.Tree.cwd)
end
populate(M.Tree.entries, M.Tree.cwd)
local stat = luv.fs_stat(M.Tree.cwd) function M.init(with_open, foldername)
M.Tree.last_modified = stat.mtime.sec M.Tree.entries = {}
M.Tree.cwd = foldername or luv.cwd()
if with_open then if with_open then
M.open() M.open()
elseif view.win_open() then
M.refresh_tree()
end end
if with_reload then load_children(M.Tree.cwd, M.Tree.entries)
renderer.draw(M.Tree, true)
M.Tree.loaded = true
end
if not first_init_done then if not first_init_done then
events._dispatch_ready() events._dispatch_ready()
@ -85,7 +77,7 @@ local function get_line_from_node(node, find_parent)
local function iter(entries, recursive) local function iter(entries, recursive)
for _, entry in ipairs(entries) do for _, entry in ipairs(entries) do
local n = M.get_last_group_node(entry) 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 return line, entry
end end
@ -132,73 +124,75 @@ function M.get_last_group_node(node)
return next return next
end end
function M.unroll_dir(node) function M.expand_or_collapse(node)
node.open = not node.open node.open = not node.open
if node.has_children then node.has_children = false end if node.has_children then node.has_children = false end
if #node.entries > 0 then if #node.entries == 0 then
renderer.draw(M.Tree, true) load_children(
node.link_to or node.absolute_path,
node.entries,
node
)
else else
if config.use_git() then M.redraw()
git.git_root(node.absolute_path)
end
populate(node.entries, node.link_to or node.absolute_path, node)
renderer.draw(M.Tree, true)
end end
diagnostics.update() diagnostics.update()
end end
local function refresh_git(node) local function refresh_nodes(node, projects)
if not node then node = M.Tree end local project_root = git.get_project_root(node.absolute_path or node.cwd)
git.update_status(node.entries, node.absolute_path or node.cwd, node, false) refresh_entries(node.entries, node.absolute_path or node.cwd, node, projects[project_root] or {})
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)
for _, entry in ipairs(node.entries) do for _, entry in ipairs(node.entries) do
if entry.entries and entry.open then if entry.entries and entry.open then
refresh_nodes(entry) refresh_nodes(entry, projects)
end end
end end
end end
-- this variable is used to bufferize the refresh actions local event_running = false
-- so only one happens every second at most function M.refresh_tree()
local refreshing = false if event_running or not M.Tree.cwd or vim.v.exiting ~= vim.NIL then
function M.refresh_tree(disable_clock)
if not M.Tree.cwd or (not disable_clock and refreshing) or vim.v.exiting ~= vim.NIL then
return return
end end
refreshing = true event_running = true
refresh_nodes(M.Tree) git.reload(function(projects)
refresh_nodes(M.Tree, projects)
local use_git = config.use_git() if view.win_open() then
if use_git then
vim.schedule(function()
git.reload_roots()
refresh_git(M.Tree)
M.redraw() M.redraw()
end) end
diagnostics.update()
event_running = false
end)
end
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
end
vim.schedule(diagnostics.update) function M.reload_git()
if not git.config.enable or event_running then
if view.win_open() then return
renderer.draw(M.Tree, true)
else
M.Tree.loaded = false
end end
event_running = true
vim.defer_fn(function() refreshing = false end, vim.g.nvim_tree_refresh_wait or 1000) git.reload(function(projects)
reload_node_status(M.Tree, projects)
M.redraw()
event_running = false
end)
end end
function M.set_index_and_redraw(fname) function M.set_index_and_redraw(fname)
@ -209,40 +203,46 @@ function M.set_index_and_redraw(fname)
else else
i = 1 i = 1
end end
local reload = false
local function iter(entries) local tree_altered = false
for _, entry in ipairs(entries) do
local function iterate_nodes(nodes)
for _, node in ipairs(nodes) do
i = i + 1 i = i + 1
if entry.absolute_path == fname then if node.absolute_path == fname then
return i return i
end end
if fname:match(entry.match_path..utils.path_separator) ~= nil then local path_matches = utils.str_find(fname, node.absolute_path..utils.path_separator)
if #entry.entries == 0 then if path_matches then
reload = true if #node.entries == 0 then
populate(entry.entries, entry.absolute_path, entry) 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
end)
end end
if entry.open == false then if node.open == false then
reload = true node.open = true
entry.open = true tree_altered = true
end end
if iter(entry.entries) ~= nil then if iterate_nodes(node.entries) ~= nil then
return i return i
end end
elseif entry.open == true then elseif node.open == true then
iter(entry.entries) iterate_nodes(node.entries)
end end
end end
end end
local index = iter(M.Tree.entries) local index = iterate_nodes(M.Tree.entries)
if not view.win_open() then if tree_altered then
M.Tree.loaded = false M.redraw()
return
end end
renderer.draw(M.Tree, reload) if index and view.win_open() then
if index then
view.set_cursor({index, 0}) view.set_cursor({index, 0})
end end
end end
@ -405,8 +405,6 @@ function M.open_file(mode, filename)
if vim.g.nvim_tree_quit_on_open == 1 then if vim.g.nvim_tree_quit_on_open == 1 then
view.close() view.close()
end end
renderer.draw(M.Tree, true)
end end
function M.open_file_in_tab(filename) function M.open_file_in_tab(filename)
@ -467,8 +465,7 @@ function M.change_dir(name)
end end
vim.cmd('lcd '..vim.fn.fnameescape(foldername)) vim.cmd('lcd '..vim.fn.fnameescape(foldername))
M.Tree.cwd = foldername M.init(false, foldername)
M.init(false, true)
end end
function M.set_target_win() function M.set_target_win()
@ -486,18 +483,19 @@ function M.open()
M.set_target_win() M.set_target_win()
local cwd = vim.fn.getcwd() 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 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) M.change_dir(cwd)
end end
renderer.draw(M.Tree, not M.Tree.loaded) if should_redraw then
M.Tree.loaded = true M.redraw()
end
end end
function M.sibling(node, direction) 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 iter = get_line_from_node(node, true)
local node_path = node.absolute_path local node_path = node.absolute_path
@ -507,7 +505,7 @@ function M.sibling(node, direction)
-- Check if current node is already at root entries -- Check if current node is already at root entries
for index, entry in ipairs(M.Tree.entries) do 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 line = index
end end
end end
@ -534,7 +532,6 @@ function M.sibling(node, direction)
line, _ = get_line_from_node(target_node)(M.Tree.entries, true) line, _ = get_line_from_node(target_node)(M.Tree.entries, true)
view.set_cursor({line, 0}) view.set_cursor({line, 0})
renderer.draw(M.Tree, true)
end end
function M.close_node(node) function M.close_node(node)
@ -543,11 +540,14 @@ end
function M.parent_node(node, should_close) function M.parent_node(node, should_close)
if node.name == '..' then return end if node.name == '..' then return end
should_close = should_close or false should_close = should_close or false
local altered_tree = false
local iter = get_line_from_node(node, true) local iter = get_line_from_node(node, true)
if node.open == true and should_close then if node.open == true and should_close then
node.open = false node.open = false
altered_tree = true
else else
local line, parent = iter(M.Tree.entries, true) local line, parent = iter(M.Tree.entries, true)
if parent == nil then if parent == nil then
@ -555,9 +555,12 @@ function M.parent_node(node, should_close)
elseif should_close then elseif should_close then
parent.open = false parent.open = false
end end
api.nvim_win_set_cursor(view.get_winnr(), {line, 0}) view.set_cursor({line, 0})
end
if altered_tree then
M.redraw()
end end
renderer.draw(M.Tree, true)
end end
function M.toggle_ignored() 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 api = vim.api
local luv = vim.loop local luv = vim.loop
local utils = require'nvim-tree.utils'
local M = { local M = {
ignore_list = {} ignore_list = {}
} }
local utils = require'nvim-tree.utils' local function dir_new(cwd, name, status, parent_ignored)
local path_to_matching_str = utils.path_to_matching_str
local function dir_new(cwd, name)
local absolute_path = utils.path_join({cwd, name}) local absolute_path = utils.path_join({cwd, name})
local stat = luv.fs_stat(absolute_path) local stat = luv.fs_stat(absolute_path)
local handle = luv.fs_scandir(absolute_path) local handle = luv.fs_scandir(absolute_path)
@ -28,16 +24,15 @@ local function dir_new(cwd, name)
absolute_path = absolute_path, absolute_path = absolute_path,
-- TODO: last modified could also involve atime and ctime -- TODO: last modified could also involve atime and ctime
last_modified = last_modified, last_modified = last_modified,
match_name = path_to_matching_str(name),
match_path = path_to_matching_str(absolute_path),
open = false, open = false,
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
has_children = has_children, 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 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 absolute_path = utils.path_join({cwd, name})
local is_exec = luv.fs_access(absolute_path, 'X') local is_exec = luv.fs_access(absolute_path, 'X')
return { return {
@ -45,8 +40,7 @@ local function file_new(cwd, name)
absolute_path = absolute_path, absolute_path = absolute_path,
executable = is_exec, executable = is_exec,
extension = string.match(name, ".?[^.]+%.(.*)") or "", extension = string.match(name, ".?[^.]+%.(.*)") or "",
match_name = path_to_matching_str(name), git_status = parent_ignored and '!!' or status.files and status.files[absolute_path],
match_path = path_to_matching_str(absolute_path),
} }
end end
@ -55,8 +49,7 @@ end
-- links (for instance libr2.so in /usr/lib) and thus even with a C program realpath fails -- 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. -- 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 -- 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. --- 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 absolute_path = utils.path_join({ cwd, name })
local link_to = luv.fs_realpath(absolute_path) local link_to = luv.fs_realpath(absolute_path)
@ -80,8 +73,7 @@ local function link_new(cwd, name)
open = open, open = open,
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
entries = entries, entries = entries,
match_name = path_to_matching_str(name), git_status = parent_ignored and '!!' or status.files and status.files[absolute_path],
match_path = path_to_matching_str(absolute_path),
} }
end end
@ -105,6 +97,9 @@ local function should_group(cwd, dirs, files, links)
end end
local function node_comparator(a, b) local function node_comparator(a, b)
if not (a and b) then
return true
end
if a.entries and not b.entries then if a.entries and not b.entries then
return true return true
elseif not a.entries and b.entries then elseif not a.entries and b.entries then
@ -130,12 +125,6 @@ local function should_ignore(path)
return false return false
end 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()) local relpath = utils.path_relative(path, vim.loop.cwd())
if M.ignore_list[relpath] == true or M.ignore_list[basename] == true then if M.ignore_list[relpath] == true or M.ignore_list[basename] == true then
return true return true
@ -151,7 +140,11 @@ local function should_ignore(path)
return false return false
end 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) local handle = luv.fs_scandir(cwd)
if type(handle) == 'string' then if type(handle) == 'string' then
api.nvim_err_writeln(handle) api.nvim_err_writeln(handle)
@ -162,6 +155,9 @@ function M.refresh_entries(entries, cwd, parent_node)
local cached_entries = {} local cached_entries = {}
local entries_idx = {} local entries_idx = {}
for i, node in ipairs(entries) do 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 cached_entries[i] = node.name
entries_idx[node.name] = i entries_idx[node.name] = i
named_entries[node.name] = node named_entries[node.name] = node
@ -179,7 +175,7 @@ function M.refresh_entries(entries, cwd, parent_node)
num_new_entries = num_new_entries + 1 num_new_entries = num_new_entries + 1
local abs = utils.path_join({cwd, name}) 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 if not t then
local stat = luv.fs_stat(abs) local stat = luv.fs_stat(abs)
t = stat and stat.type t = stat and stat.type
@ -208,7 +204,7 @@ function M.refresh_entries(entries, cwd, parent_node)
parent_node.group_next = nil parent_node.group_next = nil
named_entries[next_node.name] = next_node named_entries[next_node.name] = next_node
else else
M.refresh_entries(entries, next_node.absolute_path, next_node) M.refresh_entries(entries, next_node.absolute_path, next_node, status)
return return
end end
end end
@ -245,7 +241,7 @@ function M.refresh_entries(entries, cwd, parent_node)
for _, name in ipairs(e.entries) do for _, name in ipairs(e.entries) do
change_prev = true change_prev = true
if not named_entries[name] then 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 if e.check(n.link_to, n.absolute_path) then
new_nodes_added = true new_nodes_added = true
idx = 1 idx = 1
@ -274,7 +270,7 @@ function M.refresh_entries(entries, cwd, parent_node)
end end
end end
function M.populate(entries, cwd, parent_node) function M.populate(entries, cwd, parent_node, status)
local handle = luv.fs_scandir(cwd) local handle = luv.fs_scandir(cwd)
if type(handle) == 'string' then if type(handle) == 'string' then
api.nvim_err_writeln(handle) api.nvim_err_writeln(handle)
@ -290,7 +286,7 @@ function M.populate(entries, cwd, parent_node)
if not name then break end if not name then break end
local abs = utils.path_join({cwd, name}) 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 if not t then
local stat = luv.fs_stat(abs) local stat = luv.fs_stat(abs)
t = stat and stat.type t = stat and stat.type
@ -306,52 +302,42 @@ function M.populate(entries, cwd, parent_node)
end end
end end
-- Create Nodes -- local parent_node_ignored = parent_node and parent_node.git_status == '!!'
-- Group empty dirs -- Group empty dirs
if parent_node and vim.g.nvim_tree_group_empty == 1 then if parent_node and vim.g.nvim_tree_group_empty == 1 then
if should_group(cwd, dirs, files, links) then if should_group(cwd, dirs, files, links) then
local child_node local child_node
if dirs[1] then child_node = dir_new(cwd, dirs[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]) 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 if luv.fs_access(child_node.absolute_path, 'R') then
parent_node.group_next = child_node parent_node.group_next = child_node
child_node.git_status = parent_node.git_status 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 return
end end
end end
end end
for _, dirname in ipairs(dirs) do 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 if luv.fs_access(dir.absolute_path, 'R') then
table.insert(entries, dir) table.insert(entries, dir)
end end
end end
for _, linkname in ipairs(links) do 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 if link.link_to ~= nil then
table.insert(entries, link) table.insert(entries, link)
end end
end end
for _, filename in ipairs(files) do 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) table.insert(entries, file)
end end
utils.merge_sort(entries, node_comparator) 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 end
function M.setup(opts) 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] local icons = git_hl[git_status]
if icons == nil then 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 icons = git_hl.dirty
end end
@ -239,7 +239,7 @@ if icon_state.show_git_icon then
local icons = git_icon_state[git_status] local icons = git_icon_state[git_status]
if not icons then if not icons then
if vim.g.nvim_tree_git_hl ~= 1 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 end
icons = git_icon_state.dirty icons = git_icon_state.dirty
end end

View File

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

View File

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

View File

@ -24,7 +24,8 @@ local out_config = {
"nvim_tree_disable_keybindings", "nvim_tree_disable_keybindings",
"nvim_tree_disable_default_keybindings", "nvim_tree_disable_default_keybindings",
"nvim_tree_hide_dotfiles", "nvim_tree_hide_dotfiles",
"nvim_tree_ignore" "nvim_tree_ignore",
"nvim_tree_gitignore"
} }
local x = vim.tbl_filter(function(v) local x = vim.tbl_filter(function(v)
@ -33,5 +34,5 @@ end, out_config)
if #x > 0 then if #x > 0 then
local msg = "Following options were moved to setup, see git.io/JPhyt: " 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 end