flake_edit/app/
complete.rs1use clap::CommandFactory;
4use clap_complete::CompleteEnv;
5use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
6
7use crate::cache::{Cache, DEFAULT_URI_TYPES};
8use crate::cli::CliArgs;
9use crate::edit::{FlakeEdit, sorted_input_ids};
10use crate::input::Follows;
11
12use super::editor::Editor;
13use super::handler::root::Root;
14
15pub fn complete() {
19 CompleteEnv::with_factory(command).complete();
20}
21
22fn command() -> clap::Command {
23 let mut cmd = CliArgs::command();
24 for &(sub, arg, candidates) in COMPLETERS {
25 cmd = attach(cmd, sub, arg, candidates);
26 }
27 cmd
28}
29
30type Completer = fn() -> Vec<CompletionCandidate>;
31
32const COMPLETERS: &[(&str, &str, Completer)] = &[
34 ("remove", "id", input_candidates),
35 ("change", "id", input_candidates),
36 ("pin", "id", toplevel_candidates),
37 ("unpin", "id", toplevel_candidates),
38 ("update", "id", toplevel_candidates),
39 ("toggle", "input", toggle_candidates),
40 ("add-follow", "input", follow_candidates),
41 ("add-follow", "target", toplevel_candidates),
42 ("add", "id", uri_candidates),
43 ("add", "uri", uri_candidates),
44];
45
46fn attach(
48 cmd: clap::Command,
49 sub: &str,
50 arg: &'static str,
51 candidates: Completer,
52) -> clap::Command {
53 cmd.mut_subcommand(sub, move |c| {
54 c.mut_args(move |a| {
55 if a.get_id().as_str() == arg {
56 a.add(ArgValueCandidates::new(candidates))
57 } else {
58 a
59 }
60 })
61 })
62}
63
64fn load() -> Option<FlakeEdit> {
68 let root = Root::from_path("flake.nix").ok()?;
69 let editor = Editor::from_path(root.path().to_path_buf()).ok()?;
70 editor.create_flake_edit().ok()
71}
72
73fn input_candidates() -> Vec<CompletionCandidate> {
74 let Some(mut flake_edit) = load() else {
75 return Vec::new();
76 };
77 let inputs = flake_edit.list();
78 let mut out = Vec::new();
79 for key in sorted_input_ids(inputs) {
80 let input = &inputs[key];
81 out.push(CompletionCandidate::new(input.id().as_str()));
82 for follows in input.follows() {
83 if let Follows::Indirect { path, .. } = follows {
84 out.push(CompletionCandidate::new(format!(
85 "{}.{}",
86 input.id().as_str(),
87 path
88 )));
89 }
90 }
91 }
92 out
93}
94
95fn toplevel_candidates() -> Vec<CompletionCandidate> {
96 let Some(mut flake_edit) = load() else {
97 return Vec::new();
98 };
99 let inputs = flake_edit.list();
100 sorted_input_ids(inputs)
101 .into_iter()
102 .map(|id| CompletionCandidate::new(id.as_str()))
103 .collect()
104}
105
106fn uri_candidates() -> Vec<CompletionCandidate> {
108 let mut out: Vec<CompletionCandidate> = DEFAULT_URI_TYPES
109 .iter()
110 .map(|uri_type| CompletionCandidate::new(*uri_type))
111 .collect();
112 for uri in Cache::load().list_uris() {
113 out.push(CompletionCandidate::new(uri));
114 }
115 out
116}
117
118fn follow_candidates() -> Vec<CompletionCandidate> {
120 let Ok(lock) = crate::lock::FlakeLock::from_default_path() else {
121 return Vec::new();
122 };
123 lock.nested_inputs()
124 .into_iter()
125 .map(|nested| CompletionCandidate::new(nested.path.to_string()))
126 .collect()
127}
128
129fn toggle_candidates() -> Vec<CompletionCandidate> {
130 let Some(mut flake_edit) = load() else {
131 return Vec::new();
132 };
133 let Ok(states) = flake_edit.toggle_states() else {
134 return Vec::new();
135 };
136 states
137 .into_iter()
138 .filter(|(_, state)| !state.alternates.is_empty())
139 .map(|(id, _)| CompletionCandidate::new(id))
140 .collect()
141}