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 FORMATTER_AVAILABLE: &str = "formatter_available";
30 pub const INLAY_HINTS: &str = "inlay_hints";
31 pub const SESSION_MODE: &str = "session_mode";
32 pub const VERTICAL_SCROLLBAR: &str = "vertical_scrollbar";
33 pub const HORIZONTAL_SCROLLBAR: &str = "horizontal_scrollbar";
34 pub const SCROLL_SYNC: &str = "scroll_sync";
35 pub const HAS_SAME_BUFFER_SPLITS: &str = "has_same_buffer_splits";
36 pub const KEYMAP_DEFAULT: &str = "keymap_default";
37 pub const KEYMAP_EMACS: &str = "keymap_emacs";
38 pub const KEYMAP_VSCODE: &str = "keymap_vscode";
39 pub const KEYMAP_MACOS_GUI: &str = "keymap_macos_gui";
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
44pub struct ProcessLimits {
45 #[serde(default)]
48 pub max_memory_percent: Option<u32>,
49
50 #[serde(default)]
53 pub max_cpu_percent: Option<u32>,
54
55 #[serde(default = "default_true")]
57 pub enabled: bool,
58}
59
60fn default_true() -> bool {
61 true
62}
63
64impl Default for ProcessLimits {
65 fn default() -> Self {
66 Self {
67 max_memory_percent: Some(50), max_cpu_percent: Some(90), enabled: cfg!(target_os = "linux"), }
71 }
72}
73
74impl ProcessLimits {
75 pub fn unlimited() -> Self {
77 Self {
78 max_memory_percent: None,
79 max_cpu_percent: None,
80 enabled: false,
81 }
82 }
83
84 pub fn default_cpu_limit_percent() -> u32 {
86 90
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
96#[serde(rename_all = "snake_case")]
97pub enum LspFeature {
98 Diagnostics,
100 Completion,
102 CodeAction,
104 DocumentSymbols,
106 WorkspaceSymbols,
108 Hover,
110 Definition,
112 References,
114 Format,
116 Rename,
118 SignatureHelp,
120 InlayHints,
122 FoldingRange,
124 SemanticTokens,
126 DocumentHighlight,
128}
129
130impl LspFeature {
131 pub fn is_merged(&self) -> bool {
135 matches!(
136 self,
137 LspFeature::Diagnostics
138 | LspFeature::Completion
139 | LspFeature::CodeAction
140 | LspFeature::DocumentSymbols
141 | LspFeature::WorkspaceSymbols
142 )
143 }
144}
145
146#[derive(Debug, Clone, Default, PartialEq, Eq)]
152pub enum FeatureFilter {
153 #[default]
154 All,
155 Only(HashSet<LspFeature>),
156 Except(HashSet<LspFeature>),
157}
158
159impl FeatureFilter {
160 pub fn allows(&self, feature: LspFeature) -> bool {
162 match self {
163 FeatureFilter::All => true,
164 FeatureFilter::Only(set) => set.contains(&feature),
165 FeatureFilter::Except(set) => !set.contains(&feature),
166 }
167 }
168
169 pub fn from_config(
171 only: &Option<Vec<LspFeature>>,
172 except: &Option<Vec<LspFeature>>,
173 ) -> FeatureFilter {
174 match (only, except) {
175 (Some(only), _) => FeatureFilter::Only(only.iter().copied().collect()),
176 (_, Some(except)) => FeatureFilter::Except(except.iter().copied().collect()),
177 _ => FeatureFilter::All,
178 }
179 }
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(untagged)]
191pub enum LspLanguageConfig {
192 Multi(Vec<LspServerConfig>),
194 Single(Box<LspServerConfig>),
196}
197
198impl JsonSchema for LspLanguageConfig {
202 fn schema_name() -> std::borrow::Cow<'static, str> {
203 std::borrow::Cow::Borrowed("LspLanguageConfig")
204 }
205
206 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
207 schemars::json_schema!({
208 "description": "One or more LSP server configs for this language.\nAccepts both a single object and an array for backwards compatibility.",
209 "type": "array",
210 "items": generator.subschema_for::<LspServerConfig>()
211 })
212 }
213}
214
215impl LspLanguageConfig {
216 pub fn into_vec(self) -> Vec<LspServerConfig> {
218 match self {
219 LspLanguageConfig::Single(c) => vec![*c],
220 LspLanguageConfig::Multi(v) => v,
221 }
222 }
223
224 pub fn as_slice(&self) -> &[LspServerConfig] {
226 match self {
227 LspLanguageConfig::Single(c) => std::slice::from_ref(c.as_ref()),
228 LspLanguageConfig::Multi(v) => v,
229 }
230 }
231
232 pub fn as_mut_slice(&mut self) -> &mut [LspServerConfig] {
234 match self {
235 LspLanguageConfig::Single(c) => std::slice::from_mut(c),
236 LspLanguageConfig::Multi(v) => v,
237 }
238 }
239}
240
241impl Default for LspLanguageConfig {
242 fn default() -> Self {
243 LspLanguageConfig::Single(Box::default())
244 }
245}
246
247#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
249#[schemars(extend("x-display-field" = "/command"))]
250pub struct LspServerConfig {
251 #[serde(default)]
254 #[schemars(extend("x-order" = 1))]
255 pub command: String,
256
257 #[serde(default = "default_true")]
259 #[schemars(extend("x-order" = 2))]
260 pub enabled: bool,
261
262 #[serde(default)]
265 #[schemars(extend("x-order" = 3))]
266 pub name: Option<String>,
267
268 #[serde(default)]
270 #[schemars(extend("x-order" = 4))]
271 pub args: Vec<String>,
272
273 #[serde(default)]
276 #[schemars(extend("x-order" = 5))]
277 pub auto_start: bool,
278
279 #[serde(default)]
288 #[schemars(extend("x-order" = 6))]
289 pub root_markers: Vec<String>,
290
291 #[serde(default)]
294 #[schemars(extend("x-section" = "Advanced", "x-order" = 10))]
295 pub env: HashMap<String, String>,
296
297 #[serde(default)]
301 #[schemars(extend("x-section" = "Advanced", "x-order" = 11))]
302 pub language_id_overrides: HashMap<String, String>,
303
304 #[serde(default)]
307 #[schemars(extend("x-section" = "Advanced", "x-order" = 12))]
308 pub initialization_options: Option<serde_json::Value>,
309
310 #[serde(default)]
313 #[schemars(extend("x-section" = "Advanced", "x-order" = 13))]
314 pub only_features: Option<Vec<LspFeature>>,
315
316 #[serde(default)]
319 #[schemars(extend("x-section" = "Advanced", "x-order" = 14))]
320 pub except_features: Option<Vec<LspFeature>>,
321
322 #[serde(default)]
324 #[schemars(extend("x-section" = "Advanced", "x-order" = 15))]
325 pub process_limits: ProcessLimits,
326}
327
328impl LspServerConfig {
329 pub fn display_name(&self) -> String {
336 if let Some(ref name) = self.name {
337 return name.clone();
338 }
339 std::path::Path::new(&self.command)
341 .file_name()
342 .and_then(|n| n.to_str())
343 .unwrap_or(&self.command)
344 .to_string()
345 }
346
347 pub fn feature_filter(&self) -> FeatureFilter {
349 FeatureFilter::from_config(&self.only_features, &self.except_features)
350 }
351
352 pub fn merge_with_defaults(self, defaults: &LspServerConfig) -> LspServerConfig {
357 LspServerConfig {
358 name: self.name.or_else(|| defaults.name.clone()),
359 command: if self.command.is_empty() {
360 defaults.command.clone()
361 } else {
362 self.command
363 },
364 args: if self.args.is_empty() {
365 defaults.args.clone()
366 } else {
367 self.args
368 },
369 enabled: self.enabled,
370 auto_start: self.auto_start,
371 process_limits: self.process_limits,
372 only_features: self
373 .only_features
374 .or_else(|| defaults.only_features.clone()),
375 except_features: self
376 .except_features
377 .or_else(|| defaults.except_features.clone()),
378 initialization_options: self
379 .initialization_options
380 .or_else(|| defaults.initialization_options.clone()),
381 env: {
382 let mut merged = defaults.env.clone();
383 merged.extend(self.env);
384 merged
385 },
386 language_id_overrides: {
387 let mut merged = defaults.language_id_overrides.clone();
388 merged.extend(self.language_id_overrides);
389 merged
390 },
391 root_markers: if self.root_markers.is_empty() {
392 defaults.root_markers.clone()
393 } else {
394 self.root_markers
395 },
396 }
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use std::collections::HashSet;
404
405 #[test]
406 fn test_lsp_feature_is_merged() {
407 assert!(LspFeature::Diagnostics.is_merged());
408 assert!(LspFeature::Completion.is_merged());
409 assert!(LspFeature::CodeAction.is_merged());
410 assert!(LspFeature::DocumentSymbols.is_merged());
411 assert!(LspFeature::WorkspaceSymbols.is_merged());
412
413 assert!(!LspFeature::Hover.is_merged());
414 assert!(!LspFeature::Definition.is_merged());
415 assert!(!LspFeature::References.is_merged());
416 assert!(!LspFeature::Format.is_merged());
417 assert!(!LspFeature::Rename.is_merged());
418 assert!(!LspFeature::SignatureHelp.is_merged());
419 assert!(!LspFeature::InlayHints.is_merged());
420 assert!(!LspFeature::FoldingRange.is_merged());
421 assert!(!LspFeature::SemanticTokens.is_merged());
422 assert!(!LspFeature::DocumentHighlight.is_merged());
423 }
424
425 #[test]
426 fn test_feature_filter_all() {
427 let filter = FeatureFilter::All;
428 assert!(filter.allows(LspFeature::Hover));
429 assert!(filter.allows(LspFeature::Diagnostics));
430 assert!(filter.allows(LspFeature::Completion));
431 assert!(filter.allows(LspFeature::Rename));
432 }
433
434 #[test]
435 fn test_feature_filter_only() {
436 let mut set = HashSet::new();
437 set.insert(LspFeature::Diagnostics);
438 set.insert(LspFeature::Completion);
439 let filter = FeatureFilter::Only(set);
440
441 assert!(filter.allows(LspFeature::Diagnostics));
442 assert!(filter.allows(LspFeature::Completion));
443 assert!(!filter.allows(LspFeature::Hover));
444 assert!(!filter.allows(LspFeature::Definition));
445 }
446
447 #[test]
448 fn test_feature_filter_except() {
449 let mut set = HashSet::new();
450 set.insert(LspFeature::Format);
451 set.insert(LspFeature::Rename);
452 let filter = FeatureFilter::Except(set);
453
454 assert!(filter.allows(LspFeature::Hover));
455 assert!(filter.allows(LspFeature::Diagnostics));
456 assert!(!filter.allows(LspFeature::Format));
457 assert!(!filter.allows(LspFeature::Rename));
458 }
459
460 #[test]
461 fn test_feature_filter_from_config_none() {
462 let filter = FeatureFilter::from_config(&None, &None);
463 assert!(matches!(filter, FeatureFilter::All));
464 }
465
466 #[test]
467 fn test_feature_filter_from_config_only() {
468 let only = Some(vec![LspFeature::Diagnostics, LspFeature::Completion]);
469 let filter = FeatureFilter::from_config(&only, &None);
470 assert!(filter.allows(LspFeature::Diagnostics));
471 assert!(filter.allows(LspFeature::Completion));
472 assert!(!filter.allows(LspFeature::Hover));
473 }
474
475 #[test]
476 fn test_feature_filter_from_config_except() {
477 let except = Some(vec![LspFeature::Format]);
478 let filter = FeatureFilter::from_config(&None, &except);
479 assert!(filter.allows(LspFeature::Hover));
480 assert!(!filter.allows(LspFeature::Format));
481 }
482
483 #[test]
484 fn test_feature_filter_default() {
485 let filter = FeatureFilter::default();
486 assert!(matches!(filter, FeatureFilter::All));
487 }
488}