use std::collections::HashMap;
use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspConfig {
pub enabled: bool,
pub timeout: Duration,
pub max_servers: usize,
pub servers: HashMap<String, LspServerConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspServerConfig {
pub name: String,
pub command: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
pub file_patterns: Vec<String>,
pub enabled: bool,
pub auto_start: bool,
pub settings: serde_json::Value,
}
impl Default for LspConfig {
fn default() -> Self {
let mut servers = HashMap::new();
servers.insert("rust".to_string(), LspServerConfig {
name: "Rust Analyzer".to_string(),
command: "rust-analyzer".to_string(),
args: vec![],
env: HashMap::new(),
file_patterns: vec!["*.rs".to_string()],
enabled: true,
auto_start: true,
settings: serde_json::json!({
"rust-analyzer": {
"checkOnSave": {
"command": "clippy"
}
}
}),
});
servers.insert("go".to_string(), LspServerConfig {
name: "gopls".to_string(),
command: "gopls".to_string(),
args: vec![],
env: HashMap::new(),
file_patterns: vec!["*.go".to_string()],
enabled: true,
auto_start: true,
settings: serde_json::json!({}),
});
servers.insert("typescript".to_string(), LspServerConfig {
name: "TypeScript Language Server".to_string(),
command: "typescript-language-server".to_string(),
args: vec!["--stdio".to_string()],
env: HashMap::new(),
file_patterns: vec!["*.ts".to_string(), "*.js".to_string(), "*.tsx".to_string(), "*.jsx".to_string()],
enabled: false, auto_start: false,
settings: serde_json::json!({}),
});
servers.insert("python".to_string(), LspServerConfig {
name: "Pylsp".to_string(),
command: "pylsp".to_string(),
args: vec![],
env: HashMap::new(),
file_patterns: vec!["*.py".to_string()],
enabled: false, auto_start: false,
settings: serde_json::json!({}),
});
Self {
enabled: true,
timeout: Duration::from_secs(30),
max_servers: 5,
servers,
}
}
}
impl LspServerConfig {
pub fn handles_file(&self, file_path: &std::path::Path) -> bool {
if !self.enabled {
return false;
}
let file_name = file_path.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
self.file_patterns.iter().any(|pattern| {
if pattern.starts_with("*.") {
let ext = &pattern[2..];
file_name.ends_with(&format!(".{}", ext))
} else {
file_name == pattern
}
})
}
pub fn get_language_id(&self, file_path: &std::path::Path) -> Option<String> {
let extension = file_path.extension()
.and_then(|ext| ext.to_str())?;
match extension {
"rs" => Some("rust".to_string()),
"go" => Some("go".to_string()),
"py" => Some("python".to_string()),
"js" => Some("javascript".to_string()),
"ts" => Some("typescript".to_string()),
"jsx" => Some("javascriptreact".to_string()),
"tsx" => Some("typescriptreact".to_string()),
"c" => Some("c".to_string()),
"cpp" | "cc" | "cxx" => Some("cpp".to_string()),
"h" | "hpp" => Some("c".to_string()), "java" => Some("java".to_string()),
"json" => Some("json".to_string()),
"yaml" | "yml" => Some("yaml".to_string()),
"toml" => Some("toml".to_string()),
"md" => Some("markdown".to_string()),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_server_handles_file() {
let config = LspServerConfig {
name: "Test".to_string(),
command: "test".to_string(),
args: vec![],
env: HashMap::new(),
file_patterns: vec!["*.rs".to_string(), "*.toml".to_string()],
enabled: true,
auto_start: true,
settings: serde_json::json!({}),
};
assert!(config.handles_file(&PathBuf::from("main.rs")));
assert!(config.handles_file(&PathBuf::from("Cargo.toml")));
assert!(!config.handles_file(&PathBuf::from("main.go")));
}
#[test]
fn test_language_id_detection() {
let config = LspServerConfig::default();
assert_eq!(config.get_language_id(&PathBuf::from("main.rs")), Some("rust".to_string()));
assert_eq!(config.get_language_id(&PathBuf::from("main.go")), Some("go".to_string()));
assert_eq!(config.get_language_id(&PathBuf::from("script.py")), Some("python".to_string()));
assert_eq!(config.get_language_id(&PathBuf::from("unknown.xyz")), None);
}
#[test]
fn test_default_config() {
let config = LspConfig::default();
assert!(config.enabled);
assert!(config.servers.contains_key("rust"));
assert!(config.servers.contains_key("go"));
let rust_config = &config.servers["rust"];
assert!(rust_config.enabled);
assert_eq!(rust_config.command, "rust-analyzer");
}
}
impl Default for LspServerConfig {
fn default() -> Self {
Self {
name: "Default LSP Server".to_string(),
command: "lsp-server".to_string(),
args: vec![],
env: HashMap::new(),
file_patterns: vec![],
enabled: false,
auto_start: false,
settings: serde_json::json!({}),
}
}
}