Skip to content

Commit 31da629

Browse files
committed
Add named functions as document symbols
1 parent 68df2ed commit 31da629

File tree

3 files changed

+194
-1
lines changed

3 files changed

+194
-1
lines changed

.github/copilot-instructions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ _below_ the calling function. A general goal is to be able to read linearly from
1919
top to bottom with the relevant context and main logic first. The code should be
2020
organised like a call stack. Of course that's not always possible, use best
2121
judgement to produce the clearest code organization.
22+
23+
Keep the main logic as unnested as possible. Favour Rust's `let ... else`
24+
syntax to return early or continue a loop in the `else` clause, over `if let`.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
source: crates/ark/src/lsp/symbols.rs
3+
expression: "test_symbol(\"\n# section ----\nlist(\n foo = function() {\n 1\n }, # matched\n function() {\n 2\n }, # not matched\n bar = function() {\n 3\n }, # matched\n baz = (function() {\n 4\n }) # not matched\n)\n\")"
4+
---
5+
[
6+
DocumentSymbol {
7+
name: "section",
8+
detail: None,
9+
kind: String,
10+
tags: None,
11+
deprecated: None,
12+
range: Range {
13+
start: Position {
14+
line: 1,
15+
character: 0,
16+
},
17+
end: Position {
18+
line: 15,
19+
character: 1,
20+
},
21+
},
22+
selection_range: Range {
23+
start: Position {
24+
line: 1,
25+
character: 0,
26+
},
27+
end: Position {
28+
line: 15,
29+
character: 1,
30+
},
31+
},
32+
children: Some(
33+
[
34+
DocumentSymbol {
35+
name: "foo",
36+
detail: Some(
37+
"function()",
38+
),
39+
kind: Method,
40+
tags: None,
41+
deprecated: None,
42+
range: Range {
43+
start: Position {
44+
line: 3,
45+
character: 10,
46+
},
47+
end: Position {
48+
line: 5,
49+
character: 5,
50+
},
51+
},
52+
selection_range: Range {
53+
start: Position {
54+
line: 3,
55+
character: 10,
56+
},
57+
end: Position {
58+
line: 5,
59+
character: 5,
60+
},
61+
},
62+
children: Some(
63+
[],
64+
),
65+
},
66+
DocumentSymbol {
67+
name: "bar",
68+
detail: Some(
69+
"function()",
70+
),
71+
kind: Method,
72+
tags: None,
73+
deprecated: None,
74+
range: Range {
75+
start: Position {
76+
line: 9,
77+
character: 10,
78+
},
79+
end: Position {
80+
line: 11,
81+
character: 5,
82+
},
83+
},
84+
selection_range: Range {
85+
start: Position {
86+
line: 9,
87+
character: 10,
88+
},
89+
end: Position {
90+
line: 11,
91+
character: 5,
92+
},
93+
},
94+
children: Some(
95+
[],
96+
),
97+
},
98+
],
99+
),
100+
},
101+
]

crates/ark/src/lsp/symbols.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,66 @@ fn collect_call(
273273

274274
match fun_symbol.as_str() {
275275
"test_that" => collect_call_test_that(node, contents, symbols)?,
276-
_ => {},
276+
_ => collect_call_methods(node, contents, symbols)?,
277+
}
278+
279+
Ok(())
280+
}
281+
282+
fn collect_call_methods(
283+
node: &Node,
284+
contents: &Rope,
285+
symbols: &mut Vec<DocumentSymbol>,
286+
) -> anyhow::Result<()> {
287+
dbg!(node);
288+
let Some(arguments) = node.child_by_field_name("arguments") else {
289+
return Ok(());
290+
};
291+
292+
let mut cursor = node.walk();
293+
for arg in arguments.children(&mut cursor) {
294+
if arg.kind() != "argument" {
295+
continue;
296+
}
297+
298+
let Some(arg_value) = arg.child_by_field_name("value") else {
299+
continue;
300+
};
301+
if arg_value.kind() != "function_definition" {
302+
continue;
303+
}
304+
305+
// Process the function body to extract child symbols.
306+
// We do this even if it's not a "method", i.e. if it's not named.
307+
let body = arg_value.child_by_field_name("body").into_result()?;
308+
let mut children = Vec::new();
309+
collect_symbols(&body, contents, 0, &mut children)?;
310+
311+
// There must be a name node, we're only collecting named functions as methods
312+
let Some(arg_name) = arg.child_by_field_name("name") else {
313+
continue;
314+
};
315+
if !arg_name.is_identifier_or_string() {
316+
continue;
317+
}
318+
let arg_name_str = contents.node_slice(&arg_name)?.to_string();
319+
320+
let start = convert_point_to_position(contents, arg_value.start_position());
321+
let end = convert_point_to_position(contents, arg_value.end_position());
322+
323+
let mut symbol = new_symbol_node(
324+
arg_name_str,
325+
SymbolKind::METHOD,
326+
Range { start, end },
327+
vec![],
328+
);
329+
330+
// Don't include whole function as detail as the body often doesn't
331+
// provide useful information and only make the outline more busy (with
332+
// curly braces, newline characters, etc).
333+
symbol.detail = Some(String::from("function()"));
334+
335+
symbols.push(symbol);
277336
}
278337

279338
Ok(())
@@ -681,7 +740,37 @@ test_that('foo', {
681740
test_that('bar', {
682741
1
683742
})
743+
"
744+
));
745+
}
746+
747+
#[test]
748+
fn test_symbol_call_methods() {
749+
insta::assert_debug_snapshot!(test_symbol(
750+
"
751+
# section ----
752+
list(
753+
foo = function() {
754+
1
755+
}, # matched
756+
function() {
757+
2
758+
}, # not matched
759+
bar = function() {
760+
3
761+
}, # matched
762+
baz = (function() {
763+
4
764+
}) # not matched
765+
)
684766
"
685767
));
686768
}
687769
}
770+
771+
// chat <- r6::r6class(
772+
// "chat",
773+
// public = list(
774+
// initialize = function() "initialize",
775+
// )
776+
// )

0 commit comments

Comments
 (0)