2023-08-12 22:39:00 +02:00
|
|
|
use std::io::stderr;
|
2023-08-11 00:41:16 +02:00
|
|
|
use std::process::{exit, Stdio};
|
2025-01-03 15:58:07 +01:00
|
|
|
use std::thread;
|
2023-08-11 00:41:16 +02:00
|
|
|
use std::time::{Duration, Instant};
|
2023-08-08 20:33:49 +02:00
|
|
|
|
2024-09-25 17:55:55 +02:00
|
|
|
use colored::Colorize;
|
2024-12-06 23:37:28 +01:00
|
|
|
use inquire::*;
|
2025-01-26 18:41:56 +01:00
|
|
|
use ui::Color;
|
2023-08-01 20:29:02 +02:00
|
|
|
|
2024-11-15 15:32:05 +01:00
|
|
|
use crate::rules::match_pattern;
|
2024-12-08 16:39:29 +01:00
|
|
|
use crate::shell::{add_candidates_no_dup, module_output, shell_evaluated_commands, Data};
|
2024-12-06 23:37:28 +01:00
|
|
|
use crate::style::highlight_difference;
|
2024-12-06 17:35:48 +01:00
|
|
|
|
2024-12-06 23:37:28 +01:00
|
|
|
pub fn suggest_candidates(data: &mut Data) {
|
2025-01-07 02:07:08 +01:00
|
|
|
if data.split.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-12-10 02:33:02 +01:00
|
|
|
let executable = &data.split[0]
|
|
|
|
|
.rsplit(std::path::MAIN_SEPARATOR)
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap();
|
2024-12-08 16:39:29 +01:00
|
|
|
let command = &data.command;
|
|
|
|
|
let privilege = &data.privilege;
|
|
|
|
|
let mut suggest_candidates = vec![];
|
2025-01-03 15:58:07 +01:00
|
|
|
let mut module_candidates = vec![];
|
|
|
|
|
let mut final_candidates = vec![];
|
2024-12-08 16:39:29 +01:00
|
|
|
|
|
|
|
|
let modules = &data.modules;
|
|
|
|
|
let fallbacks = &data.fallbacks;
|
2024-12-06 17:35:48 +01:00
|
|
|
|
2024-12-08 16:48:06 +01:00
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
|
{
|
|
|
|
|
eprintln!("modules: {modules:?}");
|
|
|
|
|
eprintln!("fallbacks: {fallbacks:?}");
|
|
|
|
|
}
|
2024-12-08 16:39:29 +01:00
|
|
|
|
2025-01-03 15:58:07 +01:00
|
|
|
thread::scope(|s| {
|
|
|
|
|
s.spawn(|| {
|
|
|
|
|
for module in modules {
|
2025-01-06 20:23:50 +01:00
|
|
|
let new_candidates = module_output(data, module);
|
2024-12-08 16:39:29 +01:00
|
|
|
|
2025-01-03 15:58:07 +01:00
|
|
|
if let Some(candidates) = new_candidates {
|
|
|
|
|
add_candidates_no_dup(command, &mut module_candidates, &candidates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if privilege.is_none() {
|
|
|
|
|
if let Some(candidates) = match_pattern("_PR_privilege", data) {
|
|
|
|
|
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if let Some(candidates) = match_pattern(executable, data) {
|
2024-12-08 16:39:29 +01:00
|
|
|
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
2024-12-08 15:08:21 +01:00
|
|
|
}
|
2025-01-03 15:58:07 +01:00
|
|
|
if let Some(candidates) = match_pattern("_PR_general", data) {
|
2025-01-03 15:25:18 +01:00
|
|
|
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
|
|
|
|
}
|
2025-01-03 15:58:07 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if !module_candidates.is_empty() {
|
|
|
|
|
add_candidates_no_dup(command, &mut final_candidates, &module_candidates);
|
2025-01-03 15:25:18 +01:00
|
|
|
}
|
2025-01-03 15:58:07 +01:00
|
|
|
if !suggest_candidates.is_empty() {
|
|
|
|
|
add_candidates_no_dup(command, &mut final_candidates, &suggest_candidates);
|
2025-01-03 15:25:18 +01:00
|
|
|
}
|
|
|
|
|
|
2025-01-03 15:58:07 +01:00
|
|
|
if !final_candidates.is_empty() {
|
|
|
|
|
data.candidates = final_candidates;
|
2024-12-08 16:39:29 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for fallback in fallbacks {
|
|
|
|
|
let candidates = module_output(data, fallback);
|
|
|
|
|
if candidates.is_some() {
|
2025-01-03 15:58:07 +01:00
|
|
|
add_candidates_no_dup(command, &mut final_candidates, &candidates.unwrap());
|
|
|
|
|
data.candidates = final_candidates;
|
2024-12-06 23:37:28 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2024-11-19 14:59:15 +01:00
|
|
|
}
|
2024-12-06 23:37:28 +01:00
|
|
|
}
|
2024-11-19 14:59:15 +01:00
|
|
|
|
2024-12-06 23:37:28 +01:00
|
|
|
pub fn select_candidate(data: &mut Data) {
|
|
|
|
|
let candidates = &data.candidates;
|
2024-12-07 01:51:15 +01:00
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
|
eprintln!("candidates: {candidates:?}");
|
2024-12-06 23:37:28 +01:00
|
|
|
|
2025-02-12 18:26:41 +01:00
|
|
|
let mut highlight_candidates = candidates
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|candidate| highlight_difference(data, candidate).unwrap())
|
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
|
|
|
|
|
if highlight_candidates.iter().any(|x| x.contains('\n')) {
|
|
|
|
|
for candidate in highlight_candidates.iter_mut() {
|
|
|
|
|
*candidate = format!("* {}", candidate.replace("\n", "\n "));
|
|
|
|
|
}
|
2024-12-06 23:37:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-12 18:26:41 +01:00
|
|
|
let style = ui::Styled::default();
|
|
|
|
|
let render_config = ui::RenderConfig::default()
|
|
|
|
|
.with_prompt_prefix(style)
|
|
|
|
|
.with_highlighted_option_prefix(ui::Styled::new(">").with_fg(Color::LightBlue))
|
|
|
|
|
.with_scroll_up_prefix(ui::Styled::new("^").with_fg(Color::LightBlue))
|
|
|
|
|
.with_scroll_down_prefix(ui::Styled::new("v").with_fg(Color::LightBlue));
|
|
|
|
|
|
|
|
|
|
let msg = format!("{}", t!("multi-suggest", num = candidates.len()))
|
|
|
|
|
.bold()
|
|
|
|
|
.blue();
|
|
|
|
|
let confirm = format!("[{}]", t!("confirm-yes")).green();
|
|
|
|
|
let hint = format!("{} {} {}", "[↑/↓/j/k]".blue(), confirm, "[ESC]".red());
|
|
|
|
|
eprintln!("{}", msg);
|
|
|
|
|
eprintln!("{}", hint);
|
|
|
|
|
|
|
|
|
|
let ans = Select::new("\n", highlight_candidates.clone())
|
|
|
|
|
.with_vim_mode(true)
|
|
|
|
|
.without_filtering()
|
|
|
|
|
.without_help_message()
|
|
|
|
|
.with_render_config(render_config)
|
|
|
|
|
.prompt()
|
|
|
|
|
.unwrap_or_else(|_| exit(1));
|
|
|
|
|
let pos = highlight_candidates.iter().position(|x| x == &ans).unwrap();
|
|
|
|
|
let suggestion = candidates[pos].to_string();
|
|
|
|
|
data.update_suggest(&suggestion);
|
|
|
|
|
data.expand_suggest();
|
|
|
|
|
|
2024-12-06 23:37:28 +01:00
|
|
|
data.candidates.clear();
|
2023-08-01 20:29:02 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-06 23:37:28 +01:00
|
|
|
pub fn confirm_suggestion(data: &Data) -> Result<(), String> {
|
2024-12-06 17:35:48 +01:00
|
|
|
let shell = &data.shell;
|
|
|
|
|
let command = &data.suggest.clone().unwrap();
|
2024-12-06 21:36:08 +01:00
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
|
eprintln!("running command: {command}");
|
2024-09-25 18:30:03 +02:00
|
|
|
|
2023-08-09 21:04:59 +02:00
|
|
|
let now = Instant::now();
|
2024-12-06 19:12:40 +01:00
|
|
|
let process = run_suggestion(data, command);
|
2023-08-07 19:58:19 +02:00
|
|
|
|
2023-08-09 20:47:28 +02:00
|
|
|
if process.success() {
|
2024-12-06 19:12:40 +01:00
|
|
|
let cd = shell_evaluated_commands(shell, command);
|
2024-11-18 13:11:13 +01:00
|
|
|
if let Some(cd) = cd {
|
|
|
|
|
println!("{}", cd);
|
|
|
|
|
}
|
2023-08-07 20:21:02 +02:00
|
|
|
Ok(())
|
2023-08-07 19:58:19 +02:00
|
|
|
} else {
|
2023-08-09 21:04:59 +02:00
|
|
|
if now.elapsed() > Duration::from_secs(3) {
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
2024-12-06 19:57:59 +01:00
|
|
|
suggestion_err(data, command)
|
2023-08-07 19:58:19 +02:00
|
|
|
}
|
2023-08-01 20:29:02 +02:00
|
|
|
}
|
2024-11-18 15:29:45 +01:00
|
|
|
|
2024-12-07 01:10:31 +01:00
|
|
|
pub fn run_suggestion(data: &Data, command: &str) -> std::process::ExitStatus {
|
2024-12-06 17:35:48 +01:00
|
|
|
let shell = &data.shell;
|
|
|
|
|
let privilege = &data.privilege;
|
|
|
|
|
match privilege {
|
2024-12-06 19:12:40 +01:00
|
|
|
Some(sudo) => std::process::Command::new(sudo)
|
|
|
|
|
.arg(shell)
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg(command)
|
|
|
|
|
.stdout(stderr())
|
|
|
|
|
.stderr(Stdio::inherit())
|
2024-12-08 02:17:43 +01:00
|
|
|
.status()
|
|
|
|
|
.expect("failed to execute process"),
|
2024-12-06 19:12:40 +01:00
|
|
|
None => std::process::Command::new(shell)
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg(command)
|
|
|
|
|
.stdout(stderr())
|
|
|
|
|
.stderr(Stdio::inherit())
|
2024-12-08 02:17:43 +01:00
|
|
|
.status()
|
|
|
|
|
.expect("failed to execute process"),
|
2024-12-06 17:35:48 +01:00
|
|
|
}
|
2024-11-18 15:29:45 +01:00
|
|
|
}
|
2024-12-06 19:57:59 +01:00
|
|
|
|
|
|
|
|
fn suggestion_err(data: &Data, command: &str) -> Result<(), String> {
|
|
|
|
|
let shell = &data.shell;
|
|
|
|
|
let privilege = &data.privilege;
|
|
|
|
|
let process = match privilege {
|
|
|
|
|
Some(sudo) => std::process::Command::new(sudo)
|
|
|
|
|
.arg(shell)
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg(command)
|
|
|
|
|
.env("LC_ALL", "C")
|
|
|
|
|
.output()
|
|
|
|
|
.expect("failed to execute process"),
|
|
|
|
|
None => std::process::Command::new(shell)
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg(command)
|
|
|
|
|
.env("LC_ALL", "C")
|
|
|
|
|
.output()
|
|
|
|
|
.expect("failed to execute process"),
|
|
|
|
|
};
|
|
|
|
|
let error_msg = match process.stderr.is_empty() {
|
|
|
|
|
true => String::from_utf8_lossy(&process.stdout).to_lowercase(),
|
|
|
|
|
false => String::from_utf8_lossy(&process.stderr).to_lowercase(),
|
|
|
|
|
};
|
|
|
|
|
Err(error_msg.to_string())
|
|
|
|
|
}
|