mirror of
https://github.com/TECHNOFAB11/pay-respects.git
synced 2026-02-02 15:45:11 +01:00
feat: multi-suggest
This commit is contained in:
parent
e6ed9d617b
commit
1ddfbef7e7
8 changed files with 111 additions and 81 deletions
|
|
@ -73,6 +73,9 @@ pattern = [
|
||||||
suggest = [
|
suggest = [
|
||||||
'''
|
'''
|
||||||
#[cmd_contains(checkout)]
|
#[cmd_contains(checkout)]
|
||||||
|
git checkout {{typo[2]({{shell(git branch | sed 's/^*//')}})}} ''',
|
||||||
|
'''
|
||||||
|
#[cmd_contains(checkout)]
|
||||||
git branch {{command[2]}} && \
|
git branch {{command[2]}} && \
|
||||||
git checkout {{command[2]}} '''
|
git checkout {{command[2]}} '''
|
||||||
]
|
]
|
||||||
|
|
|
||||||
48
src/modes.rs
48
src/modes.rs
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::shell::Data;
|
use crate::shell::Data;
|
||||||
use crate::style::highlight_difference;
|
use crate::suggestions::{best_match_path, suggest_candidates};
|
||||||
use crate::suggestions::{best_match_path, suggest_command};
|
|
||||||
use crate::system;
|
use crate::system;
|
||||||
use crate::{shell, suggestions};
|
use crate::{shell, suggestions};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
@ -12,32 +11,22 @@ pub fn suggestion(data: &mut Data) {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
last_command = data.command.clone();
|
last_command = data.command.clone();
|
||||||
let suggestion = {
|
suggest_candidates(data);
|
||||||
let command = suggest_command(data);
|
if data.candidates.is_empty() {
|
||||||
if command.is_none() {
|
break;
|
||||||
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()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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() {
|
if execution.is_ok() {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
data.update_command(&suggestion);
|
data.update_command(&data.suggest.clone().unwrap());
|
||||||
let msg = Some(
|
let msg = Some(
|
||||||
execution
|
execution
|
||||||
.err()
|
.err()
|
||||||
|
|
@ -63,7 +52,6 @@ pub fn suggestion(data: &mut Data) {
|
||||||
|
|
||||||
pub fn cnf(data: &mut Data) {
|
pub fn cnf(data: &mut Data) {
|
||||||
let shell = data.shell.clone();
|
let shell = data.shell.clone();
|
||||||
let last_command = data.command.clone();
|
|
||||||
let mut split_command = data.split.clone();
|
let mut split_command = data.split.clone();
|
||||||
|
|
||||||
let executable = split_command[0].as_str();
|
let executable = split_command[0].as_str();
|
||||||
|
|
@ -73,12 +61,11 @@ pub fn cnf(data: &mut Data) {
|
||||||
let best_match = best_match.unwrap();
|
let best_match = best_match.unwrap();
|
||||||
split_command[0] = best_match;
|
split_command[0] = best_match;
|
||||||
let suggest = split_command.join(" ");
|
let suggest = split_command.join(" ");
|
||||||
data.update_suggest(&suggest);
|
|
||||||
data.expand_suggest();
|
|
||||||
|
|
||||||
let highlighted_suggestion =
|
data.candidates.push(suggest.clone());
|
||||||
highlight_difference(&shell, &suggest, &last_command).unwrap();
|
suggestions::select_candidate(data);
|
||||||
let status = suggestions::confirm_suggestion(data, &highlighted_suggestion);
|
|
||||||
|
let status = suggestions::confirm_suggestion(data);
|
||||||
if status.is_err() {
|
if status.is_err() {
|
||||||
data.update_command(&suggest);
|
data.update_command(&suggest);
|
||||||
let msg = Some(
|
let msg = Some(
|
||||||
|
|
@ -96,7 +83,6 @@ pub fn cnf(data: &mut Data) {
|
||||||
let package_manager = match system::get_package_manager(&shell) {
|
let package_manager = match system::get_package_manager(&shell) {
|
||||||
Some(package_manager) => package_manager,
|
Some(package_manager) => package_manager,
|
||||||
None => {
|
None => {
|
||||||
eprintln!("no package manager found");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -120,7 +106,7 @@ pub fn cnf(data: &mut Data) {
|
||||||
|
|
||||||
// retry after installing package
|
// retry after installing package
|
||||||
if system::install_package(&shell, &package_manager, &package) {
|
if system::install_package(&shell, &package_manager, &package) {
|
||||||
let _ = suggestions::confirm_suggestion(data, &last_command);
|
let _ = suggestions::confirm_suggestion(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/rules.rs
10
src/rules.rs
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::suggestions::*;
|
use crate::suggestions::*;
|
||||||
|
use crate::shell::Data;
|
||||||
use pay_respects_parser::parse_rules;
|
use pay_respects_parser::parse_rules;
|
||||||
|
|
||||||
pub fn match_pattern(
|
pub fn match_pattern(
|
||||||
executable: &str,
|
executable: &str,
|
||||||
last_command: &str,
|
data: &mut Data
|
||||||
error_msg: &str,
|
) {
|
||||||
shell: &str,
|
let error_msg = &data.error.clone();
|
||||||
) -> Option<String> {
|
let shell = &data.shell.clone();
|
||||||
|
let last_command = &data.command.clone();
|
||||||
parse_rules!("rules");
|
parse_rules!("rules");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::replaces;
|
use crate::replaces;
|
||||||
use crate::suggestions::*;
|
use crate::suggestions::*;
|
||||||
|
use crate::shell::Data;
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct Rule {
|
struct Rule {
|
||||||
|
|
@ -14,16 +15,19 @@ struct MatchError {
|
||||||
|
|
||||||
pub fn runtime_match(
|
pub fn runtime_match(
|
||||||
executable: &str,
|
executable: &str,
|
||||||
last_command: &str,
|
data: &mut Data,
|
||||||
error_msg: &str,
|
) {
|
||||||
shell: &str,
|
|
||||||
) -> Option<String> {
|
|
||||||
let file = get_rule(executable);
|
let file = get_rule(executable);
|
||||||
file.as_ref()?;
|
if file.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let file = std::fs::read_to_string(file.unwrap()).unwrap();
|
let file = std::fs::read_to_string(file.unwrap()).unwrap();
|
||||||
let rule: Rule = toml::from_str(&file).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;
|
let mut pure_suggest;
|
||||||
|
|
||||||
|
|
@ -79,13 +83,11 @@ pub fn runtime_match(
|
||||||
if pure_suggest.contains("{{command}}") {
|
if pure_suggest.contains("{{command}}") {
|
||||||
pure_suggest = pure_suggest.replace("{{command}}", last_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(
|
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();
|
let mut suggest = suggest.to_owned();
|
||||||
if suggest.contains("{{command}}") {
|
if suggest.contains("{{command}}") {
|
||||||
suggest = suggest.replace("{{command}}", "{last_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);
|
suggest = suggest.replace(&tag, &value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(suggest)
|
suggest
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rule(executable: &str) -> Option<String> {
|
fn get_rule(executable: &str) -> Option<String> {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ pub struct Data {
|
||||||
pub shell: String,
|
pub shell: String,
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub suggest: Option<String>,
|
pub suggest: Option<String>,
|
||||||
|
pub candidates: Vec<String>,
|
||||||
pub split: Vec<String>,
|
pub split: Vec<String>,
|
||||||
pub alias: Option<HashMap<String, String>>,
|
pub alias: Option<HashMap<String, String>>,
|
||||||
pub privilege: Option<String>,
|
pub privilege: Option<String>,
|
||||||
|
|
@ -43,6 +44,7 @@ impl Data {
|
||||||
shell,
|
shell,
|
||||||
command,
|
command,
|
||||||
suggest: None,
|
suggest: None,
|
||||||
|
candidates: vec![],
|
||||||
alias,
|
alias,
|
||||||
split: vec![],
|
split: vec![],
|
||||||
privilege: None,
|
privilege: None,
|
||||||
|
|
@ -110,6 +112,13 @@ impl Data {
|
||||||
self.suggest = Some(suggest.to_string());
|
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> {
|
pub fn split_command(command: &str) -> Vec<String> {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub fn highlight_difference(
|
||||||
shell: &str,
|
shell: &str,
|
||||||
suggested_command: &str,
|
suggested_command: &str,
|
||||||
last_command: &str,
|
last_command: &str,
|
||||||
|
difference_only: bool,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
// let replaced_newline = suggested_command.replace('\n', r" {{newline}} ");
|
// let replaced_newline = suggested_command.replace('\n', r" {{newline}} ");
|
||||||
let mut split_suggested_command = split_command(suggested_command);
|
let mut split_suggested_command = split_command(suggested_command);
|
||||||
|
|
@ -42,7 +43,9 @@ pub fn highlight_difference(
|
||||||
}
|
}
|
||||||
for old in &old_entries {
|
for old in &old_entries {
|
||||||
if old == entry {
|
if old == entry {
|
||||||
*entry = entry.blue().to_string();
|
if !difference_only {
|
||||||
|
*entry = entry.blue().to_string();
|
||||||
|
}
|
||||||
continue 'next;
|
continue 'next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,50 +3,40 @@ use std::process::{exit, Stdio};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use inquire::*;
|
||||||
use regex_lite::Regex;
|
use regex_lite::Regex;
|
||||||
|
|
||||||
use crate::files::{get_best_match_file, get_path_files};
|
use crate::files::{get_best_match_file, get_path_files};
|
||||||
use crate::rules::match_pattern;
|
use crate::rules::match_pattern;
|
||||||
use crate::shell::{shell_evaluated_commands, Data};
|
use crate::shell::{shell_evaluated_commands, Data};
|
||||||
|
use crate::style::highlight_difference;
|
||||||
|
|
||||||
pub fn suggest_command(data: &Data) -> Option<String> {
|
pub fn suggest_candidates(data: &mut Data) {
|
||||||
let shell = &data.shell;
|
let executable = &data.split[0].to_string();
|
||||||
let command = &data.command;
|
let privilege = &data.privilege.clone();
|
||||||
let split_command = &data.split;
|
|
||||||
let executable = data.split[0].as_str();
|
|
||||||
let error = &data.error;
|
|
||||||
let privilege = &data.privilege;
|
|
||||||
|
|
||||||
if privilege.is_none() {
|
if privilege.is_none() {
|
||||||
let suggest = match_pattern("_PR_privilege", command, error, shell);
|
match_pattern("_PR_privilege", data);
|
||||||
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(executable, data);
|
||||||
|
match_pattern("_PR_general", data);
|
||||||
|
|
||||||
#[cfg(feature = "runtime-rules")]
|
#[cfg(feature = "runtime-rules")]
|
||||||
{
|
{
|
||||||
use crate::runtime_rules::runtime_match;
|
use crate::runtime_rules::runtime_match;
|
||||||
let suggest = runtime_match(executable, command, error, shell);
|
runtime_match(executable, data);
|
||||||
if suggest.is_some() {
|
|
||||||
return suggest;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "request-ai")]
|
#[cfg(feature = "request-ai")]
|
||||||
{
|
{
|
||||||
|
if !data.candidates.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
use crate::requests::ai_suggestion;
|
use crate::requests::ai_suggestion;
|
||||||
use textwrap::{fill, termwidth};
|
use textwrap::{fill, termwidth};
|
||||||
|
let command = &data.command;
|
||||||
|
let split_command = &data.split;
|
||||||
|
let error = &data.error.clone();
|
||||||
|
|
||||||
// skip for commands with no arguments,
|
// skip for commands with no arguments,
|
||||||
// very likely to be an error showing the usage
|
// 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);
|
eprintln!("{}\n{}\n", warn, note);
|
||||||
let command = suggest.command;
|
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 {
|
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()]
|
matrix[a.chars().count()][b.chars().count()]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn confirm_suggestion(data: &Data, highlighted: &str) -> Result<(), String> {
|
pub fn confirm_suggestion(data: &Data) -> 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();
|
|
||||||
|
|
||||||
let shell = &data.shell;
|
let shell = &data.shell;
|
||||||
let command = &data.suggest.clone().unwrap();
|
let command = &data.suggest.clone().unwrap();
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::process::Command;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
pub fn get_package_manager(shell: &str) -> Option<String> {
|
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 {
|
for package_manager in package_managers {
|
||||||
let success = Command::new(shell)
|
let success = Command::new(shell)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue