Skip to content

Commit b8cdeb0

Browse files
committed
add basic auto require logic
1 parent 5201d15 commit b8cdeb0

File tree

10 files changed

+164
-9
lines changed

10 files changed

+164
-9
lines changed

crates/code_analysis/src/config/configs/completion.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn default_postfix() -> String {
4545
"@".to_string()
4646
}
4747

48-
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
48+
#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Copy)]
4949
#[serde(rename_all = "kebab-case")]
5050
pub enum EmmyrcFilenameConvention {
5151
Keep,

crates/code_analysis/src/config/configs/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod strict;
1010
mod semantictoken;
1111

1212

13-
pub use completion::EmmyrcCompletion;
13+
pub use completion::{EmmyrcCompletion, EmmyrcFilenameConvention};
1414
pub use diagnostics::EmmyrcDiagnostic;
1515
pub use signature::EmmyrcSignature;
1616
pub use inlayhint::EmmyrcInlayHint;

crates/code_analysis/src/config/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{collections::HashSet, path::PathBuf};
66

77
use crate::{semantic::LuaInferConfig, FileId};
88
pub use config_loader::load_configs;
9+
pub use configs::EmmyrcFilenameConvention;
910
use configs::{
1011
EmmyrcCodeLen, EmmyrcCompletion, EmmyrcDiagnostic, EmmyrcInlayHint, EmmyrcLuaVersion,
1112
EmmyrcResource, EmmyrcRuntime, EmmyrcSemanticToken, EmmyrcSignature, EmmyrcStrict,

crates/code_analysis/src/db_index/mod.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@ use std::sync::Arc;
1515

1616
use crate::{Emmyrc, FileId, Vfs};
1717
pub use declaration::*;
18-
#[allow(unused_imports)]
19-
pub use diagnostic::{AnalyzeError, DiagnosticAction, DiagnosticIndex, DiagnosticActionKind};
18+
pub use diagnostic::{AnalyzeError, DiagnosticAction, DiagnosticActionKind, DiagnosticIndex};
2019
pub use flow::{LuaFlowChain, LuaFlowIndex};
21-
#[allow(unused_imports)]
2220
pub use member::{LuaMember, LuaMemberId, LuaMemberIndex, LuaMemberKey, LuaMemberOwner};
2321
use meta::MetaFile;
2422
use module::LuaModuleIndex;
25-
#[allow(unused_imports)]
23+
pub use module::ModuleInfo;
2624
pub use operators::{LuaOperator, LuaOperatorId, LuaOperatorIndex, LuaOperatorMetaMethod};
27-
#[allow(unused_imports)]
2825
pub use property::{
2926
LuaPropertyId, LuaPropertyIndex, LuaPropertyOwnerId, LuaVersionCond, LuaVersionCondOp,
3027
};

crates/code_analysis/src/db_index/module/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ mod module_node;
33
mod test;
44

55
use log::{error, info};
6-
use module_info::ModuleInfo;
6+
pub use module_info::ModuleInfo;
77
use module_node::{ModuleNode, ModuleNodeId};
88
use regex::Regex;
99

@@ -200,6 +200,10 @@ impl LuaModuleIndex {
200200
self.module_nodes.get(module_id)
201201
}
202202

203+
pub fn get_module_infos(&self) -> Vec<&ModuleInfo> {
204+
self.file_module_map.values().collect()
205+
}
206+
203207
fn extract_module_path(&self, path: &str) -> Option<String> {
204208
let path = Path::new(path);
205209
let mut matched_module_path: Option<String> = None;

crates/code_analysis/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod semantic;
66
mod vfs;
77

88
pub use compilation::*;
9-
pub use config::{load_configs, Emmyrc};
9+
pub use config::*;
1010
pub use db_index::*;
1111
pub use diagnostic::*;
1212
use log::{error, info};

crates/emmylua_ls/src/handlers/completion/completion_builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashSet;
2+
13
use code_analysis::SemanticModel;
24
use emmylua_parser::LuaSyntaxToken;
35
use lsp_types::CompletionItem;
@@ -6,6 +8,7 @@ use tokio_util::sync::CancellationToken;
68
pub struct CompletionBuilder<'a> {
79
pub trigger_token: LuaSyntaxToken,
810
pub semantic_model: SemanticModel<'a>,
11+
pub env_duplicate_name: HashSet<String>,
912
completion_items: Vec<CompletionItem>,
1013
cancel_token: CancellationToken,
1114
stopped: bool,
@@ -21,6 +24,7 @@ impl<'a> CompletionBuilder<'a> {
2124
Self {
2225
trigger_token,
2326
semantic_model,
27+
env_duplicate_name: HashSet::new(),
2428
completion_items: Vec::new(),
2529
cancel_token,
2630
stopped: false,
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use code_analysis::{EmmyrcFilenameConvention, ModuleInfo};
2+
use emmylua_parser::{LuaAstNode, LuaNameExpr};
3+
use lsp_types::CompletionItem;
4+
5+
use crate::handlers::completion::completion_builder::CompletionBuilder;
6+
7+
pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
8+
if builder.is_cancelled() {
9+
return None;
10+
}
11+
12+
let name_expr = LuaNameExpr::cast(builder.trigger_token.parent()?)?;
13+
// optimize for large project
14+
let prefix = name_expr.get_name_text()?.to_lowercase();
15+
let file_convension = builder
16+
.semantic_model
17+
.get_emmyrc()
18+
.completion
19+
.auto_require_naming_convention;
20+
let file_id = builder.semantic_model.get_file_id();
21+
let module_infos = builder
22+
.semantic_model
23+
.get_db()
24+
.get_module_index()
25+
.get_module_infos();
26+
27+
let mut completions = Vec::new();
28+
for module_info in module_infos {
29+
if module_info.visible
30+
&& module_info.file_id != file_id
31+
&& module_info.export_type.is_some()
32+
{
33+
add_module_completion_item(
34+
builder,
35+
&prefix,
36+
&module_info,
37+
file_convension,
38+
&mut completions,
39+
);
40+
}
41+
}
42+
43+
for completion in completions {
44+
builder.add_completion_item(completion);
45+
}
46+
47+
Some(())
48+
}
49+
50+
fn add_module_completion_item(
51+
builder: &CompletionBuilder,
52+
prefix: &str,
53+
module_info: &ModuleInfo,
54+
file_convension: EmmyrcFilenameConvention,
55+
completions: &mut Vec<CompletionItem>,
56+
) -> Option<()> {
57+
let completion_name = module_name_convert(&module_info.name, file_convension);
58+
if !completion_name.to_lowercase().starts_with(prefix) {
59+
return None;
60+
}
61+
62+
if builder.env_duplicate_name.contains(&completion_name) {
63+
return None;
64+
}
65+
66+
let completion_item = CompletionItem {
67+
label: completion_name,
68+
kind: Some(lsp_types::CompletionItemKind::MODULE),
69+
label_details: Some(lsp_types::CompletionItemLabelDetails {
70+
detail: Some(format!(" (in {})", module_info.full_module_name)),
71+
..Default::default()
72+
}),
73+
..Default::default()
74+
};
75+
76+
completions.push(completion_item);
77+
78+
Some(())
79+
}
80+
81+
fn module_name_convert(name: &str, file_convension: EmmyrcFilenameConvention) -> String {
82+
let mut module_name = name.to_string();
83+
84+
match file_convension {
85+
EmmyrcFilenameConvention::SnakeCase => {
86+
module_name = to_snake_case(&module_name);
87+
}
88+
EmmyrcFilenameConvention::CamelCase => {
89+
module_name = to_camel_case(&module_name);
90+
}
91+
EmmyrcFilenameConvention::PascalCase => {
92+
module_name = to_pascal_case(&module_name);
93+
}
94+
EmmyrcFilenameConvention::Keep => {}
95+
}
96+
97+
module_name
98+
}
99+
100+
fn to_snake_case(s: &str) -> String {
101+
let mut result = String::new();
102+
for (i, ch) in s.chars().enumerate() {
103+
if ch.is_uppercase() && i != 0 {
104+
result.push('_');
105+
result.push(ch.to_ascii_lowercase());
106+
} else {
107+
result.push(ch.to_ascii_lowercase());
108+
}
109+
}
110+
result
111+
}
112+
113+
fn to_camel_case(s: &str) -> String {
114+
let mut result = String::new();
115+
let mut next_uppercase = false;
116+
for (i, ch) in s.chars().enumerate() {
117+
if ch == '_' || ch == '-' || ch == '.' {
118+
next_uppercase = true;
119+
} else if next_uppercase {
120+
result.push(ch.to_ascii_uppercase());
121+
next_uppercase = false;
122+
} else if i == 0 {
123+
result.push(ch.to_ascii_lowercase());
124+
} else {
125+
result.push(ch);
126+
}
127+
}
128+
result
129+
}
130+
131+
fn to_pascal_case(s: &str) -> String {
132+
let mut result = String::new();
133+
let mut next_uppercase = true;
134+
for ch in s.chars() {
135+
if ch == '_' || ch == '-' || ch == '.' {
136+
next_uppercase = true;
137+
} else if next_uppercase {
138+
result.push(ch.to_ascii_uppercase());
139+
next_uppercase = false;
140+
} else {
141+
result.push(ch.to_ascii_lowercase());
142+
}
143+
}
144+
result
145+
}

crates/emmylua_ls/src/handlers/completion/providers/env_provider.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,7 @@ pub fn add_completion(builder: &mut CompletionBuilder) -> Option<()> {
7979
add_decl_completion(builder, decl_id.clone(), &name, &typ);
8080
}
8181

82+
builder.env_duplicate_name.extend(duplicated_name);
83+
8284
Some(())
8385
}

crates/emmylua_ls/src/handlers/completion/providers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod file_path_provider;
77
mod keywords_provider;
88
mod member_provider;
99
mod module_path_provider;
10+
mod auto_require_provider;
1011

1112
use super::completion_builder::CompletionBuilder;
1213

@@ -17,6 +18,7 @@ pub fn add_completions(builder: &mut CompletionBuilder) -> Option<()> {
1718
type_special_provider::add_completion(builder);
1819
env_provider::add_completion(builder);
1920
member_provider::add_completion(builder);
21+
auto_require_provider::add_completion(builder);
2022
doc_tag_provider::add_completion(builder);
2123
doc_type_provider::add_completion(builder);
2224
doc_name_token_provider::add_completion(builder);

0 commit comments

Comments
 (0)