1#[derive(Debug, Clone)]
7pub struct DiagnosticsConfig {
8 pub enabled: bool,
10 pub undefined_variables: bool,
12 pub undefined_functions: bool,
14 pub undefined_classes: bool,
16 pub arity_errors: bool,
18 pub type_errors: bool,
20 pub deprecated_calls: bool,
22 pub duplicate_declarations: bool,
24 pub unused_symbols: bool,
29}
30
31impl Default for DiagnosticsConfig {
32 fn default() -> Self {
33 DiagnosticsConfig {
34 enabled: true,
35 undefined_variables: true,
36 undefined_functions: true,
37 undefined_classes: true,
38 arity_errors: true,
39 type_errors: true,
40 deprecated_calls: true,
41 duplicate_declarations: true,
42 unused_symbols: false,
43 }
44 }
45}
46
47impl DiagnosticsConfig {
48 #[cfg(test)]
51 pub fn all_enabled() -> Self {
52 DiagnosticsConfig {
53 enabled: true,
54 ..DiagnosticsConfig::default()
55 }
56 }
57
58 pub(crate) fn from_value(v: &serde_json::Value) -> Self {
59 let mut cfg = DiagnosticsConfig::default();
60 let Some(obj) = v.as_object() else { return cfg };
61 let flag = |key: &str| obj.get(key).and_then(|x| x.as_bool()).unwrap_or(true);
62 cfg.enabled = obj.get("enabled").and_then(|x| x.as_bool()).unwrap_or(true);
63 cfg.undefined_variables = flag("undefinedVariables");
64 cfg.undefined_functions = flag("undefinedFunctions");
65 cfg.undefined_classes = flag("undefinedClasses");
66 cfg.arity_errors = flag("arityErrors");
67 cfg.type_errors = flag("typeErrors");
68 cfg.deprecated_calls = flag("deprecatedCalls");
69 cfg.duplicate_declarations = flag("duplicateDeclarations");
70 cfg.unused_symbols = obj
71 .get("unusedSymbols")
72 .and_then(|x| x.as_bool())
73 .unwrap_or(false);
74 cfg
75 }
76}
77
78#[derive(Debug, Clone)]
81pub struct FeaturesConfig {
82 pub completion: bool,
83 pub hover: bool,
84 pub definition: bool,
85 pub declaration: bool,
86 pub references: bool,
87 pub document_symbols: bool,
88 pub workspace_symbols: bool,
89 pub rename: bool,
90 pub signature_help: bool,
91 pub inlay_hints: bool,
92 pub semantic_tokens: bool,
93 pub selection_range: bool,
94 pub call_hierarchy: bool,
95 pub document_highlight: bool,
96 pub implementation: bool,
97 pub code_action: bool,
98 pub type_definition: bool,
99 pub code_lens: bool,
100 pub formatting: bool,
101 pub range_formatting: bool,
102 pub on_type_formatting: bool,
103 pub document_link: bool,
104 pub linked_editing_range: bool,
105 pub inline_values: bool,
106}
107
108impl Default for FeaturesConfig {
109 fn default() -> Self {
110 FeaturesConfig {
111 completion: true,
112 hover: true,
113 definition: true,
114 declaration: true,
115 references: true,
116 document_symbols: true,
117 workspace_symbols: true,
118 rename: true,
119 signature_help: true,
120 inlay_hints: true,
121 semantic_tokens: true,
122 selection_range: true,
123 call_hierarchy: true,
124 document_highlight: true,
125 implementation: true,
126 code_action: true,
127 type_definition: true,
128 code_lens: true,
129 formatting: true,
130 range_formatting: true,
131 on_type_formatting: true,
132 document_link: true,
133 linked_editing_range: true,
134 inline_values: true,
135 }
136 }
137}
138
139impl FeaturesConfig {
140 pub(crate) fn from_value(v: &serde_json::Value) -> Self {
141 let mut cfg = FeaturesConfig::default();
142 let Some(obj) = v.as_object() else { return cfg };
143 let flag = |key: &str| obj.get(key).and_then(|x| x.as_bool()).unwrap_or(true);
144 cfg.completion = flag("completion");
145 cfg.hover = flag("hover");
146 cfg.definition = flag("definition");
147 cfg.declaration = flag("declaration");
148 cfg.references = flag("references");
149 cfg.document_symbols = flag("documentSymbols");
150 cfg.workspace_symbols = flag("workspaceSymbols");
151 cfg.rename = flag("rename");
152 cfg.signature_help = flag("signatureHelp");
153 cfg.inlay_hints = flag("inlayHints");
154 cfg.semantic_tokens = flag("semanticTokens");
155 cfg.selection_range = flag("selectionRange");
156 cfg.call_hierarchy = flag("callHierarchy");
157 cfg.document_highlight = flag("documentHighlight");
158 cfg.implementation = flag("implementation");
159 cfg.code_action = flag("codeAction");
160 cfg.type_definition = flag("typeDefinition");
161 cfg.code_lens = flag("codeLens");
162 cfg.formatting = flag("formatting");
163 cfg.range_formatting = flag("rangeFormatting");
164 cfg.on_type_formatting = flag("onTypeFormatting");
165 cfg.document_link = flag("documentLink");
166 cfg.linked_editing_range = flag("linkedEditingRange");
167 cfg.inline_values = flag("inlineValues");
168 cfg
169 }
170}
171
172pub const MAX_INDEXED_FILES: usize = 50_000;
175
176#[derive(Debug, Clone)]
178pub struct LspConfig {
179 pub php_version: Option<String>,
182 pub exclude_paths: Vec<String>,
184 pub include_paths: Vec<String>,
188 pub diagnostics: DiagnosticsConfig,
190 pub features: FeaturesConfig,
192 pub max_indexed_files: usize,
196}
197
198impl Default for LspConfig {
199 fn default() -> Self {
200 LspConfig {
201 php_version: None,
202 exclude_paths: Vec::new(),
203 include_paths: Vec::new(),
204 diagnostics: DiagnosticsConfig::default(),
205 features: FeaturesConfig::default(),
206 max_indexed_files: MAX_INDEXED_FILES,
207 }
208 }
209}
210
211impl LspConfig {
212 pub(crate) fn merge_project_configs(
220 file: Option<&serde_json::Value>,
221 editor: Option<&serde_json::Value>,
222 ) -> serde_json::Value {
223 let mut merged = file
224 .cloned()
225 .unwrap_or(serde_json::Value::Object(Default::default()));
226 let Some(editor_obj) = editor.and_then(|e| e.as_object()) else {
227 return merged;
228 };
229 let merged_obj = merged
230 .as_object_mut()
231 .expect("merged base is always an object");
232 for (key, val) in editor_obj {
233 if key == "excludePaths" || key == "includePaths" {
235 let file_arr = merged_obj
236 .get(key)
237 .and_then(|v| v.as_array())
238 .cloned()
239 .unwrap_or_default();
240 let editor_arr = val.as_array().cloned().unwrap_or_default();
241 merged_obj.insert(
242 key.clone(),
243 serde_json::Value::Array([file_arr, editor_arr].concat()),
244 );
245 } else {
246 merged_obj.insert(key.clone(), val.clone());
247 }
248 }
249 merged
250 }
251
252 pub(crate) fn from_value(v: &serde_json::Value) -> Self {
253 let mut cfg = LspConfig::default();
254 if let Some(ver) = v.get("phpVersion").and_then(|x| x.as_str()) {
255 if crate::autoload::is_valid_php_version(ver) {
256 cfg.php_version = Some(ver.to_string());
257 } else {
258 cfg.php_version = Some(crate::autoload::PHP_8_5.to_string());
260 }
261 }
262 if let Some(arr) = v.get("excludePaths").and_then(|x| x.as_array()) {
263 cfg.exclude_paths = arr
264 .iter()
265 .filter_map(|x| x.as_str().map(str::to_string))
266 .collect();
267 }
268 if let Some(arr) = v.get("includePaths").and_then(|x| x.as_array()) {
269 cfg.include_paths = arr
270 .iter()
271 .filter_map(|x| x.as_str().map(str::to_string))
272 .collect();
273 }
274 if let Some(diag_val) = v.get("diagnostics") {
275 cfg.diagnostics = DiagnosticsConfig::from_value(diag_val);
276 }
277 if let Some(feat_val) = v.get("features") {
278 cfg.features = FeaturesConfig::from_value(feat_val);
279 }
280 if let Some(n) = v.get("maxIndexedFiles").and_then(|x| x.as_u64()) {
281 cfg.max_indexed_files = n as usize;
282 }
283 cfg
284 }
285}