use anyhow::Result;
use colored::Colorize;
use rayon::prelude::*;
use serde::Serialize;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use crate::cache::FileMetaStore;
use crate::chunker::SemanticChunker;
use crate::embed::{EmbeddingService, ModelType};
use crate::file::FileWalker;
use crate::fts::FtsStore;
use crate::rerank::{rrf_fusion, vector_only, FusedResult, NeuralReranker, DEFAULT_RRF_K};
use crate::vectordb::VectorStore;
#[derive(Debug, Clone)]
pub struct SearchOptions {
pub max_results: usize,
pub per_file: Option<usize>,
pub content_lines: usize,
pub show_scores: bool,
pub compact: bool,
pub sync: bool,
pub json: bool,
pub filter_path: Option<String>,
pub model_override: Option<String>,
pub vector_only: bool,
pub rrf_k: Option<usize>,
pub rerank: bool,
pub rerank_top: Option<usize>,
}
impl Default for SearchOptions {
fn default() -> Self {
Self {
max_results: 10,
per_file: None,
content_lines: 3,
show_scores: false,
compact: false,
sync: false,
json: false,
filter_path: None,
model_override: None,
vector_only: false,
rrf_k: None,
rerank: false,
rerank_top: None,
}
}
}
#[derive(Serialize)]
struct JsonOutput {
query: String,
results: Vec<JsonResult>,
#[serde(skip_serializing_if = "Option::is_none")]
timing: Option<JsonTiming>,
}
#[derive(Serialize)]
struct JsonResult {
path: String,
start_line: usize,
end_line: usize,
kind: String,
content: String,
score: f32,
#[serde(skip_serializing_if = "Option::is_none")]
signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
context_prev: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
context_next: Option<String>,
}
#[derive(Serialize)]
struct JsonTiming {
total_ms: u64,
embed_ms: u64,
search_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
rerank_ms: Option<u64>,
}
fn get_db_path(path: Option<PathBuf>) -> Result<(PathBuf, PathBuf)> {
use crate::db_discovery::resolve_database_with_message;
resolve_database_with_message(path.as_deref(), "searching")
}
pub fn read_metadata(db_path: &Path) -> Option<(String, usize, Option<String>)> {
let metadata_path = db_path.join("metadata.json");
if let Ok(content) = std::fs::read_to_string(&metadata_path) {
if let Ok(json) = serde_json::from_str::<serde_json::Value>(&content) {
let model = json.get("model_short_name")?.as_str()?.to_string();
let dims = json.get("dimensions")?.as_u64()? as usize;
let primary_language = json
.get("primary_language")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
return Some((model, dims, primary_language));
}
}
None
}
pub fn detect_identifiers(query: &str) -> Vec<String> {
let mut identifiers = Vec::new();
for token in query.split_whitespace() {
let is_pascal = token
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
&& token.chars().any(|c| c.is_lowercase())
&& !["Find", "Show", "Get", "Where", "How", "What", "All"].contains(&token);
let is_snake =
token.contains('_') && token.chars().all(|c| c.is_alphanumeric() || c == '_');
let is_camel = token
.chars()
.next()
.map(|c| c.is_lowercase())
.unwrap_or(false)
&& token.chars().any(|c| c.is_uppercase());
if is_pascal || is_snake || is_camel {
identifiers.push(token.to_string());
}
}
identifiers
}
pub fn detect_structural_intent(query: &str) -> Option<crate::chunker::ChunkKind> {
use crate::chunker::ChunkKind;
let query_lower = query.to_lowercase();
let has_identifier = contains_identifier(query);
eprintln!(
"🔍 detect_structural_intent: query='{}', has_identifier={}",
query, has_identifier
);
if !has_identifier {
return None; }
let kind = if query_lower.contains("class ") {
Some(ChunkKind::Class)
} else if query_lower.contains("struct ") {
Some(ChunkKind::Struct)
} else if query_lower.contains("function ") || query_lower.contains("fn ") {
Some(ChunkKind::Function)
} else if query_lower.contains("method ") {
Some(ChunkKind::Method)
} else if query_lower.contains("enum ") {
Some(ChunkKind::Enum)
} else if query_lower.contains("interface ") {
Some(ChunkKind::Interface)
} else if query_lower.contains("trait ") {
Some(ChunkKind::Trait)
} else {
None
};
eprintln!("🔍 detect_structural_intent: kind={:?}", kind);
kind
}
fn contains_identifier(query: &str) -> bool {
let chars: Vec<char> = query.chars().collect();
for i in 0..chars.len().saturating_sub(1) {
if chars[i].is_uppercase() && (chars[i + 1].is_lowercase() || chars[i + 1].is_ascii_digit())
{
return true;
}
}
for i in 1..chars.len().saturating_sub(1) {
if chars[i] == '_' && chars[i - 1].is_lowercase() && chars[i + 1].is_lowercase() {
return true;
}
}
for i in 0..chars.len().saturating_sub(1) {
if chars[i].is_lowercase() && chars[i + 1].is_uppercase() {
return true;
}
}
false
}
pub fn boost_kind(
results: &mut Vec<crate::vectordb::SearchResult>,
target_kind: crate::chunker::ChunkKind,
) {
let boost_factor = 0.15; let target_kind_str = format!("{:?}", target_kind);
for result in results.iter_mut() {
if result.kind == target_kind_str {
result.score *= 1.0 + boost_factor;
}
}
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
}
fn expand_query(query: &str) -> Vec<String> {
let mut variants = Vec::new();
let original_query = query.to_string();
variants.push(query.to_string());
if query.len() < 4 || query.len() > 50 {
return variants;
}
let looks_like_function = query.contains('_') && !query.contains(' ');
let looks_like_type = query
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
&& !query.contains(' ');
const MAX_FUNCTION_VARIANTS: usize = 5;
const MAX_TYPE_VARIANTS: usize = 5;
const MAX_CONCEPT_VARIANTS: usize = 2;
const MAX_ABBREV_VARIANTS: usize = 2;
if looks_like_function {
variants.push(format!("fn {}", query));
variants.push(format!("async fn {}", query));
variants.push(format!("pub fn {}", query));
if variants.len() - 1 < MAX_FUNCTION_VARIANTS {
variants.push(format!("{} method", query));
}
if variants.len() - 1 < MAX_FUNCTION_VARIANTS {
variants.push(format!("Function: {}", query));
}
}
if looks_like_type {
variants.push(format!("struct {}", query));
variants.push(format!("impl {}", query));
variants.push(format!("enum {}", query));
if variants.len() - 1 < MAX_TYPE_VARIANTS {
variants.push(format!("class {}", query));
}
if variants.len() - 1 < MAX_TYPE_VARIANTS {
variants.push(format!("Struct: {}", query));
}
}
let is_single_concept = !query.contains('_')
&& !query.contains(' ')
&& query
.chars()
.next()
.map(|c| c.is_lowercase())
.unwrap_or(false);
if is_single_concept {
variants.push(format!("fn {}", query));
if variants.len() - 1 < MAX_CONCEPT_VARIANTS {
variants.push(format!("{} function", query));
}
}
let abbreviations: &[(&str, &str)] = &[
("auth", "authentication"),
("config", "configuration"),
("db", "database"),
("conn", "connection"),
("err", "error"),
("msg", "message"),
];
let mut abbrev_count = 0;
for (abbr, full) in abbreviations {
if abbrev_count >= MAX_ABBREV_VARIANTS {
break;
}
if query.contains(abbr) {
let expanded = query.replace(abbr, full);
if expanded != query {
variants.push(expanded);
abbrev_count += 1;
}
}
}
const MAX_TOTAL_VARIANTS: usize = 9;
if variants.len() > MAX_TOTAL_VARIANTS {
variants.truncate(MAX_TOTAL_VARIANTS);
}
if std::env::var("CODESEARCH_VERBOSE").is_ok() && variants.len() > 1 {
eprintln!(
"[optimization] Query expansion: {} -> {} variants (original + {} expansions)",
original_query,
variants.len(),
variants.len() - 1
);
}
variants
}
pub fn adapt_rrf_k(query: &str) -> (f64, f64) {
let has_identifiers = !detect_identifiers(query).is_empty();
let has_structural_intent = detect_structural_intent(query).is_some();
match (has_identifiers, has_structural_intent) {
(true, _) => (12.0, 28.0),
(_, true) => (15.0, 25.0),
_ => (20.0, 20.0),
}
}
pub async fn search(query: &str, path: Option<PathBuf>, options: SearchOptions) -> Result<()> {
let (db_path, _project_path) = get_db_path(path)?;
if !db_path.exists() {
println!("{}", "❌ No database found!".red());
println!(" Run {} first", "codesearch index".bright_cyan());
println!();
println!(
"{}",
"💡 Tip: codesearch can find databases in parent directories. Use 'codesearch list' to see all indexed projects.".dimmed()
);
return Ok(());
}
let (model_type, dimensions, primary_language) =
if let Some(ref model_name) = options.model_override {
let mt = ModelType::parse(model_name).unwrap_or_default();
(mt, mt.dimensions(), None)
} else if let Some((model_name, dims, lang)) = read_metadata(&db_path) {
if let Some(mt) = ModelType::parse(&model_name) {
(mt, dims, lang)
} else {
eprintln!(
"{}",
"⚠️ Unknown model in metadata, using default".yellow()
);
(ModelType::default(), 384, None)
}
} else {
(ModelType::default(), 384, None)
};
if options.sync {
println!("{}", "🔄 Syncing database...".yellow());
sync_database(&db_path, model_type)?;
}
let start = Instant::now();
let store = VectorStore::new(&db_path, dimensions)?;
let load_duration = start.elapsed();
let start = Instant::now();
let cache_dir = crate::constants::get_global_models_cache_dir()?;
let mut embedding_service = EmbeddingService::with_cache_dir(model_type, Some(&cache_dir))?;
let model_load_duration = start.elapsed();
let query_variants = expand_query(query);
let start = Instant::now();
let all_query_embeddings = embedding_service.embed_queries_batch(&query_variants)?;
let embed_duration = start.elapsed();
let start = Instant::now();
let has_identifiers = !detect_identifiers(query).is_empty();
let retrieval_limit = if options.vector_only {
options.max_results
} else if has_identifiers {
std::cmp::max(options.max_results * 3, 100)
} else {
std::cmp::max(options.max_results * 5, 200)
};
use std::collections::BinaryHeap;
let vector_search_results: Vec<Vec<crate::vectordb::SearchResult>> = all_query_embeddings
.par_iter()
.map(|query_emb| store.search(query_emb, retrieval_limit))
.collect::<Result<Vec<_>>>()?;
struct HeapEntry {
id: u32,
score: f32,
distance: f32,
}
impl PartialEq for HeapEntry {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for HeapEntry {}
impl PartialOrd for HeapEntry {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for HeapEntry {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.score
.partial_cmp(&other.score)
.unwrap_or(std::cmp::Ordering::Equal)
}
}
let mut top_by_id: std::collections::HashMap<u32, HeapEntry> = std::collections::HashMap::new();
let mut full_results_by_id: std::collections::HashMap<u32, crate::vectordb::SearchResult> =
std::collections::HashMap::new();
for results in vector_search_results {
for result in results {
top_by_id
.entry(result.id)
.and_modify(|e| {
if result.score > e.score {
e.score = result.score;
e.distance = result.distance;
full_results_by_id.insert(result.id, result.clone());
}
})
.or_insert_with(|| {
let entry = HeapEntry {
id: result.id,
score: result.score,
distance: result.distance,
};
full_results_by_id.insert(result.id, result.clone());
entry
});
}
}
let mut heap: BinaryHeap<HeapEntry> = top_by_id.into_values().collect();
let mut vector_results: Vec<crate::vectordb::SearchResult> =
Vec::with_capacity(retrieval_limit);
while let Some(entry) = heap.pop() {
if vector_results.len() >= retrieval_limit {
break;
}
if let Some(mut result) = full_results_by_id.get(&entry.id).cloned() {
result.score = entry.score;
result.distance = entry.distance;
vector_results.push(result);
}
}
vector_results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
const HIGH_CONFIDENCE_THRESHOLD: f32 = 0.15; const EARLY_TERMINATION_TOP_N: usize = 5;
let should_use_vector_only = !options.vector_only && {
let top_results: Vec<_> = vector_results
.iter()
.take(EARLY_TERMINATION_TOP_N.min(vector_results.len()))
.collect();
let all_high_confidence = top_results
.iter()
.all(|r| r.distance < HIGH_CONFIDENCE_THRESHOLD);
!top_results.is_empty() && all_high_confidence
};
let vector_only_mode = options.vector_only || should_use_vector_only;
if should_use_vector_only && !options.vector_only {
eprintln!(
"{}",
"⚡ Early termination: High-confidence results found, skipping FTS search".green()
);
}
let fused_results: Vec<FusedResult> = if vector_only_mode {
vector_only(&vector_results)
} else {
match FtsStore::new(&db_path) {
Ok(fts_store) => {
let identifiers = detect_identifiers(query);
let structural_intent = detect_structural_intent(query);
if identifiers.is_empty() {
let fts_results =
fts_store.search(query, retrieval_limit, structural_intent)?;
let k = options.rrf_k.unwrap_or(DEFAULT_RRF_K as usize) as f32;
rrf_fusion(&vector_results, &fts_results, k)
} else {
let fts_results =
fts_store.search(query, retrieval_limit, structural_intent)?;
let mut all_exact_results = Vec::new();
let mut seen_exact_ids = std::collections::HashSet::new();
for identifier in &identifiers {
if let Ok(exact_matches) =
fts_store.search_exact(identifier, retrieval_limit, structural_intent)
{
for exact_match in exact_matches {
if seen_exact_ids.insert(exact_match.chunk_id) {
all_exact_results.push(exact_match);
}
}
}
}
let (vector_k, fts_k) = adapt_rrf_k(query);
let k = options.rrf_k.unwrap_or(DEFAULT_RRF_K as usize) as f32;
let vector_k_adaptive = vector_k.min(k as f64) as f32;
let fts_k_adaptive = fts_k.min(k as f64) as f32;
use crate::rerank::{rrf_fusion_with_exact, EXACT_MATCH_RRF_K};
rrf_fusion_with_exact(
&vector_results,
&fts_results,
&all_exact_results,
vector_k_adaptive,
fts_k_adaptive,
EXACT_MATCH_RRF_K,
)
}
}
Err(_) => {
eprintln!(
"{}",
"⚠️ FTS index not found, using vector-only search".yellow()
);
vector_only(&vector_results)
}
}
};
let mut results: Vec<crate::vectordb::SearchResult> = Vec::new();
let chunk_id_to_result: std::collections::HashMap<u32, &crate::vectordb::SearchResult> =
vector_results.iter().map(|r| (r.id, r)).collect();
let should_filter_by_path = options.filter_path.is_some();
let filter_path_normalized = options
.filter_path
.as_ref()
.map(|f| f.trim_start_matches("./").to_string());
let take_multiplier = if should_filter_by_path { 3 } else { 1 };
let take_count = if options.rerank {
options
.rerank_top
.unwrap_or(options.max_results)
.min(fused_results.len())
} else {
options.max_results * take_multiplier
};
for fused in fused_results.iter().take(take_count) {
if let Some(result) = chunk_id_to_result.get(&fused.chunk_id) {
if should_filter_by_path {
if let Some(ref filter) = filter_path_normalized {
let path_normalized = result.path.trim_start_matches("./");
if !path_normalized.starts_with(filter) {
continue;
}
}
}
let mut r = (*result).clone();
r.score = fused.rrf_score;
results.push(r);
} else {
if let Ok(Some(mut result)) = store.get_chunk_as_result(fused.chunk_id) {
if should_filter_by_path {
if let Some(ref filter) = filter_path_normalized {
let path_normalized = result.path.trim_start_matches("./");
if !path_normalized.starts_with(filter) {
continue;
}
}
}
result.score = fused.rrf_score;
results.push(result);
}
}
}
if should_filter_by_path {
let candidates_processed = take_count;
let results_after_filtering = results.len();
let filtered_out = candidates_processed.saturating_sub(results_after_filtering);
eprintln!(
"{}",
format!(
"🔍 Path filter '{}': {} candidates → {} results ({} filtered out)",
filter_path_normalized.as_ref().unwrap_or(&"".to_string()),
candidates_processed,
results_after_filtering,
filtered_out
)
.blue()
);
}
if let Some(ref lang) = primary_language {
use crate::file::Language;
let lang_boost = 0.2; for result in results.iter_mut() {
let file_lang = format!(
"{:?}",
Language::from_path(std::path::Path::new(&result.path))
);
if file_lang == *lang {
result.score *= 1.0 + lang_boost;
}
}
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
}
if let Some(intent) = detect_structural_intent(query) {
boost_kind(&mut results, intent);
}
let identifiers = detect_identifiers(query);
if !identifiers.is_empty() && results.is_empty() {
eprintln!(
"{}",
format!(
"❓ No exact matches found for identifiers: {}",
identifiers.join(", ")
)
.yellow()
);
eprintln!("{}", " Try using broader search terms or running `codesearch index --sync` if the codebase changed.".dimmed());
}
let search_duration = start.elapsed();
let mut rerank_duration = Duration::ZERO;
if options.rerank && !results.is_empty() {
let start = Instant::now();
match NeuralReranker::new() {
Ok(mut reranker) => {
let documents: Vec<String> = results.iter().map(|r| r.content.clone()).collect();
let rrf_scores: Vec<f32> = results.iter().map(|r| r.score).collect();
match reranker.rerank_and_blend(query, &documents, &rrf_scores) {
Ok(reranked) => {
let mut reordered: Vec<crate::vectordb::SearchResult> =
Vec::with_capacity(results.len());
for (idx, score) in reranked {
let mut result = results[idx].clone();
result.score = score;
reordered.push(result);
}
results = reordered;
println!("{}", "✅ Neural reranking applied".green());
}
Err(e) => {
eprintln!("{}", format!("⚠️ Reranking failed: {}", e).yellow());
}
}
}
Err(e) => {
eprintln!("{}", format!("⚠️ Could not load reranker: {}", e).yellow());
}
}
rerank_duration = start.elapsed();
}
if let Some(ref filter) = options.filter_path {
let filter_normalized = filter.trim_start_matches("./");
results.retain(|r| {
let path_normalized = r.path.trim_start_matches("./");
path_normalized.starts_with(filter_normalized)
});
}
results.truncate(options.max_results);
if options.json {
let json_results: Vec<JsonResult> = results
.iter()
.map(|r| JsonResult {
path: r.path.clone(),
start_line: r.start_line,
end_line: r.end_line,
kind: r.kind.clone(),
content: r.content.clone(),
score: r.score,
signature: r.signature.clone(),
context_prev: r.context_prev.clone(),
context_next: r.context_next.clone(),
})
.collect();
let timing = if options.show_scores {
Some(JsonTiming {
total_ms: (load_duration
+ model_load_duration
+ embed_duration
+ search_duration
+ rerank_duration)
.as_millis() as u64,
embed_ms: embed_duration.as_millis() as u64,
search_ms: search_duration.as_millis() as u64,
rerank_ms: if options.rerank {
Some(rerank_duration.as_millis() as u64)
} else {
None
},
})
} else {
None
};
let output = JsonOutput {
query: query.to_string(),
results: json_results,
timing,
};
println!("{}", serde_json::to_string(&output)?);
return Ok(());
}
if options.compact {
let mut seen_files = std::collections::HashSet::new();
for result in &results {
if !seen_files.contains(&result.path) {
println!("{}", result.path);
seen_files.insert(result.path.clone());
}
}
return Ok(());
}
println!("{}", "🔍 Search Results".bright_cyan().bold());
println!("{}", "=".repeat(60));
println!("Query: \"{}\"", query.bright_yellow());
println!("Found {} results", results.len());
println!();
if options.show_scores {
println!("Timing:");
println!(" Database load: {:?}", load_duration);
println!(" Model load: {:?}", model_load_duration);
println!(" Query embed: {:?}", embed_duration);
println!(" Search: {:?}", search_duration);
if options.rerank {
println!(" Reranking: {:?}", rerank_duration);
}
println!(
" Total: {:?}",
load_duration
+ model_load_duration
+ embed_duration
+ search_duration
+ rerank_duration
);
println!();
}
if results.is_empty() {
println!("{}", "No matches found.".dimmed());
println!("Try:");
println!(" - Using different keywords");
println!(" - Making your query more general");
println!(
" - Running {} if the codebase changed",
"codesearch index --force".bright_cyan()
);
return Ok(());
}
if let Some(per_file) = options.per_file {
if per_file > 0 && per_file < options.max_results {
let mut by_file: std::collections::HashMap<String, Vec<_>> =
std::collections::HashMap::new();
for result in results {
by_file.entry(result.path.clone()).or_default().push(result);
}
let mut files: Vec<_> = by_file.into_iter().collect();
files.sort_by(|a, b| {
b.1.iter()
.map(|r| r.score)
.fold(0.0f32, f32::max)
.partial_cmp(&a.1.iter().map(|r| r.score).fold(0.0f32, f32::max))
.unwrap()
});
for (_file_path, mut file_results) in files {
file_results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
file_results.truncate(per_file);
for (idx, result) in file_results.iter().enumerate() {
print_result(
result,
idx == 0,
options.content_lines > 0,
options.show_scores,
)?;
}
}
} else {
for result in &results {
print_result(result, true, options.content_lines > 0, options.show_scores)?;
}
}
} else {
for result in &results {
print_result(result, true, options.content_lines > 0, options.show_scores)?;
}
}
Ok(())
}
fn sync_database(db_path: &Path, model_type: ModelType) -> Result<()> {
let project_path = db_path.parent().unwrap_or(std::path::Path::new("."));
let mut file_meta =
FileMetaStore::load_or_create(db_path, model_type.short_name(), model_type.dimensions())?;
let walker = FileWalker::new(project_path.to_path_buf());
let (files, _stats) = walker.walk()?;
let cache_dir = crate::constants::get_global_models_cache_dir()?;
let mut embedding_service = EmbeddingService::with_cache_dir(model_type, Some(&cache_dir))?;
let mut chunker = SemanticChunker::new(100, 2000, 10);
let mut store = VectorStore::new(db_path, model_type.dimensions())?;
let mut changes = 0;
for file in &files {
let (needs_reindex, old_chunk_ids) = file_meta.check_file(&file.path)?;
if !needs_reindex {
continue;
}
changes += 1;
println!(" 📝 {}", file.path.display());
if !old_chunk_ids.is_empty() {
store.delete_chunks(&old_chunk_ids)?;
}
let source_code = match std::fs::read_to_string(&file.path) {
Ok(content) => content,
Err(_) => continue,
};
let chunks = chunker.chunk_semantic(file.language, &file.path, &source_code)?;
if chunks.is_empty() {
file_meta.update_file(&file.path, vec![])?;
continue;
}
let embedded_chunks = embedding_service.embed_chunks(chunks)?;
let chunk_ids = store.insert_chunks_with_ids(embedded_chunks)?;
file_meta.update_file(&file.path, chunk_ids)?;
}
let deleted_files = file_meta.find_deleted_files();
for (path, chunk_ids) in &deleted_files {
changes += 1;
println!(" 🗑️ {} (deleted)", path);
if !chunk_ids.is_empty() {
store.delete_chunks(chunk_ids)?;
}
file_meta.remove_file(std::path::Path::new(path));
}
if changes > 0 {
println!(" 🔨 Rebuilding index...");
store.build_index()?;
file_meta.save(db_path)?;
println!(" ✅ {} file(s) synced", changes);
} else {
println!(" ✅ Already up to date");
}
Ok(())
}
fn print_result(
result: &crate::vectordb::SearchResult,
show_file: bool,
show_content: bool,
show_scores: bool,
) -> Result<()> {
if show_file {
println!("{}", "─".repeat(60));
let file_display = format!("📄 {}", result.path);
println!("{}", file_display.bright_green());
}
let location = format!(
" Lines {}-{} • {}",
result.start_line, result.end_line, result.kind
);
println!("{}", location.dimmed());
if let Some(sig) = &result.signature {
println!(" {}", sig.bright_cyan());
}
if show_scores {
let score_color = if result.score > 0.8 {
"green"
} else if result.score > 0.6 {
"yellow"
} else {
"red"
};
let score_text = format!(" Score: {:.3}", result.score);
println!(
"{}",
match score_color {
"green" => score_text.green(),
"yellow" => score_text.yellow(),
_ => score_text.red(),
}
);
}
if let Some(ctx) = &result.context {
println!(" Context: {}", ctx.dimmed());
}
if show_content {
if let Some(ctx_prev) = &result.context_prev {
println!("\n {}:", "Context (before)".dimmed());
for line in ctx_prev.lines() {
println!(" │ {}", line.bright_black());
}
}
println!("\n {}:", "Content".bright_yellow());
for line in result.content.lines().take(10) {
println!(" │ {}", line.dimmed());
}
if result.content.lines().count() > 10 {
println!(" │ {}", "...".dimmed());
}
if let Some(ctx_next) = &result.context_next {
println!("\n {}:", "Context (after)".dimmed());
for line in ctx_next.lines() {
println!(" │ {}", line.bright_black());
}
}
} else {
let snippet: String = result.content.lines().take(3).collect::<Vec<_>>().join(" ");
let snippet = if snippet.len() > 100 {
format!("{}...", &snippet[..100])
} else {
snippet
};
println!(" {}", snippet.dimmed());
}
println!();
Ok(())
}