hyper_scripter/util/
completion_util.rs

1use super::{init_repo, print_iter};
2use crate::args::{AliasRoot, Completion, Root, Subs};
3use crate::config::Config;
4use crate::error::{Error, Result};
5use crate::fuzzy::{fuzz_with_multifuzz_ratio, is_prefix, FuzzResult};
6use crate::path;
7use crate::script_repo::{RepoEntry, ScriptRepo, Visibility};
8use crate::SEP;
9use crate::{to_display_args, Either};
10use clap::Parser;
11use std::cmp::Reverse;
12
13fn sort(v: &mut Vec<RepoEntry<'_>>) {
14    v.sort_by_key(|s| Reverse(s.last_time()));
15}
16
17fn parse_alias_root(args: &[String]) -> Result<AliasRoot> {
18    match AliasRoot::try_parse_from(args) {
19        Ok(root) => Ok(root),
20        Err(e) => {
21            log::warn!("展開別名時出錯 {}", e);
22            // NOTE: -V 或 --help 也會走到這裡
23            Err(Error::Completion)
24        }
25    }
26}
27
28async fn fuzz_arr<'a>(
29    name: &str,
30    iter: impl Iterator<Item = RepoEntry<'a>>,
31) -> Result<Vec<RepoEntry<'a>>> {
32    // TODO: 測試這個複雜的函式,包括前綴和次級結果
33    let res = fuzz_with_multifuzz_ratio(name, iter, SEP, Some(60)).await?;
34    Ok(match res {
35        None => vec![],
36        Some(FuzzResult::High(t) | FuzzResult::Low(t)) => vec![t],
37        Some(FuzzResult::Multi {
38            ans,
39            others,
40            mut still_others,
41        }) => {
42            let prefix = ans.name.key();
43            let mut first_others = vec![];
44            let mut prefixed_others = vec![];
45            for candidate in others.into_iter() {
46                if is_prefix(&*prefix, &*candidate.name.key(), SEP) {
47                    prefixed_others.push(candidate);
48                } else {
49                    first_others.push(candidate);
50                }
51            }
52            first_others.push(ans);
53
54            sort(&mut first_others);
55            sort(&mut prefixed_others);
56            sort(&mut still_others);
57            first_others.append(&mut prefixed_others);
58            first_others.append(&mut still_others);
59            first_others
60        }
61    })
62}
63
64pub async fn handle_completion(comp: Completion, repo: &mut Option<ScriptRepo>) -> Result {
65    match comp {
66        Completion::LS {
67            name,
68            args,
69            limit,
70            bang,
71        } => {
72            let mut new_root = match Root::try_parse_from(args) {
73                Ok(Root {
74                    subcmd: Some(Subs::Tags(_) | Subs::Types(_) | Subs::Alias { before: None, .. }),
75                    ..
76                }) => {
77                    // XXX: 在補全腳本中處理,而不要在這邊
78                    return Err(Error::Completion);
79                }
80                Ok(t) => t,
81                Err(e) => {
82                    log::warn!("補全時出錯 {}", e);
83                    // NOTE: -V 或 --help 也會走到這裡
84                    return Err(Error::Completion);
85                }
86            };
87            log::info!("補完模式,參數為 {:?}", new_root);
88            new_root.set_home_unless_from_alias(false)?;
89            new_root.sanitize_flags(bang);
90            *repo = Some(init_repo(new_root.root_args, false).await?);
91
92            let iter = repo.as_mut().unwrap().iter_mut(Visibility::Normal);
93            let scripts = if let Some(name) = name {
94                fuzz_arr(&name, iter).await?
95            } else {
96                let mut t: Vec<_> = iter.collect();
97                sort(&mut t);
98                t
99            };
100
101            let iter = scripts.iter().map(|s| s.name.key());
102            if let Some(limit) = limit {
103                print_iter(iter.take(limit.get()), " ");
104            } else {
105                print_iter(iter, " ");
106            }
107        }
108        Completion::NoSubcommand { args } => {
109            if let Ok(root) = parse_alias_root(&args) {
110                if root.subcmd.is_some() {
111                    log::debug!("子命令 = {:?}", root.subcmd);
112                    return Err(Error::Completion);
113                }
114            } // else: 解析錯誤當然不可能有子命令啦
115        }
116        Completion::Alias { args } => {
117            let root = parse_alias_root(&args)?;
118
119            if root.root_args.no_alias {
120                log::info!("無別名模式");
121                return Err(Error::Completion);
122            }
123
124            let home = path::compute_home_path_optional(root.root_args.hs_home.as_ref(), false)?;
125            let conf = Config::load(&home)?;
126            if let Some(Either::One(new_args)) = root.expand_alias(&args, &conf) {
127                print_iter(new_args, " ");
128            } else {
129                log::info!("並非別名");
130                return Err(Error::Completion);
131            };
132        }
133        Completion::Home { args } => {
134            let root = parse_alias_root(&args)?;
135            let home = root.root_args.hs_home.ok_or_else(|| Error::Completion)?;
136            print!("{}", home);
137        }
138        Completion::ParseRun { args } => {
139            let mut root = Root::try_parse_from(args).map_err(|e| {
140                log::warn!("補全時出錯 {}", e);
141                Error::Completion
142            })?;
143            root.sanitize()?;
144            match root.subcmd {
145                Some(Subs::Run {
146                    script_query, args, ..
147                }) => {
148                    print!("{}", script_query);
149                    for arg in args {
150                        print!(" {}", to_display_args(&arg));
151                    }
152                }
153                res @ _ => {
154                    log::warn!("非執行指令 {:?}", res);
155                    return Err(Error::Completion);
156                }
157            }
158        }
159    }
160    Ok(())
161}