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