diff --git a/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_call_test_that.snap b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_call_test_that.snap new file mode 100644 index 000000000..d177e781c --- /dev/null +++ b/crates/ark/src/lsp/snapshots/ark__lsp__symbols__tests__symbol_call_test_that.snap @@ -0,0 +1,222 @@ +--- +source: crates/ark/src/lsp/symbols.rs +expression: "test_symbol(\"\ntest_that_not('foo', {\n 1\n})\n\n# title ----\n\ntest_that('foo', {\n # title1 ----\n 1\n # title2 ----\n foo <- function() {\n 2\n }\n})\n\n# title2 ----\ntest_that('bar', {\n 1\n})\n\")" +--- +[ + DocumentSymbol { + name: "title", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 15, + character: 0, + }, + }, + selection_range: Range { + start: Position { + line: 5, + character: 0, + }, + end: Position { + line: 15, + character: 0, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "Test: foo", + detail: None, + kind: Function, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 7, + character: 0, + }, + end: Position { + line: 14, + character: 2, + }, + }, + selection_range: Range { + start: Position { + line: 7, + character: 0, + }, + end: Position { + line: 14, + character: 2, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "title1", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 8, + character: 2, + }, + end: Position { + line: 9, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 8, + character: 2, + }, + end: Position { + line: 9, + character: 3, + }, + }, + children: Some( + [], + ), + }, + DocumentSymbol { + name: "title2", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 10, + character: 2, + }, + end: Position { + line: 13, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 10, + character: 2, + }, + end: Position { + line: 13, + character: 3, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "foo", + detail: Some( + "function()", + ), + kind: Function, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 11, + character: 2, + }, + end: Position { + line: 13, + character: 3, + }, + }, + selection_range: Range { + start: Position { + line: 11, + character: 2, + }, + end: Position { + line: 13, + character: 3, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, + ], + ), + }, + ], + ), + }, + DocumentSymbol { + name: "title2", + detail: None, + kind: String, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 16, + character: 0, + }, + end: Position { + line: 19, + character: 2, + }, + }, + selection_range: Range { + start: Position { + line: 16, + character: 0, + }, + end: Position { + line: 19, + character: 2, + }, + }, + children: Some( + [ + DocumentSymbol { + name: "Test: bar", + detail: None, + kind: Function, + tags: None, + deprecated: None, + range: Range { + start: Position { + line: 17, + character: 0, + }, + end: Position { + line: 19, + character: 2, + }, + }, + selection_range: Range { + start: Position { + line: 17, + character: 0, + }, + end: Position { + line: 19, + character: 2, + }, + }, + children: Some( + [], + ), + }, + ], + ), + }, +] diff --git a/crates/ark/src/lsp/symbols.rs b/crates/ark/src/lsp/symbols.rs index 26e23ab19..5ecb76391 100644 --- a/crates/ark/src/lsp/symbols.rs +++ b/crates/ark/src/lsp/symbols.rs @@ -156,6 +156,10 @@ fn collect_symbols( collect_sections(node, contents, current_level, symbols)?; }, + NodeType::Call => { + collect_call(node, contents, symbols)?; + }, + NodeType::BinaryOperator(BinaryOperatorType::LeftAssignment) | NodeType::BinaryOperator(BinaryOperatorType::EqualsAssignment) => { collect_assignment(node, contents, symbols)?; @@ -253,6 +257,72 @@ fn collect_sections( Ok(()) } +fn collect_call( + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + let Some(callee) = node.child_by_field_name("function") else { + return Ok(()); + }; + if !callee.is_identifier() { + return Ok(()); + } + + let fun_symbol = contents.node_slice(&callee)?.to_string(); + + match fun_symbol.as_str() { + "test_that" => collect_call_test_that(node, contents, symbols)?, + _ => {}, + } + + Ok(()) +} + +// https://github.com/posit-dev/positron/issues/1428 +fn collect_call_test_that( + node: &Node, + contents: &Rope, + symbols: &mut Vec, +) -> anyhow::Result<()> { + let Some(arguments) = node.child_by_field_name("arguments") else { + return Ok(()); + }; + + // We don't do any argument matching and just consider the first argument if + // a string. First skip over `(`. + let Some(first_argument) = arguments.child(1).and_then(|n| n.child(0)) else { + return Ok(()); + }; + if !first_argument.is_string() { + return Ok(()); + } + + let Some(string) = first_argument.child_by_field_name("content") else { + return Ok(()); + }; + + // Recurse in arguments. We could skip the first one if we wanted. + let mut children = Vec::new(); + 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(&value, contents, 0, &mut children)?; + } + } + + let name = contents.node_slice(&string)?.to_string(); + let name = format!("Test: {name}"); + + let start = convert_point_to_position(contents, node.start_position()); + let end = convert_point_to_position(contents, node.end_position()); + + let symbol = new_symbol_node(name, SymbolKind::FUNCTION, Range { start, end }, children); + symbols.push(symbol); + + Ok(()) +} + fn collect_assignment( node: &Node, contents: &Rope, @@ -587,4 +657,31 @@ z <- 3", assert_eq!(section_b.range.start.line, 4); assert_eq!(section_b.range.end.line, 5); // End of function body } + + #[test] + fn test_symbol_call_test_that() { + insta::assert_debug_snapshot!(test_symbol( + " +test_that_not('foo', { + 1 +}) + +# title ---- + +test_that('foo', { + # title1 ---- + 1 + # title2 ---- + foo <- function() { + 2 + } +}) + +# title2 ---- +test_that('bar', { + 1 +}) +" + )); + } }