logicaffeine_lsp/
call_hierarchy.rs1use tower_lsp::lsp_types::{
7 CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, Position, Range,
8 SymbolKind, Url,
9};
10
11use crate::document::DocumentState;
12use crate::index::DefinitionKind;
13
14pub fn prepare(
15 doc: &DocumentState,
16 position: Position,
17 uri: &Url,
18) -> Option<Vec<CallHierarchyItem>> {
19 let offset = doc.line_index.offset(position);
20 let def_idx = doc
21 .symbol_index
22 .references
23 .iter()
24 .find(|r| r.span.start <= offset && offset < r.span.end)
25 .and_then(|r| r.definition_idx)
26 .or_else(|| {
27 doc.symbol_index
28 .definitions
29 .iter()
30 .position(|d| d.span.start <= offset && offset < d.span.end)
31 })?;
32 if doc.symbol_index.definitions[def_idx].kind != DefinitionKind::Function {
33 return None;
34 }
35 Some(vec![item_for(doc, def_idx, uri)])
36}
37
38pub fn incoming_calls(
39 doc: &DocumentState,
40 item: &CallHierarchyItem,
41 uri: &Url,
42) -> Vec<CallHierarchyIncomingCall> {
43 let Some(callee) = function_index_by_name(doc, &item.name) else {
44 return Vec::new();
45 };
46
47 let mut callers: Vec<(usize, Vec<Range>)> = Vec::new();
49 for site in &doc.symbol_index.call_sites {
50 if site.callee != callee {
51 continue;
52 }
53 let Some(caller) = site.caller else { continue };
54 let range = to_range(doc, site.span);
55 match callers.iter_mut().find(|(ix, _)| *ix == caller) {
56 Some((_, ranges)) => ranges.push(range),
57 None => callers.push((caller, vec![range])),
58 }
59 }
60
61 callers
62 .into_iter()
63 .map(|(caller, from_ranges)| CallHierarchyIncomingCall {
64 from: item_for(doc, caller, uri),
65 from_ranges,
66 })
67 .collect()
68}
69
70pub fn outgoing_calls(
71 doc: &DocumentState,
72 item: &CallHierarchyItem,
73 uri: &Url,
74) -> Vec<CallHierarchyOutgoingCall> {
75 let Some(caller) = function_index_by_name(doc, &item.name) else {
76 return Vec::new();
77 };
78
79 let mut callees: Vec<(usize, Vec<Range>)> = Vec::new();
80 for site in &doc.symbol_index.call_sites {
81 if site.caller != Some(caller) {
82 continue;
83 }
84 let range = to_range(doc, site.span);
85 match callees.iter_mut().find(|(ix, _)| *ix == site.callee) {
86 Some((_, ranges)) => ranges.push(range),
87 None => callees.push((site.callee, vec![range])),
88 }
89 }
90
91 callees
92 .into_iter()
93 .map(|(callee, from_ranges)| CallHierarchyOutgoingCall {
94 to: item_for(doc, callee, uri),
95 from_ranges,
96 })
97 .collect()
98}
99
100fn function_index_by_name(doc: &DocumentState, name: &str) -> Option<usize> {
101 doc.symbol_index.name_to_defs.get(name)?.iter().copied().find(|&ix| {
102 doc.symbol_index.definitions[ix].kind == DefinitionKind::Function
103 })
104}
105
106fn item_for(doc: &DocumentState, def_idx: usize, uri: &Url) -> CallHierarchyItem {
107 let def = &doc.symbol_index.definitions[def_idx];
108 let range = to_range(doc, def.span);
109 CallHierarchyItem {
110 name: def.name.clone(),
111 kind: SymbolKind::FUNCTION,
112 tags: None,
113 detail: def.detail.clone(),
114 uri: uri.clone(),
115 range,
116 selection_range: range,
117 data: None,
118 }
119}
120
121fn to_range(doc: &DocumentState, span: logicaffeine_language::token::Span) -> Range {
122 Range {
123 start: doc.line_index.position(span.start),
124 end: doc.line_index.position(span.end),
125 }
126}