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 References,
132 Format,
134 Rename,
136 SignatureHelp,
138 InlayHints,
140 FoldingRange,
142 SemanticTokens,
144 DocumentHighlight,
146}
147
148impl LspFeature {
149 pub fn is_merged(&self) -> bool {
153 matches!(
154 self,
155 LspFeature::Diagnostics
156 | LspFeature::Completion
157 | LspFeature::CodeAction
158 | LspFeature::DocumentSymbols
159 | LspFeature::WorkspaceSymbols
160 )
161 }
162}
163
164#[derive(Debug, Clone, Default, PartialEq, Eq)]
170pub enum FeatureFilter {
171 #[default]
172 All,
173 Only(HashSet<LspFeature>),
174 Except(HashSet<LspFeature>),
175}
176
177impl FeatureFilter {
178 pub fn allows(&self, feature: LspFeature) -> bool {
180 match self {
181 FeatureFilter::All => true,
182 FeatureFilter::Only(set) => set.contains(&feature),
183 FeatureFilter::Except(set) => !set.contains(&feature),
184 }
185 }
186
187 pub fn from_config(
189 only: &Option<Vec<LspFeature>>,
190 except: &Option<Vec<LspFeature>>,
191 ) -> FeatureFilter {
192 match (only, except) {
193 (Some(only), _) => FeatureFilter::Only(only.iter().copied().collect()),
194 (_, Some(except)) => FeatureFilter::Except(except.iter().copied().collect()),
195 _ => FeatureFilter::All,
196 }
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(untagged)]
209pub enum LspLanguageConfig {
210 Multi(Vec<LspServerConfig>),
212 Single(Box<LspServerConfig>),
214}
215
216impl JsonSchema for LspLanguageConfig {
220 fn schema_name() -> std::borrow::Cow<'static, str> {
221 std::borrow::Cow::Borrowed("LspLanguageConfig")
222 }
223
224 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
225 schemars::json_schema!({
226 "description": "One or more LSP server configs for this language.\nAccepts both a single object and an array for backwards compatibility.",
227 "type": "array",
228 "items": generator.subschema_for::<LspServerConfig>()
229 })
230 }
231}
232
233impl LspLanguageConfig {
234 pub fn into_vec(self) -> Vec<LspServerConfig> {
236 match self {
237 LspLanguageConfig::Single(c) => vec![*c],
238 LspLanguageConfig::Multi(v) => v,
239 }
240 }
241
242 pub fn as_slice(&self) -> &[LspServerConfig] {
244 match self {
245 LspLanguageConfig::Single(c) => std::slice::from_ref(c.as_ref()),
246 LspLanguageConfig::Multi(v) => v,
247 }
248 }
249
250 pub fn as_mut_slice(&mut self) -> &mut [LspServerConfig] {
252 match self {
253 LspLanguageConfig::Single(c) => std::slice::from_mut(c),
254 LspLanguageConfig::Multi(v) => v,
255 }
256 }
257}
258
259impl Default for LspLanguageConfig {
260 fn default() -> Self {
261 LspLanguageConfig::Single(Box::default())
262 }
263}
264
265#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
267#[schemars(extend("x-display-field" = "/command"))]
268pub struct LspServerConfig {
269 #[serde(default)]
272 #[schemars(extend("x-order" = 1))]
273 pub command: String,
274
275 #[serde(default = "default_true")]
277 #[schemars(extend("x-order" = 2))]
278 pub enabled: bool,
279
280 #[serde(default)]
283 #[schemars(extend("x-order" = 3))]
284 pub name: Option<String>,
285
286 #[serde(default)]
288 #[schemars(extend("x-order" = 4))]
289 pub args: Vec<String>,
290
291 #[serde(default = "default_true")]
296 #[schemars(extend("x-order" = 5))]
297 pub auto_start: bool,
298
299 #[serde(default)]
308 #[schemars(extend("x-order" = 6))]
309 pub root_markers: Vec<String>,
310
311 #[serde(default)]
314 #[schemars(extend("x-section" = "Advanced", "x-order" = 10))]
315 pub env: HashMap<String, String>,
316
317 #[serde(default)]
321 #[schemars(extend("x-section" = "Advanced", "x-order" = 11))]
322 pub language_id_overrides: HashMap<String, String>,
323
324 #[serde(default)]
327 #[schemars(extend("x-section" = "Advanced", "x-order" = 12))]
328 pub initialization_options: Option<serde_json::Value>,
329
330 #[serde(default)]
333 #[schemars(extend("x-section" = "Advanced", "x-order" = 13))]
334 pub only_features: Option<Vec<LspFeature>>,
335
336 #[serde(default)]
339 #[schemars(extend("x-section" = "Advanced", "x-order" = 14))]
340 pub except_features: Option<Vec<LspFeature>>,
341
342 #[serde(default)]
344 #[schemars(
345 default = "process_limits_schema_default",
346 extend("x-section" = "Advanced", "x-order" = 15)
347 )]
348 pub process_limits: ProcessLimits,
349}
350
351impl LspServerConfig {
352 pub fn display_name(&self) -> String {
359 if let Some(ref name) = self.name {
360 return name.clone();
361 }
362 std::path::Path::new(&self.command)
364 .file_name()
365 .and_then(|n| n.to_str())
366 .unwrap_or(&self.command)
367 .to_string()
368 }
369
370 pub fn feature_filter(&self) -> FeatureFilter {
372 FeatureFilter::from_config(&self.only_features, &self.except_features)
373 }
374
375 pub fn merge_with_defaults(self, defaults: &LspServerConfig) -> LspServerConfig {
380 LspServerConfig {
381 name: self.name.or_else(|| defaults.name.clone()),
382 command: if self.command.is_empty() {
383 defaults.command.clone()
384 } else {
385 self.command
386 },
387 args: if self.args.is_empty() {
388 defaults.args.clone()
389 } else {
390 self.args
391 },
392 enabled: self.enabled,
393 auto_start: self.auto_start,
394 process_limits: self.process_limits,
395 only_features: self
396 .only_features
397 .or_else(|| defaults.only_features.clone()),
398 except_features: self
399 .except_features
400 .or_else(|| defaults.except_features.clone()),
401 initialization_options: self
402 .initialization_options
403 .or_else(|| defaults.initialization_options.clone()),
404 env: {
405 let mut merged = defaults.env.clone();
406 merged.extend(self.env);
407 merged
408 },
409 language_id_overrides: {
410 let mut merged = defaults.language_id_overrides.clone();
411 merged.extend(self.language_id_overrides);
412 merged
413 },
414 root_markers: if self.root_markers.is_empty() {
415 defaults.root_markers.clone()
416 } else {
417 self.root_markers
418 },
419 }
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426 use std::collections::HashSet;
427
428 #[test]
429 fn test_lsp_feature_is_merged() {
430 assert!(LspFeature::Diagnostics.is_merged());
431 assert!(LspFeature::Completion.is_merged());
432 assert!(LspFeature::CodeAction.is_merged());
433 assert!(LspFeature::DocumentSymbols.is_merged());
434 assert!(LspFeature::WorkspaceSymbols.is_merged());
435
436 assert!(!LspFeature::Hover.is_merged());
437 assert!(!LspFeature::Definition.is_merged());
438 assert!(!LspFeature::References.is_merged());
439 assert!(!LspFeature::Format.is_merged());
440 assert!(!LspFeature::Rename.is_merged());
441 assert!(!LspFeature::SignatureHelp.is_merged());
442 assert!(!LspFeature::InlayHints.is_merged());
443 assert!(!LspFeature::FoldingRange.is_merged());
444 assert!(!LspFeature::SemanticTokens.is_merged());
445 assert!(!LspFeature::DocumentHighlight.is_merged());
446 }
447
448 #[test]
449 fn test_feature_filter_all() {
450 let filter = FeatureFilter::All;
451 assert!(filter.allows(LspFeature::Hover));
452 assert!(filter.allows(LspFeature::Diagnostics));
453 assert!(filter.allows(LspFeature::Completion));
454 assert!(filter.allows(LspFeature::Rename));
455 }
456
457 #[test]
458 fn test_feature_filter_only() {
459 let mut set = HashSet::new();
460 set.insert(LspFeature::Diagnostics);
461 set.insert(LspFeature::Completion);
462 let filter = FeatureFilter::Only(set);
463
464 assert!(filter.allows(LspFeature::Diagnostics));
465 assert!(filter.allows(LspFeature::Completion));
466 assert!(!filter.allows(LspFeature::Hover));
467 assert!(!filter.allows(LspFeature::Definition));
468 }
469
470 #[test]
471 fn test_feature_filter_except() {
472 let mut set = HashSet::new();
473 set.insert(LspFeature::Format);
474 set.insert(LspFeature::Rename);
475 let filter = FeatureFilter::Except(set);
476
477 assert!(filter.allows(LspFeature::Hover));
478 assert!(filter.allows(LspFeature::Diagnostics));
479 assert!(!filter.allows(LspFeature::Format));
480 assert!(!filter.allows(LspFeature::Rename));
481 }
482
483 #[test]
484 fn test_feature_filter_from_config_none() {
485 let filter = FeatureFilter::from_config(&None, &None);
486 assert!(matches!(filter, FeatureFilter::All));
487 }
488
489 #[test]
490 fn test_feature_filter_from_config_only() {
491 let only = Some(vec![LspFeature::Diagnostics, LspFeature::Completion]);
492 let filter = FeatureFilter::from_config(&only, &None);
493 assert!(filter.allows(LspFeature::Diagnostics));
494 assert!(filter.allows(LspFeature::Completion));
495 assert!(!filter.allows(LspFeature::Hover));
496 }
497
498 #[test]
499 fn test_feature_filter_from_config_except() {
500 let except = Some(vec![LspFeature::Format]);
501 let filter = FeatureFilter::from_config(&None, &except);
502 assert!(filter.allows(LspFeature::Hover));
503 assert!(!filter.allows(LspFeature::Format));
504 }
505
506 #[test]
507 fn test_feature_filter_default() {
508 let filter = FeatureFilter::default();
509 assert!(matches!(filter, FeatureFilter::All));
510 }
511}