lsp-mcp 0.1.0

MCP server providing unified access to Language Server Protocol features
Documentation
use crate::error::{Language, LspMcpError, Result};
use crate::lsp::LanguageServerManager;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Result of activating a workspace
#[derive(Debug, Serialize, Deserialize)]
pub struct ActivateWorkspaceResult {
    pub workspace: String,
    pub languages_started: Vec<String>,
    pub languages_failed: Vec<String>,
}

/// Activate a workspace and start language servers
pub fn activate_workspace(
    manager: &LanguageServerManager,
    workspace_path: &str,
    languages: Option<Vec<String>>,
) -> Result<ActivateWorkspaceResult> {
    let path = PathBuf::from(workspace_path);

    if !path.exists() {
        return Err(LspMcpError::WorkspaceNotFound(path));
    }

    // Convert language strings to Language enum
    let language_enums: Option<Vec<Language>> = languages.map(|langs| {
        langs
            .iter()
            .filter_map(|l| Language::from_str(l))
            .collect()
    });

    // Track failed languages
    let requested_languages: Vec<String> = language_enums
        .as_ref()
        .map(|langs| langs.iter().map(|l| l.to_string()).collect())
        .unwrap_or_default();

    let started = manager.activate_workspace(path.clone(), language_enums)?;
    let started_strings: Vec<String> = started.iter().map(|l| l.to_string()).collect();

    // Determine which failed
    let failed: Vec<String> = if requested_languages.is_empty() {
        vec![]
    } else {
        requested_languages
            .into_iter()
            .filter(|l| !started_strings.contains(l))
            .collect()
    };

    Ok(ActivateWorkspaceResult {
        workspace: path.to_string_lossy().to_string(),
        languages_started: started_strings,
        languages_failed: failed,
    })
}

/// Result of listing workspaces
#[derive(Debug, Serialize, Deserialize)]
pub struct ListWorkspacesResult {
    pub workspaces: Vec<WorkspaceEntry>,
    pub active_workspace: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WorkspaceEntry {
    pub path: String,
    pub languages: Vec<String>,
}

/// List all active workspaces
pub fn list_workspaces(manager: &LanguageServerManager) -> ListWorkspacesResult {
    let workspaces = manager.list_workspaces();
    let active = manager.active_workspace();

    ListWorkspacesResult {
        workspaces: workspaces
            .into_iter()
            .map(|w| WorkspaceEntry {
                path: w.path.to_string_lossy().to_string(),
                languages: w.languages.iter().map(|l| l.to_string()).collect(),
            })
            .collect(),
        active_workspace: active.map(|p| p.to_string_lossy().to_string()),
    }
}

/// Deactivate a workspace
pub fn deactivate_workspace(
    manager: &LanguageServerManager,
    workspace_path: &str,
) -> Result<String> {
    let path = PathBuf::from(workspace_path);
    manager.deactivate_workspace(&path)?;
    Ok(format!("Deactivated workspace: {}", workspace_path))
}