1use lsp_types::Url;
2use rustc_hash::FxHashMap;
3use serde::{Deserialize, Deserializer};
4use shuck_config::{FormatConfig, LintConfig, ShuckConfig};
5
6use crate::session::settings::GlobalClientSettings;
7use crate::{Client, logging};
8
9pub(crate) type WorkspaceOptionsMap = FxHashMap<Url, ClientOptions>;
10
11#[derive(Debug, Deserialize, Default)]
13#[serde(rename_all = "camelCase")]
14pub struct GlobalOptions {
15 #[serde(flatten)]
16 client: ClientOptions,
17 #[serde(default)]
18 pub(crate) tracing: TracingOptions,
19}
20
21impl GlobalOptions {
22 pub fn into_settings(self, client: Client) -> GlobalClientSettings {
24 GlobalClientSettings::new(self.client, client)
25 }
26}
27
28#[derive(Clone, Debug, Default, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct ClientOptions {
32 #[serde(default)]
33 pub lint: Option<LintConfig>,
35 #[serde(default)]
36 pub format: Option<FormatConfig>,
38 #[serde(default)]
39 pub fix_all: Option<bool>,
41 #[serde(default)]
42 pub unsafe_fixes: Option<bool>,
44 #[serde(default)]
45 pub show_syntax_errors: Option<bool>,
47 #[serde(default)]
48 pub server: ServerOptions,
50}
51
52impl ClientOptions {
53 pub(crate) fn to_config_overrides(&self) -> ShuckConfig {
54 ShuckConfig {
55 lint: self.lint.clone().unwrap_or_default(),
56 format: self.format.clone().unwrap_or_default(),
57 ..ShuckConfig::default()
58 }
59 }
60}
61
62#[derive(Clone, Debug, Default, PartialEq, Eq)]
64pub struct ServerOptions {
65 pub workspace_symbols: WorkspaceSymbolFeatureOptions,
67 pub completion: CompletionFeatureOptions,
69 pub rename: RenameFeatureOptions,
71 pub call_hierarchy: CallHierarchyFeatureOptions,
73 workspace_symbols_overrides: WorkspaceSymbolFeatureOptionsOverrides,
74 completion_overrides: CompletionFeatureOptionsOverrides,
75 rename_overrides: RenameFeatureOptionsOverrides,
76 call_hierarchy_overrides: CallHierarchyFeatureOptionsOverrides,
77}
78
79impl ServerOptions {
80 pub(crate) fn workspace_symbols_layered_over(
81 &self,
82 base: WorkspaceSymbolFeatureOptions,
83 ) -> WorkspaceSymbolFeatureOptions {
84 if self.workspace_symbols_overrides.has_overrides() {
85 self.workspace_symbols_overrides.apply_to(base)
86 } else if self.workspace_symbols != WorkspaceSymbolFeatureOptions::default() {
87 self.workspace_symbols
88 } else {
89 base
90 }
91 }
92
93 pub(crate) fn completion_layered_over(
94 &self,
95 base: CompletionFeatureOptions,
96 ) -> CompletionFeatureOptions {
97 if self.completion_overrides.has_overrides() {
98 self.completion_overrides.apply_to(base)
99 } else if self.completion != CompletionFeatureOptions::default() {
100 self.completion
101 } else {
102 base
103 }
104 }
105
106 pub(crate) fn rename_layered_over(&self, base: RenameFeatureOptions) -> RenameFeatureOptions {
107 if self.rename_overrides.has_overrides() {
108 self.rename_overrides.apply_to(base)
109 } else if self.rename != RenameFeatureOptions::default() {
110 self.rename
111 } else {
112 base
113 }
114 }
115
116 pub(crate) fn call_hierarchy_layered_over(
117 &self,
118 base: CallHierarchyFeatureOptions,
119 ) -> CallHierarchyFeatureOptions {
120 if self.call_hierarchy_overrides.has_overrides() {
121 self.call_hierarchy_overrides.apply_to(base)
122 } else if self.call_hierarchy != CallHierarchyFeatureOptions::default() {
123 self.call_hierarchy
124 } else {
125 base
126 }
127 }
128}
129
130impl<'de> Deserialize<'de> for ServerOptions {
131 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
132 where
133 D: Deserializer<'de>,
134 {
135 #[derive(Deserialize, Default)]
136 #[serde(rename_all = "camelCase")]
137 struct RawServerOptions {
138 #[serde(default)]
139 workspace_symbols: WorkspaceSymbolFeatureOptionsOverrides,
140 #[serde(default)]
141 completion: CompletionFeatureOptionsOverrides,
142 #[serde(default)]
143 rename: RenameFeatureOptionsOverrides,
144 #[serde(default)]
145 call_hierarchy: CallHierarchyFeatureOptionsOverrides,
146 }
147
148 let raw = RawServerOptions::deserialize(deserializer)?;
149 Ok(Self {
150 workspace_symbols: raw
151 .workspace_symbols
152 .apply_to(WorkspaceSymbolFeatureOptions::default()),
153 completion: raw.completion.apply_to(CompletionFeatureOptions::default()),
154 rename: raw.rename.apply_to(RenameFeatureOptions::default()),
155 call_hierarchy: raw
156 .call_hierarchy
157 .apply_to(CallHierarchyFeatureOptions::default()),
158 workspace_symbols_overrides: raw.workspace_symbols,
159 completion_overrides: raw.completion,
160 rename_overrides: raw.rename,
161 call_hierarchy_overrides: raw.call_hierarchy,
162 })
163 }
164}
165
166#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
167#[serde(rename_all = "camelCase")]
168struct WorkspaceSymbolFeatureOptionsOverrides {
169 #[serde(default)]
170 enabled: Option<bool>,
171 #[serde(default)]
172 max_files: Option<usize>,
173}
174
175impl WorkspaceSymbolFeatureOptionsOverrides {
176 fn has_overrides(self) -> bool {
177 self.enabled.is_some() || self.max_files.is_some()
178 }
179
180 fn apply_to(self, base: WorkspaceSymbolFeatureOptions) -> WorkspaceSymbolFeatureOptions {
181 WorkspaceSymbolFeatureOptions {
182 enabled: self.enabled.unwrap_or(base.enabled),
183 max_files: self.max_files.unwrap_or(base.max_files),
184 }
185 }
186}
187
188#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
190#[serde(rename_all = "camelCase")]
191pub struct WorkspaceSymbolFeatureOptions {
192 #[serde(default = "default_workspace_symbols_enabled")]
194 pub enabled: bool,
195 #[serde(default = "default_workspace_symbols_max_files")]
197 pub max_files: usize,
198}
199
200#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
201#[serde(rename_all = "camelCase")]
202struct CompletionFeatureOptionsOverrides {
203 #[serde(default)]
204 include_runtime_names: Option<bool>,
205 #[serde(default)]
206 include_keywords: Option<bool>,
207}
208
209impl CompletionFeatureOptionsOverrides {
210 fn has_overrides(self) -> bool {
211 self.include_runtime_names.is_some() || self.include_keywords.is_some()
212 }
213
214 fn apply_to(self, base: CompletionFeatureOptions) -> CompletionFeatureOptions {
215 CompletionFeatureOptions {
216 include_runtime_names: self
217 .include_runtime_names
218 .unwrap_or(base.include_runtime_names),
219 include_keywords: self.include_keywords.unwrap_or(base.include_keywords),
220 }
221 }
222}
223
224#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
226#[serde(rename_all = "camelCase")]
227pub struct CompletionFeatureOptions {
228 #[serde(default = "default_completion_include_runtime_names")]
230 pub include_runtime_names: bool,
231 #[serde(default = "default_completion_include_keywords")]
233 pub include_keywords: bool,
234}
235
236impl Default for CompletionFeatureOptions {
237 fn default() -> Self {
238 Self {
239 include_runtime_names: true,
240 include_keywords: true,
241 }
242 }
243}
244
245#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
246#[serde(rename_all = "camelCase")]
247struct RenameFeatureOptionsOverrides {
248 #[serde(default)]
249 allow_cross_file: Option<bool>,
250}
251
252impl RenameFeatureOptionsOverrides {
253 fn has_overrides(self) -> bool {
254 self.allow_cross_file.is_some()
255 }
256
257 fn apply_to(self, base: RenameFeatureOptions) -> RenameFeatureOptions {
258 RenameFeatureOptions {
259 allow_cross_file: self.allow_cross_file.unwrap_or(base.allow_cross_file),
260 }
261 }
262}
263
264#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
266#[serde(rename_all = "camelCase")]
267pub struct RenameFeatureOptions {
268 #[serde(default)]
270 pub allow_cross_file: bool,
271}
272
273impl Default for WorkspaceSymbolFeatureOptions {
274 fn default() -> Self {
275 Self {
276 enabled: true,
277 max_files: 5000,
278 }
279 }
280}
281
282fn default_workspace_symbols_enabled() -> bool {
283 true
284}
285
286#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
287#[serde(rename_all = "camelCase")]
288struct CallHierarchyFeatureOptionsOverrides {
289 #[serde(default)]
290 max_files: Option<usize>,
291}
292
293impl CallHierarchyFeatureOptionsOverrides {
294 fn has_overrides(self) -> bool {
295 self.max_files.is_some()
296 }
297
298 fn apply_to(self, base: CallHierarchyFeatureOptions) -> CallHierarchyFeatureOptions {
299 CallHierarchyFeatureOptions {
300 max_files: self.max_files.unwrap_or(base.max_files),
301 }
302 }
303}
304
305#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
307#[serde(rename_all = "camelCase")]
308pub struct CallHierarchyFeatureOptions {
309 #[serde(default = "default_call_hierarchy_max_files")]
311 pub max_files: usize,
312}
313
314impl Default for CallHierarchyFeatureOptions {
315 fn default() -> Self {
316 Self {
317 max_files: default_call_hierarchy_max_files(),
318 }
319 }
320}
321
322fn default_call_hierarchy_max_files() -> usize {
323 10_000
324}
325
326fn default_workspace_symbols_max_files() -> usize {
327 5000
328}
329
330fn default_completion_include_runtime_names() -> bool {
331 true
332}
333
334fn default_completion_include_keywords() -> bool {
335 true
336}
337
338#[derive(Debug, Deserialize, Default)]
339#[serde(rename_all = "camelCase")]
340pub(crate) struct TracingOptions {
341 pub(crate) log_file: Option<std::path::PathBuf>,
342 pub(crate) log_level: Option<logging::LogLevel>,
343}
344
345#[derive(Debug, Default)]
346pub(crate) struct AllOptions {
347 pub(crate) global: GlobalOptions,
348 pub(crate) workspace: Option<WorkspaceOptionsMap>,
349}
350
351#[derive(Debug, Deserialize, Default)]
352#[serde(rename_all = "camelCase")]
353struct InitializationOptions {
354 #[serde(default)]
355 shuck: GlobalOptions,
356 #[serde(default)]
357 workspace: Option<WorkspaceOptionsMap>,
358}
359
360impl AllOptions {
361 pub(crate) fn from_value(value: serde_json::Value, _client: &Client) -> Self {
362 if value
363 .as_object()
364 .is_some_and(|object| object.contains_key("shuck"))
365 {
366 let options =
367 serde_json::from_value::<InitializationOptions>(value).unwrap_or_default();
368 return Self {
369 global: options.shuck,
370 workspace: options.workspace,
371 };
372 }
373
374 let global = serde_json::from_value::<GlobalOptions>(value).unwrap_or_default();
375 Self {
376 global,
377 workspace: None,
378 }
379 }
380}