1use std::collections::HashMap;
7use std::collections::HashSet;
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12pub mod context_keys {
15 pub const HAS_BUFFER: &str = "has_buffer";
21 pub const LINE_NUMBERS: &str = "line_numbers";
22 pub const LINE_WRAP: &str = "line_wrap";
23 pub const PAGE_VIEW: &str = "page_view";
24 pub const COMPOSE_MODE: &str = "compose_mode";
26 pub const FILE_EXPLORER: &str = "file_explorer";
27 pub const MENU_BAR: &str = "menu_bar";
28 pub const FILE_EXPLORER_FOCUSED: &str = "file_explorer_focused";
29 pub const MOUSE_CAPTURE: &str = "mouse_capture";
30 pub const MOUSE_HOVER: &str = "mouse_hover";
31 pub const LSP_AVAILABLE: &str = "lsp_available";
32 pub const FILE_EXPLORER_SHOW_HIDDEN: &str = "file_explorer_show_hidden";
33 pub const FILE_EXPLORER_SHOW_GITIGNORED: &str = "file_explorer_show_gitignored";
34 pub const HAS_SELECTION: &str = "has_selection";
35 pub const CAN_COPY: &str = "can_copy";
36 pub const CAN_PASTE: &str = "can_paste";
37 pub const FORMATTER_AVAILABLE: &str = "formatter_available";
38 pub const INLAY_HINTS: &str = "inlay_hints";
39 pub const SESSION_MODE: &str = "session_mode";
40 pub const VERTICAL_SCROLLBAR: &str = "vertical_scrollbar";
41 pub const HORIZONTAL_SCROLLBAR: &str = "horizontal_scrollbar";
42 pub const SCROLL_SYNC: &str = "scroll_sync";
43 pub const HAS_SAME_BUFFER_SPLITS: &str = "has_same_buffer_splits";
44 pub const KEYMAP_DEFAULT: &str = "keymap_default";
45 pub const KEYMAP_EMACS: &str = "keymap_emacs";
46 pub const KEYMAP_VSCODE: &str = "keymap_vscode";
47 pub const KEYMAP_MACOS_GUI: &str = "keymap_macos_gui";
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
52pub struct ProcessLimits {
53 #[serde(default)]
56 pub max_memory_percent: Option<u32>,
57
58 #[serde(default)]
61 pub max_cpu_percent: Option<u32>,
62
63 #[serde(default = "default_true")]
65 pub enabled: bool,
66}
67
68fn default_true() -> bool {
69 true
70}
71
72fn process_limits_schema_default() -> ProcessLimits {
75 ProcessLimits {
76 max_memory_percent: Some(50),
77 max_cpu_percent: Some(90),
78 enabled: true,
79 }
80}
81
82impl Default for ProcessLimits {
83 fn default() -> Self {
84 Self {
85 max_memory_percent: Some(50), max_cpu_percent: Some(90), enabled: cfg!(target_os = "linux"), }
89 }
90}
91
92impl ProcessLimits {
93 pub fn unlimited() -> Self {
95 Self {
96 max_memory_percent: None,
97 max_cpu_percent: None,
98 enabled: false,
99 }
100 }
101
102 pub fn default_cpu_limit_percent() -> u32 {
104 90
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
114#[serde(rename_all = "snake_case")]
115pub enum LspFeature {
116 Diagnostics,
118 Completion,
120 CodeAction,
122 DocumentSymbols,
124 WorkspaceSymbols,
126 Hover,
128 Definition,
130 Implementation,
132 References,
134 Format,
136 Rename,
138 SignatureHelp,
140 InlayHints,
142 FoldingRange,
144 SemanticTokens,
146 DocumentHighlight,
148}
149
150impl LspFeature {
151 pub fn is_merged(&self) -> bool {
155 matches!(
156 self,
157 LspFeature::Diagnostics
158 | LspFeature::Completion
159 | LspFeature::CodeAction
160 | LspFeature::DocumentSymbols
161 | LspFeature::WorkspaceSymbols
162 )
163 }
164}
165
166#[derive(Debug, Clone, Default, PartialEq, Eq)]
172pub enum FeatureFilter {
173 #[default]
174 All,
175 Only(HashSet<LspFeature>),
176 Except(HashSet<LspFeature>),
177}
178
179impl FeatureFilter {
180 pub fn allows(&self, feature: LspFeature) -> bool {
182 match self {
183 FeatureFilter::All => true,
184 FeatureFilter::Only(set) => set.contains(&feature),
185 FeatureFilter::Except(set) => !set.contains(&feature),
186 }
187 }
188
189 pub fn from_config(
191 only: &Option<Vec<LspFeature>>,
192 except: &Option<Vec<LspFeature>>,
193 ) -> FeatureFilter {
194 match (only, except) {
195 (Some(only), _) => FeatureFilter::Only(only.iter().copied().collect()),
196 (_, Some(except)) => FeatureFilter::Except(except.iter().copied().collect()),
197 _ => FeatureFilter::All,
198 }
199 }
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(untagged)]
211pub enum LspLanguageConfig {
212 Multi(Vec<LspServerConfig>),
214 Single(Box<LspServerConfig>),
216}
217
218impl JsonSchema for LspLanguageConfig {
222 fn schema_name() -> std::borrow::Cow<'static, str> {
223 std::borrow::Cow::Borrowed("LspLanguageConfig")
224 }
225
226 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
227 schemars::json_schema!({
228 "description": "One or more LSP server configs for this language.\nAccepts both a single object and an array for backwards compatibility.",
229 "type": "array",
230 "items": generator.subschema_for::<LspServerConfig>()
231 })
232 }
233}
234
235impl LspLanguageConfig {
236 pub fn into_vec(self) -> Vec<LspServerConfig> {
238 match self {
239 LspLanguageConfig::Single(c) => vec![*c],
240 LspLanguageConfig::Multi(v) => v,
241 }
242 }
243
244 pub fn as_slice(&self) -> &[LspServerConfig] {
246 match self {
247 LspLanguageConfig::Single(c) => std::slice::from_ref(c.as_ref()),
248 LspLanguageConfig::Multi(v) => v,
249 }
250 }
251
252 pub fn as_mut_slice(&mut self) -> &mut [LspServerConfig] {
254 match self {
255 LspLanguageConfig::Single(c) => std::slice::from_mut(c),
256 LspLanguageConfig::Multi(v) => v,
257 }
258 }
259}
260
261impl Default for LspLanguageConfig {
262 fn default() -> Self {
263 LspLanguageConfig::Single(Box::default())
264 }
265}
266
267#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
269#[schemars(extend("x-display-field" = "/command"))]
270pub struct LspServerConfig {
271 #[serde(default)]
274 #[schemars(extend("x-order" = 1))]
275 pub command: String,
276
277 #[serde(default = "default_true")]
279 #[schemars(extend("x-order" = 2))]
280 pub enabled: bool,
281
282 #[serde(default)]
285 #[schemars(extend("x-order" = 3))]
286 pub name: Option<String>,
287
288 #[serde(default)]
290 #[schemars(extend("x-order" = 4))]
291 pub args: Vec<String>,
292
293 #[serde(default = "default_true")]
298 #[schemars(extend("x-order" = 5))]
299 pub auto_start: bool,
300
301 #[serde(default)]
310 #[schemars(extend("x-order" = 6))]
311 pub root_markers: Vec<String>,
312
313 #[serde(default)]
316 #[schemars(extend("x-section" = "Advanced", "x-order" = 10))]
317 pub env: HashMap<String, String>,
318
319 #[serde(default)]
323 #[schemars(extend("x-section" = "Advanced", "x-order" = 11))]
324 pub language_id_overrides: HashMap<String, String>,
325
326 #[serde(default)]
329 #[schemars(extend("x-section" = "Advanced", "x-order" = 12))]
330 pub initialization_options: Option<serde_json::Value>,
331
332 #[serde(default)]
335 #[schemars(extend("x-section" = "Advanced", "x-order" = 13))]
336 pub only_features: Option<Vec<LspFeature>>,
337
338 #[serde(default)]
341 #[schemars(extend("x-section" = "Advanced", "x-order" = 14))]
342 pub except_features: Option<Vec<LspFeature>>,
343
344 #[serde(default)]
346 #[schemars(
347 default = "process_limits_schema_default",
348 extend("x-section" = "Advanced", "x-order" = 15)
349 )]
350 pub process_limits: ProcessLimits,
351}
352
353impl LspServerConfig {
354 pub fn display_name(&self) -> String {
361 if let Some(ref name) = self.name {
362 return name.clone();
363 }
364 std::path::Path::new(&self.command)
366 .file_name()
367 .and_then(|n| n.to_str())
368 .unwrap_or(&self.command)
369 .to_string()
370 }
371
372 pub fn feature_filter(&self) -> FeatureFilter {
374 FeatureFilter::from_config(&self.only_features, &self.except_features)
375 }
376
377 pub fn merge_with_defaults(self, defaults: &LspServerConfig) -> LspServerConfig {
382 LspServerConfig {
383 name: self.name.or_else(|| defaults.name.clone()),
384 command: if self.command.is_empty() {
385 defaults.command.clone()
386 } else {
387 self.command
388 },
389 args: if self.args.is_empty() {
390 defaults.args.clone()
391 } else {
392 self.args
393 },
394 enabled: self.enabled,
395 auto_start: self.auto_start,
396 process_limits: self.process_limits,
397 only_features: self
398 .only_features
399 .or_else(|| defaults.only_features.clone()),
400 except_features: self
401 .except_features
402 .or_else(|| defaults.except_features.clone()),
403 initialization_options: self
404 .initialization_options
405 .or_else(|| defaults.initialization_options.clone()),
406 env: {
407 let mut merged = defaults.env.clone();
408 merged.extend(self.env);
409 merged
410 },
411 language_id_overrides: {
412 let mut merged = defaults.language_id_overrides.clone();
413 merged.extend(self.language_id_overrides);
414 merged
415 },
416 root_markers: if self.root_markers.is_empty() {
417 defaults.root_markers.clone()
418 } else {
419 self.root_markers
420 },
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use std::collections::HashSet;
429
430 #[test]
431 fn test_lsp_feature_is_merged() {
432 assert!(LspFeature::Diagnostics.is_merged());
433 assert!(LspFeature::Completion.is_merged());
434 assert!(LspFeature::CodeAction.is_merged());
435 assert!(LspFeature::DocumentSymbols.is_merged());
436 assert!(LspFeature::WorkspaceSymbols.is_merged());
437
438 assert!(!LspFeature::Hover.is_merged());
439 assert!(!LspFeature::Definition.is_merged());
440 assert!(!LspFeature::References.is_merged());
441 assert!(!LspFeature::Format.is_merged());
442 assert!(!LspFeature::Rename.is_merged());
443 assert!(!LspFeature::SignatureHelp.is_merged());
444 assert!(!LspFeature::InlayHints.is_merged());
445 assert!(!LspFeature::FoldingRange.is_merged());
446 assert!(!LspFeature::SemanticTokens.is_merged());
447 assert!(!LspFeature::DocumentHighlight.is_merged());
448 }
449
450 #[test]
451 fn test_feature_filter_all() {
452 let filter = FeatureFilter::All;
453 assert!(filter.allows(LspFeature::Hover));
454 assert!(filter.allows(LspFeature::Diagnostics));
455 assert!(filter.allows(LspFeature::Completion));
456 assert!(filter.allows(LspFeature::Rename));
457 }
458
459 #[test]
460 fn test_feature_filter_only() {
461 let mut set = HashSet::new();
462 set.insert(LspFeature::Diagnostics);
463 set.insert(LspFeature::Completion);
464 let filter = FeatureFilter::Only(set);
465
466 assert!(filter.allows(LspFeature::Diagnostics));
467 assert!(filter.allows(LspFeature::Completion));
468 assert!(!filter.allows(LspFeature::Hover));
469 assert!(!filter.allows(LspFeature::Definition));
470 }
471
472 #[test]
473 fn test_feature_filter_except() {
474 let mut set = HashSet::new();
475 set.insert(LspFeature::Format);
476 set.insert(LspFeature::Rename);
477 let filter = FeatureFilter::Except(set);
478
479 assert!(filter.allows(LspFeature::Hover));
480 assert!(filter.allows(LspFeature::Diagnostics));
481 assert!(!filter.allows(LspFeature::Format));
482 assert!(!filter.allows(LspFeature::Rename));
483 }
484
485 #[test]
486 fn test_feature_filter_from_config_none() {
487 let filter = FeatureFilter::from_config(&None, &None);
488 assert!(matches!(filter, FeatureFilter::All));
489 }
490
491 #[test]
492 fn test_feature_filter_from_config_only() {
493 let only = Some(vec![LspFeature::Diagnostics, LspFeature::Completion]);
494 let filter = FeatureFilter::from_config(&only, &None);
495 assert!(filter.allows(LspFeature::Diagnostics));
496 assert!(filter.allows(LspFeature::Completion));
497 assert!(!filter.allows(LspFeature::Hover));
498 }
499
500 #[test]
501 fn test_feature_filter_from_config_except() {
502 let except = Some(vec![LspFeature::Format]);
503 let filter = FeatureFilter::from_config(&None, &except);
504 assert!(filter.allows(LspFeature::Hover));
505 assert!(!filter.allows(LspFeature::Format));
506 }
507
508 #[test]
509 fn test_feature_filter_default() {
510 let filter = FeatureFilter::default();
511 assert!(matches!(filter, FeatureFilter::All));
512 }
513}