xvc_core/util/
completer.rs

1//! Completion helpers for commands and options
2use crate::{
3    types::xvcroot::{find_root, XvcRootInner},
4    Result, XvcPath,
5};
6use std::{env, ffi::OsStr, path::Path};
7
8use clap_complete::CompletionCandidate;
9use xvc_ecs::{Storable, XvcStore};
10
11/// Return completions for all Git references starting with `current` in the current directory
12/// Used in `--from-ref` option.
13pub fn git_reference_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
14    let current = current.to_string_lossy();
15    crate::git::gix_list_references(Path::new("."))
16        .map(|refs| {
17            refs.iter()
18                .filter_map(|r| {
19                    if r.starts_with(current.as_ref()) {
20                        Some(CompletionCandidate::new(r))
21                    } else {
22                        None
23                    }
24                })
25                .collect()
26        })
27        .unwrap_or_default()
28}
29
30/// Return completions for all Git branches starting with `current` in the current directory
31/// Used in `--to-branch` option
32pub fn git_branch_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
33    let current = current.to_string_lossy();
34    crate::git::gix_list_branches(Path::new("."))
35        .map(|refs| {
36            refs.iter()
37                .filter_map(|r| {
38                    if r.starts_with(current.as_ref()) {
39                        Some(CompletionCandidate::new(r))
40                    } else {
41                        None
42                    }
43                })
44                .collect()
45        })
46        .unwrap_or_default()
47}
48
49/// A generic function to convert [strum_macros::VariantNames] to [CompletionCandidate] values. It
50/// can be used when an enum uses strum to parse string values.
51pub fn strum_variants_completer<T: strum::VariantNames>(
52    current: &std::ffi::OsStr,
53) -> Vec<CompletionCandidate> {
54    let current = current.to_string_lossy();
55    let variants = T::VARIANTS;
56    variants
57        .iter()
58        .filter_map(|v| {
59            if (**v).starts_with(current.as_ref()) {
60                Some(CompletionCandidate::new(v))
61            } else {
62                None
63            }
64        })
65        .collect()
66}
67
68/// Returns a store to complete an attribute for a component.
69///
70/// It doesn't load [XvcRoot] or any configuration files. It just checks the presense of .xvc
71/// directory in parent directories and loads a store from there.
72///
73/// Returns Err(CannotFindXvcRoot) if the root is not found. Actual completers should handle errors
74/// to return empty list.
75pub fn load_store_for_completion<T: Storable>(current_dir: &Path) -> Result<XvcStore<T>> {
76    let xvc_root_path = find_root(current_dir)?;
77    let xvc_dir = xvc_root_path.join(XvcRootInner::XVC_DIR);
78    let store_root = xvc_dir.join(XvcRootInner::STORE_DIR);
79    XvcStore::<T>::load_store(&store_root).map_err(|e| e.into())
80}
81
82/// Complete all XvcPath items in the store starting with prefix
83pub fn xvc_path_completer(prefix: &OsStr) -> Vec<CompletionCandidate> {
84    // FIXME: What should we do for Non-UTF-8 paths?
85    let prefix = prefix.to_str().unwrap_or("");
86    if let Ok(current_dir) = env::current_dir() {
87        load_store_for_completion::<XvcPath>(&current_dir)
88            .map(|xvc_path_store| {
89                // FIXME: This doesn't consider current dir to filter the elements
90                let filtered = xvc_path_store.filter(|_, xp| xp.starts_with_str(prefix));
91                filtered
92                    .iter()
93                    .map(|(_, xp)| xp.to_string().into())
94                    .collect()
95            })
96            .unwrap_or_default()
97    } else {
98        vec![]
99    }
100}