@@ -6,6 +6,201 @@ local explorer_node = require "nvim-tree.explorer.node"
6
6
local diagnostics = require " nvim-tree.diagnostics"
7
7
8
8
local 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
9
204
10
205
--- @class NavigationItemOpts
11
206
--- @field where string
@@ -15,47 +210,27 @@ local M = {}
15
210
--- @return fun ()
16
211
function M .fn (opts )
17
212
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
22
215
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
27
219
end
28
220
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
46
224
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
53
228
end
54
229
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 )
59
234
end
60
235
end
61
236
end
0 commit comments