use super::{ListQuery, ScriptQuery, ScriptQueryInner};
use crate::config::{Config, PromptLevel};
use crate::error::{Error, Result};
use crate::fuzzy;
use crate::script::ScriptInfo;
use crate::script_repo::{RepoEntry, ScriptRepo};
use crate::util::{get_display_type, hijack_ctrlc_once};
use crate::SEP;
use fxhash::FxHashSet as HashSet;
pub async fn do_list_query<'a>(
repo: &'a mut ScriptRepo,
queries: &[ListQuery],
) -> Result<Vec<RepoEntry<'a>>> {
if queries.is_empty() {
return Ok(repo.iter_mut(false).collect());
}
let mut mem = HashSet::<i64>::default();
let mut ret = vec![];
let repo_ptr = repo as *mut ScriptRepo;
for query in queries.iter() {
macro_rules! insert {
($script:ident) => {
if mem.contains(&$script.id) {
continue;
}
mem.insert($script.id);
ret.push($script);
};
}
let repo = unsafe { &mut *repo_ptr };
match query {
ListQuery::Pattern(re, og) => {
let mut is_empty = true;
for script in repo.iter_mut(false) {
if re.is_match(&script.name.key()) {
is_empty = false;
insert!(script);
}
}
if is_empty {
return Err(Error::ScriptNotFound(og.to_owned()));
}
}
ListQuery::Query(query) => {
let script = match do_script_query_strict(query, repo).await {
Err(Error::DontFuzz) => continue,
Ok(entry) => entry,
Err(e) => return Err(e),
};
insert!(script);
}
}
}
if ret.is_empty() {
log::debug!("列表查不到東西,卻又不是因為 pattern not match,想必是因為使用者取消了模糊搜");
Err(Error::DontFuzz)
} else {
Ok(ret)
}
}
pub async fn do_script_query<'b>(
script_query: &ScriptQuery,
script_repo: &'b mut ScriptRepo,
finding_filtered: bool,
forbid_prompt: bool,
) -> Result<Option<RepoEntry<'b>>> {
log::debug!("開始尋找 `{:?}`", script_query);
let all = script_query.bang;
match &script_query.inner {
ScriptQueryInner::Prev(prev) => {
assert!(!finding_filtered); let latest = script_repo.latest_mut(*prev, all);
log::trace!("找最新腳本");
return if latest.is_some() {
Ok(latest)
} else {
Err(Error::Empty)
};
}
ScriptQueryInner::Exact(name) => {
if finding_filtered {
Ok(script_repo.get_hidden_mut(name))
} else {
Ok(script_repo.get_mut(name, all))
}
}
ScriptQueryInner::Fuzz(name) => {
let level = if forbid_prompt {
PromptLevel::Never
} else {
Config::get_prompt_level()
};
let iter = if finding_filtered {
script_repo.iter_hidden_mut()
} else {
script_repo.iter_mut(all)
};
let fuzz_res = fuzzy::fuzz(name, iter, SEP).await?;
let mut is_low = false;
let mut is_multi_fuzz = false;
let entry = match fuzz_res {
Some(fuzzy::High(entry)) => entry,
Some(fuzzy::Low(entry)) => {
is_low = true;
entry
}
#[cfg(feature = "benching")]
Some(fuzzy::Multi { ans, .. }) => {
is_multi_fuzz = true;
ans
}
#[cfg(not(feature = "benching"))]
Some(fuzzy::Multi { ans, others, .. }) => {
is_multi_fuzz = true;
let prefix = ans.name.key();
let prefix = prefix.as_ref();
let true_ans = others
.into_iter()
.filter(|t| !fuzzy::is_prefix(prefix, &*t.name.key(), SEP))
.max_by_key(|t| t.last_time());
match true_ans {
Some(t) if t.last_time() > ans.last_time() => t,
_ => ans,
}
}
None => return Ok(None),
};
let need_prompt = {
match level {
PromptLevel::Always => true,
PromptLevel::Never => false,
PromptLevel::Smart => is_low || is_multi_fuzz,
PromptLevel::OnMultiFuzz => is_multi_fuzz,
}
};
if need_prompt {
prompt_fuzz_acceptable(&*entry)?;
}
Ok(Some(entry))
}
}
}
pub async fn do_script_query_strict<'b>(
script_query: &ScriptQuery,
script_repo: &'b mut ScriptRepo,
) -> Result<RepoEntry<'b>> {
let ptr = script_repo as *mut ScriptRepo;
if let Some(info) = do_script_query(script_query, unsafe { &mut *ptr }, false, false).await? {
return Ok(info);
}
#[cfg(not(feature = "benching"))]
if !script_query.bang {
let filtered = do_script_query(script_query, script_repo, true, true).await?;
if let Some(mut filtered) = filtered {
filtered.update(|script| script.miss()).await?;
return Err(Error::ScriptIsFiltered(filtered.name.key().to_string()));
}
}
Err(Error::ScriptNotFound(script_query.to_string()))
}
fn prompt_fuzz_acceptable(script: &ScriptInfo) -> Result {
use colored::{Color, Colorize};
use console::{Key, Term};
let term = Term::stderr();
let ty = get_display_type(&script.ty);
let msg = format!(
"{} [Y/N]",
format!("{}({})?", script.name, ty.display())
.color(ty.color())
.bold(),
);
term.hide_cursor()?;
hijack_ctrlc_once();
let ok = loop {
term.write_str(&msg)?;
match term.read_key() {
Ok(Key::Char('Y' | 'y') | Key::Enter) => break true,
Ok(Key::Char('N' | 'n')) => break false,
Ok(Key::Char(ch)) => term.write_line(&format!(" Unknown key '{}'", ch))?,
Err(e) => {
if e.kind() == std::io::ErrorKind::Interrupted {
term.show_cursor()?;
std::process::exit(1);
} else {
return Err(e.into());
}
}
_ => term.write_line(&format!(" Unknown key"))?,
}
};
term.show_cursor()?;
if ok {
term.write_line(&" Y".color(Color::Green).to_string())?;
Ok(())
} else {
term.write_line(&" N".color(Color::Red).to_string())?;
Err(Error::DontFuzz)
}
}