use std::path::Path;
use clap::Parser;
use repograph_core::{Config, RepographError, search};
use crate::output::{OutputMode, render_hits};
const DEFAULT_LIMIT: usize = 10;
#[derive(Debug, Parser)]
pub struct Args {
#[arg(value_name = "QUERY")]
pub query: String,
#[arg(long, value_name = "NAME")]
pub workspace: Option<String>,
#[arg(long, value_name = "N", default_value_t = DEFAULT_LIMIT)]
pub limit: usize,
#[arg(long)]
pub semantic: bool,
#[arg(long)]
pub json: bool,
}
#[tracing::instrument(skip(args, config_dir, data_dir), fields(
workspace = args.workspace.as_deref().unwrap_or("<all>"),
limit = args.limit,
semantic = args.semantic,
json = args.json,
))]
pub fn run(args: &Args, config_dir: &Path, data_dir: &Path) -> Result<(), RepographError> {
tracing::debug!("find: start");
let config = Config::load(config_dir)?;
let mode = OutputMode::detect(args.json);
let repos_filter = match &args.workspace {
Some(ws) => {
let (live, _dangling) = config.resolve_workspace(ws)?;
live.into_iter().map(|(name, _)| name.clone()).collect()
}
None => Vec::new(),
};
let outcome = search(
data_dir,
&args.query,
&repos_filter,
args.limit,
args.semantic,
)?;
if let Some(reason) = &outcome.degraded {
tracing::warn!(reason = %reason, "find: semantic unavailable; used lexical");
eprintln!("note: {reason}; showing keyword results");
}
render_hits(
mode,
&args.query,
&outcome.hits,
outcome.semantic_used,
outcome.degraded.as_deref(),
)?;
tracing::info!(
hits = outcome.hits.len(),
semantic_used = outcome.semantic_used,
"find: rendered",
);
Ok(())
}