Skip to content

Commit b4e6d62

Browse files
committed
支持调用参数显示枚举名称
默认关闭, 通过`hint.enumParamHint`启用. 作用是如果调用参数是常量, 那么在他的右侧显示`enumName.keyName`
1 parent 025f5c3 commit b4e6d62

4 files changed

Lines changed: 342 additions & 4 deletions

File tree

crates/emmylua_code_analysis/resources/schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"$ref": "#/$defs/EmmyrcInlayHint",
6464
"default": {
6565
"enable": true,
66+
"enumParamHint": false,
6667
"indexHint": true,
6768
"localHint": true,
6869
"metaCallHint": true,
@@ -637,6 +638,11 @@
637638
"type": "boolean",
638639
"default": true
639640
},
641+
"enumParamHint": {
642+
"description": "Whether to enable enum parameter hints.",
643+
"type": "boolean",
644+
"default": false
645+
},
640646
"indexHint": {
641647
"description": "Whether to enable index hints.",
642648
"type": "boolean",

crates/emmylua_code_analysis/src/config/configs/inlayhint.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub struct EmmyrcInlayHint {
2323
/// Whether to enable meta __call operator hints.
2424
#[serde(default = "default_true")]
2525
pub meta_call_hint: bool,
26+
/// Whether to enable enum parameter hints.
27+
#[serde(default = "default_false")]
28+
pub enum_param_hint: bool,
2629
}
2730

2831
impl Default for EmmyrcInlayHint {
@@ -34,10 +37,15 @@ impl Default for EmmyrcInlayHint {
3437
local_hint: default_true(),
3538
override_hint: default_true(),
3639
meta_call_hint: default_true(),
40+
enum_param_hint: default_false(),
3741
}
3842
}
3943
}
4044

4145
fn default_true() -> bool {
4246
true
4347
}
48+
49+
fn default_false() -> bool {
50+
false
51+
}

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

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ use std::collections::HashMap;
22
use std::sync::Arc;
33

