use crate::completion::imports::{ImportEntity, ImportResolver};
use crate::completion::tokenizer;
use crate::completion::types::{Symbol, SymbolKind};
use crate::graph::MagellanIntegration;
use anyhow::Result;
use rusqlite::Connection;
use serde_json::Value as JsonValue;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct CompletionContext {
pub file_path: PathBuf,
pub cursor_line: usize,
pub cursor_col: usize,
pub enclosing_function: Option<Symbol>,
pub enclosing_module: Option<Symbol>,
pub visible_symbols: Vec<Symbol>,
pub current_token: Option<String>,
}
impl CompletionContext {
fn normalize_path(file_path: &PathBuf) -> PathBuf {
if file_path.is_absolute() {
file_path.clone()
} else {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(file_path)
.canonicalize()
.unwrap_or_else(|_| file_path.clone())
}
}
pub fn analyze(
file_path: &PathBuf,
line: usize,
column: usize,
magellan: &Arc<MagellanIntegration>,
) -> Result<Self> {
let normalized_path = Self::normalize_path(file_path);
let enclosing_function = Self::find_enclosing_function(&normalized_path, line, magellan)?;
let enclosing_module = Self::find_enclosing_module(&normalized_path, magellan)?;
let local_symbols =
Self::get_visible_symbols(&normalized_path, &enclosing_module, magellan)?;
let imported_symbols =
Self::get_imported_symbols(&normalized_path, magellan).unwrap_or_default();
let mut visible_symbols = local_symbols;
visible_symbols.extend(imported_symbols);
let current_token = tokenizer::extract_current_token(&normalized_path, line, column);
Ok(Self {
file_path: normalized_path,
cursor_line: line,
cursor_col: column,
enclosing_function,
enclosing_module,
visible_symbols,
current_token,
})
}
fn find_enclosing_function(
file_path: &PathBuf,
line: usize,
magellan: &Arc<MagellanIntegration>,
) -> Result<Option<Symbol>> {
let db_query = r#"
SELECT id, name, kind, file_path, data
FROM graph_entities
WHERE file_path = ?1
AND kind = 'Symbol'
LIMIT 1000
"#;
let conn = Connection::open(magellan.db_path())
.map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))?;
let mut stmt = conn.prepare(db_query)?;
let rows = stmt.query_map([&file_path.to_string_lossy()], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, String>(3)?,
row.get::<_, String>(4)?,
))
})?;
let mut best_match: Option<Symbol> = None;
for row_result in rows {
let (id, name, _kind, path, data_str) = row_result?;
let id = id.to_string();
if let Ok(data) = serde_json::from_str::<JsonValue>(&data_str) {
if let Some(start_line) = data["start_line"].as_u64() {
if let Some(end_line) = data["end_line"].as_u64() {
if (line as u64) >= start_line && (line as u64) <= end_line {
if let Some(symbol_kind) = data["kind"].as_str() {
if symbol_kind == "Function" || symbol_kind == "Method" {
let symbol = Symbol {
id,
name,
kind: Self::parse_symbol_kind(symbol_kind),
path,
line: start_line as usize,
column: data["start_col"].as_u64().unwrap_or(0) as usize,
};
if best_match.is_none()
|| symbol.line > best_match.as_ref().unwrap().line
{
best_match = Some(symbol);
}
}
}
}
}
}
}
}
Ok(best_match)
}
fn find_enclosing_module(
_file_path: &PathBuf,
_magellan: &Arc<MagellanIntegration>,
) -> Result<Option<Symbol>> {
Ok(None)
}
fn get_visible_symbols(
file_path: &PathBuf,
_module: &Option<Symbol>,
magellan: &Arc<MagellanIntegration>,
) -> Result<Vec<Symbol>> {
let db_query = r#"
SELECT id, name, kind, file_path, data
FROM graph_entities
WHERE file_path = ?1
AND kind = 'Symbol'
ORDER BY name
LIMIT 100
"#;
let conn = Connection::open(magellan.db_path())
.map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))?;
let mut stmt = conn.prepare(db_query)?;
let rows = stmt.query_map([&file_path.to_string_lossy()], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, String>(3)?,
row.get::<_, String>(4)?,
))
})?;
let mut symbols = Vec::new();
for row_result in rows {
let (id, name, _kind, path, data_str) = row_result?;
let id = id.to_string();
if let Ok(data) = serde_json::from_str::<JsonValue>(&data_str) {
let kind_str = data["kind"].as_str().unwrap_or("Unknown");
let start_line = data["start_line"].as_u64().unwrap_or(0) as usize;
let start_col = data["start_col"].as_u64().unwrap_or(0) as usize;
let symbol = Symbol {
id,
name,
kind: Self::parse_symbol_kind(kind_str),
path,
line: start_line,
column: start_col,
};
symbols.push(symbol);
}
}
Ok(symbols)
}
fn get_imported_symbols(
file_path: &PathBuf,
magellan: &Arc<MagellanIntegration>,
) -> Result<Vec<Symbol>> {
let resolver = ImportResolver::new(&magellan.db_path().to_path_buf());
let imports = resolver.get_file_imports(file_path)?;
let mut imported_symbols = Vec::new();
for import in imports {
let target_files = Self::resolve_import_targets(&import, file_path);
for target_file in target_files {
if let Ok(symbols) = Self::get_public_symbols(&target_file, magellan) {
let filtered = if import.is_glob {
symbols } else {
symbols
.into_iter()
.filter(|s| import.imported_names.contains(&s.name))
.collect()
};
imported_symbols.extend(filtered);
}
}
}
Ok(imported_symbols)
}
fn resolve_import_targets(import: &ImportEntity, current_file: &PathBuf) -> Vec<PathBuf> {
let mut targets = Vec::new();
if let Some(parent) = current_file.parent() {
for segment in &import.import_path {
let target = parent.join(segment).with_extension("rs");
if target.exists() {
targets.push(target);
}
}
let mod_path = parent.join("mod.rs");
if mod_path.exists() {
targets.push(mod_path);
}
}
targets
}
fn get_public_symbols(
file_path: &PathBuf,
magellan: &Arc<MagellanIntegration>,
) -> Result<Vec<Symbol>> {
let db_query = r#"
SELECT id, name, kind, file_path, data
FROM graph_entities
WHERE file_path = ?1
AND kind = 'Symbol'
LIMIT 100
"#;
let conn = Connection::open(magellan.db_path())
.map_err(|e| anyhow::anyhow!("Failed to open database: {}", e))?;
let mut stmt = conn.prepare(db_query)?;
let rows = stmt.query_map([file_path.to_string_lossy()], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, String>(3)?,
row.get::<_, String>(4)?,
))
})?;
let mut symbols = Vec::new();
for row_result in rows {
let (id, name, _kind, path, data_str) = row_result?;
let id = id.to_string();
if let Ok(data) = serde_json::from_str::<JsonValue>(&data_str) {
let is_public = data.get("canonical_fqn").is_some()
|| data.get("visibility").and_then(|v| v.as_str()) == Some("public");
if is_public {
let kind_str = data
.get("kind")
.and_then(|v| v.as_str())
.unwrap_or("Unknown");
let start_line =
data.get("start_line").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
let start_col =
data.get("start_col").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
let symbol = Symbol {
id: id.clone(),
name,
kind: Self::parse_symbol_kind(kind_str),
path,
line: start_line,
column: start_col,
};
symbols.push(symbol);
}
}
}
Ok(symbols)
}
fn parse_symbol_kind(kind_str: &str) -> SymbolKind {
match kind_str {
"Function" | "Method" => SymbolKind::Function,
"Struct" => SymbolKind::Struct,
"Enum" => SymbolKind::Enum,
"Trait" => SymbolKind::Trait,
"Module" => SymbolKind::Module,
"Constant" => SymbolKind::Constant,
"TypeAlias" => SymbolKind::TypeAlias,
"Constructor" => SymbolKind::Constructor,
"Impl" => SymbolKind::Impl,
_ => SymbolKind::Variable,
}
}
}