1use std::collections::HashMap;
7use std::collections::HashSet;
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12pub mod context_keys {
15 pub const LINE_NUMBERS: &str = "line_numbers";
16 pub const LINE_WRAP: &str = "line_wrap";
17 pub const PAGE_VIEW: &str = "page_view";
18 pub const COMPOSE_MODE: &str = "compose_mode";
20 pub const FILE_EXPLORER: &str = "file_explorer";
21 pub const MENU_BAR: &str = "menu_bar";
22 pub const FILE_EXPLORER_FOCUSED: &str = "file_explorer_focused";
23 pub const MOUSE_CAPTURE: &str = "mouse_capture";
24 pub const MOUSE_HOVER: &str = "mouse_hover";
25 pub const LSP_AVAILABLE: &str = "lsp_available";
26 pub const FILE_EXPLORER_SHOW_HIDDEN: &str = "file_explorer_show_hidden";
27 pub const FILE_EXPLORER_SHOW_GITIGNORED: &str = "file_explorer_show_gitignored";
28 pub const HAS_SELECTION: &str = "has_selection";
29 pub const CAN_COPY: &str = "can_copy";
30 pub const CAN_PASTE: &str = "can_paste";
31 pub const FORMATTER_AVAILABLE: &str = "formatter_available";
32 pub const INLAY_HINTS: &str = "inlay_hints";
33 pub const SESSION_MODE: &str = "session_mode";
34 pub const VERTICAL_SCROLLBAR: &str = "vertical_scrollbar";
35 pub const HORIZONTAL_SCROLLBAR: &str = "horizontal_scrollbar";
36 pub const SCROLL_SYNC: &str = "scroll_sync";
37 pub const HAS_SAME_BUFFER_SPLITS: &str = "has_same_buffer_splits";
38 pub const KEYMAP_DEFAULT: &str = "keymap_default";
39 pub const KEYMAP_EMACS: &str = "keymap_emacs";
40 pub const KEYMAP_VSCODE: &str = "keymap_vscode";
41 pub const KEYMAP_MACOS_GUI: &str = "keymap_macos_gui";
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
46pub struct ProcessLimits {
47 #[serde(default)]
50 pub max_memory_percent: Option<u32>,
51
52 #[serde(default)]
55 pub max_cpu_percent: Option<u32>,
56
57 #[serde(default = "default_true")]
59 pub enabled: bool,
60}
61
62fn default_true() -> bool {
63 true
64}
65
66impl Default for ProcessLimits {
67 fn default() -> Self {
68 Self {
69 max_memory_percent: Some(50), max_cpu_percent: Some(90), enabled: cfg!(target_os = "linux"), }
73 }
74}
75
76impl ProcessLimits {
77 pub fn unlimited() -> Self {
79 Self {
80 max_memory_percent: None,
81 max_cpu_percent: None,
82 enabled: false,
83 }
84 }
85
86 pub fn default_cpu_limit_percent() -> u32 {
88 90
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
98#[serde(rename_all = "snake_case")]
99pub enum LspFeature {
100 Diagnostics,
102 Completion,
104 CodeAction,
106 DocumentSymbols,
108 WorkspaceSymbols,
110 Hover,
112 Definition,
114 References,
116 Format,
118 Rename,
120 SignatureHelp,
122 InlayHints,
124 FoldingRange,
126 SemanticTokens,
128 DocumentHighlight,
130}
131
132impl LspFeature {
133 pub fn is_merged(&self) -> bool {
137 matches!(
138 self,
139 LspFeature::Diagnostics
140 | LspFeature::Completion
141 | LspFeature::CodeAction
142 | LspFeature::DocumentSymbols
143 | LspFeature::WorkspaceSymbols
144 )
145 }
146}
147
148#[derive(Debug, Clone, Default, PartialEq, Eq)]
154pub enum FeatureFilter {
155 #[default]
156 All,
157 Only(HashSet<LspFeature>),
158 Except(HashSet<LspFeature>),
159}
160
161impl FeatureFilter {
162 pub fn allows(&self, feature: LspFeature) -> bool {
164 match self {
165 FeatureFilter::All => true,
166 FeatureFilter::Only(set) => set.contains(&feature),
167 FeatureFilter::Except(set) => !set.contains(&feature),
168 }
169 }
170
171 pub fn from_config(
173 only: &Option<Vec<LspFeature>>,
174 except: &Option<Vec<LspFeature>>,
175 ) -> FeatureFilter {
176 match (only, except) {
177 (Some(only), _) => FeatureFilter::Only(only.iter().copied().collect()),
178 (_, Some(except)) => FeatureFilter::Except(except.iter().copied().collect()),
179 _ => FeatureFilter::All,
180 }
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(untagged)]
193pub enum LspLanguageConfig {
194 Multi(Vec<LspServerConfig>),
196 Single(Box<LspServerConfig>),
198}
199
200impl JsonSchema for LspLanguageConfig {
204 fn schema_name() -> std::borrow::Cow<'static, str> {
205 std::borrow::Cow::Borrowed("LspLanguageConfig")
206 }
207
208 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
209 schemars::json_schema!({
210 "description": "One or more LSP server configs for this language.\nAccepts both a single object and an array for backwards compatibility.",
211 "type": "array",
212 "items": generator.subschema_for::<LspServerConfig>()
213 })
214 }
215}
216
217impl LspLanguageConfig {
218 pub fn into_vec(self) -> Vec<LspServerConfig> {
220 match self {
221 LspLanguageConfig::Single(c) => vec![*c],
222 LspLanguageConfig::Multi(v) => v,
223 }
224 }
225
226 pub fn as_slice(&self) -> &[LspServerConfig] {
228 match self {
229 LspLanguageConfig::Single(c) => std::slice::from_ref(c.as_ref()),
230 LspLanguageConfig::Multi(v) => v,
231 }
232 }
233
234 pub fn as_mut_slice(&mut self) -> &mut [LspServerConfig] {
236 match self {
237 LspLanguageConfig::Single(c) => std::slice::from_mut(c),
238 LspLanguageConfig::Multi(v) => v,
239 }
240 }
241}
242
243impl Default for LspLanguageConfig {
244 fn default() -> Self {
245 LspLanguageConfig::Single(Box::default())
246 }
247}
248
249#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
251#[schemars(extend("x-display-field" = "/command"))]
252pub struct LspServerConfig {
253 #[serde(default)]
256 #[schemars(extend("x-order" = 1))]
257 pub command: String,
258
259 #[serde(default = "default_true")]
261 #[schemars(extend("x-order" = 2))]
262 pub enabled: bool,
263
264 #[serde(default)]
267 #[schemars(extend("x-order" = 3))]
268 pub name: Option<String>,
269
270 #[serde(default)]
272 #[schemars(extend("x-order" = 4))]
273 pub args: Vec<String>,
274
275 #[serde(default)]
278 #[schemars(extend("x-order" = 5))]
279 pub auto_start: bool,
280
281 #[serde(default)]
290 #[schemars(extend("x-order" = 6))]
291 pub root_markers: Vec<String>,
292
293 #[serde(default)]
296 #[schemars(extend("x-section" = "Advanced", "x-order" = 10))]
297 pub env: HashMap<String, String>,
298
299 #[serde(default)]
303 #[schemars(extend("x-section" = "Advanced", "x-order" = 11))]
304 pub language_id_overrides: HashMap<String, String>,
305
306 #[serde(default)]
309 #[schemars(extend("x-section" = "Advanced", "x-order" = 12))]
310 pub initialization_options: Option<serde_json::Value>,
311
312 #[serde(default)]
315 #[schemars(extend("x-section" = "Advanced", "x-order" = 13))]
316 pub only_features: Option<Vec<LspFeature>>,
317
318 #[serde(default)]
321 #[schemars(extend("x-section" = "Advanced", "x-order" = 14))]
322 pub except_features: Option<Vec<LspFeature>>,
323
324 #[serde(default)]
326 #[schemars(extend("x-section" = "Advanced", "x-order" = 15))]
327 pub process_limits: ProcessLimits,
328}
329
330impl LspServerConfig {
331 pub fn display_name(&self) -> String {
338 if let Some(ref name) = self.name {
339 return name.clone();
340 }
341 std::path::Path::new(&self.command)
343 .file_name()
344 .and_then(|n| n.to_str())
345 .unwrap_or(&self.command)
346 .to_string()
347 }
348
349 pub fn feature_filter(&self) -> FeatureFilter {
351 FeatureFilter::from_config(&self.only_features, &self.except_features)
352 }
353
354 pub fn merge_with_defaults(self, defaults: &LspServerConfig) -> LspServerConfig {
359 LspServerConfig {
360 name: self.name.or_else(|| defaults.name.clone()),
361 command: if self.command.is_empty() {
362 defaults.command.clone()
363 } else {
364 self.command
365 },
366 args: if self.args.is_empty() {
367 defaults.args.clone()
368 } else {
369 self.args
370 },
371 enabled: self.enabled,
372 auto_start: self.auto_start,
373 process_limits: self.process_limits,
374 only_features: self
375 .only_features
376 .or_else(|| defaults.only_features.clone()),
377 except_features: self
378 .except_features
379 .or_else(|| defaults.except_features.clone()),
380 initialization_options: self
381 .initialization_options
382 .or_else(|| defaults.initialization_options.clone()),
383 env: {
384 let mut merged = defaults.env.clone();
385 merged.extend(self.env);
386 merged
387 },
388 language_id_overrides: {
389 let mut merged = defaults.language_id_overrides.clone();
390 merged.extend(self.language_id_overrides);
391 merged
392 },
393 root_markers: if self.root_markers.is_empty() {
394 defaults.root_markers.clone()
395 } else {
396 self.root_markers
397 },
398 }
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use std::collections::HashSet;
406
407 #[test]
408 fn test_lsp_feature_is_merged() {
409 assert!(LspFeature::Diagnostics.is_merged());
410 assert!(LspFeature::Completion.is_merged());
411 assert!(LspFeature::CodeAction.is_merged());
412 assert!(LspFeature::DocumentSymbols.is_merged());
413 assert!(LspFeature::WorkspaceSymbols.is_merged());
414
415 assert!(!LspFeature::Hover.is_merged());
416 assert!(!LspFeature::Definition.is_merged());
417 assert!(!LspFeature::References.is_merged());
418 assert!(!LspFeature::Format.is_merged());
419 assert!(!LspFeature::Rename.is_merged());
420 assert!(!LspFeature::SignatureHelp.is_merged());
421 assert!(!LspFeature::InlayHints.is_merged());
422 assert!(!LspFeature::FoldingRange.is_merged());
423 assert!(!LspFeature::SemanticTokens.is_merged());
424 assert!(!LspFeature::DocumentHighlight.is_merged());
425 }
426
427 #[test]
428 fn test_feature_filter_all() {
429 let filter = FeatureFilter::All;
430 assert!(filter.allows(LspFeature::Hover));
431 assert!(filter.allows(LspFeature::Diagnostics));
432 assert!(filter.allows(LspFeature::Completion));
433 assert!(filter.allows(LspFeature::Rename));
434 }
435
436 #[test]
437 fn test_feature_filter_only() {
438 let mut set = HashSet::new();
439 set.insert(LspFeature::Diagnostics);
440 set.insert(LspFeature::Completion);
441 let filter = FeatureFilter::Only(set);
442
443 assert!(filter.allows(LspFeature::Diagnostics));
444 assert!(filter.allows(LspFeature::Completion));
445 assert!(!filter.allows(LspFeature::Hover));
446 assert!(!filter.allows(LspFeature::Definition));
447 }
448
449 #[test]
450 fn test_feature_filter_except() {
451 let mut set = HashSet::new();
452 set.insert(LspFeature::Format);
453 set.insert(LspFeature::Rename);
454 let filter = FeatureFilter::Except(set);
455
456 assert!(filter.allows(LspFeature::Hover));
457 assert!(filter.allows(LspFeature::Diagnostics));
458 assert!(!filter.allows(LspFeature::Format));
459 assert!(!filter.allows(LspFeature::Rename));
460 }
461
462 #[test]
463 fn test_feature_filter_from_config_none() {
464 let filter = FeatureFilter::from_config(&None, &None);
465 assert!(matches!(filter, FeatureFilter::All));
466 }
467
468 #[test]
469 fn test_feature_filter_from_config_only() {
470 let only = Some(vec![LspFeature::Diagnostics, LspFeature::Completion]);
471 let filter = FeatureFilter::from_config(&only, &None);
472 assert!(filter.allows(LspFeature::Diagnostics));
473 assert!(filter.allows(LspFeature::Completion));
474 assert!(!filter.allows(LspFeature::Hover));
475 }
476
477 #[test]
478 fn test_feature_filter_from_config_except() {
479 let except = Some(vec![LspFeature::Format]);
480 let filter = FeatureFilter::from_config(&None, &except);
481 assert!(filter.allows(LspFeature::Hover));
482 assert!(!filter.allows(LspFeature::Format));
483 }
484
485 #[test]
486 fn test_feature_filter_default() {
487 let filter = FeatureFilter::default();
488 assert!(matches!(filter, FeatureFilter::All));
489 }
490}