coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! LSP configuration types

use std::collections::HashMap;
use std::time::Duration;
use serde::{Deserialize, Serialize};

/// Configuration for LSP integration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspConfig {
    /// Whether LSP integration is enabled
    pub enabled: bool,
    
    /// Timeout for LSP operations
    pub timeout: Duration,
    
    /// Maximum number of concurrent LSP servers
    pub max_servers: usize,
    
    /// Configuration for individual language servers
    pub servers: HashMap<String, LspServerConfig>,
}

/// Configuration for a specific language server
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LspServerConfig {
    /// Display name for the server
    pub name: String,
    
    /// Command to start the server
    pub command: String,
    
    /// Arguments to pass to the server
    pub args: Vec<String>,
    
    /// Environment variables for the server
    pub env: HashMap<String, String>,
    
    /// File patterns this server handles (e.g., "*.rs", "*.go")
    pub file_patterns: Vec<String>,
    
    /// Whether this server is enabled
    pub enabled: bool,
    
    /// Whether to auto-start this server
    pub auto_start: bool,
    
    /// Server-specific settings
    pub settings: serde_json::Value,
}

impl Default for LspConfig {
    fn default() -> Self {
        let mut servers = HashMap::new();
        
        // Rust analyzer
        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"
                    }
                }
            }),
        });
        
        // Go language server
        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!({}),
        });
        
        // TypeScript/JavaScript
        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, // Disabled by default since it requires npm install
            auto_start: false,
            settings: serde_json::json!({}),
        });
        
        // Python
        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, // Disabled by default since it requires pip install
            auto_start: false,
            settings: serde_json::json!({}),
        });
        
        Self {
            enabled: true,
            timeout: Duration::from_secs(30),
            max_servers: 5,
            servers,
        }
    }
}

impl LspServerConfig {
    /// Check if this server handles the given file
    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| {
            // Simple glob matching - just check extensions for now
            if pattern.starts_with("*.") {
                let ext = &pattern[2..];
                file_name.ends_with(&format!(".{}", ext))
            } else {
                // Exact match
                file_name == pattern
            }
        })
    }
    
    /// Get the language ID for LSP based on file extension
    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()), // Could be C or C++
            "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!({}),
        }
    }
}