#![cfg(feature = "geometric-backend")]
use magellan::validation::normalize_path;
use std::path::Path;
#[derive(Debug, Clone)]
pub enum SymbolLookupResult {
Unique(magellan::graph::geometric_backend::SymbolInfo),
Ambiguous {
path: String,
name: String,
candidates: Vec<magellan::graph::geometric_backend::SymbolInfo>,
},
NotFound,
}
#[derive(Debug, Clone)]
pub enum SearchResult {
Unique(magellan::graph::geometric_backend::SymbolInfo),
Ambiguous {
query: String,
candidates: Vec<magellan::graph::geometric_backend::SymbolInfo>,
path_filter: Option<String>,
},
NotFound {
query: String,
path_filter: Option<String>,
},
}
impl SearchResult {
pub fn from_symbols(
symbols: Vec<magellan::graph::geometric_backend::SymbolInfo>,
query: String,
path_filter: Option<String>,
) -> Self {
if symbols.is_empty() {
SearchResult::NotFound { query, path_filter }
} else if symbols.len() == 1 {
if let Some(sym) = symbols.into_iter().next() {
SearchResult::Unique(sym)
} else {
SearchResult::NotFound { query, path_filter }
}
} else {
SearchResult::Ambiguous {
query,
candidates: symbols,
path_filter,
}
}
}
pub fn into_symbols(self) -> Vec<magellan::graph::geometric_backend::SymbolInfo> {
match self {
SearchResult::Unique(sym) => vec![sym],
SearchResult::Ambiguous { candidates, .. } => candidates,
SearchResult::NotFound { .. } => vec![],
}
}
pub fn has_symbols(&self) -> bool {
!matches!(self, SearchResult::NotFound { .. })
}
pub fn count(&self) -> usize {
match self {
SearchResult::Unique(_) => 1,
SearchResult::Ambiguous { candidates, .. } => candidates.len(),
SearchResult::NotFound { .. } => 0,
}
}
}
#[derive(Debug, Clone)]
pub struct CodeChunk {
pub id: Option<i64>,
pub file_path: String,
pub byte_start: usize,
pub byte_end: usize,
pub content: String,
pub symbol_name: Option<String>,
pub symbol_kind: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ChunkLookupResult {
Found(Vec<CodeChunk>),
NotAvailable,
Error(String),
}
impl From<magellan::generation::schema::CodeChunk> for CodeChunk {
fn from(chunk: magellan::generation::schema::CodeChunk) -> Self {
CodeChunk {
id: chunk.id,
file_path: chunk.file_path,
byte_start: chunk.byte_start,
byte_end: chunk.byte_end,
content: chunk.content,
symbol_name: chunk.symbol_name,
symbol_kind: chunk.symbol_kind,
}
}
}
pub fn normalize_path_for_query(path: &str) -> String {
use std::path::Path;
let preprocessed = path.replace("//", "/").replace('\\', "/");
match normalize_path(Path::new(&preprocessed)) {
Ok(normalized) => normalized,
Err(_) => {
preprocessed
}
}
}
pub fn paths_equivalent(path1: &str, path2: &str) -> bool {
let norm1 = normalize_path_for_query(path1);
let norm2 = normalize_path_for_query(path2);
norm1 == norm2
}
pub fn lookup_symbol_by_path_and_name(
backend: &magellan::graph::geometric_backend::GeometricBackend,
path: &str,
name: &str,
) -> SymbolLookupResult {
let normalized_path = normalize_path_for_query(path);
match backend.find_symbol_id_by_name_and_path(name, &normalized_path) {
Some(id) => {
match backend.find_symbol_by_id_info(id) {
Some(info) => SymbolLookupResult::Unique(info),
None => SymbolLookupResult::NotFound,
}
}
None => {
let all_symbols = backend
.symbols_in_file(&normalized_path)
.unwrap_or_default();
let matching: Vec<_> = all_symbols.into_iter().filter(|s| s.name == name).collect();
if matching.len() > 1 {
SymbolLookupResult::Ambiguous {
path: normalized_path,
name: name.to_string(),
candidates: matching,
}
} else {
SymbolLookupResult::NotFound
}
}
}
}
pub fn apply_path_filter(
symbols: Vec<magellan::graph::geometric_backend::SymbolInfo>,
path_filter: &Path,
) -> Vec<magellan::graph::geometric_backend::SymbolInfo> {
let normalized_filter = normalize_path_for_query(path_filter.to_str().unwrap_or(""));
symbols
.into_iter()
.filter(|info| {
let normalized_symbol_path = normalize_path_for_query(&info.file_path);
normalized_symbol_path.contains(&normalized_filter)
|| normalized_filter.contains(&normalized_symbol_path)
})
.collect()
}
pub fn get_chunks_for_symbol(
backend: &magellan::graph::geometric_backend::GeometricBackend,
file_path: &str,
symbol_name: &str,
) -> ChunkLookupResult {
let normalized_path = normalize_path_for_query(file_path);
match backend.get_code_chunks_for_symbol(&normalized_path, symbol_name) {
Ok(chunks) => {
if chunks.is_empty() {
ChunkLookupResult::NotAvailable
} else {
let converted: Vec<CodeChunk> = chunks.into_iter().map(|c| c.into()).collect();
ChunkLookupResult::Found(converted)
}
}
Err(e) => ChunkLookupResult::Error(format!("Failed to get chunks: {}", e)),
}
}
pub fn get_chunks_for_file(
backend: &magellan::graph::geometric_backend::GeometricBackend,
file_path: &str,
) -> ChunkLookupResult {
let normalized_path = normalize_path_for_query(file_path);
match backend.get_code_chunks(&normalized_path) {
Ok(chunks) => {
if chunks.is_empty() {
ChunkLookupResult::NotAvailable
} else {
let converted: Vec<CodeChunk> = chunks.into_iter().map(|c| c.into()).collect();
ChunkLookupResult::Found(converted)
}
}
Err(e) => ChunkLookupResult::Error(format!("Failed to get chunks: {}", e)),
}
}
pub fn get_chunk_by_span(
backend: &magellan::graph::geometric_backend::GeometricBackend,
file_path: &str,
byte_start: usize,
byte_end: usize,
) -> ChunkLookupResult {
let normalized_path = normalize_path_for_query(file_path);
match backend.get_code_chunk_by_span(&normalized_path, byte_start, byte_end) {
Ok(Some(chunk)) => ChunkLookupResult::Found(vec![chunk.into()]),
Ok(None) => ChunkLookupResult::NotAvailable,
Err(e) => ChunkLookupResult::Error(format!("Failed to get chunk: {}", e)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_path_for_query() {
let result = normalize_path_for_query("./src/lib.rs");
assert!(result.ends_with("src/lib.rs") || result == "src/lib.rs");
let result = normalize_path_for_query("nonexistent//lib.rs");
assert!(!result.contains("//"));
let result = normalize_path_for_query("nonexistent\\lib.rs");
assert!(!result.contains("\\"));
}
#[test]
fn test_paths_equivalent() {
assert!(paths_equivalent(
"./nonexistent/lib.rs",
"nonexistent/lib.rs"
));
assert!(paths_equivalent(
"nonexistent//lib.rs",
"nonexistent/lib.rs"
));
}
#[test]
fn test_normalize_path_fallback() {
let result = normalize_path_for_query("");
assert!(result.is_empty() || result == "");
}
}