use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspServerConfig {
pub command: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub extensions: Vec<String>,
#[serde(default)]
pub initialization_options: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspConfig {
#[serde(default)]
pub servers: HashMap<String, LspServerConfig>,
#[serde(default)]
pub workspace_root: Option<PathBuf>,
#[serde(default = "default_lsp_timeout")]
pub timeout_ms: u64,
}
impl Default for LspConfig {
fn default() -> Self {
Self {
servers: HashMap::new(),
workspace_root: None,
timeout_ms: default_lsp_timeout(),
}
}
}
impl LspConfig {
pub fn prefab_defaults() -> Self {
let (cfg, _missing) = crate::lsp::registry::auto_config(true);
cfg
}
pub fn with_prefab_fallback(mut self) -> Self {
if self.servers.is_empty() {
return Self::prefab_defaults();
}
let (prefab, _) = crate::lsp::registry::auto_config(true);
for (lang, server) in prefab.servers {
self.servers.entry(lang).or_insert(server);
}
self
}
pub fn default_extensions(language: &str) -> Vec<&'static str> {
match language {
"go" => vec!["go"],
"typescript" | "javascript" => vec!["ts", "tsx", "js", "jsx"],
"python" => vec!["py"],
"rust" => vec!["rs"],
"java" => vec!["java"],
"kotlin" => vec!["kt", "kts"],
"ruby" => vec!["rb"],
"csharp" => vec!["cs"],
"cpp" | "c" => vec!["cpp", "cxx", "cc", "hpp", "h"],
"php" => vec!["php"],
"dart" => vec!["dart"],
_ => vec![],
}
}
}
fn default_lsp_timeout() -> u64 {
5000
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_extensions_for_common_languages() {
assert!(LspConfig::default_extensions("go").contains(&"go"));
assert!(LspConfig::default_extensions("typescript").contains(&"ts"));
assert!(LspConfig::default_extensions("rust").contains(&"rs"));
}
#[test]
fn empty_config_serializes_and_deserializes() {
let cfg = LspConfig::default();
let json = serde_json::to_string(&cfg).unwrap();
let back: LspConfig = serde_json::from_str(&json).unwrap();
assert_eq!(back.timeout_ms, 5000);
assert!(back.servers.is_empty());
}
#[test]
fn prefab_defaults_includes_go_and_typescript() {
let cfg = LspConfig::prefab_defaults();
assert!(cfg.servers.contains_key("go"), "expected gopls entry");
assert!(
cfg.servers.contains_key("typescript"),
"expected typescript-language-server entry"
);
assert!(cfg.servers.contains_key("python"), "expected pyright/pylsp");
}
#[test]
fn with_prefab_fallback_fills_empty() {
let cfg = LspConfig::default().with_prefab_fallback();
assert!(!cfg.servers.is_empty());
}
}