Skip to content

Commit ba2bf62

Browse files
committed
Refactor to use mutation instead of passing stacks around
1 parent 38c067e commit ba2bf62

File tree

1 file changed

+131
-135
lines changed

1 file changed

+131
-135
lines changed

crates/ark/src/lsp/symbols.rs

Lines changed: 131 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
use std::result::Result::Ok;
1111

12-
use anyhow::anyhow;
1312
use ropey::Rope;
1413
use stdext::unwrap::IntoResult;
1514
use tower_lsp::lsp_types::DocumentSymbol;
@@ -32,8 +31,6 @@ use crate::treesitter::BinaryOperatorType;
3231
use crate::treesitter::NodeType;
3332
use crate::treesitter::NodeTypeExt;
3433

35-
type StoreStack = Vec<(usize, Option<DocumentSymbol>, Vec<DocumentSymbol>)>;
36-
3734
fn new_symbol(name: String, kind: SymbolKind, range: Range) -> DocumentSymbol {
3835
DocumentSymbol {
3936
name,
@@ -114,6 +111,15 @@ pub fn symbols(params: &WorkspaceSymbolParams) -> anyhow::Result<Vec<SymbolInfor
114111
Ok(info)
115112
}
116113

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+
117123
pub(crate) fn document_symbols(
118124
state: &WorldState,
119125
params: &DocumentSymbolParams,
@@ -123,158 +129,124 @@ pub(crate) fn document_symbols(
123129
let ast = &document.ast;
124130
let contents = &document.contents;
125131

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();
127135

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());
135140
}
141+
142+
Ok(result)
136143
}
137144

138-
fn index_node(
145+
/// Collect all document symbols from a node recursively
146+
fn collect_symbols(
139147
node: &Node,
140-
store: Vec<DocumentSymbol>,
141148
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() {
145153
NodeType::Program | NodeType::BracedExpression => {
146-
index_expression_list(&node, store, contents)?
154+
collect_sections(node, contents, current_level, symbols)?;
147155
},
148-
// Index assignments as object or function symbols
156+
157+
// Handle assignments
149158
NodeType::BinaryOperator(BinaryOperatorType::LeftAssignment) |
150159
NodeType::BinaryOperator(BinaryOperatorType::EqualsAssignment) => {
151-
index_assignment(&node, store, contents)?
160+
collect_assignment(node, contents, symbols)?;
152161
},
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-
})
162+
163+
// For all other node types, no symbols need to be added
164+
_ => {},
165+
}
166+
167+
Ok(())
158168
}
159169

160-
// Handles root node and braced lists
161-
fn index_expression_list(
170+
fn collect_sections(
162171
node: &Node,
163-
store: Vec<DocumentSymbol>,
164172
contents: &Rope,
165-
) -> anyhow::Result<Vec<DocumentSymbol>> {
173+
current_level: usize,
174+
symbols: &mut Vec<DocumentSymbol>,
175+
) -> anyhow::Result<()> {
176+
// In lists of expressions we track and collect section comments, then
177+
// collect symbols from children nodes
178+
166179
let mut cursor = node.walk();
167180

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)];
181+
// Track active sections at each level
182+
let mut active_sections: Vec<Section> = Vec::new();
174183

175184
for child in node.children(&mut cursor) {
176185
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-
}
186+
let comment_text = contents.node_slice(&child)?.to_string();
187+
188+
// If we have a section comment, add it to our stack and close any sections if needed
189+
if let Some((level, title)) = parse_comment_as_section(&comment_text) {
190+
let absolute_level = current_level + level;
191+
192+
// Close any sections with equal or higher level
193+
while !active_sections.is_empty() &&
194+
active_sections.last().unwrap().level >= absolute_level
195+
{
196+
finalize_section(&mut active_sections, symbols)?;
197+
}
198+
199+
let range = Range {
200+
start: convert_point_to_position(contents, child.start_position()),
201+
end: convert_point_to_position(contents, child.end_position()),
202+
};
203+
let section = Section {
204+
title,
205+
level: absolute_level,
206+
range,
207+
children: Vec::new(),
208+
};
209+
active_sections.push(section);
210+
}
191211

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);
212+
continue;
197213
}
198-
}
199214

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"));
215+
// If we get to this point, `child` is not a section comment.
216+
// Recurse into child.
217+
218+
if active_sections.is_empty() {
219+
// If no active section, extend current vector of symbols
220+
collect_symbols(&child, contents, current_level, symbols)?;
221+
} else {
222+
// Otherwise create new store of symbols for the current section
223+
let mut child_symbols = Vec::new();
224+
collect_symbols(&child, contents, current_level, &mut child_symbols)?;
225+
226+
// Nest them inside last section
227+
if !child_symbols.is_empty() {
228+
active_sections
229+
.last_mut()
230+
.unwrap()
231+
.children
232+
.extend(child_symbols);
262233
}
263-
continue;
264234
}
235+
}
265236

