Skip to content

Commit 1099c3f

Browse files
committed
Add named functions as document symbols
1 parent 68df2ed commit 1099c3f

File tree

3 files changed

+193
-1
lines changed

3 files changed

+193
-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: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,65 @@ 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+
let Some(arguments) = node.child_by_field_name("arguments") else {
288+
return Ok(());
289+
};
290+
291+
let mut cursor = node.walk();
292+
for arg in arguments.children(&mut cursor) {
293+
if arg.kind() != "argument" {
294+
continue;
295+
}
296+
297+
let Some(arg_value) = arg.child_by_field_name("value") else {
298+
continue;
299+
};
300+
if arg_value.kind() != "function_definition" {
301+
continue;
302+
}
303+
304+
// Process the function body to extract child symbols.
305+
// We do this even if it's not a "method", i.e. if it's not named.
306+
let body = arg_value.child_by_field_name("body").into_result()?;
307+
let mut children = Vec::new();
308+
collect_symbols(&body, contents, 0, &mut children)?;
309+
310+
// There must be a name node, we're only collecting named functions as methods
311+
let Some(arg_name) = arg.child_by_field_name("name") else {
312+
continue;
313+
};
314+
if !arg_name.is_identifier_or_string() {
315+
continue;
316+
}
317+
let arg_name_str = contents.node_slice(&arg_name)?.to_string();
318+
319+
let start = convert_point_to_position(contents, arg_value.start_position());
320+
let end = convert_point_to_position(contents, arg_value.end_position());
321+
322+
let mut symbol = new_symbol_node(
323+
arg_name_str,
324+
SymbolKind::METHOD,
325+
Range { start, end },
326+
vec![],
327+
);
328+
329+
// Don't include whole function as detail as the body often doesn't
330+
// provide useful information and only make the outline more busy (with
331+
// curly braces, newline characters, etc).
332+
symbol.detail = Some(String::from("function()"));
333+
334+
symbols.push(symbol);
277335
}
278336

279337
Ok(())
@@ -681,7 +739,37 @@ test_that('foo', {
681739
test_that('bar', {
682740
1
683741
})
742+
"
743+
));
744+
}
745+
746+
#[test]
747+
fn test_symbol_call_methods() {
748+
insta::assert_debug_snapshot!(test_symbol(
749+
"
750+
# section ----
751+
list(
752+
foo = function() {
753+
1
754+
}, # matched
755+
function() {
756+
2
757+
}, # not matched
758+
bar = function() {
759+
3
760+
}, # matched
761+
baz = (function() {
762+
4
763+
}) # not matched
764+
)
684765
"
685766
));
686767
}
687768
}
769+
770+
// chat <- r6::r6class(
771+
// "chat",
772+
// public = list(
773+
// initialize = function() "initialize",
774+
// )
775+
// )

0 commit comments

Comments
 (0)