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::{GotoDefinitionResponse, Location, LocationLink};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Location information
#[derive(Debug, Serialize, Deserialize)]
pub struct LocationInfo {
    pub file: String,
    pub line: u32,
    pub character: u32,
    pub end_line: Option<u32>,
    pub end_character: Option<u32>,
}

/// Result of goto definition
#[derive(Debug, Serialize, Deserialize)]
pub struct DefinitionResult {
    pub locations: Vec<LocationInfo>,
}

/// Go to definition
pub fn goto_definition(
    manager: &LanguageServerManager,
    file_path: &str,
    line: u32,
    character: u32,
) -> Result<DefinitionResult> {
    let path = PathBuf::from(file_path);
    let (client, uri) = ensure_document_open(manager, &path)?;

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

    let locations = match response {
        Some(GotoDefinitionResponse::Scalar(loc)) => vec![convert_location(&loc)],
        Some(GotoDefinitionResponse::Array(locs)) => {
            locs.iter().map(convert_location).collect()
        }
        Some(GotoDefinitionResponse::Link(links)) => {
            links.iter().map(convert_location_link).collect()
        }
        None => vec![],
    };

    Ok(DefinitionResult { locations })
}

fn convert_location(loc: &Location) -> LocationInfo {
    LocationInfo {
        file: uri_to_path(&loc.uri),
        line: loc.range.start.line,
        character: loc.range.start.character,
        end_line: Some(loc.range.end.line),
        end_character: Some(loc.range.end.character),
    }
}

fn convert_location_link(link: &LocationLink) -> LocationInfo {
    LocationInfo {
        file: uri_to_path(&link.target_uri),
        line: link.target_selection_range.start.line,
        character: link.target_selection_range.start.character,
        end_line: Some(link.target_selection_range.end.line),
        end_character: Some(link.target_selection_range.end.character),
    }
}

fn uri_to_path(uri: &url::Url) -> String {
    uri.to_file_path()
        .map(|p| p.to_string_lossy().to_string())
        .unwrap_or_else(|_| uri.to_string())
}