use std::io::{self, Write};
use std::time::Duration;
use indicatif::{ProgressBar, ProgressStyle};
use termimad::crossterm::style::Attribute;
use termimad::{rgb, CompoundStyle, MadSkin, StyledChar};
use super::color::{red, white};
fn create_spinner(template: &str, message: &str) -> ProgressBar {
let spinner = ProgressBar::new_spinner();
let style = ProgressStyle::default_spinner()
.template(template)
.unwrap_or_else(|_| ProgressStyle::default_spinner())
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏");
spinner.set_style(style);
spinner.set_message(message.to_string());
spinner.enable_steady_tick(Duration::from_millis(80));
spinner
}
pub fn jarvis_talk(message: &str) {
println!("🤵 {}", white(message));
}
pub fn jarvis_notice(command: &str) {
println!("\n👉 {command}\n");
}
pub fn jarvis_read_file(path: &str) -> ProgressBar {
create_spinner("📖 {spinner} Reading file: {msg}", path)
}
pub fn jarvis_write_file(path: &str) -> ProgressBar {
create_spinner("📝 {spinner} Writing file: {msg}", path)
}
pub fn jarvis_search_replace(path: &str) -> ProgressBar {
create_spinner("🔧 {spinner} Patching file: {msg}", path)
}
pub fn jarvis_spinner() -> ProgressBar {
create_spinner("🤵 {spinner} {msg}", "Thinking...")
}
fn jarvish_skin() -> MadSkin {
let gold = rgb(255, 184, 0);
let light_gold = rgb(255, 210, 100);
let warm_gold = rgb(220, 180, 100);
let crimson = rgb(220, 50, 50);
let soft_red = rgb(230, 130, 120);
let code_fg = rgb(240, 210, 170);
let code_bg = rgb(40, 20, 20);
let dark_red = rgb(140, 40, 40);
let deep_gold = rgb(180, 140, 50);
let mut skin = MadSkin::default_dark();
skin.headers[0].set_fg(gold);
skin.headers[0].add_attr(Attribute::Bold);
skin.headers[1].set_fg(light_gold);
skin.headers[2].set_fg(warm_gold);
skin.bold.set_fg(crimson);
skin.bold.add_attr(Attribute::Bold);
skin.italic.set_fg(soft_red);
skin.inline_code.set_fgbg(code_fg, code_bg);
skin.code_block.set_fgbg(code_fg, code_bg);
skin.bullet = StyledChar::new(CompoundStyle::with_fg(gold), '•');
skin.quote_mark = StyledChar::new(
CompoundStyle::new(Some(crimson), None, Attribute::Bold.into()),
'▐',
);
skin.horizontal_rule = StyledChar::new(CompoundStyle::with_fg(dark_red), '―');
skin.table.set_fg(deep_gold);
skin
}
pub fn jarvis_render_markdown(text: &str) {
print!("🤵 ");
let skin = jarvish_skin();
skin.print_text(text);
}
pub fn jarvis_print_plain(text: &str) {
println!("🤵 {text}");
}
pub fn render_markdown(text: &str) {
let skin = jarvish_skin();
skin.print_text(text);
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypoAction {
Accept,
Reject,
Abort,
}
fn read_line_ignoring_sigint() -> Option<String> {
unsafe {
let mut old_action: libc::sigaction = std::mem::zeroed();
let mut new_action: libc::sigaction = std::mem::zeroed();
new_action.sa_sigaction = libc::SIG_IGN;
libc::sigaction(libc::SIGINT, &new_action, &mut old_action);
let mut input = String::new();
let result = io::stdin().read_line(&mut input);
libc::sigaction(libc::SIGINT, &old_action, std::ptr::null_mut());
match result {
Ok(0) | Err(_) => None,
Ok(_) => Some(input),
}
}
}
pub fn jarvis_ask_typo_correction(typo: &str, suggestion: &str) -> TypoAction {
print!(
"🤵 {} {} {} {}",
white("jarvish: correct"),
red(&format!("'{typo}'")),
white(&format!("to '{suggestion}'")),
white("[nyae]? "),
);
let _ = io::stdout().flush();
let Some(input) = read_line_ignoring_sigint() else {
println!();
return TypoAction::Abort;
};
println!();
match input.trim().to_lowercase().as_str() {
"" | "y" | "yes" => TypoAction::Accept,
"a" | "e" => TypoAction::Abort,
_ => TypoAction::Reject,
}
}
pub fn jarvis_ask_investigate(exit_code: i32) -> bool {
print!(
"🤵 Sir, {} {}",
red(&format!(
"the command exited with an error (code: {exit_code})."
)),
white("Would you like to investigate? [Y/n]: ")
);
let _ = io::stdout().flush();
let Some(input) = read_line_ignoring_sigint() else {
println!();
return false;
};
println!();
let trimmed = input.trim().to_lowercase();
trimmed.is_empty() || trimmed == "y" || trimmed == "yes"
}