use clap::CommandFactory;
use clap_complete::CompleteEnv;
use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
use crate::cache::{Cache, DEFAULT_URI_TYPES};
use crate::cli::CliArgs;
use crate::edit::{FlakeEdit, sorted_input_ids};
use crate::input::Follows;
use super::editor::Editor;
use super::handler::root::Root;
pub fn complete() {
CompleteEnv::with_factory(command).complete();
}
fn command() -> clap::Command {
let mut cmd = CliArgs::command();
for &(sub, arg, candidates) in COMPLETERS {
cmd = attach(cmd, sub, arg, candidates);
}
cmd
}
type Completer = fn() -> Vec<CompletionCandidate>;
const COMPLETERS: &[(&str, &str, Completer)] = &[
("remove", "id", input_candidates),
("change", "id", input_candidates),
("pin", "id", toplevel_candidates),
("unpin", "id", toplevel_candidates),
("update", "id", toplevel_candidates),
("toggle", "input", toggle_candidates),
("add-follow", "input", follow_candidates),
("add-follow", "target", toplevel_candidates),
("add", "id", uri_candidates),
("add", "uri", uri_candidates),
];
fn attach(
cmd: clap::Command,
sub: &str,
arg: &'static str,
candidates: Completer,
) -> clap::Command {
cmd.mut_subcommand(sub, move |c| {
c.mut_args(move |a| {
if a.get_id().as_str() == arg {
a.add(ArgValueCandidates::new(candidates))
} else {
a
}
})
})
}
fn load() -> Option<FlakeEdit> {
let root = Root::from_path("flake.nix").ok()?;
let editor = Editor::from_path(root.path().to_path_buf()).ok()?;
editor.create_flake_edit().ok()
}
fn input_candidates() -> Vec<CompletionCandidate> {
let Some(mut flake_edit) = load() else {
return Vec::new();
};
let inputs = flake_edit.list();
let mut out = Vec::new();
for key in sorted_input_ids(inputs) {
let input = &inputs[key];
out.push(CompletionCandidate::new(input.id().as_str()));
for follows in input.follows() {
if let Follows::Indirect { path, .. } = follows {
out.push(CompletionCandidate::new(format!(
"{}.{}",
input.id().as_str(),
path
)));
}
}
}
out
}
fn toplevel_candidates() -> Vec<CompletionCandidate> {
let Some(mut flake_edit) = load() else {
return Vec::new();
};
let inputs = flake_edit.list();
sorted_input_ids(inputs)
.into_iter()
.map(|id| CompletionCandidate::new(id.as_str()))
.collect()
}
fn uri_candidates() -> Vec<CompletionCandidate> {
let mut out: Vec<CompletionCandidate> = DEFAULT_URI_TYPES
.iter()
.map(|uri_type| CompletionCandidate::new(*uri_type))
.collect();
for uri in Cache::load().list_uris() {
out.push(CompletionCandidate::new(uri));
}
out
}
fn follow_candidates() -> Vec<CompletionCandidate> {
let Ok(lock) = crate::lock::FlakeLock::from_default_path() else {
return Vec::new();
};
lock.nested_inputs()
.into_iter()
.map(|nested| CompletionCandidate::new(nested.path.to_string()))
.collect()
}
fn toggle_candidates() -> Vec<CompletionCandidate> {
let Some(mut flake_edit) = load() else {
return Vec::new();
};
let Ok(states) = flake_edit.toggle_states() else {
return Vec::new();
};
states
.into_iter()
.filter(|(_, state)| !state.alternates.is_empty())
.map(|(id, _)| CompletionCandidate::new(id))
.collect()
}