lsp-mcp 0.1.0

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

/// Diagnostic severity
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticSeverity {
    Error,
    Warning,
    Information,
    Hint,
}

/// Diagnostic information
#[derive(Debug, Serialize, Deserialize)]
pub struct DiagnosticInfo {
    pub message: String,
    pub severity: DiagnosticSeverity,
    pub range: RangeInfo,
    pub source: Option<String>,
    pub code: Option<String>,
}

/// Result of diagnostics request
#[derive(Debug, Serialize, Deserialize)]
pub struct DiagnosticsResult {
    pub file: String,
    pub diagnostics: Vec<DiagnosticInfo>,
    pub error_count: usize,
    pub warning_count: usize,
}

/// Get diagnostics for a file
///
/// Note: Most LSP servers push diagnostics via notifications rather than
/// pull-based requests. This function triggers a document open/sync to
/// get diagnostics. For real-time diagnostics, the MCP server would need
/// to track pushed notifications.
pub fn get_diagnostics(
    manager: &LanguageServerManager,
    file_path: &str,
) -> Result<DiagnosticsResult> {
    let path = PathBuf::from(file_path);

    // Ensure document is open to trigger diagnostics
    let client = manager.get_client_for_file(&path)?;
    let uri = crate::tools::file_to_url(&path)?;
    let content = crate::tools::read_file(&path)?;
    let language_id = client.language().language_id();

    client.open_document(&uri, &content, language_id)?;

    // Note: In a real implementation, we would need to:
    // 1. Store diagnostics from publishDiagnostics notifications
    // 2. Return cached diagnostics for the requested file
    //
    // For now, return empty diagnostics as a placeholder.
    // The proper implementation requires async notification handling.

    Ok(DiagnosticsResult {
        file: file_path.to_string(),
        diagnostics: vec![],
        error_count: 0,
        warning_count: 0,
    })
}

/// Convert LSP diagnostic severity
#[allow(dead_code)]
fn convert_severity(severity: Option<lsp_types::DiagnosticSeverity>) -> DiagnosticSeverity {
    match severity {
        Some(lsp_types::DiagnosticSeverity::ERROR) => DiagnosticSeverity::Error,
        Some(lsp_types::DiagnosticSeverity::WARNING) => DiagnosticSeverity::Warning,
        Some(lsp_types::DiagnosticSeverity::INFORMATION) => DiagnosticSeverity::Information,
        Some(lsp_types::DiagnosticSeverity::HINT) => DiagnosticSeverity::Hint,
        _ => DiagnosticSeverity::Information,
    }
}