use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::RwLock;
use super::client::LspClient;
use super::config::LspConfig;
use super::types::{
LspError, LspDiagnostic, Position, Range, Location,
CompletionItem, Hover, CodeAction, SymbolInformation
};
use super::LspService;
use async_trait::async_trait;
pub struct LspManager {
config: LspConfig,
clients: Arc<RwLock<HashMap<String, LspClient>>>,
workspace_root: Option<PathBuf>,
}
impl LspManager {
pub fn new(config: LspConfig) -> Self {
Self {
config,
clients: Arc::new(RwLock::new(HashMap::new())),
workspace_root: None,
}
}
pub async fn initialize(&mut self, workspace_root: Option<PathBuf>) -> Result<(), LspError> {
self.workspace_root = workspace_root.clone();
if !self.config.enabled {
tracing::info!("LSP integration is disabled");
return Ok(());
}
for (name, server_config) in &self.config.servers {
if server_config.enabled && server_config.auto_start {
if let Err(e) = self.start_server(name).await {
tracing::warn!("Failed to start LSP server {}: {}", name, e);
}
}
}
tracing::info!("LSP manager initialized with {} servers", self.config.servers.len());
Ok(())
}
pub async fn start_server(&self, server_name: &str) -> Result<(), LspError> {
let server_config = self.config.servers.get(server_name)
.ok_or_else(|| LspError::ServerNotFound(server_name.to_string()))?
.clone();
if !server_config.enabled {
return Err(LspError::ServerNotFound(format!("Server {} is disabled", server_name)));
}
let mut client = LspClient::new(server_config);
client.start(self.workspace_root.clone()).await?;
let mut clients = self.clients.write().await;
clients.insert(server_name.to_string(), client);
tracing::info!("Started LSP server: {}", server_name);
Ok(())
}
pub async fn stop_server(&self, server_name: &str) -> Result<(), LspError> {
let mut clients = self.clients.write().await;
if let Some(mut client) = clients.remove(server_name) {
client.stop().await?;
tracing::info!("Stopped LSP server: {}", server_name);
}
Ok(())
}
pub async fn stop_all(&self) -> Result<(), LspError> {
let mut clients = self.clients.write().await;
for (name, mut client) in clients.drain() {
if let Err(e) = client.stop().await {
tracing::warn!("Failed to stop LSP server {}: {}", name, e);
}
}
tracing::info!("Stopped all LSP servers");
Ok(())
}
async fn find_client_for_file(&self, file_path: &Path) -> Option<String> {
let clients = self.clients.read().await;
for (name, client) in clients.iter() {
if client.handles_file(file_path) {
return Some(name.clone());
}
}
None
}
async fn get_or_start_client(&self, file_path: &Path) -> Result<Option<String>, LspError> {
if let Some(client_name) = self.find_client_for_file(file_path).await {
return Ok(Some(client_name));
}
for (name, server_config) in &self.config.servers {
if server_config.handles_file(file_path) && server_config.enabled {
if let Err(e) = self.start_server(name).await {
tracing::warn!("Failed to start LSP server {} for file {}: {}",
name, file_path.display(), e);
continue;
}
return Ok(Some(name.clone()));
}
}
Ok(None)
}
pub async fn active_servers(&self) -> Vec<String> {
let clients = self.clients.read().await;
clients.keys().cloned().collect()
}
pub async fn get_completions(&self, file_path: &Path, position: Position) -> Result<Vec<CompletionItem>, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.get_completions(file_path, position).await;
}
}
Ok(Vec::new())
}
pub async fn goto_definition(&self, file_path: &Path, position: Position) -> Result<Vec<Location>, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.goto_definition(file_path, position).await;
}
}
Ok(Vec::new())
}
pub async fn get_hover(&self, file_path: &Path, position: Position) -> Result<Option<Hover>, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.get_hover(file_path, position).await;
}
}
Ok(None)
}
pub async fn find_references(&self, file_path: &Path, position: Position, include_declaration: bool) -> Result<Vec<Location>, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.find_references(file_path, position, include_declaration).await;
}
}
Ok(Vec::new())
}
pub async fn get_code_actions(&self, file_path: &Path, range: Range) -> Result<Vec<CodeAction>, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.get_code_actions(file_path, range).await;
}
}
Ok(Vec::new())
}
pub async fn get_document_symbols(&self, file_path: &Path) -> Result<Vec<SymbolInformation>, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.get_document_symbols(file_path).await;
}
}
Ok(Vec::new())
}
pub async fn format_document(&self, file_path: &Path) -> Result<String, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.format_document(file_path).await;
}
}
Err(LspError::ServerNotFound(format!("No LSP server for {}", file_path.display())))
}
pub async fn format_range(&self, file_path: &Path, range: Range) -> Result<String, LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.format_range(file_path, range).await;
}
}
Err(LspError::ServerNotFound(format!("No LSP server for {}", file_path.display())))
}
pub fn has_server_for_file(&self, file_path: &Path) -> bool {
self.config.servers.values()
.any(|config| config.handles_file(file_path) && config.enabled)
}
}
#[async_trait]
impl LspService for LspManager {
async fn get_diagnostics(&self, file_path: &Path) -> Result<Vec<LspDiagnostic>, LspError> {
if let Some(client_name) = self.find_client_for_file(file_path).await {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.get_diagnostics(file_path).await;
}
}
Ok(Vec::new())
}
async fn did_change_file(&self, file_path: &Path, content: &str) -> Result<(), LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.did_change(file_path, content).await;
}
}
Ok(())
}
async fn did_open_file(&self, file_path: &Path, content: &str) -> Result<(), LspError> {
if let Some(client_name) = self.get_or_start_client(file_path).await? {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.did_open(file_path, content).await;
}
}
Ok(())
}
async fn did_close_file(&self, file_path: &Path) -> Result<(), LspError> {
if let Some(client_name) = self.find_client_for_file(file_path).await {
let clients = self.clients.read().await;
if let Some(client) = clients.get(&client_name) {
return client.did_close(file_path).await;
}
}
Ok(())
}
fn supports_file(&self, file_path: &Path) -> bool {
self.has_server_for_file(file_path)
}
async fn get_completions(&self, file_path: &Path, position: Position) -> Result<Vec<CompletionItem>, LspError> {
self.get_completions(file_path, position).await
}
async fn goto_definition(&self, file_path: &Path, position: Position) -> Result<Vec<Location>, LspError> {
self.goto_definition(file_path, position).await
}
async fn get_hover(&self, file_path: &Path, position: Position) -> Result<Option<Hover>, LspError> {
self.get_hover(file_path, position).await
}
async fn find_references(&self, file_path: &Path, position: Position, include_declaration: bool) -> Result<Vec<Location>, LspError> {
self.find_references(file_path, position, include_declaration).await
}
async fn get_code_actions(&self, file_path: &Path, range: Range) -> Result<Vec<CodeAction>, LspError> {
self.get_code_actions(file_path, range).await
}
async fn get_document_symbols(&self, file_path: &Path) -> Result<Vec<SymbolInformation>, LspError> {
self.get_document_symbols(file_path).await
}
async fn format_document(&self, file_path: &Path) -> Result<String, LspError> {
self.format_document(file_path).await
}
async fn format_range(&self, file_path: &Path, range: Range) -> Result<String, LspError> {
self.format_range(file_path, range).await
}
}
impl Drop for LspManager {
fn drop(&mut self) {
let clients = self.clients.clone();
tokio::spawn(async move {
let mut clients = clients.write().await;
for (name, mut client) in clients.drain() {
if let Err(e) = client.stop().await {
tracing::warn!("Failed to stop LSP server {} in drop: {}", name, e);
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[tokio::test]
async fn test_manager_creation() {
let config = LspConfig::default();
let manager = LspManager::new(config);
assert!(manager.active_servers().await.is_empty());
}
#[tokio::test]
async fn test_file_support_detection() {
let config = LspConfig::default();
let manager = LspManager::new(config);
assert!(manager.supports_file(&PathBuf::from("main.rs")));
assert!(manager.supports_file(&PathBuf::from("main.go")));
assert!(!manager.supports_file(&PathBuf::from("unknown.xyz")));
}
#[tokio::test]
async fn test_client_finding() {
let config = LspConfig::default();
let manager = LspManager::new(config);
assert!(manager.find_client_for_file(&PathBuf::from("main.rs")).await.is_none());
}
}