266-
break;
237+
// Close any remaining active sections
238+
while !active_sections.is_empty() {
239+
finalize_section(&mut active_sections, symbols)?;
267240
}
268241

269-
store_stack.push((level, Some(symbol), vec![]));
270-
Ok(store_stack)
242+
Ok(())
271243
}
272244

273-
fn index_assignment(
245+
fn collect_assignment(
274246
node: &Node,
275-
mut store: Vec<DocumentSymbol>,
276247
contents: &Rope,
277-
) -> anyhow::Result<Vec<DocumentSymbol>> {
248+
symbols: &mut Vec<DocumentSymbol>,
249+
) -> anyhow::Result<()> {
278250
// Check for assignment
279251
matches!(
280252
node.node_type(),
@@ -291,7 +263,7 @@ fn index_assignment(
291263
let function = lhs.is_identifier_or_string() && rhs.is_function_definition();
292264

293265
if function {
294-
return index_assignment_with_function(node, store, contents);
266+
return collect_assignment_with_function(node, contents, symbols);
295267
}
296268

297269
// otherwise, just index as generic object
@@ -301,16 +273,16 @@ fn index_assignment(
301273
let end = convert_point_to_position(contents, lhs.end_position());
302274

303275
let symbol = new_symbol(name, SymbolKind::VARIABLE, Range { start, end });
304-
store.push(symbol);
276+
symbols.push(symbol);
305277

306-
Ok(store)
278+
Ok(())
307279
}
308280

309-
fn index_assignment_with_function(
281+
fn collect_assignment_with_function(
310282
node: &Node,
311-
mut store: Vec<DocumentSymbol>,
312283
contents: &Rope,
313-
) -> anyhow::Result<Vec<DocumentSymbol>> {
284+
symbols: &mut Vec<DocumentSymbol>,
285+
) -> anyhow::Result<()> {
314286
// check for lhs, rhs
315287
let lhs = node.child_by_field_name("lhs").into_result()?;
316288
let rhs = node.child_by_field_name("rhs").into_result()?;
@@ -336,15 +308,36 @@ fn index_assignment_with_function(
336308

337309
let body = rhs.child_by_field_name("body").into_result()?;
338310

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)?;
311+
// Process the function body to extract child symbols
312+
let mut children = Vec::new();
313+
collect_symbols(&body, contents, 0, &mut children)?;
342314

343315
let mut symbol = new_symbol_node(name, SymbolKind::FUNCTION, range, children);
344316
symbol.detail = Some(detail);
345-
store.push(symbol);
317+
symbols.push(symbol);
318+
319+
Ok(())
320+
}
321+
322+
/// Finalize a section by creating a symbol and adding it to the parent section or output
323+
fn finalize_section(
324+
active_sections: &mut Vec<Section>,
325+
symbols: &mut Vec<DocumentSymbol>,
326+
) -> anyhow::Result<()> {
327+
if let Some(section) = active_sections.pop() {
328+
let symbol = new_symbol(section.title, SymbolKind::STRING, section.range);
329+
330+
let mut final_symbol = symbol;
331+
final_symbol.children = Some(section.children);
332+
333+
if let Some(parent) = active_sections.last_mut() {
334+
parent.children.push(final_symbol);
335+
} else {
336+
symbols.push(final_symbol);
337+
}
338+
}
346339

347-
Ok(store)
340+
Ok(())
348341
}
349342

350343
// Function to parse a comment and return the section level and title
@@ -374,7 +367,10 @@ mod tests {
374367
let doc = Document::new(code, None);
375368
let node = doc.ast.root_node();
376369

377-
index_node(&node, vec![], &doc.contents).unwrap()
370+
// Use the collect_symbols function directly
371+
let mut symbols = Vec::new();
372+
collect_symbols(&node, &doc.contents, 0, &mut symbols).unwrap();
373+
symbols
378374
}
379375

380376
#[test]

0 commit comments

Comments
 (0)