feat: multi-suggest

This commit is contained in:
iff 2024-12-06 23:37:28 +01:00
parent e6ed9d617b
commit 1ddfbef7e7
8 changed files with 111 additions and 81 deletions

View file

@ -1,6 +1,5 @@
use crate::shell::Data;
use crate::style::highlight_difference;
use crate::suggestions::{best_match_path, suggest_command};
use crate::suggestions::{best_match_path, suggest_candidates};
use crate::system;
use crate::{shell, suggestions};
use colored::Colorize;
@ -12,32 +11,22 @@ pub fn suggestion(data: &mut Data) {
loop {
last_command = data.command.clone();
let suggestion = {
let command = suggest_command(data);
if command.is_none() {
break;
};
let mut command = command.unwrap();
shell::shell_syntax(&shell, &mut command);
command
};
data.update_suggest(&suggestion);
data.expand_suggest();
let highlighted_suggestion = {
let difference = highlight_difference(&shell, &suggestion, &last_command);
if difference.is_none() {
break;
};
difference.unwrap()
suggest_candidates(data);
if data.candidates.is_empty() {
break;
};
let execution = suggestions::confirm_suggestion(data, &highlighted_suggestion);
for candidate in &mut data.candidates {
shell::shell_syntax(&shell, candidate);
}
suggestions::select_candidate(data);
let execution = suggestions::confirm_suggestion(data);
if execution.is_ok() {
return;
} else {
data.update_command(&suggestion);
data.update_command(&data.suggest.clone().unwrap());
let msg = Some(
execution
.err()
@ -63,7 +52,6 @@ pub fn suggestion(data: &mut Data) {
pub fn cnf(data: &mut Data) {
let shell = data.shell.clone();
let last_command = data.command.clone();
let mut split_command = data.split.clone();
let executable = split_command[0].as_str();
@ -73,12 +61,11 @@ pub fn cnf(data: &mut Data) {
let best_match = best_match.unwrap();
split_command[0] = best_match;
let suggest = split_command.join(" ");
data.update_suggest(&suggest);
data.expand_suggest();
let highlighted_suggestion =
highlight_difference(&shell, &suggest, &last_command).unwrap();
let status = suggestions::confirm_suggestion(data, &highlighted_suggestion);
data.candidates.push(suggest.clone());
suggestions::select_candidate(data);
let status = suggestions::confirm_suggestion(data);
if status.is_err() {
data.update_command(&suggest);
let msg = Some(
@ -96,7 +83,6 @@ pub fn cnf(data: &mut Data) {
let package_manager = match system::get_package_manager(&shell) {
Some(package_manager) => package_manager,
None => {
eprintln!("no package manager found");
return;
}
};
@ -120,7 +106,7 @@ pub fn cnf(data: &mut Data) {
// retry after installing package
if system::install_package(&shell, &package_manager, &package) {
let _ = suggestions::confirm_suggestion(data, &last_command);
let _ = suggestions::confirm_suggestion(data);
}
}
}

View file

@ -1,11 +1,13 @@
use crate::suggestions::*;
use crate::shell::Data;
use pay_respects_parser::parse_rules;
pub fn match_pattern(
executable: &str,
last_command: &str,
error_msg: &str,
shell: &str,
) -> Option<String> {
data: &mut Data
) {
let error_msg = &data.error.clone();
let shell = &data.shell.clone();
let last_command = &data.command.clone();
parse_rules!("rules");
}

View file

@ -1,5 +1,6 @@
use crate::replaces;
use crate::suggestions::*;
use crate::shell::Data;
#[derive(serde::Deserialize)]
struct Rule {
@ -14,16 +15,19 @@ struct MatchError {
pub fn runtime_match(
executable: &str,
last_command: &str,
error_msg: &str,
shell: &str,
) -> Option<String> {
data: &mut Data,
) {
let file = get_rule(executable);
file.as_ref()?;
if file.is_none() {
return;
}
let file = std::fs::read_to_string(file.unwrap()).unwrap();
let rule: Rule = toml::from_str(&file).unwrap();
let split_command = split_command(last_command);
let split_command = &data.split.clone();
let shell = &data.shell.clone();
let last_command = &data.command.clone();
let error_msg = &data.error.clone();
let mut pure_suggest;
@ -79,13 +83,11 @@ pub fn runtime_match(
if pure_suggest.contains("{{command}}") {
pure_suggest = pure_suggest.replace("{{command}}", last_command);
}
return eval_suggest(&pure_suggest, last_command, error_msg, shell);
data.add_candidate(&eval_suggest(&pure_suggest, last_command, error_msg, shell));
}
}
}
}
None
}
fn eval_condition(
@ -108,7 +110,7 @@ fn eval_condition(
}
}
fn eval_suggest(suggest: &str, last_command: &str, error_msg: &str, shell: &str) -> Option<String> {
fn eval_suggest(suggest: &str, last_command: &str, error_msg: &str, shell: &str) -> String {
let mut suggest = suggest.to_owned();
if suggest.contains("{{command}}") {
suggest = suggest.replace("{{command}}", "{last_command}");
@ -130,7 +132,7 @@ fn eval_suggest(suggest: &str, last_command: &str, error_msg: &str, shell: &str)
suggest = suggest.replace(&tag, &value);
}
Some(suggest)
suggest
}
fn get_rule(executable: &str) -> Option<String> {

View file

@ -18,6 +18,7 @@ pub struct Data {
pub shell: String,
pub command: String,
pub suggest: Option<String>,
pub candidates: Vec<String>,
pub split: Vec<String>,
pub alias: Option<HashMap<String, String>>,
pub privilege: Option<String>,
@ -43,6 +44,7 @@ impl Data {
shell,
command,
suggest: None,
candidates: vec![],
alias,
split: vec![],
privilege: None,
@ -110,6 +112,13 @@ impl Data {
self.suggest = Some(suggest.to_string());
};
}
pub fn add_candidate(&mut self, candidate: &str) {
let candidate = candidate.trim();
if candidate != self.command {
self.candidates.push(candidate.to_string());
}
}
}
pub fn split_command(command: &str) -> Vec<String> {

View file

@ -8,6 +8,7 @@ pub fn highlight_difference(
shell: &str,
suggested_command: &str,
last_command: &str,
difference_only: bool,
) -> Option<String> {
// let replaced_newline = suggested_command.replace('\n', r" {{newline}} ");
let mut split_suggested_command = split_command(suggested_command);
@ -42,7 +43,9 @@ pub fn highlight_difference(
}
for old in &old_entries {
if old == entry {
*entry = entry.blue().to_string();
if !difference_only {
*entry = entry.blue().to_string();
}
continue 'next;
}
}

View file

@ -3,50 +3,40 @@ use std::process::{exit, Stdio};
use std::time::{Duration, Instant};
use colored::Colorize;
use inquire::*;
use regex_lite::Regex;
use crate::files::{get_best_match_file, get_path_files};
use crate::rules::match_pattern;
use crate::shell::{shell_evaluated_commands, Data};
use crate::style::highlight_difference;
pub fn suggest_command(data: &Data) -> Option<String> {
let shell = &data.shell;
let command = &data.command;
let split_command = &data.split;
let executable = data.split[0].as_str();
let error = &data.error;
let privilege = &data.privilege;
pub fn suggest_candidates(data: &mut Data) {
let executable = &data.split[0].to_string();
let privilege = &data.privilege.clone();
if privilege.is_none() {
let suggest = match_pattern("_PR_privilege", command, error, shell);
if suggest.is_some() {
return suggest;
}
}
let suggest = match_pattern(executable, command, error, shell);
if suggest.is_some() {
return suggest;
}
let suggest = match_pattern("_PR_general", command, error, shell);
if suggest.is_some() {
return suggest;
match_pattern("_PR_privilege", data);
}
match_pattern(executable, data);
match_pattern("_PR_general", data);
#[cfg(feature = "runtime-rules")]
{
use crate::runtime_rules::runtime_match;
let suggest = runtime_match(executable, command, error, shell);
if suggest.is_some() {
return suggest;
}
runtime_match(executable, data);
}
#[cfg(feature = "request-ai")]
{
if !data.candidates.is_empty() {
return;
}
use crate::requests::ai_suggestion;
use textwrap::{fill, termwidth};
let command = &data.command;
let split_command = &data.split;
let error = &data.error.clone();
// skip for commands with no arguments,
// very likely to be an error showing the usage
@ -60,12 +50,52 @@ pub fn suggest_command(data: &Data) -> Option<String> {
eprintln!("{}\n{}\n", warn, note);
let command = suggest.command;
return Some(command);
data.add_candidate(&command);
}
}
}
}
None
pub fn select_candidate(data: &mut Data) {
let candidates = &data.candidates;
if candidates.len() == 1 {
let suggestion = candidates[0].to_string();
let highlighted = highlight_difference(&data.shell, &suggestion, &data.command, false).unwrap();
eprintln!("{}\n", highlighted);
let confirm = format!("[{}]", t!("confirm-yes")).green();
eprintln!("{}: {} {}", t!("confirm"), confirm, "[Ctrl+C]".red());
std::io::stdin().read_line(&mut String::new()).unwrap();
data.update_suggest(&suggestion);
data.expand_suggest();
} else {
let mut highlight_candidates = candidates
.iter()
.map(|candidate| highlight_difference(&data.shell, candidate, &data.command, true).unwrap())
.collect::<Vec<String>>();
for candidate in highlight_candidates.iter_mut() {
let lines = candidate.lines().collect::<Vec<&str>>();
let mut formated = String::new();
for (j, line) in lines.iter().enumerate() {
if j == 0 {
formated = line.to_string();
} else {
formated = format!("{}\n {}", formated, line);
}
}
*candidate = formated;
}
let ans = Select::new("Select a suggestion:", highlight_candidates.clone())
.prompt()
.unwrap();
let pos = highlight_candidates.iter().position(|x| x == &ans).unwrap();
let suggestion = candidates[pos].to_string();
data.update_suggest(&suggestion);
data.expand_suggest();
}
data.candidates.clear();
}
pub fn check_executable(shell: &str, executable: &str) -> bool {
@ -244,12 +274,7 @@ pub fn compare_string(a: &str, b: &str) -> usize {
matrix[a.chars().count()][b.chars().count()]
}
pub fn confirm_suggestion(data: &Data, highlighted: &str) -> Result<(), String> {
eprintln!("{}\n", highlighted);
let confirm = format!("[{}]", t!("confirm-yes")).green();
eprintln!("{}: {} {}", t!("confirm"), confirm, "[Ctrl+C]".red());
std::io::stdin().read_line(&mut String::new()).unwrap();
pub fn confirm_suggestion(data: &Data) -> Result<(), String> {
let shell = &data.shell;
let command = &data.suggest.clone().unwrap();
#[cfg(debug_assertions)]

View file

@ -3,7 +3,7 @@ use std::process::Command;
use std::process::Stdio;
pub fn get_package_manager(shell: &str) -> Option<String> {
let package_managers = vec!["pacman", "apt", "dnf"];
let package_managers = vec!["pacman"];
for package_manager in package_managers {
let success = Command::new(shell)