hyper_scripter/query/
util.rs1use super::the_multifuzz_algo::{the_multifuzz_algo, MultiFuzzObj};
2use super::{ListQuery, ScriptQuery, ScriptQueryInner, StableRepo};
3use crate::color::Stylize;
4use crate::config::{Config, PromptLevel};
5use crate::error::{Error, Result};
6use crate::fuzzy;
7use crate::script_repo::{RepoEntry, Visibility};
8use crate::util::{get_display_type, prompt};
9use crate::Either;
10use crate::SEP;
11use fxhash::FxHashSet as HashSet;
12
13fn compute_vis(bang: bool) -> Visibility {
14 if bang {
15 Visibility::All
16 } else {
17 Visibility::Normal
18 }
19}
20
21pub async fn do_list_query<'a, R: StableRepo>(
22 repo: &'a mut R,
23 queries: impl IntoIterator<Item = ListQuery>,
24) -> Result<Vec<RepoEntry<'a>>> {
25 do_list_query_with_handler(repo, queries, &mut super::DefaultListQueryHandler).await
26}
27
28pub async fn do_list_query_with_handler<
29 'a,
30 R: StableRepo,
31 I,
32 H: super::ListQueryHandler<Item = I>,
33>(
34 repo: &'a mut R,
35 queries: impl IntoIterator<Item = I>,
36 handler: &mut H,
37) -> Result<Vec<RepoEntry<'a>>> {
38 let mut is_empty = true;
39 let mut mem = HashSet::<i64>::default();
40 let mut ret = vec![];
41 let repo_ptr = repo as *mut R;
42 for query in queries {
43 is_empty = false;
44 macro_rules! insert {
45 ($script:ident) => {
46 if mem.contains(&$script.id) {
47 continue;
48 }
49 mem.insert($script.id);
50 ret.push($script);
51 };
52 }
53 let repo = unsafe { &mut *repo_ptr };
55 let query = handler.handle_item(query);
56 match query {
57 None => (),
58 Some(ListQuery::Pattern(re, og, bang)) => {
59 let mut is_empty = true;
60 for script in repo.iter_mut(compute_vis(bang)) {
61 if re.is_match(&script.name.key()) {
62 is_empty = false;
63 insert!(script);
64 }
65 }
66 if is_empty {
67 return Err(Error::ScriptNotFound(og.to_owned()));
68 }
69 }
70 Some(ListQuery::Query(query)) => {
71 let script = match handler.handle_query(query, repo).await {
72 Ok(Some(entry)) => entry,
73 Ok(None) => continue,
74 Err(e) => return Err(e),
75 };
76 insert!(script);
77 }
78 }
79 }
80 if is_empty && H::should_return_all_on_empty() {
81 return Ok(repo.iter_mut(Visibility::Normal).collect());
82 }
83 if ret.is_empty() && H::should_raise_dont_fuzz_on_empty() {
84 log::debug!("列表查不到東西,卻又不是因為 pattern not match,想必是因為使用者取消了模糊搜");
85 Err(Error::DontFuzz)
86 } else {
87 Ok(ret)
88 }
89}
90
91impl<'a> MultiFuzzObj for RepoEntry<'a> {
92 fn beats(&self, other: &Self) -> bool {
93 self.last_time() > other.last_time()
94 }
95}
96
97pub async fn do_script_query<'b, R: StableRepo>(
98 script_query: &ScriptQuery,
99 script_repo: &'b mut R,
100 finding_filtered: bool,
101 forbid_prompt: bool,
102) -> Result<Option<RepoEntry<'b>>> {
103 log::debug!("開始尋找 `{:?}`", script_query);
104 let mut visibility = compute_vis(script_query.bang);
105 if finding_filtered {
106 visibility = visibility.invert();
107 }
108 match &script_query.inner {
109 ScriptQueryInner::Prev(prev) => {
110 assert!(!finding_filtered); let latest = script_repo.latest_mut(prev.get(), visibility);
112 log::trace!("找最新腳本");
113 return if latest.is_some() {
114 Ok(latest)
115 } else {
116 Err(Error::Empty)
117 };
118 }
119 ScriptQueryInner::Exact(name) => Ok(script_repo.get_mut(name, visibility)),
120 ScriptQueryInner::Fuzz(name) => {
121 let level = if forbid_prompt {
122 PromptLevel::Never
123 } else {
124 Config::get_prompt_level()
125 };
126
127 let iter = script_repo.iter_mut(visibility);
128 let fuzz_res = fuzzy::fuzz(name, iter, SEP).await?;
129 let mut is_low = false;
130 let mut is_multi_fuzz = false;
131 let entry = match fuzz_res {
132 Some(fuzzy::High(entry)) => entry,
133 Some(fuzzy::Low(entry)) => {
134 is_low = true;
135 entry
136 }
137 #[cfg(feature = "benching")]
138 Some(fuzzy::Multi { ans, .. }) => {
139 is_multi_fuzz = true;
140 ans
141 }
142 #[cfg(not(feature = "benching"))]
143 Some(fuzzy::Multi { ans, others, .. }) => {
144 is_multi_fuzz = true;
145 match handle_special_dot_anonymous(name, ans, others) {
146 Either::One(res) => res,
147 Either::Two((ans, others)) => the_multifuzz_algo(ans, others),
148 }
149 }
150 None => return Ok(None),
151 };
152 let need_prompt = {
153 match level {
154 PromptLevel::Always => true,
155 PromptLevel::Never => false,
156 PromptLevel::Smart => is_low || is_multi_fuzz,
157 PromptLevel::OnMultiFuzz => is_multi_fuzz,
158 }
159 };
160 if need_prompt {
161 let ty = get_display_type(&entry.ty);
162 let msg = format!("{}({})?", entry.name, ty.display());
163 let yes = prompt(msg.stylize().color(ty.color()).bold(), true)?;
164 if !yes {
165 return Err(Error::DontFuzz);
166 }
167 }
168 Ok(Some(entry))
169 }
170 }
171}
172pub async fn do_script_query_strict<'b, R: StableRepo>(
173 script_query: &ScriptQuery,
174 script_repo: &'b mut R,
175) -> Result<RepoEntry<'b>> {
176 let ptr = script_repo as *mut R;
178 if let Some(info) = do_script_query(script_query, script_repo, false, false).await? {
179 return Ok(info);
180 }
181
182 let script_repo = unsafe { &mut *ptr };
183 #[cfg(not(feature = "benching"))]
184 if !script_query.bang {
185 let filtered = do_script_query(script_query, script_repo, true, true).await?;
186 if let Some(filtered) = filtered {
187 return Err(Error::ScriptIsFiltered(filtered.name.key().to_string()));
188 }
189 };
190
191 Err(Error::ScriptNotFound(script_query.to_string()))
192}
193
194fn handle_special_dot_anonymous<'a>(
198 pattern: &str,
199 ans: RepoEntry<'a>,
200 others: Vec<RepoEntry<'a>>,
201) -> Either<RepoEntry<'a>, (RepoEntry<'a>, Vec<RepoEntry<'a>>)> {
202 if pattern != "." {
203 return Either::Two((ans, others));
204 }
205 if !ans.name.is_anonymous() || others.iter().any(|e| !e.name.is_anonymous()) {
207 return Either::Two((ans, others));
208 }
209
210 let res = std::iter::once(ans)
211 .chain(others.into_iter())
212 .max_by_key(|e| e.last_time())
213 .unwrap();
214 Either::One(res)
215}