diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_for_statement.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_for_statement.snap new file mode 100644 index 000000000..b55ba90b9 --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_for_statement.snap @@ -0,0 +1,67 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\n for (i in 1:10) {\n # section in for ----\n x <- i\n }\n \")" +--- +[ + DocumentSymbol { + name: "section in for", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "x", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, +] diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_function_definition.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_function_definition.snap new file mode 100644 index 000000000..ce198ba24 --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_function_definition.snap @@ -0,0 +1,66 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\nfunction(\n X,\n # Section in parameters ----\n y\n) {\n # Section in body ----\n x <- 1\n}\n\")" +--- +[ + DocumentSymbol { + name: "Section in parameters", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 2, + }, + end: Position { + line: 4, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 2, + }, + end: Position { + line: 4, + character: 3, + }, + }, + children: Some( + [], + ), + }, + DocumentSymbol { + name: "Section in body", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 6, + character: 2, + }, + end: Position { + line: 7, + character: 8, + }, + }, + selection_range: Range { + start: Position { + line: 6, + character: 2, + }, + end: Position { + line: 7, + character: 8, + }, + }, + children: Some( + [], + ), + }, +] diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_if_statement.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_if_statement.snap new file mode 100644 index 000000000..7a102fce4 --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_if_statement.snap @@ -0,0 +1,128 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\n if (TRUE) {\n # section in if ----\n x <- 1\n } else {\n # section in else ----\n y <- 2\n }\n \")" +--- +[ + DocumentSymbol { + name: "section in if", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "x", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, + DocumentSymbol { + name: "section in else", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 5, + character: 14, + }, + end: Position { + line: 6, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 5, + character: 14, + }, + end: Position { + line: 6, + character: 20, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "y", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 6, + character: 14, + }, + end: Position { + line: 6, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 6, + character: 14, + }, + end: Position { + line: 6, + character: 20, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, +] diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_nested_control_flow.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_nested_control_flow.snap new file mode 100644 index 000000000..c4c4fd9ec --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_nested_control_flow.snap @@ -0,0 +1,160 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\n # top level section ----\n if (TRUE) {\n # section in if ----\n for (i in 1:10) {\n # nested section in for ----\n while (j < i) {\n # deeply nested section ----\n j <- j + 1\n }\n }\n }\n \")" +--- +[ + DocumentSymbol { + name: "top level section", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 1, + character: 12, + }, + end: Position { + line: 11, + character: 13, + }, + }, + selection_range: Range { + start: Position { + line: 1, + character: 12, + }, + end: Position { + line: 11, + character: 13, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "section in if", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 10, + character: 15, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 10, + character: 15, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "nested section in for", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 5, + character: 16, + }, + end: Position { + line: 9, + character: 17, + }, + }, + selection_range: Range { + start: Position { + line: 5, + character: 16, + }, + end: Position { + line: 9, + character: 17, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "deeply nested section", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 7, + character: 18, + }, + end: Position { + line: 8, + character: 28, + }, + }, + selection_range: Range { + start: Position { + line: 7, + character: 18, + }, + end: Position { + line: 8, + character: 28, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "j", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 8, + character: 18, + }, + end: Position { + line: 8, + character: 28, + }, + }, + selection_range: Range { + start: Position { + line: 8, + character: 18, + }, + end: Position { + line: 8, + character: 28, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, + ], + ), + }, + ], + ), + }, + ], + ), + }, +] diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_repeat_statement.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_repeat_statement.snap new file mode 100644 index 000000000..ef67ee1ed --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_repeat_statement.snap @@ -0,0 +1,97 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\n repeat {\n # section in repeat ----\n x <- i\n if (TRUE) {\n y <- i\n }\n }\n \")" +--- +[ + DocumentSymbol { + name: "section in repeat", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 6, + character: 15, + }, + }, + selection_range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 6, + character: 15, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "x", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [], + ), + }, + DocumentSymbol { + name: "y", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 5, + character: 16, + }, + end: Position { + line: 5, + character: 22, + }, + }, + selection_range: Range { + start: Position { + line: 5, + character: 16, + }, + end: Position { + line: 5, + character: 22, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, +] diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_section_in_blocks.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_section_in_blocks.snap index a397ccb0c..7943c12c8 100644 --- a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_section_in_blocks.snap +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_section_in_blocks.snap @@ -1,6 +1,6 @@ --- source: crates/ark/src/lsp/symbols.rs -expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 ## bar ----\n 3\n 4\n ## baz ----\n})\n\n## level 2 ----\n\nlist({\n # foo ----\n 1\n 2 # bar ----\n 3\n 4\n # baz ----\n})\n\")" +expression: "test_symbol(\"\n# level 1 ----\n\n{\n ## foo ----\n 1\n 2 ## bar ----\n 3\n 4\n {\n ## qux ----\n 5\n }\n ## baz ----\n}\n\nlist({\n ## foo ----\n 1\n 2 ## bar ----\n 3\n 4\n {\n ## qux ----\n 5\n }\n ## baz ----\n})\n\n## level 2 ----\n\n{\n # foo ----\n 1\n 2 # bar ----\n 3\n 4\n {\n # qux ----\n 5\n }\n # baz ----\n}\n\nlist({\n # foo ----\n 1\n 2 # bar ----\n 3\n 4\n {\n # qux ----\n 5\n }\n # baz ----\n})\n\")" --- [ DocumentSymbol { @@ -15,7 +15,7 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # character: 0, }, end: Position { - line: 21, + line: 55, character: 2, }, }, @@ -25,7 +25,7 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # character: 0, }, end: Position { - line: 21, + line: 55, character: 2, }, }, @@ -73,7 +73,7 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # character: 4, }, end: Position { - line: 8, + line: 12, character: 3, }, }, @@ -83,14 +83,166 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # character: 4, }, end: Position { - line: 8, + line: 12, character: 3, }, }, + children: Some( + [ + DocumentSymbol { + name: "qux", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 10, + character: 4, + }, + end: Position { + line: 11, + character: 5, + }, + }, + selection_range: Range { + start: Position { + line: 10, + character: 4, + }, + end: Position { + line: 11, + character: 5, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, + DocumentSymbol { + name: "baz", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 13, + character: 2, + }, + end: Position { + line: 13, + character: 13, + }, + }, + selection_range: Range { + start: Position { + line: 13, + character: 2, + }, + end: Position { + line: 13, + character: 13, + }, + }, children: Some( [], ), }, + DocumentSymbol { + name: "foo", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 17, + character: 2, + }, + end: Position { + line: 18, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 17, + character: 2, + }, + end: Position { + line: 18, + character: 3, + }, + }, + children: Some( + [], + ), + }, + DocumentSymbol { + name: "bar", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 19, + character: 4, + }, + end: Position { + line: 25, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 19, + character: 4, + }, + end: Position { + line: 25, + character: 3, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "qux", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 23, + character: 4, + }, + end: Position { + line: 24, + character: 5, + }, + }, + selection_range: Range { + start: Position { + line: 23, + character: 4, + }, + end: Position { + line: 24, + character: 5, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, DocumentSymbol { name: "baz", detail: None, @@ -99,21 +251,21 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # deprecated: None, range: Range { start: Position { - line: 9, + line: 26, character: 2, }, end: Position { - line: 9, + line: 26, character: 13, }, }, selection_range: Range { start: Position { - line: 9, + line: 26, character: 2, }, end: Position { - line: 9, + line: 26, character: 13, }, }, @@ -129,21 +281,21 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # deprecated: None, range: Range { start: Position { - line: 12, + line: 29, character: 0, }, end: Position { - line: 21, + line: 55, character: 2, }, }, selection_range: Range { start: Position { - line: 12, + line: 29, character: 0, }, end: Position { - line: 21, + line: 55, character: 2, }, }, @@ -157,21 +309,21 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # deprecated: None, range: Range { start: Position { - line: 15, + line: 32, character: 2, }, end: Position { - line: 16, + line: 33, character: 3, }, }, selection_range: Range { start: Position { - line: 15, + line: 32, character: 2, }, end: Position { - line: 16, + line: 33, character: 3, }, }, @@ -187,28 +339,180 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # deprecated: None, range: Range { start: Position { - line: 17, + line: 34, character: 4, }, end: Position { - line: 19, + line: 40, character: 3, }, }, selection_range: Range { start: Position { - line: 17, + line: 34, character: 4, }, end: Position { - line: 19, + line: 40, character: 3, }, }, + children: Some( + [ + DocumentSymbol { + name: "qux", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 38, + character: 4, + }, + end: Position { + line: 39, + character: 5, + }, + }, + selection_range: Range { + start: Position { + line: 38, + character: 4, + }, + end: Position { + line: 39, + character: 5, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, + DocumentSymbol { + name: "baz", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 41, + character: 2, + }, + end: Position { + line: 41, + character: 12, + }, + }, + selection_range: Range { + start: Position { + line: 41, + character: 2, + }, + end: Position { + line: 41, + character: 12, + }, + }, children: Some( [], ), }, + DocumentSymbol { + name: "foo", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 45, + character: 2, + }, + end: Position { + line: 46, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 45, + character: 2, + }, + end: Position { + line: 46, + character: 3, + }, + }, + children: Some( + [], + ), + }, + DocumentSymbol { + name: "bar", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 47, + character: 4, + }, + end: Position { + line: 53, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 47, + character: 4, + }, + end: Position { + line: 53, + character: 3, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "qux", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 51, + character: 4, + }, + end: Position { + line: 52, + character: 5, + }, + }, + selection_range: Range { + start: Position { + line: 51, + character: 4, + }, + end: Position { + line: 52, + character: 5, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, DocumentSymbol { name: "baz", detail: None, @@ -217,21 +521,21 @@ expression: "test_symbol(\"\n# level 1 ----\n\nlist({\n ## foo ----\n 1\n 2 # deprecated: None, range: Range { start: Position { - line: 20, + line: 54, character: 2, }, end: Position { - line: 20, + line: 54, character: 12, }, }, selection_range: Range { start: Position { - line: 20, + line: 54, character: 2, }, end: Position { - line: 20, + line: 54, character: 12, }, }, diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_while_statement.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_while_statement.snap new file mode 100644 index 000000000..bc4162004 --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_while_statement.snap @@ -0,0 +1,67 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\n while (i < 10) {\n # section in while ----\n x <- i\n }\n \")" +--- +[ + DocumentSymbol { + name: "section in while", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 2, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "x", + detail: None, + kind: Variable, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + selection_range: Range { + start: Position { + line: 3, + character: 14, + }, + end: Position { + line: 3, + character: 20, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, +] diff --git a/crates/ark/src/lsp/symbols.rs b/crates/ark/src/lsp/symbols.rs index f374f3347..e1f72d62e 100644 --- a/crates/ark/src/lsp/symbols.rs +++ b/crates/ark/src/lsp/symbols.rs @@ -173,7 +173,7 @@ pub(crate) fn document_symbols( ctx.include_assignments_in_blocks = state.config.symbols.include_assignments_in_blocks; // Extract and process all symbols from the AST - if let Err(err) = collect_symbols(&mut ctx, &root_node, contents, 0, &mut result) { + if let Err(err) = collect_symbols(&mut ctx, &root_node, contents, &mut result) { log::error!("Failed to collect symbols: {err:?}"); return Ok(Vec::new()); } @@ -186,23 +186,25 @@ fn collect_symbols( ctx: &mut CollectContext, node: &Node, contents: &Rope, - current_level: usize, symbols: &mut Vec, ) -> anyhow::Result<()> { match node.node_type() { NodeType::Program => { - collect_list_sections(ctx, node, contents, current_level, symbols)?; + collect_list_sections(ctx, node, contents, symbols)?; }, NodeType::BracedExpression => { - let old = ctx.top_level; - ctx.top_level = false; - collect_list_sections(ctx, node, contents, current_level, symbols)?; - ctx.top_level = old; + collect_list_sections(ctx, node, contents, symbols)?; }, - NodeType::Call => { - collect_call(ctx, node, contents, symbols)?; + NodeType::IfStatement => { + // An `if` statement doesn't reset top-level, e.g.: + // if (TRUE) { + // x <- top_level_assignment + // } else { + // x <- top_level_assignment + // } + collect_if_statement(ctx, node, contents, symbols)?; }, NodeType::BinaryOperator(BinaryOperatorType::LeftAssignment) | @@ -210,9 +212,119 @@ fn collect_symbols( collect_assignment(ctx, node, contents, symbols)?; }, + NodeType::ForStatement => { + collect_for_statement(ctx, node, contents, symbols)?; + }, + + NodeType::WhileStatement => { + collect_while_statement(ctx, node, contents, symbols)?; + }, + + NodeType::RepeatStatement => { + collect_repeat_statement(ctx, node, contents, symbols)?; + }, + + NodeType::Call => { + let old = ctx.top_level; + ctx.top_level = false; + collect_call(ctx, node, contents, symbols)?; + ctx.top_level = old; + }, + + NodeType::FunctionDefinition => { + let old = ctx.top_level; + ctx.top_level = false; + collect_function(ctx, node, contents, symbols)?; + ctx.top_level = old; + }, + // For all other node types, no symbols need to be added _ => {}, } + + Ok(()) +} + +fn collect_if_statement( + ctx: &mut CollectContext, + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + if let Some(condition) = node.child_by_field_name("condition") { + collect_symbols(ctx, &condition, contents, symbols)?; + } + if let Some(consequent) = node.child_by_field_name("consequence") { + collect_symbols(ctx, &consequent, contents, symbols)?; + } + if let Some(alternative) = node.child_by_field_name("alternative") { + collect_symbols(ctx, &alternative, contents, symbols)?; + } + + Ok(()) +} + +fn collect_for_statement( + ctx: &mut CollectContext, + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + if let Some(variable) = node.child_by_field_name("variable") { + collect_symbols(ctx, &variable, contents, symbols)?; + } + if let Some(iterator) = node.child_by_field_name("iterator") { + collect_symbols(ctx, &iterator, contents, symbols)?; + } + if let Some(body) = node.child_by_field_name("body") { + collect_symbols(ctx, &body, contents, symbols)?; + } + + Ok(()) +} + +fn collect_while_statement( + ctx: &mut CollectContext, + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + if let Some(condition) = node.child_by_field_name("condition") { + collect_symbols(ctx, &condition, contents, symbols)?; + } + if let Some(body) = node.child_by_field_name("body") { + collect_symbols(ctx, &body, contents, symbols)?; + } + + Ok(()) +} + +fn collect_repeat_statement( + ctx: &mut CollectContext, + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + if let Some(body) = node.child_by_field_name("body") { + collect_symbols(ctx, &body, contents, symbols)?; + } + + Ok(()) +} + +fn collect_function( + ctx: &mut CollectContext, + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + if let Some(parameters) = node.child_by_field_name("parameters") { + collect_function_parameters(ctx, ¶meters, contents, symbols)?; + } + if let Some(body) = node.child_by_field_name("body") { + collect_symbols(ctx, &body, contents, symbols)?; + } + Ok(()) } @@ -265,7 +377,6 @@ fn collect_sections( ctx: &mut CollectContext, node: &Node, contents: &Rope, - current_level: usize, symbols: &mut Vec, mut handle_child: F, ) -> anyhow::Result<()> @@ -286,11 +397,8 @@ where // If we have a section comment, add it to our stack and close any sections if needed if let Some((level, title)) = parse_comment_as_section(&comment_text) { - let absolute_level = current_level + level; - // Close any sections with equal or higher level - while !active_sections.is_empty() && - active_sections.last().unwrap().level >= absolute_level + while !active_sections.is_empty() && active_sections.last().unwrap().level >= level { // Set end position for the section being closed if let Some(section) = active_sections.last_mut() { @@ -302,7 +410,7 @@ where let section = Section { title, - level: absolute_level, + level, start_position: child.start_position(), end_position: None, children: Vec::new(), @@ -355,18 +463,14 @@ fn collect_list_sections( ctx: &mut CollectContext, node: &Node, contents: &Rope, - current_level: usize, symbols: &mut Vec, ) -> anyhow::Result<()> { collect_sections( ctx, node, contents, - current_level, symbols, - |ctx, child, contents, symbols| { - collect_symbols(ctx, child, contents, current_level, symbols) - }, + |ctx, child, contents, symbols| collect_symbols(ctx, child, contents, symbols), ) } @@ -407,33 +511,46 @@ fn collect_call_arguments( ctx, &arguments, contents, - 0, symbols, |ctx, child, contents, symbols| { let Some(arg_value) = child.child_by_field_name("value") else { return Ok(()); }; - // Recurse into arguments. They might be a braced list, another call - // that might contain functions, etc. - collect_symbols(ctx, &arg_value, contents, 0, symbols)?; - + // If this is a named function, collect it as a method (new node in the tree) if arg_value.kind() == "function_definition" { if let Some(arg_fun) = child.child_by_field_name("name") { - // If this is a named function, collect it as a method collect_method(ctx, &arg_fun, &arg_value, contents, symbols)?; - } else { - // Otherwise, just recurse into the function - let body = arg_value.child_by_field_name("body").into_result()?; - collect_symbols(ctx, &body, contents, 0, symbols)?; + return Ok(()); }; + // else fallthrough } + collect_symbols(ctx, &arg_value, contents, symbols)?; + Ok(()) }, ) } +fn collect_function_parameters( + ctx: &mut CollectContext, + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + collect_sections( + ctx, + &node, + contents, + symbols, + |_ctx, _child, _contents, _symbols| { + // We only collect sections and don't recurse inside parameters + return Ok(()); + }, + ) +} + fn collect_method( ctx: &mut CollectContext, arg_fun: &Node, @@ -449,9 +566,8 @@ fn collect_method( let start = convert_point_to_position(contents, arg_value.start_position()); let end = convert_point_to_position(contents, arg_value.end_position()); - let body = arg_value.child_by_field_name("body").into_result()?; let mut children = vec![]; - collect_symbols(ctx, &body, contents, 0, &mut children)?; + collect_symbols(ctx, arg_value, contents, &mut children)?; let mut symbol = new_symbol_node( arg_name_str, @@ -499,7 +615,7 @@ fn collect_call_test_that( let mut cursor = arguments.walk(); for child in arguments.children_by_field_name("argument", &mut cursor) { if let Some(value) = child.child_by_field_name("value") { - collect_symbols(ctx, &value, contents, 0, &mut children)?; + collect_symbols(ctx, &value, contents, &mut children)?; } } @@ -551,13 +667,13 @@ fn collect_assignment( // Now recurse into RHS let mut children = Vec::new(); - collect_symbols(ctx, &rhs, contents, 0, &mut children)?; + collect_symbols(ctx, &rhs, contents, &mut children)?; let symbol = new_symbol_node(name, SymbolKind::VARIABLE, Range { start, end }, children); symbols.push(symbol); } else { // Recurse into RHS - collect_symbols(ctx, &rhs, contents, 0, symbols)?; + collect_symbols(ctx, &rhs, contents, symbols)?; } Ok(()) @@ -592,11 +708,9 @@ fn collect_assignment_with_function( end: convert_point_to_position(contents, rhs.end_position()), }; - let body = rhs.child_by_field_name("body").into_result()?; - // Process the function body to extract child symbols let mut children = Vec::new(); - collect_symbols(ctx, &body, contents, 0, &mut children)?; + collect_symbols(ctx, &rhs, contents, &mut children)?; let mut symbol = new_symbol_node(name, SymbolKind::FUNCTION, range, children); symbol.detail = Some(detail); @@ -671,13 +785,85 @@ mod tests { &mut CollectContext::new(), &node, &doc.contents, - 0, &mut symbols, ) .unwrap(); symbols } + #[test] + fn test_symbol_if_statement() { + insta::assert_debug_snapshot!(test_symbol( + " + if (TRUE) { + # section in if ---- + x <- 1 + } else { + # section in else ---- + y <- 2 + } + " + )); + } + + #[test] + fn test_symbol_for_statement() { + insta::assert_debug_snapshot!(test_symbol( + " + for (i in 1:10) { + # section in for ---- + x <- i + } + " + )); + } + + #[test] + fn test_symbol_while_statement() { + insta::assert_debug_snapshot!(test_symbol( + " + while (i < 10) { + # section in while ---- + x <- i + } + " + )); + } + + #[test] + fn test_symbol_repeat_statement() { + insta::assert_debug_snapshot!(test_symbol( + " + repeat { + # section in repeat ---- + x <- i + if (TRUE) { + y <- i + } + } + " + )); + } + + #[test] + fn test_symbol_nested_control_flow() { + insta::assert_debug_snapshot!(test_symbol( + " + # top level section ---- + if (TRUE) { + # section in if ---- + for (i in 1:10) { + # nested section in for ---- + while (j < i) { + # deeply nested section ---- + j <- j + 1 + } + } + } + " + )); + } + #[test] fn test_symbol_parse_comment_as_section() { assert_eq!(parse_comment_as_section("# foo"), None); @@ -945,7 +1131,7 @@ a <- function() { outer <- 4 " )); - assert_eq!(test_symbol("{ foo <- 1 }"), vec![]); + assert_eq!(test_symbol("call({ foo <- 1 })"), vec![]); } #[test] @@ -969,7 +1155,7 @@ outer <- 4 ctx.include_assignments_in_blocks = true; let mut symbols = Vec::new(); - collect_symbols(ctx, &node, &doc.contents, 0, &mut symbols).unwrap(); + collect_symbols(ctx, &node, &doc.contents, &mut symbols).unwrap(); insta::assert_debug_snapshot!(symbols); } @@ -1019,23 +1205,57 @@ outer <- 4 " # level 1 ---- +{ + ## foo ---- + 1 + 2 ## bar ---- + 3 + 4 + { + ## qux ---- + 5 + } + ## baz ---- +} + list({ ## foo ---- 1 2 ## bar ---- 3 4 + { + ## qux ---- + 5 + } ## baz ---- }) ## level 2 ---- +{ + # foo ---- + 1 + 2 # bar ---- + 3 + 4 + { + # qux ---- + 5 + } + # baz ---- +} + list({ # foo ---- 1 2 # bar ---- 3 4 + { + # qux ---- + 5 + } # baz ---- }) " @@ -1067,6 +1287,22 @@ list( 4 # baz ---- ) +" + )); + } + + #[test] + fn test_symbol_function_definition() { + insta::assert_debug_snapshot!(test_symbol( + " +function( + X, + # Section in parameters ---- + y +) { + # Section in body ---- + x <- 1 +} " )); }