@@ -6,6 +6,201 @@ local explorer_node = require "nvim-tree.explorer.node"
66local diagnostics = require " nvim-tree.diagnostics"
77
88local M = {}
9+ local MAX_DEPTH = 100
10+
11+ --- Return the status of the node or nil if no status, depending on the type of
12+ --- status.
13+ --- @param node table node to inspect
14+ --- @param what string type of status
15+ --- @param skip_gitignored boolean default false
16+ --- @return boolean
17+ local function status_is_valid (node , what , skip_gitignored )
18+ if what == " git" then
19+ local git_status = explorer_node .get_git_status (node )
20+ return git_status ~= nil and (not skip_gitignored or git_status [1 ] ~= " !!" )
21+ elseif what == " diag" then
22+ local diag_status = diagnostics .get_diag_status (node )
23+ return diag_status ~= nil and diag_status .value ~= nil
24+ elseif what == " opened" then
25+ return vim .fn .bufloaded (node .absolute_path ) ~= 0
26+ end
27+
28+ return false
29+ end
30+
31+ --- Move to the next node that has a valid status. If none found, don't move.
32+ --- @param where string where to move (forwards or backwards )
33+ --- @param what string type of status
34+ --- @param skip_gitignored boolean default false
35+ local function move (where , what , skip_gitignored )
36+ local node_cur = lib .get_node_at_cursor ()
37+ local first_node_line = core .get_nodes_starting_line ()
38+ local nodes_by_line = utils .get_nodes_by_line (core .get_explorer ().nodes , first_node_line )
39+ local iter_start , iter_end , iter_step , cur , first , nex
40+
41+ if where == " next" then
42+ iter_start , iter_end , iter_step = first_node_line , # nodes_by_line , 1
43+ elseif where == " prev" then
44+ iter_start , iter_end , iter_step = # nodes_by_line , first_node_line , - 1
45+ end
46+
47+ for line = iter_start , iter_end , iter_step do
48+ local node = nodes_by_line [line ]
49+ local valid = status_is_valid (node , what , skip_gitignored )
50+
51+ if not first and valid then
52+ first = line
53+ end
54+
55+ if node == node_cur then
56+ cur = line
57+ elseif valid and cur then
58+ nex = line
59+ break
60+ end
61+ end
62+
63+ if nex then
64+ view .set_cursor { nex , 0 }
65+ elseif vim .o .wrapscan and first then
66+ view .set_cursor { first , 0 }
67+ end
68+ end
69+
70+ local function expand_node (node )
71+ if not node .open then
72+ -- Expand the node.
73+ -- Should never collapse since we checked open.
74+ lib .expand_or_collapse (node )
75+ end
76+ end
77+
78+ --- Move to the next node recursively.
79+ --- @param what string type of status
80+ --- @param skip_gitignored boolean default false
81+ local function move_next_recursive (what , skip_gitignored )
82+ -- If the current node:
83+ -- * is a directory
84+ -- * and is not the root node
85+ -- * and has a git/diag status
86+ -- * and is not opened
87+ -- expand it.
88+ local node_init = lib .get_node_at_cursor ()
89+ if not node_init then
90+ return
91+ end
92+ local valid = false
93+ if node_init .name ~= " .." then -- root node cannot have a status
94+ valid = status_is_valid (node_init , what , skip_gitignored )
95+ end
96+ if node_init .nodes ~= nil and valid and not node_init .open then
97+ lib .expand_or_collapse (node_init )
98+ end
99+
100+ move (" next" , what , skip_gitignored )
101+
102+ local node_cur = lib .get_node_at_cursor ()
103+ if not node_cur then
104+ return
105+ end
106+
107+ -- If we haven't moved at all at this point, return.
108+ if node_init == node_cur then
109+ return
110+ end
111+
112+ -- i is used to limit iterations.
113+ local i = 0
114+ local is_dir = node_cur .nodes ~= nil
115+ while is_dir and i < MAX_DEPTH do
116+ expand_node (node_cur )
117+
118+ move (" next" , what , skip_gitignored )
119+
120+ -- Save current node.
121+ node_cur = lib .get_node_at_cursor ()
122+ -- Update is_dir.
123+ if node_cur then
124+ is_dir = node_cur .nodes ~= nil
125+ else
126+ is_dir = false
127+ end
128+
129+ i = i + 1
130+ end
131+ end
132+
133+ --- Move to the previous node recursively.
134+ ---
135+ --- move_prev_recursive:
136+ ---
137+ --- 1) Save current as node_init.
138+ -- 2) Call a non-recursive prev.
139+ --- 3) If current node is node_init's parent, call move_prev_recursive.
140+ --- 4) Else:
141+ --- 4.1) If current node is nil, is node_init (we didn't move), or is a file, return.
142+ --- 4.2) The current file is a directory, expand it.
143+ --- 4.3) Find node_init in current window, and move to it (if not found, return).
144+ --- If node_init is the root node (name = ".."), directly move to position 1.
145+ --- 4.4) Call a non-recursive prev.
146+ --- 4.5) Save the current node and start back from 4.1.
147+ ---
148+ --- @param what string type of status
149+ --- @param skip_gitignored boolean default false
150+ local function move_prev_recursive (what , skip_gitignored )
151+ local node_init , node_cur
152+
153+ -- 1)
154+ node_init = lib .get_node_at_cursor ()
155+ if node_init == nil then
156+ return
157+ end
158+
159+ -- 2)
160+ move (" prev" , what , skip_gitignored )
161+
162+ node_cur = lib .get_node_at_cursor ()
163+ if node_cur == node_init .parent then
164+ -- 3)
165+ move_prev_recursive (what , skip_gitignored )
166+ else
167+ -- i is used to limit iterations.
168+ local i = 0
169+ while i < MAX_DEPTH do
170+ -- 4.1)
171+ if
172+ node_cur == nil
173+ or node_cur == node_init -- we didn't move
174+ or not node_cur .nodes -- node is a file
175+ then
176+ return
177+ end
178+
179+ -- 4.2)
180+ local node_dir = node_cur
181+ expand_node (node_dir )
182+
183+ -- 4.3)
184+ if node_init .name == " .." then -- root node
185+ view .set_cursor { 1 , 0 } -- move to root node (position 1)
186+ else
187+ local node_init_line = utils .find_node_line (node_init )
188+ if node_init_line < 0 then
189+ return
190+ end
191+ view .set_cursor { node_init_line , 0 }
192+ end
193+
194+ -- 4.4)
195+ move (" prev" , what , skip_gitignored )
196+
197+ -- 4.5)
198+ node_cur = lib .get_node_at_cursor ()
199+
200+ i = i + 1
201+ end
202+ end
203+ end
9204
10205--- @class NavigationItemOpts
11206--- @field where string
@@ -15,47 +210,27 @@ local M = {}
15210--- @return fun ()
16211function M .fn (opts )
17212 return function ()
18- local node_cur = lib .get_node_at_cursor ()
19- local first_node_line = core .get_nodes_starting_line ()
20- local nodes_by_line = utils .get_nodes_by_line (core .get_explorer ().nodes , first_node_line )
21- local iter_start , iter_end , iter_step , cur , first , nex
213+ local recurse = false
214+ local skip_gitignored = false
22215
23- if opts .where == " next" then
24- iter_start , iter_end , iter_step = first_node_line , # nodes_by_line , 1
25- elseif opts .where == " prev" then
26- iter_start , iter_end , iter_step = # nodes_by_line , first_node_line , - 1
216+ -- recurse only valid for git and diag moves.
217+ if (opts .what == " git" or opts .what == " diag" ) and opts .recurse ~= nil then
218+ recurse = opts .recurse
27219 end
28220
29- for line = iter_start , iter_end , iter_step do
30- local node = nodes_by_line [line ]
31- local valid = false
32-
33- if opts .what == " git" then
34- local git_status = explorer_node .get_git_status (node )
35- valid = git_status ~= nil and (not opts .skip_gitignored or git_status [1 ] ~= " !!" )
36- elseif opts .what == " diag" then
37- local diag_status = diagnostics .get_diag_status (node )
38- valid = diag_status ~= nil and diag_status .value ~= nil
39- elseif opts .what == " opened" then
40- valid = vim .fn .bufloaded (node .absolute_path ) ~= 0
41- end
42-
43- if not first and valid then
44- first = line
45- end
221+ if opts .skip_gitignored ~= nil then
222+ skip_gitignored = opts .skip_gitignored
223+ end
46224
47- if node == node_cur then
48- cur = line
49- elseif valid and cur then
50- nex = line
51- break
52- end
225+ if not recurse then
226+ move (opts .where , opts .what , skip_gitignored )
227+ return
53228 end
54229
55- if nex then
56- view . set_cursor { nex , 0 }
57- elseif vim . o . wrapscan and first then
58- view . set_cursor { first , 0 }
230+ if opts . where == " next " then
231+ move_next_recursive ( opts . what , skip_gitignored )
232+ elseif opts . where == " prev " then
233+ move_prev_recursive ( opts . what , skip_gitignored )
59234 end
60235 end
61236end
0 commit comments