Skip to content

Commit 2acfd42

Browse files
committed
support for call hierarchy
Close #353
1 parent 117e344 commit 2acfd42

File tree

5 files changed

+365
-0
lines changed

5 files changed

+365
-0
lines changed

crates/emmylua_code_analysis/src/semantic/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub use cache::{CacheEntry, CacheKey, CacheOptions, LuaAnalysisPhase, LuaInferCa
1616
use emmylua_parser::{LuaCallExpr, LuaChunk, LuaExpr, LuaSyntaxNode, LuaSyntaxToken, LuaTableExpr};
1717
use infer::{infer_left_value_type_from_right_value, infer_multi_value_adjusted_expression_types};
1818
pub use infer::{infer_table_field_value_should_be, infer_table_should_be};
19+
use lsp_types::Uri;
1920
pub use member::infer_member_map;
2021
use member::infer_members;
2122
pub use member::LuaMemberInfo;
@@ -80,6 +81,11 @@ impl<'a> SemanticModel<'a> {
8081
self.db.get_vfs().get_document(&file_id)
8182
}
8283

84+
pub fn get_document_by_uri(&self, uri: &Uri) -> Option<LuaDocument> {
85+
let file_id = self.db.get_vfs().get_file_id(&uri)?;
86+
self.db.get_vfs().get_document(&file_id)
87+
}
88+
8389
pub fn get_root_by_file_id(&self, file_id: FileId) -> Option<LuaChunk> {
8490
Some(
8591
self.db
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use emmylua_code_analysis::{
2+
DbIndex, FileId, LuaCompilation, LuaDeclId, LuaMemberId, LuaSemanticDeclId, LuaTypeOwner,
3+
SemanticModel,
4+
};
5+
use emmylua_parser::{
6+
LuaAst, LuaAstNode, LuaAstToken, LuaBlock, LuaGeneralToken, LuaStat, LuaTokenKind, LuaVarExpr,
7+
PathTrait,
8+
};
9+
use lsp_types::{CallHierarchyIncomingCall, CallHierarchyItem, Location, SymbolKind};
10+
use rowan::TokenAtOffset;
11+
use serde::{Deserialize, Serialize};
12+
13+
use crate::handlers::references::{search_decl_references, search_member_references};
14+
15+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
16+
pub struct CallHierarchyItemData {
17+
pub semantic_decl: LuaSemanticDeclId,
18+
pub file_id: FileId,
19+
}
20+
21+
pub fn build_call_hierarchy_item(
22+
semantic_model: &SemanticModel,
23+
semantic_decl: LuaSemanticDeclId,
24+
) -> Option<CallHierarchyItem> {
25+
let db = semantic_model.get_db();
26+
let file_id = semantic_model.get_file_id();
27+
let data = CallHierarchyItemData {
28+
semantic_decl: semantic_decl.clone(),
29+
file_id,
30+
};
31+
match semantic_decl {
32+
LuaSemanticDeclId::LuaDecl(decl_id) => {
33+
let decl = db.get_decl_index().get_decl(&decl_id)?;
34+
let range = decl.get_range();
35+
let file_id = decl.get_file_id();
36+
let document = semantic_model.get_document_by_file_id(file_id)?;
37+
let uri = document.get_uri();
38+
let name = decl.get_name().to_string();
39+
let lsp_range = document.to_lsp_range(range)?;
40+
41+
Some(CallHierarchyItem {
42+
name,
43+
kind: get_kind(db, decl_id.into()),
44+
tags: None,
45+
detail: None,
46+
uri,
47+
range: lsp_range,
48+
selection_range: lsp_range,
49+
data: Some(serde_json::to_value(data).ok()?),
50+
})
51+
}
52+
LuaSemanticDeclId::Member(member_id) => {
53+
let member = db.get_member_index().get_member(&member_id)?;
54+
let range = member.get_range();
55+
let file_id = member.get_file_id();
56+
let document = semantic_model.get_document_by_file_id(file_id)?;
57+
let uri = document.get_uri();
58+
let name = member.get_key().get_name()?.to_string();
59+
let lsp_range = document.to_lsp_range(range)?;
60+
61+
Some(CallHierarchyItem {
62+
name,
63+
kind: get_kind(db, member_id.into()),
64+
tags: None,
65+
detail: None,
66+
uri,
67+
range: lsp_range,
68+
selection_range: lsp_range,
69+
data: Some(serde_json::to_value(data).ok()?),
70+
})
71+
}
72+
_ => None,
73+
}
74+
}
75+
76+
fn get_kind(db: &DbIndex, type_owner: LuaTypeOwner) -> SymbolKind {
77+
let type_cache = db.get_type_index().get_type_cache(&type_owner);
78+
match type_cache {
79+
Some(typ) => {
80+
if typ.is_function() {
81+
return SymbolKind::FUNCTION;
82+
} else if typ.is_ref() || typ.is_def() {
83+
return SymbolKind::CLASS;
84+
} else if typ.is_const() {
85+
return SymbolKind::CONSTANT;
86+
} else {
87+
return SymbolKind::VARIABLE;
88+
}
89+
}
90+
None => SymbolKind::VARIABLE,
91+
}
92+
}
93+
94+
pub fn build_incoming_hierarchy(
95+
semantic_model: &SemanticModel,
96+
compilation: &LuaCompilation,
97+
semantic_decl: LuaSemanticDeclId,
98+
) -> Option<Vec<CallHierarchyIncomingCall>> {
99+
let mut result = vec![];
100+
let mut locations = vec![];
101+
match semantic_decl {
102+
LuaSemanticDeclId::LuaDecl(decl_id) => {
103+
search_decl_references(semantic_model, decl_id, &mut locations);
104+
}
105+
LuaSemanticDeclId::Member(member_id) => {
106+
search_member_references(semantic_model, compilation, member_id, &mut locations);
107+
}
108+
_ => return None,
109+
}
110+
111+
for location in locations {
112+
build_incoming_hierarchy_item(compilation, &location, &mut result);
113+
}
114+
115+
Some(result)
116+
}
117+
118+
fn build_incoming_hierarchy_item(
119+
compilation: &LuaCompilation,
120+
location: &Location,
121+
result: &mut Vec<CallHierarchyIncomingCall>,
122+
) -> Option<()> {
123+
let db = compilation.get_db();
124+
let uri = location.uri.clone();
125+
let range = location.range;
126+
let file_id = db.get_vfs().get_file_id(&uri)?;
127+
let tree = db.get_vfs().get_syntax_tree(&file_id)?;
128+
let root_chunk = tree.get_chunk_node();
129+
let document = db.get_vfs().get_document(&file_id)?;
130+
let pos = document.get_offset(range.start.line as usize, range.start.character as usize)?;
131+
let token = match root_chunk.syntax().token_at_offset(pos) {
132+
TokenAtOffset::Single(token) => token,
133+
TokenAtOffset::Between(left, right) => {
134+
if left.kind() == LuaTokenKind::TkName.into() {
135+
left
136+
} else {
137+
right
138+
}
139+
}
140+
TokenAtOffset::None => {
141+
return None;
142+
}
143+
};
144+
145+
let general_token = LuaGeneralToken::cast(token)?;
146+
let blocks = general_token.ancestors::<LuaBlock>();
147+
for block in blocks {
148+
let block_parent = block.get_parent::<LuaAst>()?;
149+
match block_parent {
150+
LuaAst::LuaChunk(_) => {
151+
let item = CallHierarchyItem {
152+
name: document.get_file_name()?,
153+
kind: SymbolKind::MODULE,
154+
tags: None,
155+
detail: None,
156+
uri: uri.clone(),
157+
range: document.get_document_lsp_range(),
158+
selection_range: document.get_document_lsp_range(),
159+
data: None,
160+
};
161+
162+
result.push(CallHierarchyIncomingCall {
163+
from: item,
164+
from_ranges: vec![range],
165+
});
166+
}
167+
LuaAst::LuaClosureExpr(closure) => {
168+
let closure_parent = match closure.get_parent::<LuaStat>() {
169+
Some(stat) => stat,
170+
None => continue,
171+
};
172+
173+
match closure_parent {
174+
LuaStat::FuncStat(func_stat) => {
175+
let func_name = func_stat.get_func_name()?;
176+
let name_lsp_range = document.to_lsp_range(func_name.get_range())?;
177+
let access_path = func_name.get_access_path()?;
178+
let semantic_decl = match func_name {
179+
LuaVarExpr::IndexExpr(index_expr) => LuaSemanticDeclId::Member(
180+
LuaMemberId::new(index_expr.get_syntax_id(), file_id),
181+
),
182+
LuaVarExpr::NameExpr(name_expr) => LuaSemanticDeclId::LuaDecl(
183+
LuaDeclId::new(file_id, name_expr.get_position()),
184+
),
185+
};
186+
187+
let item = CallHierarchyItem {
188+
name: access_path,
189+
kind: SymbolKind::FUNCTION,
190+
tags: None,
191+
detail: None,
192+
uri: uri.clone(),
193+
range: name_lsp_range,
194+
selection_range: name_lsp_range,
195+
data: Some(
196+
serde_json::to_value(CallHierarchyItemData {
197+
semantic_decl,
198+
file_id,
199+
})
200+
.ok()?,
201+
),
202+
};
203+
204+
result.push(CallHierarchyIncomingCall {
205+
from: item,
206+
from_ranges: vec![range],
207+
});
208+
}
209+
LuaStat::LocalFuncStat(local_func_stat) => {
210+
let func_name = local_func_stat.get_local_name()?;
211+
let name_lsp_range = document.to_lsp_range(func_name.get_range())?;
212+
let name = func_name.get_name_token()?.get_text().to_string();
213+
let semantic_decl = LuaSemanticDeclId::LuaDecl(LuaDeclId::new(
214+
file_id,
215+
func_name.get_position(),
216+
));
217+
218+
let item = CallHierarchyItem {
219+
name,
220+
kind: SymbolKind::FUNCTION,
221+
tags: None,
222+
detail: None,
223+
uri: uri.clone(),
224+
range: name_lsp_range,
225+
selection_range: name_lsp_range,
226+
data: Some(
227+
serde_json::to_value(CallHierarchyItemData {
228+
semantic_decl,
229+
file_id,
230+
})
231+
.ok()?,
232+
),
233+
};
234+
235+
result.push(CallHierarchyIncomingCall {
236+
from: item,
237+
from_ranges: vec![range],
238+
});
239+
}
240+
_ => continue,
241+
}
242+
243+
break;
244+
}
245+
_ => {
246+
return None;
247+
}
248+
}
249+
}
250+
251+
Some(())
252+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
mod build_call_hierarchy;
2+
3+
use build_call_hierarchy::{
4+
build_call_hierarchy_item, build_incoming_hierarchy, CallHierarchyItemData,
5+
};
6+
use emmylua_code_analysis::SemanticDeclLevel;
7+
use emmylua_parser::{LuaAstNode, LuaTokenKind};
8+
use lsp_types::{
9+
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
10+
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
11+
ClientCapabilities, ServerCapabilities,
12+
};
13+
use rowan::TokenAtOffset;
14+
use tokio_util::sync::CancellationToken;
15+
16+
use crate::context::ServerContextSnapshot;
17+
18+
use super::RegisterCapabilities;
19+
20+
pub async fn on_prepare_call_hierarchy_handler(
21+
context: ServerContextSnapshot,
22+
params: CallHierarchyPrepareParams,
23+
_: CancellationToken,
24+
) -> Option<Vec<CallHierarchyItem>> {
25+
let uri = params.text_document_position_params.text_document.uri;
26+
let analysis = context.analysis.read().await;
27+
let file_id = analysis.get_file_id(&uri)?;
28+
let position = params.text_document_position_params.position;
29+
let semantic_model = analysis.compilation.get_semantic_model(file_id)?;
30+
let root = semantic_model.get_root();
31+
let position_offset = {
32+
let document = semantic_model.get_document();
33+
document.get_offset(position.line as usize, position.character as usize)?
34+
};
35+
36+
if position_offset > root.syntax().text_range().end() {
37+
return None;
38+
}
39+
40+
let token = match root.syntax().token_at_offset(position_offset) {
41+
TokenAtOffset::Single(token) => token,
42+
TokenAtOffset::Between(left, right) => {
43+
if left.kind() == LuaTokenKind::TkName.into() {
44+
left
45+
} else {
46+
right
47+
}
48+
}
49+
TokenAtOffset::None => {
50+
return None;
51+
}
52+
};
53+
54+
let semantic_decl =
55+
semantic_model.find_decl(token.clone().into(), SemanticDeclLevel::default())?;
56+
57+
Some(vec![build_call_hierarchy_item(
58+
&semantic_model,
59+
semantic_decl,
60+
)?])
61+
}
62+
63+
pub async fn on_incoming_calls_handler(
64+
context: ServerContextSnapshot,
65+
params: CallHierarchyIncomingCallsParams,
66+
_: CancellationToken,
67+
) -> Option<Vec<CallHierarchyIncomingCall>> {
68+
let item = params.item;
69+
let data = item.data.as_ref()?;
70+
let data = serde_json::from_value::<CallHierarchyItemData>(data.clone()).ok()?;
71+
let analysis = context.analysis.read().await;
72+
let semantic_model = analysis.compilation.get_semantic_model(data.file_id)?;
73+
let semantic_decl_id = data.semantic_decl;
74+
75+
build_incoming_hierarchy(&semantic_model, &analysis.compilation, semantic_decl_id)
76+
}
77+
78+
pub async fn on_outgoing_calls_handler(
79+
_: ServerContextSnapshot,
80+
_: CallHierarchyOutgoingCallsParams,
81+
_: CancellationToken,
82+
) -> Option<Vec<CallHierarchyOutgoingCall>> {
83+
None
84+
}
85+
86+
pub struct CallHierarchyCapabilities;
87+
88+
impl RegisterCapabilities for CallHierarchyCapabilities {
89+
fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
90+
server_capabilities.call_hierarchy_provider = Some(true.into());
91+
}
92+
}

crates/emmylua_ls/src/handlers/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod call_hierarchy;
12
mod code_actions;
23
mod code_lens;
34
mod command;
@@ -116,6 +117,10 @@ pub fn server_capabilities(client_capabilities: &ClientCapabilities) -> ServerCa
116117
&mut server_capabilities,
117118
client_capabilities,
118119
);
120+
register::<call_hierarchy::CallHierarchyCapabilities>(
121+
&mut server_capabilities,
122+
client_capabilities,
123+
);
119124

120125
server_capabilities
121126
}

0 commit comments

Comments
 (0)