use anyhow::{Context, Result};
use clap::{Args, ValueEnum};
use codeprysm_backend::{Backend, SearchOptions};
use super::create_backend;
use crate::GlobalOptions;
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum SearchMode {
Hybrid,
Semantic,
Code,
}
impl SearchMode {
fn to_option_str(self) -> Option<&'static str> {
match self {
SearchMode::Hybrid => None,
SearchMode::Semantic => Some("info"),
SearchMode::Code => Some("code"),
}
}
}
#[derive(Args, Debug)]
pub struct SearchArgs {
query: String,
#[arg(long, short = 'n', default_value = "10")]
limit: usize,
#[arg(long, short = 'm', value_enum, default_value = "hybrid")]
mode: SearchMode,
#[arg(long, short = 't')]
types: Vec<String>,
#[arg(long)]
min_score: Option<f32>,
#[arg(long, short = 'o', default_value = "text")]
output: OutputFormat,
#[arg(long, short = 's')]
snippets: bool,
#[arg(long)]
files_only: bool,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum OutputFormat {
Text,
Json,
}
pub async fn execute(args: SearchArgs, global: GlobalOptions) -> Result<()> {
let backend = create_backend(&global).await?;
let options = SearchOptions {
node_types: args.types.clone(),
file_patterns: vec![],
mode: args.mode.to_option_str().map(String::from),
include_snippets: args.snippets,
min_score: args.min_score,
};
let results = backend
.search(&args.query, args.limit, Some(options))
.await
.context("Search failed")?;
if results.is_empty() {
if !global.quiet {
eprintln!("No results found for: {}", args.query);
}
return Ok(());
}
match args.output {
OutputFormat::Json => {
let json =
serde_json::to_string_pretty(&results).context("Failed to serialize results")?;
println!("{}", json);
}
OutputFormat::Text => {
if args.files_only {
let mut seen = std::collections::HashSet::new();
for result in &results {
let key = format!("{}:{}", result.file_path, result.line_range.0);
if seen.insert(key.clone()) {
println!("{}", key);
}
}
} else {
if !global.quiet {
println!("Found {} results for \"{}\":\n", results.len(), args.query);
}
for (i, result) in results.iter().enumerate() {
println!("{}. {} ({})", i + 1, result.name, result.kind);
println!(
" {}:{}-{}",
result.file_path, result.line_range.0, result.line_range.1
);
println!(
" Score: {:.3} Sources: {}",
result.score,
result.sources.join(", ")
);
if args.snippets && !result.code_snippet.is_empty() {
println!(" ---");
for line in result.code_snippet.lines().take(5) {
println!(" {}", line);
}
if result.code_snippet.lines().count() > 5 {
println!(" ...");
}
}
println!();
}
}
}
}
Ok(())
}