Skip to content

Commit 30f4cd8

Browse files
committed
Refactor to use mutation instead of passing stacks around
1 parent 966388c commit 30f4cd8

File tree

1 file changed

+129
-135
lines changed

1 file changed

+129
-135
lines changed

crates/ark/src/lsp/symbols.rs

Lines changed: 129 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,123 @@ 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+
149157
NodeType::BinaryOperator(BinaryOperatorType::LeftAssignment) |
150158
NodeType::BinaryOperator(BinaryOperatorType::EqualsAssignment) => {
151-
index_assignment(&node, store, contents)?
159+
collect_assignment(node, contents, symbols)?;
152160
},
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(())
158167
}
159168

160-
// Handles root node and braced lists
161-
fn index_expression_list(
169+
fn collect_sections(
162170
node: &Node,
163-
store: Vec<DocumentSymbol>,
164171
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+
166178
let mut cursor = node.walk();
167179

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

175183
for child in node.children(&mut cursor) {
176184
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+
}
191210

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;
197212
}
198-
}
199213

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);
262232
}
263-
continue;
264233
}
234+
}
265235

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

269-
store_stack.push((level, Some(symbol), vec![]));
270-
Ok(store_stack)
241+
Ok(())
271242
}
272243

273-
fn index_assignment(
244+
fn collect_assignment(
274245
node: &Node,
275-
mut store: Vec<DocumentSymbol>,
276246
contents: &Rope,
277-
) -> anyhow::Result<Vec<DocumentSymbol>> {
247+
symbols: &mut Vec<DocumentSymbol>,
248+
) -> anyhow::Result<()> {
278249
// Check for assignment
279250
matches!(
280251
node.node_type(),
@@ -291,7 +262,7 @@ fn index_assignment(
291262
let function = lhs.is_identifier_or_string() && rhs.is_function_definition();
292263

293264
if function {
294-
return index_assignment_with_function(node, store, contents);
265+
return collect_assignment_with_function(node, contents, symbols);
295266
}
296267

297268
// otherwise, just index as generic object
@@ -301,16 +272,16 @@ fn index_assignment(
301272
let end = convert_point_to_position(contents, lhs.end_position());
302273

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

306-
Ok(store)
277+
Ok(())
307278
}
308279

309-
fn index_assignment_with_function(
280+
fn collect_assignment_with_function(
310281
node: &Node,
311-
mut store: Vec<DocumentSymbol>,
312282
contents: &Rope,
313-
) -> anyhow::Result<Vec<DocumentSymbol>> {
283+
symbols: &mut Vec<DocumentSymbol>,
284+
) -> anyhow::Result<()> {
314285
// check for lhs, rhs
315286
let lhs = node.child_by_field_name("lhs").into_result()?;
316287
let rhs = node.child_by_field_name("rhs").into_result()?;
@@ -336,15 +307,36 @@ fn index_assignment_with_function(
336307

337308
let body = rhs.child_by_field_name("body").into_result()?;
338309

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)?;
342313

343314
let mut symbol = new_symbol_node(name, SymbolKind::FUNCTION, range, children);
344315
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+
}
346338

347-
Ok(store)
339+
Ok(())
348340
}
349341

350342
// Function to parse a comment and return the section level and title
@@ -374,7 +366,9 @@ mod tests {
374366
let doc = Document::new(code, None);
375367
let node = doc.ast.root_node();
376368

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
378372
}
379373

380374
#[test]

0 commit comments

Comments
 (0)