use lsp_types::DocumentSymbolResponse;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use mcp_utils::display_meta::{ToolDisplayMeta, ToolResultMeta, basename};
use crate::lsp::common::{LocationResult, path_to_uri};
use crate::lsp::error::LspError;
use crate::lsp::registry::LspRegistry;
use aether_lspd::symbol_kind_to_string;
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct LspDocumentInput {
#[serde(alias = "file_path")]
pub file_path: String,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct DocumentSymbolResult {
pub name: String,
pub kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
pub range: LocationResult,
pub selection_range: LocationResult,
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<DocumentSymbolResult>>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct LspDocumentOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbols: Option<Vec<DocumentSymbolResult>>,
pub total_count: usize,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
#[schemars(skip)]
pub meta: Option<ToolResultMeta>,
}
pub async fn execute_lsp_document(
input: LspDocumentInput,
registry: &LspRegistry,
) -> Result<LspDocumentOutput, LspError> {
let uri = path_to_uri(Path::new(&input.file_path))?;
let client = registry.get_or_spawn(Path::new(&input.file_path)).await?;
let response = client.document_symbol(uri).await?;
let symbols = convert_document_symbols(&input.file_path, response);
let total_count = symbols.len();
let display_meta =
ToolDisplayMeta::new("LSP document", format!("{}, {total_count} symbols", basename(&input.file_path)));
Ok(LspDocumentOutput { symbols: Some(symbols), total_count, meta: Some(display_meta.into()) })
}
fn convert_document_symbols(file_path: &str, response: DocumentSymbolResponse) -> Vec<DocumentSymbolResult> {
match response {
DocumentSymbolResponse::Flat(symbols) => symbols
.into_iter()
.map(|sym| {
let range = LocationResult::from_range(file_path.to_string(), &sym.location.range);
DocumentSymbolResult {
name: sym.name,
kind: symbol_kind_to_string(sym.kind).to_string(),
detail: None,
range: range.clone(),
selection_range: range,
children: None,
}
})
.collect(),
DocumentSymbolResponse::Nested(symbols) => {
symbols.into_iter().map(|sym| convert_document_symbol(file_path, sym)).collect()
}
}
}
fn convert_document_symbol(file_path: &str, sym: lsp_types::DocumentSymbol) -> DocumentSymbolResult {
let range = LocationResult::from_range(file_path.to_string(), &sym.range);
let selection_range = LocationResult::from_range(file_path.to_string(), &sym.selection_range);
let children = sym
.children
.map(|children| children.into_iter().map(|child| convert_document_symbol(file_path, child)).collect());
DocumentSymbolResult {
name: sym.name,
kind: symbol_kind_to_string(sym.kind).to_string(),
detail: sym.detail,
range,
selection_range,
children,
}
}