* feat(#1389): add next recursive for git and diag moves The recurse opt can be used to directly go to the next item showing git/diagnostic status recursively. Signed-off-by: Antonin Godard <antoningodard@pm.me> * refactor: status logic in single function Rename get_status to status_is_valid. Use status_is_valid function in multiple place to avoid duplicating code. Signed-off-by: Antonin Godard <antoningodard@pm.me> * feat(#1389): add prev recursive for git and diag moves Signed-off-by: Antonin Godard <antoningodard@pm.me> * fix(#1389): next recursive: take root node into account The root node cannot have a status. Previously if moving from the root node, status_is_valid was trying to fetch the status from it and errored. Signed-off-by: Antonin Godard <antoningodard@pm.me> * fix(#1389): doc: remove show_on_open_dirs limitation Signed-off-by: Antonin Godard <antoningodard@pm.me> * feat(#1389): move find_node_line to utils Signed-off-by: Antonin Godard <antoningodard@pm.me> * feat(#1389): doc: note recursive moves are to files only, tidy --------- Signed-off-by: Antonin Godard <antoningodard@pm.me> Co-authored-by: Alexander Courtis <alex@courtis.org>
This commit is contained in:
parent
6a99f5af78
commit
5d13cc8205
@ -1875,6 +1875,12 @@ node.open.preview_no_picker() *nvim-tree-api.node.open.preview_no_picker()*
|
||||
node.navigate.git.next() *nvim-tree-api.node.navigate.git.next()*
|
||||
Navigate to the next item showing git status.
|
||||
|
||||
*nvim-tree-api.node.navigate.git.next_recursive()*
|
||||
node.navigate.git.next_recursive()
|
||||
Alternative to |nvim-tree-api.node.navigate.git.next()| that navigates to
|
||||
the next file showing git status, recursively.
|
||||
Needs |nvim-tree.git.show_on_dirs| set.
|
||||
|
||||
*nvim-tree-api.node.navigate.git.next_skip_gitignored()*
|
||||
node.navigate.git.next_skip_gitignored()
|
||||
Same as |node.navigate.git.next()|, but skips gitignored files.
|
||||
@ -1882,6 +1888,12 @@ node.navigate.git.next_skip_gitignored()
|
||||
node.navigate.git.prev() *nvim-tree-api.node.navigate.git.prev()*
|
||||
Navigate to the previous item showing git status.
|
||||
|
||||
*nvim-tree-api.node.navigate.git.prev_recursive()*
|
||||
node.navigate.git.prev_recursive()
|
||||
Alternative to |nvim-tree-api.node.navigate.git.prev()| that navigates to
|
||||
the previous file showing git status, recursively.
|
||||
Needs |nvim-tree.git.show_on_dirs| set.
|
||||
|
||||
*nvim-tree-api.node.navigate.git.prev_skip_gitignored()*
|
||||
node.navigate.git.prev_skip_gitignored()
|
||||
Same as |node.navigate.git.prev()|, but skips gitignored files.
|
||||
@ -1890,10 +1902,22 @@ node.navigate.git.prev_skip_gitignored()
|
||||
node.navigate.diagnostics.next()
|
||||
Navigate to the next item showing diagnostic status.
|
||||
|
||||
*nvim-tree-api.node.navigate.diagnostics.next_recursive()*
|
||||
node.navigate.diagnostics.next_recursive()
|
||||
Alternative to |nvim-tree-api.node.navigate.diagnostics.next()| that
|
||||
navigates to the next file showing diagnostic status, recursively.
|
||||
Needs |nvim-tree.diagnostics.show_on_dirs| set.
|
||||
|
||||
*nvim-tree-api.node.navigate.diagnostics.prev()*
|
||||
node.navigate.diagnostics.prev()
|
||||
Navigate to the next item showing diagnostic status.
|
||||
|
||||
*nvim-tree-api.node.navigate.diagnostics.prev_recursive()*
|
||||
node.navigate.diagnostics.prev_recursive()
|
||||
Alternative to |nvim-tree-api.node.navigate.diagnostics.prev()| that
|
||||
navigates to the previous file showing diagnostic status, recursively.
|
||||
Needs |nvim-tree.diagnostics.show_on_dirs| set.
|
||||
|
||||
*nvim-tree-api.node.navigate.opened.next()*
|
||||
node.navigate.opened.next()
|
||||
Navigate to the next |bufloaded()| item.
|
||||
|
||||
@ -6,39 +6,47 @@ local explorer_node = require "nvim-tree.explorer.node"
|
||||
local diagnostics = require "nvim-tree.diagnostics"
|
||||
|
||||
local M = {}
|
||||
local MAX_DEPTH = 100
|
||||
|
||||
---@class NavigationItemOpts
|
||||
---@field where string
|
||||
---@field what string
|
||||
---Return the status of the node or nil if no status, depending on the type of
|
||||
---status.
|
||||
---@param node table 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_status = explorer_node.get_git_status(node)
|
||||
return git_status ~= nil and (not skip_gitignored or git_status[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
|
||||
|
||||
---@param opts NavigationItemOpts
|
||||
---@return fun()
|
||||
function M.fn(opts)
|
||||
return function()
|
||||
return false
|
||||
end
|
||||
|
||||
---Move to the next node that has a valid status. If none found, don't move.
|
||||
---@param where string where to move (forwards or backwards)
|
||||
---@param what string type of status
|
||||
---@param skip_gitignored boolean default false
|
||||
local function move(where, what, skip_gitignored)
|
||||
local node_cur = lib.get_node_at_cursor()
|
||||
local first_node_line = core.get_nodes_starting_line()
|
||||
local nodes_by_line = utils.get_nodes_by_line(core.get_explorer().nodes, first_node_line)
|
||||
local iter_start, iter_end, iter_step, cur, first, nex
|
||||
|
||||
if opts.where == "next" then
|
||||
if where == "next" then
|
||||
iter_start, iter_end, iter_step = first_node_line, #nodes_by_line, 1
|
||||
elseif opts.where == "prev" then
|
||||
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 = false
|
||||
|
||||
if opts.what == "git" then
|
||||
local git_status = explorer_node.get_git_status(node)
|
||||
valid = git_status ~= nil and (not opts.skip_gitignored or git_status[1] ~= "!!")
|
||||
elseif opts.what == "diag" then
|
||||
local diag_status = diagnostics.get_diag_status(node)
|
||||
valid = diag_status ~= nil and diag_status.value ~= nil
|
||||
elseif opts.what == "opened" then
|
||||
valid = vim.fn.bufloaded(node.absolute_path) ~= 0
|
||||
end
|
||||
local valid = status_is_valid(node, what, skip_gitignored)
|
||||
|
||||
if not first and valid then
|
||||
first = line
|
||||
@ -58,6 +66,173 @@ function M.fn(opts)
|
||||
view.set_cursor { first, 0 }
|
||||
end
|
||||
end
|
||||
|
||||
local function expand_node(node)
|
||||
if not node.open then
|
||||
-- Expand the node.
|
||||
-- Should never collapse since we checked open.
|
||||
lib.expand_or_collapse(node)
|
||||
end
|
||||
end
|
||||
|
||||
--- Move to the next node recursively.
|
||||
---@param what string type of status
|
||||
---@param skip_gitignored boolean default false
|
||||
local function move_next_recursive(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 = lib.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
|
||||
if node_init.nodes ~= nil and valid and not node_init.open then
|
||||
lib.expand_or_collapse(node_init)
|
||||
end
|
||||
|
||||
move("next", what, skip_gitignored)
|
||||
|
||||
local node_cur = lib.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 is_dir = node_cur.nodes ~= nil
|
||||
while is_dir and i < MAX_DEPTH do
|
||||
expand_node(node_cur)
|
||||
|
||||
move("next", what, skip_gitignored)
|
||||
|
||||
-- Save current node.
|
||||
node_cur = lib.get_node_at_cursor()
|
||||
-- Update is_dir.
|
||||
if node_cur then
|
||||
is_dir = node_cur.nodes ~= nil
|
||||
else
|
||||
is_dir = false
|
||||
end
|
||||
|
||||
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 what string type of status
|
||||
---@param skip_gitignored boolean default false
|
||||
local function move_prev_recursive(what, skip_gitignored)
|
||||
local node_init, node_cur
|
||||
|
||||
-- 1)
|
||||
node_init = lib.get_node_at_cursor()
|
||||
if node_init == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- 2)
|
||||
move("prev", what, skip_gitignored)
|
||||
|
||||
node_cur = lib.get_node_at_cursor()
|
||||
if node_cur == node_init.parent then
|
||||
-- 3)
|
||||
move_prev_recursive(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 not node_cur.nodes -- node is a file
|
||||
then
|
||||
return
|
||||
end
|
||||
|
||||
-- 4.2)
|
||||
local node_dir = node_cur
|
||||
expand_node(node_dir)
|
||||
|
||||
-- 4.3)
|
||||
if node_init.name == ".." then -- root node
|
||||
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.set_cursor { node_init_line, 0 }
|
||||
end
|
||||
|
||||
-- 4.4)
|
||||
move("prev", what, skip_gitignored)
|
||||
|
||||
-- 4.5)
|
||||
node_cur = lib.get_node_at_cursor()
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@class NavigationItemOpts
|
||||
---@field where string
|
||||
---@field what string
|
||||
|
||||
---@param opts NavigationItemOpts
|
||||
---@return fun()
|
||||
function M.fn(opts)
|
||||
return function()
|
||||
local recurse = false
|
||||
local skip_gitignored = 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 opts.skip_gitignored ~= nil then
|
||||
skip_gitignored = opts.skip_gitignored
|
||||
end
|
||||
|
||||
if not recurse then
|
||||
move(opts.where, opts.what, skip_gitignored)
|
||||
return
|
||||
end
|
||||
|
||||
if opts.where == "next" then
|
||||
move_next_recursive(opts.what, skip_gitignored)
|
||||
elseif opts.where == "prev" then
|
||||
move_prev_recursive(opts.what, skip_gitignored)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -210,10 +210,14 @@ Api.node.navigate.parent = wrap_node(actions.moves.parent.fn(false))
|
||||
Api.node.navigate.parent_close = wrap_node(actions.moves.parent.fn(true))
|
||||
Api.node.navigate.git.next = wrap_node(actions.moves.item.fn { where = "next", what = "git" })
|
||||
Api.node.navigate.git.next_skip_gitignored = wrap_node(actions.moves.item.fn { where = "next", what = "git", skip_gitignored = true })
|
||||
Api.node.navigate.git.next_recursive = wrap_node(actions.moves.item.fn { where = "next", what = "git", recurse = true })
|
||||
Api.node.navigate.git.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "git" })
|
||||
Api.node.navigate.git.prev_skip_gitignored = wrap_node(actions.moves.item.fn { where = "prev", what = "git", skip_gitignored = true })
|
||||
Api.node.navigate.git.prev_recursive = wrap_node(actions.moves.item.fn { where = "prev", what = "git", recurse = true })
|
||||
Api.node.navigate.diagnostics.next = wrap_node(actions.moves.item.fn { where = "next", what = "diag" })
|
||||
Api.node.navigate.diagnostics.next_recursive = wrap_node(actions.moves.item.fn { where = "next", what = "diag", recurse = true })
|
||||
Api.node.navigate.diagnostics.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "diag" })
|
||||
Api.node.navigate.diagnostics.prev_recursive = wrap_node(actions.moves.item.fn { where = "prev", what = "diag", recurse = true })
|
||||
Api.node.navigate.opened.next = wrap_node(actions.moves.item.fn { where = "next", what = "opened" })
|
||||
Api.node.navigate.opened.prev = wrap_node(actions.moves.item.fn { where = "prev", what = "opened" })
|
||||
|
||||
|
||||
@ -116,6 +116,28 @@ function M.find_node(nodes, fn)
|
||||
return node, i
|
||||
end
|
||||
|
||||
-- Find the line number of a node.
|
||||
-- Return -1 is node is nil or not found.
|
||||
---@param node Node|nil
|
||||
---@return integer
|
||||
function M.find_node_line(node)
|
||||
if not node then
|
||||
return -1
|
||||
end
|
||||
|
||||
local first_node_line = require("nvim-tree.core").get_nodes_starting_line()
|
||||
local nodes_by_line = M.get_nodes_by_line(require("nvim-tree.core").get_explorer().nodes, first_node_line)
|
||||
local iter_start, iter_end = first_node_line, #nodes_by_line
|
||||
|
||||
for line = iter_start, iter_end, 1 do
|
||||
if nodes_by_line[line] == node then
|
||||
return line
|
||||
end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
-- get the node in the tree state depending on the absolute path of the node
|
||||
-- (grouped or hidden too)
|
||||
---@param path string
|
||||
|
||||
Loading…
Reference in New Issue
Block a user