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, make_position};
use lsp_types::{CompletionItem, CompletionItemKind, CompletionResponse};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Completion item info
#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionItemInfo {
    pub label: String,
    pub kind: Option<String>,
    pub detail: Option<String>,
    pub documentation: Option<String>,
    pub insert_text: Option<String>,
}

/// Result of completion request
#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionResult {
    pub items: Vec<CompletionItemInfo>,
    pub is_incomplete: bool,
}

/// Get completion suggestions
pub fn completion(
    manager: &LanguageServerManager,
    file_path: &str,
    line: u32,
    character: u32,
    trigger_character: Option<String>,
) -> Result<CompletionResult> {
    let path = PathBuf::from(file_path);
    let (client, uri) = ensure_document_open(manager, &path)?;

    let position = make_position(line, character);
    let response = client.completion(&uri, position, trigger_character)?;

    let (items, is_incomplete) = match response {
        Some(CompletionResponse::Array(items)) => (items, false),
        Some(CompletionResponse::List(list)) => (list.items, list.is_incomplete),
        None => (vec![], false),
    };

    let completion_items: Vec<CompletionItemInfo> = items
        .into_iter()
        .map(convert_completion_item)
        .collect();

    Ok(CompletionResult {
        items: completion_items,
        is_incomplete,
    })
}

fn convert_completion_item(item: CompletionItem) -> CompletionItemInfo {
    CompletionItemInfo {
        label: item.label,
        kind: item.kind.map(completion_kind_to_string),
        detail: item.detail,
        documentation: item.documentation.map(|doc| match doc {
            lsp_types::Documentation::String(s) => s,
            lsp_types::Documentation::MarkupContent(m) => m.value,
        }),
        insert_text: item.insert_text.or_else(|| item.text_edit.map(|edit| {
            match edit {
                lsp_types::CompletionTextEdit::Edit(e) => e.new_text,
                lsp_types::CompletionTextEdit::InsertAndReplace(e) => e.new_text,
            }
        })),
    }
}

fn completion_kind_to_string(kind: CompletionItemKind) -> String {
    match kind {
        CompletionItemKind::TEXT => "text",
        CompletionItemKind::METHOD => "method",
        CompletionItemKind::FUNCTION => "function",
        CompletionItemKind::CONSTRUCTOR => "constructor",
        CompletionItemKind::FIELD => "field",
        CompletionItemKind::VARIABLE => "variable",
        CompletionItemKind::CLASS => "class",
        CompletionItemKind::INTERFACE => "interface",
        CompletionItemKind::MODULE => "module",
        CompletionItemKind::PROPERTY => "property",
        CompletionItemKind::UNIT => "unit",
        CompletionItemKind::VALUE => "value",
        CompletionItemKind::ENUM => "enum",
        CompletionItemKind::KEYWORD => "keyword",
        CompletionItemKind::SNIPPET => "snippet",
        CompletionItemKind::COLOR => "color",
        CompletionItemKind::FILE => "file",
        CompletionItemKind::REFERENCE => "reference",
        CompletionItemKind::FOLDER => "folder",
        CompletionItemKind::ENUM_MEMBER => "enum_member",
        CompletionItemKind::CONSTANT => "constant",
        CompletionItemKind::STRUCT => "struct",
        CompletionItemKind::EVENT => "event",
        CompletionItemKind::OPERATOR => "operator",
        CompletionItemKind::TYPE_PARAMETER => "type_parameter",
        _ => "unknown",
    }
    .to_string()
}