use std::fmt::Write;
use std::path::{Path, PathBuf};
use serde_json::Value;
use tracing::warn;
use opendev_tools_lsp::protocol::{SourceRange, UnifiedSymbolInfo};
use crate::error::{SymbolError, ToolResult};
use crate::util::{relative_display, truncate};
const MAX_PREVIEW_CHARS: usize = 200;
const MAX_PREVIEW_LINES: usize = 5;
pub fn handle_find_symbol(arguments: &Value, workspace_root: &Path) -> ToolResult {
let symbol_name = match arguments.get("symbol_name").and_then(|v| v.as_str()) {
Some(s) if !s.is_empty() => s,
_ => return ToolResult::err("Missing required argument: symbol_name"),
};
let file_path = arguments
.get("file_path")
.and_then(|v| v.as_str())
.map(PathBuf::from);
match find_symbols_in_workspace(symbol_name, file_path.as_deref(), workspace_root) {
Ok(symbols) => format_symbol_results(&symbols, workspace_root),
Err(e) => ToolResult::err(e.to_string()),
}
}
fn find_symbols_in_workspace(
_symbol_name: &str,
_file_path: Option<&Path>,
_workspace_root: &Path,
) -> Result<Vec<UnifiedSymbolInfo>, SymbolError> {
Ok(Vec::new())
}
pub fn format_symbol_results(symbols: &[UnifiedSymbolInfo], workspace_root: &Path) -> ToolResult {
if symbols.is_empty() {
return ToolResult::ok("No symbols found matching the pattern.");
}
let mut output = String::new();
let mut symbol_dicts = Vec::new();
for sym in symbols {
let rel_path = relative_display(&sym.file_path, workspace_root);
let line_num = sym.range.start.line + 1;
let _ = writeln!(
output,
"{} `{}` — {}:{}",
sym.kind.display_name(),
sym.name,
rel_path,
line_num,
);
if let Some(preview) = get_body_preview(&sym.file_path, &sym.range) {
let _ = writeln!(output, " {}", preview);
}
symbol_dicts.push(serde_json::json!({
"name": sym.name,
"kind": sym.kind.display_name(),
"file_path": sym.file_path.display().to_string(),
"line": line_num,
"container": sym.container_name,
}));
}
ToolResult::ok_with(output, serde_json::json!({ "symbols": symbol_dicts }))
}
fn get_body_preview(file_path: &Path, range: &SourceRange) -> Option<String> {
let content = match std::fs::read_to_string(file_path) {
Ok(c) => c,
Err(e) => {
warn!("Failed to read file for preview: {}", e);
return None;
}
};
let lines: Vec<&str> = content.lines().collect();
let start = range.start.line as usize;
let end = (range.end.line as usize).min(lines.len().saturating_sub(1));
if start >= lines.len() {
return None;
}
let preview_end = end.min(start + MAX_PREVIEW_LINES - 1);
let preview_lines: Vec<&str> = lines[start..=preview_end].to_vec();
let mut preview = preview_lines.join("\n");
preview = truncate(&preview, MAX_PREVIEW_CHARS);
Some(preview)
}
#[cfg(test)]
#[path = "find_symbol_tests.rs"]
mod tests;