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::ensure_document_open;
use lsp_types::{DocumentSymbol, DocumentSymbolResponse, SymbolInformation, SymbolKind};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Symbol information
#[derive(Debug, Serialize, Deserialize)]
pub struct SymbolInfo {
    pub name: String,
    pub kind: String,
    pub detail: Option<String>,
    pub file: Option<String>,
    pub line: u32,
    pub character: u32,
    pub children: Option<Vec<SymbolInfo>>,
}

/// Result of document symbols request
#[derive(Debug, Serialize, Deserialize)]
pub struct DocumentSymbolsResult {
    pub file: String,
    pub symbols: Vec<SymbolInfo>,
}

/// Result of workspace symbols request
#[derive(Debug, Serialize, Deserialize)]
pub struct WorkspaceSymbolsResult {
    pub query: String,
    pub symbols: Vec<SymbolInfo>,
}

/// Get document symbols
pub fn document_symbols(
    manager: &LanguageServerManager,
    file_path: &str,
) -> Result<DocumentSymbolsResult> {
    let path = PathBuf::from(file_path);
    let (client, uri) = ensure_document_open(manager, &path)?;

    let response = client.document_symbols(&uri)?;

    let symbols = match response {
        Some(DocumentSymbolResponse::Flat(infos)) => {
            infos.into_iter().map(convert_symbol_information).collect()
        }
        Some(DocumentSymbolResponse::Nested(docs)) => {
            docs.into_iter().map(convert_document_symbol).collect()
        }
        None => vec![],
    };

    Ok(DocumentSymbolsResult {
        file: file_path.to_string(),
        symbols,
    })
}

/// Search workspace symbols
pub fn workspace_symbols(
    manager: &LanguageServerManager,
    query: &str,
) -> Result<WorkspaceSymbolsResult> {
    // Get active workspace
    let workspace = manager
        .active_workspace()
        .ok_or(crate::error::LspMcpError::NoActiveWorkspace)?;

    // Get any client from the workspace
    let workspaces = manager.list_workspaces();
    let workspace_info = workspaces
        .iter()
        .find(|w| w.path == workspace)
        .ok_or(crate::error::LspMcpError::NoActiveWorkspace)?;

    // Use first available language
    let language = workspace_info
        .languages
        .first()
        .ok_or(crate::error::LspMcpError::NoActiveWorkspace)?;

    let client = manager.get_client(workspace, *language)?;
    let response = client.workspace_symbols(query)?;

    let symbols: Vec<SymbolInfo> = response
        .unwrap_or_default()
        .into_iter()
        .map(convert_symbol_information)
        .collect();

    Ok(WorkspaceSymbolsResult {
        query: query.to_string(),
        symbols,
    })
}

#[allow(deprecated)]
fn convert_symbol_information(info: SymbolInformation) -> SymbolInfo {
    SymbolInfo {
        name: info.name,
        kind: symbol_kind_to_string(info.kind),
        detail: None,
        file: Some(
            info.location
                .uri
                .to_file_path()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_else(|_| info.location.uri.to_string()),
        ),
        line: info.location.range.start.line,
        character: info.location.range.start.character,
        children: None,
    }
}

fn convert_document_symbol(doc: DocumentSymbol) -> SymbolInfo {
    let children = if doc.children.as_ref().map(|c| c.is_empty()).unwrap_or(true) {
        None
    } else {
        Some(
            doc.children
                .unwrap_or_default()
                .into_iter()
                .map(convert_document_symbol)
                .collect(),
        )
    };

    SymbolInfo {
        name: doc.name,
        kind: symbol_kind_to_string(doc.kind),
        detail: doc.detail,
        file: None,
        line: doc.selection_range.start.line,
        character: doc.selection_range.start.character,
        children,
    }
}

fn symbol_kind_to_string(kind: SymbolKind) -> String {
    match kind {
        SymbolKind::FILE => "file",
        SymbolKind::MODULE => "module",
        SymbolKind::NAMESPACE => "namespace",
        SymbolKind::PACKAGE => "package",
        SymbolKind::CLASS => "class",
        SymbolKind::METHOD => "method",
        SymbolKind::PROPERTY => "property",
        SymbolKind::FIELD => "field",
        SymbolKind::CONSTRUCTOR => "constructor",
        SymbolKind::ENUM => "enum",
        SymbolKind::INTERFACE => "interface",
        SymbolKind::FUNCTION => "function",
        SymbolKind::VARIABLE => "variable",
        SymbolKind::CONSTANT => "constant",
        SymbolKind::STRING => "string",
        SymbolKind::NUMBER => "number",
        SymbolKind::BOOLEAN => "boolean",
        SymbolKind::ARRAY => "array",
        SymbolKind::OBJECT => "object",
        SymbolKind::KEY => "key",
        SymbolKind::NULL => "null",
        SymbolKind::ENUM_MEMBER => "enum_member",
        SymbolKind::STRUCT => "struct",
        SymbolKind::EVENT => "event",
        SymbolKind::OPERATOR => "operator",
        SymbolKind::TYPE_PARAMETER => "type_parameter",
        _ => "unknown",
    }
    .to_string()
}