44
use emmylua_code_analysis::{
5-
FileId, InferGuard, LuaFunctionType, LuaMemberId, LuaMemberKey, LuaOperatorId,
6-
LuaOperatorMetaMethod, LuaSemanticDeclId, LuaType, SemanticModel,
5+
FileId, InferGuard, LuaFunctionType, LuaMember, LuaMemberId, LuaMemberKey, LuaMemberOwner,
6+
LuaOperatorId, LuaOperatorMetaMethod, LuaSemanticDeclId, LuaType, LuaTypeDecl, SemanticModel,
77
};
88
use emmylua_parser::{
99
LuaAst, LuaAstNode, LuaCallExpr, LuaExpr, LuaFuncStat, LuaIndexExpr, LuaIndexKey,
10-
LuaLocalFuncStat, LuaLocalName, LuaLocalStat, LuaStat, LuaSyntaxId, LuaVarExpr,
10+
LuaLiteralToken, LuaLocalFuncStat, LuaLocalName, LuaLocalStat, LuaStat, LuaSyntaxId,
11+
LuaVarExpr,
1112
};
1213
use emmylua_parser::{LuaAstToken, LuaTokenKind};
1314
use lsp_types::{InlayHint, InlayHintKind, InlayHintLabel, InlayHintLabelPart, Location};
@@ -30,7 +31,8 @@ pub fn build_inlay_hints(semantic_model: &SemanticModel) -> Option<Vec<InlayHint
3031
LuaAst::LuaCallExpr(call_expr) => {
3132
build_call_expr_param_hint(semantic_model, &mut result, call_expr.clone());
3233
build_call_expr_await_hint(semantic_model, &mut result, call_expr.clone());
33-
build_call_expr_meta_call_hint(semantic_model, &mut result, call_expr);
34+
build_call_expr_meta_call_hint(semantic_model, &mut result, call_expr.clone());
35+
build_enum_param_hint(semantic_model, &mut result, call_expr);
3436
}
3537
LuaAst::LuaLocalName(local_name) => {
3638
build_local_name_hint(semantic_model, &mut result, local_name);
@@ -674,3 +676,181 @@ fn build_index_expr_hint(
674676
result.push(hint);
675677
Some(())
676678
}
679+
680+
fn build_enum_param_hint(
681+
semantic_model: &SemanticModel,
682+
result: &mut Vec<InlayHint>,
683+
call_expr: LuaCallExpr,
684+
) -> Option<()> {
685+
if !semantic_model.get_emmyrc().hint.enum_param_hint {
686+
return Some(());
687+
}
688+
689+
let func_type = semantic_model.infer_call_expr_func(call_expr.clone(), None)?;
690+
let call_args = call_expr.get_args_list()?.get_args().collect::<Vec<_>>();
691+
let params = func_type.get_params();
692+
693+
let colon_call = call_expr.is_colon_call();
694+
let colon_define = func_type.is_colon_define();
695+
696+
let param_offset: i32 = match (colon_call, colon_define) {
697+
(true, false) => 1,
698+
(false, true) => -1,
699+
_ => 0,
700+
};
701+
702+
for (i, arg) in call_args.iter().enumerate() {
703+
let param_index = i as i32 + param_offset;
704+
if param_index < 0 {
705+
continue;
706+
}
707+
process_enum_hint_for_arg(semantic_model, result, arg, params, param_index as usize);
708+
}
709+
710+
Some(())
711+
}
712+
713+
fn process_enum_hint_for_arg(
714+
semantic_model: &SemanticModel,
715+
result: &mut Vec<InlayHint>,
716+
arg: &LuaExpr,
717+
params: &[(String, Option<LuaType>)],
718+
param_index: usize,
719+
) -> Option<()> {
720+
let (_, param_type) = params.get(param_index)?;
721+
let param_type = param_type.as_ref()?;
722+
723+
let type_id = match param_type {
724+
LuaType::Ref(id) => id,
725+
_ => return None,
726+
};
727+
728+
let type_decl = semantic_model
729+
.get_db()
730+
.get_type_index()
731+
.get_type_decl(type_id)?;
732+
if !type_decl.is_enum() {
733+
return None;
734+
}
735+
736+
// 推断参数类型
737+
let arg_type = semantic_model.infer_expr(arg.clone()).ok()?;
738+
739+
// 查找对应的枚举成员
740+
let member_decl = find_matching_enum_member(semantic_model, type_decl, &arg_type)?;
741+
let member_name = member_decl.get_key().to_path();
742+
743+
match arg {
744+
LuaExpr::LiteralExpr(literal_expr) => {
745+
if let Some(literal_token) = literal_expr.get_literal() {
746+
match literal_token {
747+
LuaLiteralToken::String(string_token) => {
748+
if string_token.get_value() == member_name {
749+
return None;
750+
}
751+
}
752+
LuaLiteralToken::Number(number_token) => {
753+
if number_token.is_int() {
754+
let number_value = format!("[{}]", number_token.get_int_value());
755+
if number_value == member_name {
756+
return None;
757+
}
758+
}
759+
}
760+
_ => {}
761+
}
762+
}
763+
}
764+
LuaExpr::NameExpr(name_expr) => {
765+
if let Some(arg_name) = name_expr.get_name_text() {
766+
if member_name == arg_name {
767+
return None;
768+
}
769+
// 名称里包含了枚举名和成员名(忽略大小写)也不显示提示
770+
let lower_arg_name = arg_name.to_lowercase();
771+
let lower_enum_name = type_decl.get_name().to_lowercase();
772+
let lower_member_name = member_name.to_lowercase();
773+
if lower_arg_name.contains(&lower_enum_name)
774+
&& lower_arg_name.contains(&lower_member_name)
775+
{
776+
return None;
777+
}
778+
}
779+
}
780+
LuaExpr::IndexExpr(index_expr) => {
781+
// 对索引访问需要完全匹配尾名称
782+
if let Some(index_name_token) = index_expr.get_index_name_token() {
783+
if let Some(name_token) =
784+
emmylua_parser::LuaNameToken::cast(index_name_token.clone())
785+
{
786+
let index_name = name_token.get_name_text();
787+
if index_name == member_name {
788+
return None;
789+
}
790+
}
791+
}
792+
}
793+
_ => {}
794+
}
795+
796+
let enum_name = type_decl.get_name();
797+
let hint_text = format!("{}.{}", enum_name, member_name);
798+
799+
let document = semantic_model.get_document();
800+
let range = arg.get_range();
801+
let lsp_range = document.to_lsp_range(range)?;
802+
803+
let hint = InlayHint {
804+
kind: Some(InlayHintKind::PARAMETER),
805+
label: InlayHintLabel::String(hint_text),
806+
position: lsp_range.end,
807+
text_edits: None,
808+
tooltip: None,
809+
padding_left: Some(true),
810+
padding_right: None,
811+
data: None,
812+
};
813+
result.push(hint);
814+
815+
Some(())
816+
}
817+
818+
fn find_matching_enum_member<'a>(
819+
semantic_model: &'a SemanticModel,
820+
type_decl: &LuaTypeDecl,
821+
arg_type: &LuaType,
822+
) -> Option<&'a LuaMember> {
823+
let enum_member_owner = LuaMemberOwner::Type(type_decl.get_id());
824+
let enum_members = semantic_model
825+
.get_db()
826+
.get_member_index()
827+
.get_members(&enum_member_owner)?;
828+
let is_enum_key = type_decl.is_enum_key();
829+
830+
for member_decl in enum_members {
831+
let is_match = if is_enum_key {
832+
let member_key = member_decl.get_key();
833+
match (member_key, arg_type) {
834+
(LuaMemberKey::Name(s), LuaType::StringConst(arg_s)) => s == arg_s.as_ref(),
835+
(LuaMemberKey::Integer(i), LuaType::IntegerConst(arg_i)) => *i == *arg_i,
836+
(LuaMemberKey::ExprType(typ), _) => typ == arg_type,
837+
_ => false,
838+
}
839+
} else {
840+
if let Some(type_cache) = semantic_model
841+
.get_db()
842+
.get_type_index()
843+
.get_type_cache(&member_decl.get_id().into())
844+
{
845+
type_cache.as_type() == arg_type
846+
} else {
847+
false
848+
}
849+
};
850+
851+
if is_match {
852+
return Some(member_decl);
853+
}
854+
}
855+
None
856+
}

0 commit comments

Comments
 (0)