9
9
10
10
use std:: result:: Result :: Ok ;
11
11
12
- use anyhow:: anyhow;
13
12
use ropey:: Rope ;
14
13
use stdext:: unwrap:: IntoResult ;
15
14
use tower_lsp:: lsp_types:: DocumentSymbol ;
@@ -32,8 +31,6 @@ use crate::treesitter::BinaryOperatorType;
32
31
use crate :: treesitter:: NodeType ;
33
32
use crate :: treesitter:: NodeTypeExt ;
34
33
35
- type StoreStack = Vec < ( usize , Option < DocumentSymbol > , Vec < DocumentSymbol > ) > ;
36
-
37
34
fn new_symbol ( name : String , kind : SymbolKind , range : Range ) -> DocumentSymbol {
38
35
DocumentSymbol {
39
36
name,
@@ -114,6 +111,15 @@ pub fn symbols(params: &WorkspaceSymbolParams) -> anyhow::Result<Vec<SymbolInfor
114
111
Ok ( info)
115
112
}
116
113
114
+ /// Represents a section in the document with its title, level, range, and children
115
+ #[ derive( Debug ) ]
116
+ struct Section {
117
+ title : String ,
118
+ level : usize ,
119
+ range : Range ,
120
+ children : Vec < DocumentSymbol > ,
121
+ }
122
+
117
123
pub ( crate ) fn document_symbols (
118
124
state : & WorldState ,
119
125
params : & DocumentSymbolParams ,
@@ -123,158 +129,123 @@ pub(crate) fn document_symbols(
123
129
let ast = & document. ast ;
124
130
let contents = & document. contents ;
125
131
126
- let node = ast. root_node ( ) ;
132
+ // Start walking from the root node
133
+ let root_node = ast. root_node ( ) ;
134
+ let mut result = Vec :: new ( ) ;
127
135
128
- // Index from the root
129
- match index_node ( & node, vec ! [ ] , & contents) {
130
- Ok ( children) => Ok ( children) ,
131
- Err ( err) => {
132
- log:: error!( "Error indexing node: {err:?}" ) ;
133
- return Ok ( Vec :: new ( ) ) ;
134
- } ,
136
+ // Extract and process all symbols from the AST
137
+ if let Err ( err) = collect_symbols ( & root_node, contents, 0 , & mut result) {
138
+ log:: error!( "Failed to collect symbols: {err:?}" ) ;
139
+ return Ok ( Vec :: new ( ) ) ;
135
140
}
141
+
142
+ Ok ( result)
136
143
}
137
144
138
- fn index_node (
145
+ /// Collect all document symbols from a node recursively
146
+ fn collect_symbols (
139
147
node : & Node ,
140
- store : Vec < DocumentSymbol > ,
141
148
contents : & Rope ,
142
- ) -> anyhow:: Result < Vec < DocumentSymbol > > {
143
- Ok ( match node. node_type ( ) {
144
- // Handle comment sections in expression lists
149
+ current_level : usize ,
150
+ symbols : & mut Vec < DocumentSymbol > ,
151
+ ) -> anyhow:: Result < ( ) > {
152
+ match node. node_type ( ) {
145
153
NodeType :: Program | NodeType :: BracedExpression => {
146
- index_expression_list ( & node, store , contents ) ?
154
+ collect_sections ( node, contents , current_level , symbols ) ? ;
147
155
} ,
148
- // Index assignments as object or function symbols
156
+
149
157
NodeType :: BinaryOperator ( BinaryOperatorType :: LeftAssignment ) |
150
158
NodeType :: BinaryOperator ( BinaryOperatorType :: EqualsAssignment ) => {
151
- index_assignment ( & node, store , contents ) ?
159
+ collect_assignment ( node, contents , symbols ) ? ;
152
160
} ,
153
- // Nothing to index. FIXME: We should handle argument lists, e.g. to
154
- // index inside functions passed as arguments, or inside `test_that()`
155
- // blocks.
156
- _ => store,
157
- } )
161
+
162
+ // For all other node types, no symbols need to be added
163
+ _ => { } ,
164
+ }
165
+
166
+ Ok ( ( ) )
158
167
}
159
168
160
- // Handles root node and braced lists
161
- fn index_expression_list (
169
+ fn collect_sections (
162
170
node : & Node ,
163
- store : Vec < DocumentSymbol > ,
164
171
contents : & Rope ,
165
- ) -> anyhow:: Result < Vec < DocumentSymbol > > {
172
+ current_level : usize ,
173
+ symbols : & mut Vec < DocumentSymbol > ,
174
+ ) -> anyhow:: Result < ( ) > {
175
+ // In lists of expressions we track and collect section comments, then
176
+ // collect symbols from children nodes
177
+
166
178
let mut cursor = node. walk ( ) ;
167
179
168
- // This is a stack of section levels and associated stores for comments of
169
- // the type `# title ----`. It contains all currently active sections.
170
- // The top-level section is the current store and has level 0. It should
171
- // always be in the stack and popping it before we have finished indexing
172
- // the whole expression list is a logic error.
173
- let mut store_stack: StoreStack = vec ! [ ( 0 , None , store) ] ;
180
+ // Track active sections at each level
181
+ let mut active_sections: Vec < Section > = Vec :: new ( ) ;
174
182
175
183
for child in node. children ( & mut cursor) {
176
184
if let NodeType :: Comment = child. node_type ( ) {
177
- store_stack = index_comments ( & child, store_stack, contents) ?;
178
- continue ;
179
- }
180
-
181
- // Get the current store to index the child subtree with.
182
- // We restore the store in the stack right after that.
183
- let Some ( ( level, symbol, store) ) = store_stack. pop ( ) else {
184
- return Err ( anyhow ! (
185
- "Internal error: Store stack must have at least one element"
186
- ) ) ;
187
- } ;
188
- let store = index_node ( & child, store, contents) ?;
189
- store_stack. push ( ( level, symbol, store) ) ;
190
- }
185
+ let comment_text = contents. node_slice ( & child) ?. to_string ( ) ;
186
+
187
+ // If we have a section comment, add it to our stack and close any sections if needed
188
+ if let Some ( ( level, title) ) = parse_comment_as_section ( & comment_text) {
189
+ let absolute_level = current_level + level;
190
+
191
+ // Close any sections with equal or higher level
192
+ while !active_sections. is_empty ( ) &&
193
+ active_sections. last ( ) . unwrap ( ) . level >= absolute_level
194
+ {
195
+ finalize_section ( & mut active_sections, symbols) ?;
196
+ }
197
+
198
+ let range = Range {
199
+ start : convert_point_to_position ( contents, child. start_position ( ) ) ,
200
+ end : convert_point_to_position ( contents, child. end_position ( ) ) ,
201
+ } ;
202
+ let section = Section {
203
+ title,
204
+ level : absolute_level,
205
+ range,
206
+ children : Vec :: new ( ) ,
207
+ } ;
208
+ active_sections. push ( section) ;
209
+ }
191
210
192
- // Pop all sections from the stack, assigning their childrens and their
193
- // parents along the way
194
- while store_stack. len ( ) > 0 {
195
- if let Some ( store) = store_stack_pop ( & mut store_stack) ? {
196
- return Ok ( store) ;
211
+ continue ;
197
212
}
198
- }
199
213
200
- Err ( anyhow ! (
201
- "Internal error: Store stack must have at least one element"
202
- ) )
203
- }
204
-
205
- // Pop store from the stack, recording its children and adding it as child to
206
- // its parent (which becomes the last element in the stack).
207
- fn store_stack_pop ( store_stack : & mut StoreStack ) -> anyhow:: Result < Option < Vec < DocumentSymbol > > > {
208
- let Some ( ( _, symbol, last) ) = store_stack. pop ( ) else {
209
- return Ok ( None ) ;
210
- } ;
211
-
212
- if let Some ( mut sym) = symbol {
213
- // Assign children to symbol
214
- sym. children = Some ( last) ;
215
-
216
- let Some ( ( _, _, ref mut parent_store) ) = store_stack. last_mut ( ) else {
217
- return Err ( anyhow ! (
218
- "Internal error: Store stack must have at least one element"
219
- ) ) ;
220
- } ;
221
-
222
- // Store symbol as child of the last symbol on the stack
223
- parent_store. push ( sym) ;
224
-
225
- Ok ( None )
226
- } else {
227
- Ok ( Some ( last) )
228
- }
229
- }
230
-
231
- fn index_comments (
232
- node : & Node ,
233
- mut store_stack : StoreStack ,
234
- contents : & Rope ,
235
- ) -> anyhow:: Result < StoreStack > {
236
- let comment_text = contents. node_slice ( & node) ?. to_string ( ) ;
237
-
238
- // Check if the comment starts with one or more '#' followed by any text and ends with 4+ punctuations
239
- let Some ( ( level, title) ) = parse_comment_as_section ( & comment_text) else {
240
- return Ok ( store_stack) ;
241
- } ;
242
-
243
- // Create a section symbol based on the parsed comment
244
- let start = convert_point_to_position ( contents, node. start_position ( ) ) ;
245
- let end = convert_point_to_position ( contents, node. end_position ( ) ) ;
246
- let symbol = new_symbol ( title, SymbolKind :: STRING , Range { start, end } ) ;
247
-
248
- // Now pop all sections still on the stack that have a higher or equal
249
- // level. Because we pop sections with equal levels, i.e. siblings, we
250
- // ensure that there is only one active section per level on the stack.
251
- // That simplifies things because we need to assign popped sections to their
252
- // parents and we can assume the relevant parent is always the next on the
253
- // stack.
254
- loop {
255
- let Some ( ( last_level, _, _) ) = store_stack. last ( ) else {
256
- return Err ( anyhow ! ( "Unexpectedly reached the end of the store stack" ) ) ;
257
- } ;
258
-
259
- if * last_level >= level {
260
- if store_stack_pop ( & mut store_stack) ?. is_some ( ) {
261
- return Err ( anyhow ! ( "Unexpectedly reached the end of the store stack" ) ) ;
214
+ // If we get to this point, `child` is not a section comment.
215
+ // Recurse into child.
216
+
217
+ if active_sections. is_empty ( ) {
218
+ // If no active section, extend current vector of symbols
219
+ collect_symbols ( & child, contents, current_level, symbols) ?;
220
+ } else {
221
+ // Otherwise create new store of symbols for the current section
222
+ let mut child_symbols = Vec :: new ( ) ;
223
+ collect_symbols ( & child, contents, current_level, & mut child_symbols) ?;
224
+
225
+ // Nest them inside last section
226
+ if !child_symbols. is_empty ( ) {
227
+ active_sections
228
+ . last_mut ( )
229
+ . unwrap ( )
230
+ . children
231
+ . extend ( child_symbols) ;
262
232
}
263
- continue ;
264
233
}
234
+ }
265
235
266
- break ;
236
+ // Close any remaining active sections
237
+ while !active_sections. is_empty ( ) {
238
+ finalize_section ( & mut active_sections, symbols) ?;
267
239
}
268
240
269
- store_stack. push ( ( level, Some ( symbol) , vec ! [ ] ) ) ;
270
- Ok ( store_stack)
241
+ Ok ( ( ) )
271
242
}
272
243
273
- fn index_assignment (
244
+ fn collect_assignment (
274
245
node : & Node ,
275
- mut store : Vec < DocumentSymbol > ,
276
246
contents : & Rope ,
277
- ) -> anyhow:: Result < Vec < DocumentSymbol > > {
247
+ symbols : & mut Vec < DocumentSymbol > ,
248
+ ) -> anyhow:: Result < ( ) > {
278
249
// Check for assignment
279
250
matches ! (
280
251
node. node_type( ) ,
@@ -291,7 +262,7 @@ fn index_assignment(
291
262
let function = lhs. is_identifier_or_string ( ) && rhs. is_function_definition ( ) ;
292
263
293
264
if function {
294
- return index_assignment_with_function ( node, store , contents ) ;
265
+ return collect_assignment_with_function ( node, contents , symbols ) ;
295
266
}
296
267
297
268
// otherwise, just index as generic object
@@ -301,16 +272,16 @@ fn index_assignment(
301
272
let end = convert_point_to_position ( contents, lhs. end_position ( ) ) ;
302
273
303
274
let symbol = new_symbol ( name, SymbolKind :: VARIABLE , Range { start, end } ) ;
304
- store . push ( symbol) ;
275
+ symbols . push ( symbol) ;
305
276
306
- Ok ( store )
277
+ Ok ( ( ) )
307
278
}
308
279
309
- fn index_assignment_with_function (
280
+ fn collect_assignment_with_function (
310
281
node : & Node ,
311
- mut store : Vec < DocumentSymbol > ,
312
282
contents : & Rope ,
313
- ) -> anyhow:: Result < Vec < DocumentSymbol > > {
283
+ symbols : & mut Vec < DocumentSymbol > ,
284
+ ) -> anyhow:: Result < ( ) > {
314
285
// check for lhs, rhs
315
286
let lhs = node. child_by_field_name ( "lhs" ) . into_result ( ) ?;
316
287
let rhs = node. child_by_field_name ( "rhs" ) . into_result ( ) ?;
@@ -336,15 +307,36 @@ fn index_assignment_with_function(
336
307
337
308
let body = rhs. child_by_field_name ( "body" ) . into_result ( ) ?;
338
309
339
- // At this point we increase the nesting level. Recurse into the function
340
- // node with a new store of children nodes.
341
- let children = index_node ( & body, vec ! [ ] , contents ) ?;
310
+ // Process the function body to extract child symbols
311
+ let mut children = Vec :: new ( ) ;
312
+ collect_symbols ( & body, contents , 0 , & mut children ) ?;
342
313
343
314
let mut symbol = new_symbol_node ( name, SymbolKind :: FUNCTION , range, children) ;
344
315
symbol. detail = Some ( detail) ;
345
- store. push ( symbol) ;
316
+ symbols. push ( symbol) ;
317
+
318
+ Ok ( ( ) )
319
+ }
320
+
321
+ /// Finalize a section by creating a symbol and adding it to the parent section or output
322
+ fn finalize_section (
323
+ active_sections : & mut Vec < Section > ,
324
+ symbols : & mut Vec < DocumentSymbol > ,
325
+ ) -> anyhow:: Result < ( ) > {
326
+ if let Some ( section) = active_sections. pop ( ) {
327
+ let symbol = new_symbol ( section. title , SymbolKind :: STRING , section. range ) ;
328
+
329
+ let mut final_symbol = symbol;
330
+ final_symbol. children = Some ( section. children ) ;
331
+
332
+ if let Some ( parent) = active_sections. last_mut ( ) {
333
+ parent. children . push ( final_symbol) ;
334
+ } else {
335
+ symbols. push ( final_symbol) ;
336
+ }
337
+ }
346
338
347
- Ok ( store )
339
+ Ok ( ( ) )
348
340
}
349
341
350
342
// Function to parse a comment and return the section level and title
@@ -374,7 +366,9 @@ mod tests {
374
366
let doc = Document :: new ( code, None ) ;
375
367
let node = doc. ast . root_node ( ) ;
376
368
377
- index_node ( & node, vec ! [ ] , & doc. contents ) . unwrap ( )
369
+ let mut symbols = Vec :: new ( ) ;
370
+ collect_symbols ( & node, & doc. contents , 0 , & mut symbols) . unwrap ( ) ;
371
+ symbols
378
372
}
379
373
380
374
#[ test]
0 commit comments