Skip to content

Commit 86ca4dd

Browse files
committed
optimize inlay_hint: closure params 现在可以跳转到实际类型定义处
1 parent dd5e631 commit 86ca4dd

File tree

8 files changed

+266
-68
lines changed

8 files changed

+266
-68
lines changed

crates/emmylua_code_analysis/src/db_index/type/humanize_type.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::collections::HashSet;
22

3+
use itertools::Itertools;
4+
35
use crate::{
46
DbIndex, GenericTpl, LuaAliasCallType, LuaFunctionType, LuaGenericType, LuaInstanceType,
57
LuaIntersectionType, LuaMemberKey, LuaMemberOwner, LuaObjectType, LuaSignatureId,
@@ -404,9 +406,11 @@ fn humanize_object_type(db: &DbIndex, object: &LuaObjectType, level: RenderLevel
404406
} else {
405407
""
406408
};
409+
407410
let fields = object
408411
.get_fields()
409412
.iter()
413+
.sorted_by(|a, b| a.0.cmp(&b.0))
410414
.take(num)
411415
.map(|field| {
412416
let name = field.0.clone();
@@ -419,7 +423,7 @@ fn humanize_object_type(db: &DbIndex, object: &LuaObjectType, level: RenderLevel
419423
}
420424
})
421425
.collect::<Vec<_>>()
422-
.join(",");
426+
.join(", ");
423427

424428
let access = object
425429
.get_index_access()

crates/emmylua_ls/src/handlers/hover/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub use build_hover::build_hover_content_for_completion;
1010
use build_hover::build_semantic_info_hover;
1111
use emmylua_code_analysis::{EmmyLuaAnalysis, FileId};
1212
use emmylua_parser::LuaAstNode;
13-
pub use find_origin::{find_member_origin_owner, find_all_same_named_members};
13+
pub use find_origin::{find_all_same_named_members, find_member_origin_owner};
1414
pub use hover_builder::HoverBuilder;
1515
pub use hover_humanize::infer_prefix_global_name;
1616
use keyword_hover::{hover_keyword, is_keyword};
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::collections::HashMap;
2+
3+
use emmylua_code_analysis::{humanize_type, LuaSignatureId, LuaType, RenderLevel, SemanticModel};
4+
use emmylua_parser::{LuaAstNode, LuaClosureExpr};
5+
use itertools::Itertools;
6+
use lsp_types::{InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, Location};
7+
8+
pub fn build_closure_hint(
9+
semantic_model: &SemanticModel,
10+
result: &mut Vec<InlayHint>,
11+
closure: LuaClosureExpr,
12+
) -> Option<()> {
13+
if !semantic_model.get_emmyrc().hint.param_hint {
14+
return Some(());
15+
}
16+
let file_id = semantic_model.get_file_id();
17+
let signature_id = LuaSignatureId::from_closure(file_id, &closure);
18+
let signature = semantic_model
19+
.get_db()
20+
.get_signature_index()
21+
.get(&signature_id)?;
22+
23+
let lua_params = closure.get_params_list()?;
24+
let signature_params = signature.get_type_params();
25+
let mut lua_params_map = HashMap::new();
26+
for param in lua_params.get_params() {
27+
if let Some(name_token) = param.get_name_token() {
28+
let name = name_token.get_name_text().to_string();
29+
lua_params_map.insert(name, param);
30+
} else if param.is_dots() {
31+
lua_params_map.insert("...".to_string(), param);
32+
}
33+
}
34+
35+
let document = semantic_model.get_document();
36+
for (signature_param_name, typ) in &signature_params {
37+
if let Some(typ) = typ {
38+
if typ.is_any() {
39+
continue;
40+
}
41+
42+
if let Some(lua_param) = lua_params_map.get(signature_param_name) {
43+
let lsp_range = document.to_lsp_range(lua_param.get_range())?;
44+
// 构造 label
45+
let mut label_parts = build_label_parts(semantic_model, &typ);
46+
// 为空时添加默认值
47+
if label_parts.is_empty() {
48+
let typ_desc = format!(
49+
": {}",
50+
humanize_type(semantic_model.get_db(), &typ, RenderLevel::Simple)
51+
);
52+
label_parts.push(InlayHintLabelPart {
53+
value: typ_desc,
54+
location: Some(
55+
get_type_location(semantic_model, typ)
56+
.unwrap_or(Location::new(document.get_uri(), lsp_range)),
57+
),
58+
..Default::default()
59+
});
60+
}
61+
let hint = InlayHint {
62+
kind: Some(InlayHintKind::TYPE),
63+
label: InlayHintLabel::LabelParts(label_parts),
64+
position: lsp_range.end,
65+
text_edits: None,
66+
tooltip: None,
67+
padding_left: Some(true),
68+
padding_right: None,
69+
data: None,
70+
};
71+
result.push(hint);
72+
}
73+
}
74+
}
75+
76+
Some(())
77+
}
78+
79+
fn build_label_parts(semantic_model: &SemanticModel, typ: &LuaType) -> Vec<InlayHintLabelPart> {
80+
let mut parts: Vec<InlayHintLabelPart> = Vec::new();
81+
match typ {
82+
LuaType::Union(union) => {
83+
for typ in union.get_types() {
84+
if let Some(part) = get_part(semantic_model, typ) {
85+
parts.push(part);
86+
}
87+
}
88+
}
89+
_ => {
90+
if let Some(part) = get_part(semantic_model, typ) {
91+
parts.push(part);
92+
}
93+
}
94+
}
95+
// 去重
96+
let parts: Vec<InlayHintLabelPart> = parts
97+
.into_iter()
98+
.unique_by(|part| part.value.clone())
99+
.collect();
100+
// 将 "?" 标签移到最后
101+
let mut normal_parts = Vec::new();
102+
let mut nil_parts = Vec::new();
103+
for part in parts {
104+
if part.value == "?" {
105+
nil_parts.push(part);
106+
} else {
107+
normal_parts.push(part);
108+
}
109+
}
110+
normal_parts.append(&mut nil_parts);
111+
let mut result = Vec::new();
112+
for (i, part) in normal_parts.into_iter().enumerate() {
113+
let mut part = part;
114+
if part.value != "?" {
115+
part.value = format!("{}{}", if i == 0 { ": " } else { "|" }, part.value);
116+
}
117+
result.push(part);
118+
}
119+
result
120+
}
121+
122+
fn get_part(semantic_model: &SemanticModel, typ: &LuaType) -> Option<InlayHintLabelPart> {
123+
match typ {
124+
LuaType::Union(_) => None,
125+
LuaType::Nil => {
126+
return Some(InlayHintLabelPart {
127+
value: "?".to_string(),
128+
location: get_type_location(semantic_model, typ),
129+
..Default::default()
130+
});
131+
}
132+
_ => {
133+
let value = humanize_type(semantic_model.get_db(), typ, RenderLevel::Simple);
134+
let location = get_type_location(semantic_model, typ);
135+
return Some(InlayHintLabelPart {
136+
value,
137+
location,
138+
..Default::default()
139+
});
140+
}
141+
}
142+
}
143+
144+
fn get_type_location(semantic_model: &SemanticModel, typ: &LuaType) -> Option<Location> {
145+
match typ {
146+
LuaType::Ref(id) => {
147+
let type_decl = semantic_model
148+
.get_db()
149+
.get_type_index()
150+
.get_type_decl(&id)?;
151+
let location = type_decl.get_locations().first()?;
152+
let document = semantic_model.get_document_by_file_id(location.file_id)?;
153+
let lsp_range = document.to_lsp_range(location.range)?;
154+
Some(Location::new(document.get_uri(), lsp_range))
155+
}
156+
LuaType::Any => get_base_type_location(semantic_model, "any"),
157+
LuaType::Nil => get_base_type_location(semantic_model, "nil"),
158+
LuaType::Unknown => get_base_type_location(semantic_model, "string"),
159+
LuaType::Userdata => get_base_type_location(semantic_model, "userdata"),
160+
LuaType::Function => get_base_type_location(semantic_model, "function"),
161+
LuaType::Thread => get_base_type_location(semantic_model, "thread"),
162+
LuaType::Table => get_base_type_location(semantic_model, "table"),
163+
_ if typ.is_string() => get_base_type_location(semantic_model, "string"),
164+
_ if typ.is_integer() => get_base_type_location(semantic_model, "integer"),
165+
_ if typ.is_number() => get_base_type_location(semantic_model, "number"),
166+
_ if typ.is_boolean() => get_base_type_location(semantic_model, "boolean"),
167+
_ => None,
168+
}
169+
}
170+
171+
fn get_base_type_location(semantic_model: &SemanticModel, name: &str) -> Option<Location> {
172+
let type_decl = semantic_model
173+
.get_db()
174+
.get_type_index()
175+
.find_type_decl(semantic_model.get_file_id(), name)?;
176+
let location = type_decl.get_locations().first()?;
177+
let document = semantic_model.get_document_by_file_id(location.file_id)?;
178+
let lsp_range = document.to_lsp_range(location.range)?;
179+
Some(Location::new(document.get_uri(), lsp_range))
180+
}

crates/emmylua_ls/src/handlers/inlay_hint/build_inlay_hint.rs

Lines changed: 6 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
use std::collections::HashMap;
22

33
use emmylua_code_analysis::{
4-
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaSemanticDeclId,
5-
LuaSignatureId, LuaType, RenderLevel, SemanticModel,
4+
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaSemanticDeclId, LuaType,
5+
RenderLevel, SemanticModel,
66
};
77
use emmylua_parser::LuaTokenKind;
88
use emmylua_parser::{
9-
LuaAst, LuaAstNode, LuaCallExpr, LuaClosureExpr, LuaExpr, LuaFuncStat, LuaIndexExpr,
10-
LuaLocalName, LuaStat, LuaSyntaxId, LuaVarExpr,
9+
LuaAst, LuaAstNode, LuaCallExpr, LuaExpr, LuaFuncStat, LuaIndexExpr, LuaLocalName, LuaStat,
10+
LuaSyntaxId, LuaVarExpr,
1111
};
1212
use lsp_types::{InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, Location};
1313
use rowan::NodeOrToken;
1414

1515
use emmylua_code_analysis::humanize_type;
1616
use rowan::TokenAtOffset;
1717

18+
use crate::handlers::inlay_hint::build_function_hint::build_closure_hint;
19+
1820
pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint>> {
1921
let mut result = Vec::new();
2022
let root = semantic_model.get_root();
@@ -40,66 +42,6 @@ pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint
4042
Some(result)
4143
}
4244

43-
fn build_closure_hint(
44-
semantic_model: &SemanticModel,
45-
result: &mut Vec<InlayHint>,
46-
closure: LuaClosureExpr,
47-
) -> Option<()> {
48-
if !semantic_model.get_emmyrc().hint.param_hint {
49-
return Some(());
50-
}
51-
let file_id = semantic_model.get_file_id();
52-
let signature_id = LuaSignatureId::from_closure(file_id, &closure);
53-
let signature = semantic_model
54-
.get_db()
55-
.get_signature_index()
56-
.get(&signature_id)?;
57-
58-
let lua_params = closure.get_params_list()?;
59-
let signature_params = signature.get_type_params();
60-
let mut lua_params_map = HashMap::new();
61-
for param in lua_params.get_params() {
62-
if let Some(name_token) = param.get_name_token() {
63-
let name = name_token.get_name_text().to_string();
64-
lua_params_map.insert(name, param);
65-
} else if param.is_dots() {
66-
lua_params_map.insert("...".to_string(), param);
67-
}
68-
}
69-
70-
let document = semantic_model.get_document();
71-
let db = semantic_model.get_db();
72-
for (signature_param_name, typ) in &signature_params {
73-
if let Some(typ) = typ {
74-
if typ.is_any() {
75-
continue;
76-
}
77-
78-
if let Some(lua_param) = lua_params_map.get(signature_param_name) {
79-
let lsp_range = document.to_lsp_range(lua_param.get_range())?;
80-
let typ_desc = format!(": {}", humanize_type(db, &typ, RenderLevel::Simple));
81-
let hint = InlayHint {
82-
kind: Some(InlayHintKind::TYPE),
83-
label: InlayHintLabel::LabelParts(vec![InlayHintLabelPart {
84-
value: typ_desc,
85-
location: Some(Location::new(document.get_uri(), lsp_range)),
86-
..Default::default()
87-
}]),
88-
position: lsp_range.end,
89-
text_edits: None,
90-
tooltip: None,
91-
padding_left: Some(true),
92-
padding_right: None,
93-
data: None,
94-
};
95-
result.push(hint);
96-
}
97-
}
98-
}
99-
100-
Some(())
101-
}
102-
10345
fn build_call_expr_param_hint(
10446
semantic_model: &SemanticModel,
10547
result: &mut Vec<InlayHint>,

crates/emmylua_ls/src/handlers/inlay_hint/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
mod build_function_hint;
12
mod build_inlay_hint;
23

34
use build_inlay_hint::build_inlay_hints;
5+
use emmylua_code_analysis::{EmmyLuaAnalysis, FileId};
46
use lsp_types::{
57
ClientCapabilities, InlayHint, InlayHintOptions, InlayHintParams, InlayHintServerCapabilities,
68
OneOf, ServerCapabilities,
@@ -18,7 +20,10 @@ pub async fn on_inlay_hint_handler(
1820
) -> Option<Vec<InlayHint>> {
1921
let uri = params.text_document.uri;
2022
let analysis = context.analysis.read().await;
21-
let file_id = analysis.get_file_id(&uri)?;
23+
inlay_hint(&analysis, analysis.get_file_id(&uri)?)
24+
}
25+
26+
pub fn inlay_hint(analysis: &EmmyLuaAnalysis, file_id: FileId) -> Option<Vec<InlayHint>> {
2227
let mut semantic_model = analysis.compilation.get_semantic_model(file_id)?;
2328
build_inlay_hints(&mut semantic_model)
2429
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#[cfg(test)]
2+
mod tests {
3+
use lsp_types::{InlayHint, InlayHintLabel, Location, Position, Range};
4+
5+
use crate::handlers::test_lib::ProviderVirtualWorkspace;
6+
7+
fn extract_first_label_part_location(inlay_hint: &InlayHint) -> Option<&Location> {
8+
match &inlay_hint.label {
9+
InlayHintLabel::LabelParts(parts) => parts.first()?.location.as_ref(),
10+
InlayHintLabel::String(_) => None,
11+
}
12+
}
13+
14+
#[test]
15+
fn test_1() {
16+
let mut ws = ProviderVirtualWorkspace::new();
17+
let result = ws
18+
.check_inlay_hint(
19+
r#"
20+
---@class Hint1
21+
22+
---@param a Hint1
23+
local function test(a)
24+
local b = a
25+
end
26+
"#,
27+
)
28+
.unwrap();
29+
30+
let first = result.first().unwrap();
31+
let location = extract_first_label_part_location(first).unwrap();
32+
33+
assert_eq!(
34+
location.range,
35+
Range::new(Position::new(1, 26), Position::new(1, 31))
36+
);
37+
}
38+
39+
#[test]
40+
fn test_2() {
41+
let mut ws = ProviderVirtualWorkspace::new_with_init_std_lib();
42+
let result = ws
43+
.check_inlay_hint(
44+
r#"
45+
46+
---@param a number
47+
local function test(a)
48+
end
49+
"#,
50+
)
51+
.unwrap();
52+
53+
let first = result.first().unwrap();
54+
let location = extract_first_label_part_location(first).unwrap();
55+
assert!(location.uri.path().as_str().ends_with("builtin.lua"));
56+
}
57+
}

crates/emmylua_ls/src/handlers/test/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ mod definition_test;
44
mod hover_function_test;
55
mod hover_test;
66
mod implementation_test;
7+
mod inlay_hint_test;
78
mod signature_helper_test;

0 commit comments

Comments
 (0)