use crate::graph;
use crate::suggest::kind_rank;
use super::coverage::TestsModel;
use super::impact::ImpactModel;
use super::impact_seed::{resolve_impact_seed, SeedResolution};
use super::index::GraphIndex;
use super::member::{resolve_member_seed, MemberSeedResolution};
use super::refs::RefsModel;
use super::symbol::{resolve_symbol, Resolution};
pub type NextArgv = Vec<String>;
fn argv(verb: &str, arg: &str) -> NextArgv {
vec![verb.to_string(), arg.to_string()]
}
fn symbol_target_resolves(index: &GraphIndex, seed: &str) -> bool {
match resolve_symbol(index, seed) {
Resolution::Resolved(_) => true,
Resolution::Ambiguous(_) => false,
Resolution::NotFound => matches!(
resolve_member_seed(index, seed),
MemberSeedResolution::Resolved(_)
),
}
}
fn impact_target_resolves(index: &GraphIndex, seed: &str) -> bool {
matches!(
resolve_impact_seed(index, seed),
SeedResolution::Resolved { .. }
)
}
fn reads_as_a_seed(argv: &NextArgv) -> bool {
argv.iter().skip(1).all(|arg| !arg.starts_with("--"))
}
fn dedup(candidates: Vec<NextArgv>) -> Vec<NextArgv> {
let mut out: Vec<NextArgv> = Vec::new();
for candidate in candidates {
if reads_as_a_seed(&candidate) && !out.contains(&candidate) {
out.push(candidate);
}
}
out
}
pub fn merge_suggestions(lists: Vec<Vec<NextArgv>>) -> Vec<NextArgv> {
dedup(lists.into_iter().flatten().collect())
}
pub fn refs_next(index: &GraphIndex, model: &RefsModel) -> Vec<NextArgv> {
let mut candidates = Vec::new();
if symbol_target_resolves(index, &model.id) {
candidates.push(argv("tests", &model.id));
}
if let Some(site) = model.sites.first() {
if impact_target_resolves(index, &site.file) {
candidates.push(argv("impact", &site.file));
}
}
dedup(candidates)
}
pub fn tests_next(index: &GraphIndex, model: &TestsModel) -> Vec<NextArgv> {
let Some(def) = index.def(&model.symbol) else {
return Vec::new();
};
if impact_target_resolves(index, &def.file) {
dedup(vec![argv("impact", &def.file)])
} else {
Vec::new()
}
}
pub fn impact_next(index: &GraphIndex, model: &ImpactModel) -> Vec<NextArgv> {
let Some(id) = model.rows.first().and_then(|r| r.top_symbol_id.as_deref()) else {
return Vec::new();
};
if symbol_target_resolves(index, id) {
dedup(vec![argv("refs", id)])
} else {
Vec::new()
}
}
fn declared_def<'g>(index: &GraphIndex<'g>, name: &graph::GraphName) -> Option<&'g graph::Def> {
let graph = index.graph;
index
.by_file
.get(&name.file)?
.iter()
.map(|&i| &graph.defs[i])
.find(|d| {
d.name == name.name
&& (d.line == name.line
|| d.also_in
.iter()
.any(|site| site.file == name.file && site.line == name.line))
})
}
pub fn find_next(index: &GraphIndex, name: &graph::GraphName) -> Vec<NextArgv> {
if kind_rank(&name.kind) > 1 {
return Vec::new();
}
let id = if name.owner.is_empty() {
match declared_def(index, name) {
Some(def) => def.id.as_str(),
None => return Vec::new(),
}
} else {
name.owner.as_str()
};
if symbol_target_resolves(index, id) {
dedup(vec![argv("read", id)])
} else {
Vec::new()
}
}