248 lines
6.7 KiB
Lua
248 lines
6.7 KiB
Lua
local utils = require("nvim-tree.utils")
|
|
local view = require("nvim-tree.view")
|
|
local core = require("nvim-tree.core")
|
|
local diagnostics = require("nvim-tree.diagnostics")
|
|
|
|
local FileNode = require("nvim-tree.node.file")
|
|
local DirectoryNode = require("nvim-tree.node.directory")
|
|
|
|
local M = {}
|
|
local MAX_DEPTH = 100
|
|
|
|
---Return the status of the node or nil if no status, depending on the type of
|
|
---status.
|
|
---@param node Node to inspect
|
|
---@param what string? type of status
|
|
---@param skip_gitignored boolean? default false
|
|
---@return boolean
|
|
local function status_is_valid(node, what, skip_gitignored)
|
|
if what == "git" then
|
|
local git_xy = node:get_git_xy()
|
|
return git_xy ~= nil and (not skip_gitignored or git_xy[1] ~= "!!")
|
|
elseif what == "diag" then
|
|
local diag_status = diagnostics.get_diag_status(node)
|
|
return diag_status ~= nil and diag_status.value ~= nil
|
|
elseif what == "opened" then
|
|
return vim.fn.bufloaded(node.absolute_path) ~= 0
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
---Move to the next node that has a valid status. If none found, don't move.
|
|
---@param explorer Explorer
|
|
---@param where string? where to move (forwards or backwards)
|
|
---@param what string? type of status
|
|
---@param skip_gitignored boolean? default false
|
|
local function move(explorer, where, what, skip_gitignored)
|
|
local first_node_line = core.get_nodes_starting_line()
|
|
local nodes_by_line = utils.get_nodes_by_line(explorer.nodes, first_node_line)
|
|
local iter_start, iter_end, iter_step, cur, first, nex
|
|
|
|
local cursor = explorer:get_cursor_position()
|
|
if cursor and cursor[1] < first_node_line then
|
|
cur = cursor[1]
|
|
end
|
|
|
|
if where == "next" then
|
|
iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1
|
|
elseif where == "prev" then
|
|
iter_start, iter_end, iter_step = #nodes_by_line, first_node_line, -1
|
|
end
|
|
|
|
for line = iter_start, iter_end, iter_step do
|
|
local node = nodes_by_line[line]
|
|
local valid = status_is_valid(node, what, skip_gitignored)
|
|
|
|
if not first and valid then
|
|
first = line
|
|
end
|
|
|
|
if cursor and line == cursor[1] then
|
|
cur = line
|
|
elseif valid and cur then
|
|
nex = line
|
|
break
|
|
end
|
|
end
|
|
|
|
if nex then
|
|
view.View:set_cursor({ nex, 0 })
|
|
elseif vim.o.wrapscan and first then
|
|
view.View:set_cursor({ first, 0 })
|
|
end
|
|
end
|
|
|
|
---@param node DirectoryNode
|
|
local function expand_node(node)
|
|
if not node.open then
|
|
-- Expand the node.
|
|
-- Should never collapse since we checked open.
|
|
node:expand_or_collapse(false)
|
|
end
|
|
end
|
|
|
|
--- Move to the next node recursively.
|
|
---@param explorer Explorer
|
|
---@param what string? type of status
|
|
---@param skip_gitignored? boolean default false
|
|
local function move_next_recursive(explorer, what, skip_gitignored)
|
|
-- If the current node:
|
|
-- * is a directory
|
|
-- * and is not the root node
|
|
-- * and has a git/diag status
|
|
-- * and is not opened
|
|
-- expand it.
|
|
local node_init = explorer:get_node_at_cursor()
|
|
if not node_init then
|
|
return
|
|
end
|
|
local valid = false
|
|
if node_init.name ~= ".." then -- root node cannot have a status
|
|
valid = status_is_valid(node_init, what, skip_gitignored)
|
|
end
|
|
local node_dir = node_init:as(DirectoryNode)
|
|
if node_dir and valid and not node_dir.open then
|
|
node_dir:expand_or_collapse(false)
|
|
end
|
|
|
|
move(explorer, "next", what, skip_gitignored)
|
|
|
|
local node_cur = explorer:get_node_at_cursor()
|
|
if not node_cur then
|
|
return
|
|
end
|
|
|
|
-- If we haven't moved at all at this point, return.
|
|
if node_init == node_cur then
|
|
return
|
|
end
|
|
|
|
-- i is used to limit iterations.
|
|
local i = 0
|
|
local dir_cur = node_cur:as(DirectoryNode)
|
|
while dir_cur and i < MAX_DEPTH do
|
|
expand_node(dir_cur)
|
|
|
|
move(explorer, "next", what, skip_gitignored)
|
|
|
|
-- Save current node.
|
|
node_cur = explorer:get_node_at_cursor()
|
|
dir_cur = node_cur and node_cur:as(DirectoryNode)
|
|
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
--- Move to the previous node recursively.
|
|
---
|
|
--- move_prev_recursive:
|
|
---
|
|
--- 1) Save current as node_init.
|
|
-- 2) Call a non-recursive prev.
|
|
--- 3) If current node is node_init's parent, call move_prev_recursive.
|
|
--- 4) Else:
|
|
--- 4.1) If current node is nil, is node_init (we didn't move), or is a file, return.
|
|
--- 4.2) The current file is a directory, expand it.
|
|
--- 4.3) Find node_init in current window, and move to it (if not found, return).
|
|
--- If node_init is the root node (name = ".."), directly move to position 1.
|
|
--- 4.4) Call a non-recursive prev.
|
|
--- 4.5) Save the current node and start back from 4.1.
|
|
---
|
|
---@param explorer Explorer
|
|
---@param what string? type of status
|
|
---@param skip_gitignored boolean? default false
|
|
local function move_prev_recursive(explorer, what, skip_gitignored)
|
|
local node_init, node_cur
|
|
|
|
-- 1)
|
|
node_init = explorer:get_node_at_cursor()
|
|
if node_init == nil then
|
|
return
|
|
end
|
|
|
|
-- 2)
|
|
move(explorer, "prev", what, skip_gitignored)
|
|
|
|
node_cur = explorer:get_node_at_cursor()
|
|
if node_cur == node_init.parent then
|
|
-- 3)
|
|
move_prev_recursive(explorer, what, skip_gitignored)
|
|
else
|
|
-- i is used to limit iterations.
|
|
local i = 0
|
|
while i < MAX_DEPTH do
|
|
-- 4.1)
|
|
if
|
|
node_cur == nil
|
|
or node_cur == node_init -- we didn't move
|
|
or node_cur:is(FileNode) -- node is a file
|
|
then
|
|
return
|
|
end
|
|
|
|
-- 4.2)
|
|
local node_dir = node_cur:as(DirectoryNode)
|
|
if node_dir then
|
|
expand_node(node_dir)
|
|
end
|
|
|
|
-- 4.3)
|
|
if node_init.name == ".." then -- root node
|
|
view.View:set_cursor({ 1, 0 }) -- move to root node (position 1)
|
|
else
|
|
local node_init_line = utils.find_node_line(node_init)
|
|
if node_init_line < 0 then
|
|
return
|
|
end
|
|
view.View:set_cursor({ node_init_line, 0 })
|
|
end
|
|
|
|
-- 4.4)
|
|
move(explorer, "prev", what, skip_gitignored)
|
|
|
|
-- 4.5)
|
|
node_cur = explorer:get_node_at_cursor()
|
|
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
---@class NavigationItemOpts
|
|
---@field where string?
|
|
---@field what string?
|
|
---@field skip_gitignored boolean?
|
|
---@field recurse boolean?
|
|
|
|
---@param opts NavigationItemOpts
|
|
---@return fun()
|
|
function M.fn(opts)
|
|
return function()
|
|
local explorer = core.get_explorer()
|
|
if not explorer then
|
|
return
|
|
end
|
|
|
|
local recurse = false
|
|
|
|
-- recurse only valid for git and diag moves.
|
|
if (opts.what == "git" or opts.what == "diag") and opts.recurse ~= nil then
|
|
recurse = opts.recurse
|
|
end
|
|
|
|
if not recurse then
|
|
move(explorer, opts.where, opts.what, opts.skip_gitignored)
|
|
return
|
|
end
|
|
|
|
if opts.where == "next" then
|
|
move_next_recursive(explorer, opts.what, opts.skip_gitignored)
|
|
elseif opts.where == "prev" then
|
|
move_prev_recursive(explorer, opts.what, opts.skip_gitignored)
|
|
end
|
|
end
|
|
end
|
|
|
|
return M
|