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 workspace_symbols_overrides: WorkspaceSymbolFeatureOptionsOverrides,
72 completion_overrides: CompletionFeatureOptionsOverrides,
73 rename_overrides: RenameFeatureOptionsOverrides,
74}
75
76impl ServerOptions {
77 pub(crate) fn workspace_symbols_layered_over(
78 &self,
79 base: WorkspaceSymbolFeatureOptions,
80 ) -> WorkspaceSymbolFeatureOptions {
81 if self.workspace_symbols_overrides.has_overrides() {
82 self.workspace_symbols_overrides.apply_to(base)
83 } else if self.workspace_symbols != WorkspaceSymbolFeatureOptions::default() {
84 self.workspace_symbols
85 } else {
86 base
87 }
88 }
89
90 pub(crate) fn completion_layered_over(
91 &self,
92 base: CompletionFeatureOptions,
93 ) -> CompletionFeatureOptions {
94 if self.completion_overrides.has_overrides() {
95 self.completion_overrides.apply_to(base)
96 } else if self.completion != CompletionFeatureOptions::default() {
97 self.completion
98 } else {
99 base
100 }
101 }
102
103 pub(crate) fn rename_layered_over(&self, base: RenameFeatureOptions) -> RenameFeatureOptions {
104 if self.rename_overrides.has_overrides() {
105 self.rename_overrides.apply_to(base)
106 } else if self.rename != RenameFeatureOptions::default() {
107 self.rename
108 } else {
109 base
110 }
111 }
112}
113
114impl<'de> Deserialize<'de> for ServerOptions {
115 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116 where
117 D: Deserializer<'de>,
118 {
119 #[derive(Deserialize, Default)]
120 #[serde(rename_all = "camelCase")]
121 struct RawServerOptions {
122 #[serde(default)]
123 workspace_symbols: WorkspaceSymbolFeatureOptionsOverrides,
124 #[serde(default)]
125 completion: CompletionFeatureOptionsOverrides,
126 #[serde(default)]
127 rename: RenameFeatureOptionsOverrides,
128 }
129
130 let raw = RawServerOptions::deserialize(deserializer)?;
131 Ok(Self {
132 workspace_symbols: raw
133 .workspace_symbols
134 .apply_to(WorkspaceSymbolFeatureOptions::default()),
135 completion: raw.completion.apply_to(CompletionFeatureOptions::default()),
136 rename: raw.rename.apply_to(RenameFeatureOptions::default()),
137 workspace_symbols_overrides: raw.workspace_symbols,
138 completion_overrides: raw.completion,
139 rename_overrides: raw.rename,
140 })
141 }
142}
143
144#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
145#[serde(rename_all = "camelCase")]
146struct WorkspaceSymbolFeatureOptionsOverrides {
147 #[serde(default)]
148 enabled: Option<bool>,
149 #[serde(default)]
150 max_files: Option<usize>,
151}
152
153impl WorkspaceSymbolFeatureOptionsOverrides {
154 fn has_overrides(self) -> bool {
155 self.enabled.is_some() || self.max_files.is_some()
156 }
157
158 fn apply_to(self, base: WorkspaceSymbolFeatureOptions) -> WorkspaceSymbolFeatureOptions {
159 WorkspaceSymbolFeatureOptions {
160 enabled: self.enabled.unwrap_or(base.enabled),
161 max_files: self.max_files.unwrap_or(base.max_files),
162 }
163 }
164}
165
166#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "camelCase")]
169pub struct WorkspaceSymbolFeatureOptions {
170 #[serde(default = "default_workspace_symbols_enabled")]
172 pub enabled: bool,
173 #[serde(default = "default_workspace_symbols_max_files")]
175 pub max_files: usize,
176}
177
178#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
179#[serde(rename_all = "camelCase")]
180struct CompletionFeatureOptionsOverrides {
181 #[serde(default)]
182 include_runtime_names: Option<bool>,
183 #[serde(default)]
184 include_keywords: Option<bool>,
185}
186
187impl CompletionFeatureOptionsOverrides {
188 fn has_overrides(self) -> bool {
189 self.include_runtime_names.is_some() || self.include_keywords.is_some()
190 }
191
192 fn apply_to(self, base: CompletionFeatureOptions) -> CompletionFeatureOptions {
193 CompletionFeatureOptions {
194 include_runtime_names: self
195 .include_runtime_names
196 .unwrap_or(base.include_runtime_names),
197 include_keywords: self.include_keywords.unwrap_or(base.include_keywords),
198 }
199 }
200}
201
202#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
204#[serde(rename_all = "camelCase")]
205pub struct CompletionFeatureOptions {
206 #[serde(default = "default_completion_include_runtime_names")]
208 pub include_runtime_names: bool,
209 #[serde(default = "default_completion_include_keywords")]
211 pub include_keywords: bool,
212}
213
214impl Default for CompletionFeatureOptions {
215 fn default() -> Self {
216 Self {
217 include_runtime_names: true,
218 include_keywords: true,
219 }
220 }
221}
222
223#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
224#[serde(rename_all = "camelCase")]
225struct RenameFeatureOptionsOverrides {
226 #[serde(default)]
227 allow_cross_file: Option<bool>,
228}
229
230impl RenameFeatureOptionsOverrides {
231 fn has_overrides(self) -> bool {
232 self.allow_cross_file.is_some()
233 }
234
235 fn apply_to(self, base: RenameFeatureOptions) -> RenameFeatureOptions {
236 RenameFeatureOptions {
237 allow_cross_file: self.allow_cross_file.unwrap_or(base.allow_cross_file),
238 }
239 }
240}
241
242#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
244#[serde(rename_all = "camelCase")]
245pub struct RenameFeatureOptions {
246 #[serde(default)]
248 pub allow_cross_file: bool,
249}
250
251impl Default for WorkspaceSymbolFeatureOptions {
252 fn default() -> Self {
253 Self {
254 enabled: true,
255 max_files: 5000,
256 }
257 }
258}
259
260fn default_workspace_symbols_enabled() -> bool {
261 true
262}
263
264fn default_workspace_symbols_max_files() -> usize {
265 5000
266}
267
268fn default_completion_include_runtime_names() -> bool {
269 true
270}
271
272fn default_completion_include_keywords() -> bool {
273 true
274}
275
276#[derive(Debug, Deserialize, Default)]
277#[serde(rename_all = "camelCase")]
278pub(crate) struct TracingOptions {
279 pub(crate) log_file: Option<std::path::PathBuf>,
280 pub(crate) log_level: Option<logging::LogLevel>,
281}
282
283#[derive(Debug, Default)]
284pub(crate) struct AllOptions {
285 pub(crate) global: GlobalOptions,
286 pub(crate) workspace: Option<WorkspaceOptionsMap>,
287}
288
289#[derive(Debug, Deserialize, Default)]
290#[serde(rename_all = "camelCase")]
291struct InitializationOptions {
292 #[serde(default)]
293 shuck: GlobalOptions,
294 #[serde(default)]
295 workspace: Option<WorkspaceOptionsMap>,
296}
297
298impl AllOptions {
299 pub(crate) fn from_value(value: serde_json::Value, _client: &Client) -> Self {
300 if value
301 .as_object()
302 .is_some_and(|object| object.contains_key("shuck"))
303 {
304 let options =
305 serde_json::from_value::<InitializationOptions>(value).unwrap_or_default();
306 return Self {
307 global: options.shuck,
308 workspace: options.workspace,
309 };
310 }
311
312 let global = serde_json::from_value::<GlobalOptions>(value).unwrap_or_default();
313 Self {
314 global,
315 workspace: None,
316 }
317 }
318}