From e0ca8be79f4cf195ca528da455f185d8f3b2c24d Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 17 Aug 2023 10:10:38 -0700 Subject: [PATCH] Added terminal color detection Added configuration Cleaned up context args --- src/command/mod.rs | 4 +- src/context.rs | 62 ++++++++++++++ src/entrypoint/unix/unix.rs | 41 +++++++-- src/evaluate/operator.rs | 12 +-- src/formattedtext.rs | 125 ++++++++++++++++++++++------ src/main.rs | 8 +- src/parser/expression/expression.rs | 19 +++-- src/parser/expression/operator.rs | 80 +++++++++--------- src/quantity/quantity.rs | 11 +-- src/quantity/unit/unit.rs | 17 +++- src/tests.rs | 6 +- 11 files changed, 282 insertions(+), 103 deletions(-) diff --git a/src/command/mod.rs b/src/command/mod.rs index fde964c..68f16bc 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -170,7 +170,7 @@ pub fn do_command( t.push(&format!( " {key}{padding} = [c]{v}[n]\n", - v = value.to_string(), + v = value.display(context), )); } } @@ -184,7 +184,7 @@ pub fn do_command( t.push(&format!( " {s}{padding} = [c]{v}[n]\n", - v = exp.to_string(), + v = exp.display(context), )); } } diff --git a/src/context.rs b/src/context.rs index 5bd7097..b1fef92 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,68 @@ use crate::quantity::freeunit_from_string; use std::collections::HashMap; #[derive(Debug)] +#[derive(Clone)] +pub struct Config { + + // How to color terminal text. + // 0: No colors + // 1: ANSI-compatible, 8 colors + // 2: Full 256 color and special styles + pub term_color_type: u8, + + // Should we accept input and print in unicode? + //pub enable_unicode: bool, + + // Should we replace certain strings (like "pi") + // with prettier unicode alternatives? + // + // Automatically disabled if enable_unicode is off. + //pub enable_substituion: bool, + + // Should we print simple powers + // as unicode superscript chars? + // + // Automatically disables if enable_unicode is off. + pub enable_super_powers: bool, + + // Should we write "one-over" fractions + // as -1 powers? + // + // Automatically disabled if enable_super_powers is off. + pub enable_one_over_power: bool, +} + +impl Config { + pub fn new() -> Config { + Config{ + term_color_type: 2, + //enable_substituion: true, + //enable_unicode: true, + enable_super_powers: true, + enable_one_over_power: true + } + } + + pub fn check(&mut self) { + //if !self.enable_unicode { + // self.enable_substituion = false; + // self.enable_super_powers = false; + //} + + if !self.enable_super_powers { + self.enable_one_over_power = false + } + } +} + + + + +#[derive(Debug)] +#[derive(Clone)] pub struct Context { + pub config: Config, + history: Vec, variables: HashMap, functions: HashMap, Expression)>, @@ -16,6 +77,7 @@ pub struct Context { impl Context { pub fn new() -> Context { Context{ + config: Config::new(), history: Vec::new(), variables: HashMap::new(), functions: HashMap::new(), diff --git a/src/entrypoint/unix/unix.rs b/src/entrypoint/unix/unix.rs index 7edfb41..b0a6347 100644 --- a/src/entrypoint/unix/unix.rs +++ b/src/entrypoint/unix/unix.rs @@ -1,4 +1,3 @@ -use std::io::Write; use std::io::stdout; use std::io::stdin; use std::env; @@ -6,19 +5,33 @@ use std::env; use termion::{ event::Key, input::TermRead, - raw::IntoRawMode + raw::IntoRawMode, + color::DetectColors }; use super::promptbuffer::PromptBuffer; use crate::command; use crate::context::Context; - +use crate::FormattedText; #[inline(always)] pub fn main() -> Result<(), std::io::Error> { let mut stdout = stdout().into_raw_mode().unwrap(); let mut pb: PromptBuffer = PromptBuffer::new(64); - let mut context: Context = Context::new(); + let mut context = Context::new(); + + // Set color compatibilty + let term_colors = stdout.available_colors().unwrap_or(0); + if term_colors >= 256 { + context.config.term_color_type = 2 + } else if term_colors >= 8 { + context.config.term_color_type = 1 + } else { + context.config.term_color_type = 0 + } + + context.config.check(); + // Handle command-line arguments @@ -28,7 +41,21 @@ pub fn main() -> Result<(), std::io::Error> { t.write(&context, &mut stdout)?; return Ok(()); } else if args.iter().any(|s| s == "--version") { - write!(stdout, "Daisy v{}\r\n", env!("CARGO_PKG_VERSION"))?; + let t = FormattedText::new(format!( + "Daisy v{}\n", env!("CARGO_PKG_VERSION") + )); + t.write(&context, &mut stdout)?; + return Ok(()); + } else if args.iter().any(|s| s == "--debug") { + let t = FormattedText::new(format!( + concat!( + "Daisy v{}\n", + "Your terminal supports {} colors.\n" + ), + env!("CARGO_PKG_VERSION"), + term_colors + )); + t.write(&context, &mut stdout)?; return Ok(()); } @@ -45,7 +72,7 @@ pub fn main() -> Result<(), std::io::Error> { // while inside a substitution pb.write_prompt_nocursor(&mut context, &mut stdout)?; let in_str = pb.enter(); - write!(stdout, "\r\n")?; + FormattedText::newline(&mut stdout)?; if in_str == "" { break; } if in_str.trim() == "quit" { @@ -83,6 +110,6 @@ pub fn main() -> Result<(), std::io::Error> { } } - write!(stdout, "\r\n")?; + FormattedText::newline(&mut stdout)?; return Ok(()); } \ No newline at end of file diff --git a/src/evaluate/operator.rs b/src/evaluate/operator.rs index 05e828f..bc77de4 100644 --- a/src/evaluate/operator.rs +++ b/src/evaluate/operator.rs @@ -78,8 +78,8 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result Result Result Option { + Some(match c { + 'n'|'i'|'t'|'a'| + 'e'|'c'|'s'|'r' + => { "".to_string() }, + _ => { return None } + }) +} + +fn format_map_ansi(c: char) -> Option { + Some(match c { + 'n' => { // Normal text + format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) + }, + 'i' => { // Normal italic text + format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) + }, + 't' => { // Title text + format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset)) + }, + 'a' => { // Colored text + format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) + }, + 'e' => { // Error titles + format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset)) + }, + 'c' => { // Console text + format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7))) + }, + 's' => { // Repeat prompt (how => is styled) + format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset)) + }, + 'r' => { // Result prompt (how = is styled) + format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset)) + }, + + _ => { return None } + }) +} + + +fn format_map_full(c: char) -> Option { + Some(match c { + 'n' => { // Normal text + format!("{}{}", color::Fg(color::Reset), style::Reset) + }, + 'i' => { // Normal italic text + format!("{}{}", color::Fg(color::Reset), style::Italic) + }, + 't' => { // Title text + format!("{}{}", color::Fg(color::Magenta), style::Bold) + }, + 'a' => { // Colored text + format!("{}{}", color::Fg(color::Magenta), style::Reset) + }, + 'e' => { // Error titles + format!("{}{}", color::Fg(color::Red), style::Bold) + }, + 'c' => { // Console text + format!("{}{}", color::Fg(color::LightBlack), style::Italic) + }, + 's' => { // Repeat prompt (how => is styled) + format!("{}{}", color::Fg(color::Magenta), style::Bold) + }, + 'r' => { // Result prompt (how = is styled) + format!("{}{}", color::Fg(color::Green), style::Bold) + }, + + _ => { return None } + }) +} + + +impl FormattedText { + pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> { + write!( + stdout, + "\r\n", + )?; + return Ok(()); + } +} + + +impl FormattedText { pub fn new(s: String) -> FormattedText { return FormattedText { text: s @@ -58,30 +143,22 @@ impl FormattedText { let b = chars.next().unwrap(); match (a, b) { - ('n', ']') => { // Normal text - s.push_str(&format!("{}{}", color::Fg(color::Reset), style::Reset)); - }, - ('i', ']') => { // Normal italic text - s.push_str(&format!("{}{}", color::Fg(color::Reset), style::Italic)); - }, - ('t', ']') => { // Title text - s.push_str(&format!("{}{}", color::Fg(color::Magenta), style::Bold)); - }, - ('a', ']') => { // Colored text - s.push_str(&format!("{}{}", color::Fg(color::Magenta), style::Reset)); - }, - ('e', ']') => { // Error titles - s.push_str(&format!("{}{}", color::Fg(color::Red), style::Bold)); - }, - ('c', ']') => { // Console text - s.push_str(&format!("{}{}", color::Fg(color::LightBlack), style::Italic)); - }, + (c, ']') => { // Normal text - ('s', ']') => { // Repeat prompt (how => is styled) - s.push_str(&format!("{}{}", color::Fg(color::Magenta), style::Bold)); - }, - ('r', ']') => { // Result prompt (how = is styled) - s.push_str(&format!("{}{}", color::Fg(color::Green), style::Bold)); + let q = match context.config.term_color_type { + 0 => format_map_none(c), + 1 => format_map_ansi(c), + 2 => format_map_full(c), + _ => unreachable!("Invalid term_color_type") + }; + + if q.is_some() { + s.push_str(&q.unwrap()); + } else { + s.push('['); + s.push(a); + s.push(b); + } }, _ => { diff --git a/src/main.rs b/src/main.rs index 4703d4e..0841f07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,13 +85,13 @@ fn do_expression( // Display parsed string output.push(&format!( " [s]=>[n] {}\n\n", - g.to_string() + g.display(context) )); // Display result output.push(&format!( " [r]=[n] {}\n\n", - g_evaluated.to_string_outer(), + g_evaluated.display_outer(context), )); return Ok((output, g_evaluated)); @@ -224,7 +224,7 @@ fn do_assignment( // Display parsed string output.push(&format!( " [s]=>[n] {left} = {}\n\n", - g.to_string() + g.display(context) )); // Evaluate expression with shadow variables @@ -266,7 +266,7 @@ fn do_assignment( // Display parsed string output.push(&format!( " [t]=>[n] {left} = {}\n\n", - g.to_string() + g.display(context) )); // Evaluate expression diff --git a/src/parser/expression/expression.rs b/src/parser/expression/expression.rs index 74f9a61..564a162 100644 --- a/src/parser/expression/expression.rs +++ b/src/parser/expression/expression.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; use crate::quantity::Quantity; +use crate::context::Context; use super::Operator; use super::Constant; @@ -17,17 +18,17 @@ pub enum Expression { Tuple(LineLocation, VecDeque), } -impl ToString for Expression { - fn to_string(&self) -> String { +impl Expression { + pub fn display(&self, context: &Context) -> String { match self { - Expression::Quantity(_, v) => v.to_string(), + Expression::Quantity(_, v) => v.display(context), Expression::Constant(_, c) => c.to_string(), Expression::Variable(_, s) => s.clone(), - Expression::Operator(_, o,a) => o.print(a), + Expression::Operator(_, o,a) => o.display(context, a), Expression::Tuple(_, v) => { format!("({})", v.iter() - .map(|x| x.to_string()) + .map(|x| x.display(context)) .collect::>() .join(", ") ) @@ -39,16 +40,16 @@ impl ToString for Expression { impl Expression { // This is called only when this is the outermost Expression. // This sometimes leads to different--usually more verbose--behavior. - pub fn to_string_outer(&self) -> String { + pub fn display_outer(&self, context: &Context) -> String { match self { - Expression::Quantity(_, v) => v.to_string_outer(), + Expression::Quantity(_, v) => v.display_outer(context), Expression::Constant(_, c) => c.to_string(), Expression::Variable(_, s) => s.clone(), - Expression::Operator(_, o,a) => o.print(a), + Expression::Operator(_, o,a) => o.display(context, a), Expression::Tuple(_, v) => { format!("({})", v.iter() - .map(|x| x.to_string()) + .map(|x| x.display(context)) .collect::>() .join(", ") ) diff --git a/src/parser/expression/operator.rs b/src/parser/expression/operator.rs index a4b42cc..0ae5b1a 100644 --- a/src/parser/expression/operator.rs +++ b/src/parser/expression/operator.rs @@ -128,8 +128,8 @@ impl Operator { } #[inline(always)] - fn add_parens_to_arg(&self, arg: &Expression) -> String { - let mut astr: String = arg.to_string(); + fn add_parens_to_arg(&self, context: &Context, arg: &Expression) -> String { + let mut astr: String = arg.display(context); if let Expression::Operator(_, o,_) = arg { if o.print_map() < self.print_map() { astr = format!("({})", astr); @@ -139,8 +139,8 @@ impl Operator { } #[inline(always)] - fn add_parens_to_arg_strict(&self, arg: &Expression) -> String { - let mut astr: String = arg.to_string(); + fn add_parens_to_arg_strict(&self, context: &Context, arg: &Expression) -> String { + let mut astr: String = arg.display(context); if let Expression::Operator(_, o,_) = arg { if o.print_map() <= self.print_map() { astr = format!("({})", astr); @@ -150,56 +150,56 @@ impl Operator { } - pub fn print(&self, args: &VecDeque) -> String { + pub fn display(&self, context: &Context, args: &VecDeque) -> String { match self { Operator::Negative => { - return format!("-{}", self.add_parens_to_arg(&args[0])); + return format!("-{}", self.add_parens_to_arg(context, &args[0])); }, Operator::Sqrt => { return format!( "√{}", - self.add_parens_to_arg(&args[0]), + self.add_parens_to_arg(context, &args[0]), ); }, Operator::ModuloLong => { return format!( "{} mod {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::DivideLong => { return format!( "{} per {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::UnitConvert => { return format!( "{} to {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::Modulo => { return format!( "{} % {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::Subtract => { return format!( "{} - {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, @@ -208,13 +208,13 @@ impl Operator { let q = &args[1]; if { - //context.config.enable_super_powers && + context.config.enable_super_powers && q.is_unitless_integer() && - !q.to_string().contains("e") + !q.display(context).contains("e") } { // Write integer powers as a superscript let mut b = String::new(); - for c in q.to_string().chars() { + for c in q.display(context).chars() { b.push(match c { '-' => '⁻', '0' => '⁰', @@ -233,27 +233,27 @@ impl Operator { return format!( "{}{}", - self.add_parens_to_arg_strict(&args[0]), + self.add_parens_to_arg_strict(context, &args[0]), b ); } else { return format!( "{}^{}", - self.add_parens_to_arg_strict(&args[0]), - self.add_parens_to_arg_strict(&args[1]) + self.add_parens_to_arg_strict(context, &args[0]), + self.add_parens_to_arg_strict(context, &args[1]) ); } }, Operator::Factorial => { - return format!("{}!", self.add_parens_to_arg(&args[0])); + return format!("{}!", self.add_parens_to_arg(context, &args[0])); }, Operator::Add => { return format!( "{} + {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, @@ -283,26 +283,26 @@ impl Operator { if let Expression::Quantity(_, u) = b { if u.unit.no_space() { return format!("{}{}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); } else { return format!("{} {}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); } } else { return format!("{}{}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); }; } else { return format!("{} × {}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); } }, @@ -313,25 +313,25 @@ impl Operator { if let Expression::Quantity(_, q) = a { - if q.is_one() { + if q.is_one() && context.config.enable_one_over_power { return format!("{}⁻¹", - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, b) ); } } return format!("{} ÷ {}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); }, Operator::Function(s) => { - return format!("{}({})", s.to_string(), args[0].to_string()); + return format!("{}({})", s.to_string(), args[0].display(context)); }, Operator::UserFunction(s) => { - return format!("{}({})", s.to_string(), args[0].to_string()); + return format!("{}({})", s, args[0].display(context)); } }; } diff --git a/src/quantity/quantity.rs b/src/quantity/quantity.rs index fd63e5b..b42af9f 100644 --- a/src/quantity/quantity.rs +++ b/src/quantity/quantity.rs @@ -7,6 +7,7 @@ use std::ops::{ }; use std::cmp::Ordering; +use crate::context::Context; use crate::quantity::Unit; use crate::quantity::FreeUnit; @@ -21,12 +22,12 @@ pub struct Quantity { -impl ToString for Quantity { - fn to_string(&self) -> String { +impl Quantity { + pub fn display(&self, context: &Context) -> String { let n = self.scalar.to_string(); if self.unitless() { return n; } - let u = self.unit.to_string(); + let u = self.unit.display(context); if self.is_one() { return u; }; if self.unit.no_space() { @@ -38,11 +39,11 @@ impl ToString for Quantity { } impl Quantity { - pub fn to_string_outer(&self) -> String { + pub fn display_outer(&self, context: &Context) -> String { let n = self.scalar.to_string(); if self.unitless() { return n; } - let u = self.unit.to_string(); + let u = self.unit.display(context); if self.unit.no_space() { return format!("{n}{u}"); } else { diff --git a/src/quantity/unit/unit.rs b/src/quantity/unit/unit.rs index 40e35c4..c49e0b3 100644 --- a/src/quantity/unit/unit.rs +++ b/src/quantity/unit/unit.rs @@ -4,6 +4,7 @@ use std::ops::{ MulAssign, DivAssign }; +use crate::context::Context; use crate::quantity::Scalar; use crate::quantity::Quantity; use super::FreeUnit; @@ -16,8 +17,8 @@ pub struct Unit { pub val: HashMap } -impl ToString for Unit { - fn to_string(&self) -> String { +impl Unit { + pub fn display(&self, context: &Context) -> String { if self.unitless() { return String::new(); }; @@ -37,7 +38,11 @@ impl ToString for Unit { if *p == Scalar::new_rational(1f64).unwrap() { t.push_str(&format!("{c}·")); - } else if p.is_int() && !p.to_string().contains("e"){ + } else if { + context.config.enable_super_powers && + p.is_int() && + !p.to_string().contains("e") + } { t.push_str(&c); for c in p.to_string().chars() { t.push( match c { @@ -74,7 +79,11 @@ impl ToString for Unit { bottom_count += 1; if t.len() != 0 && *p == Scalar::new_rational(-1f64).unwrap() { b.push_str(&format!("{c}·")); - } else if p.is_int() && !p.to_string().contains("e") { + } else if { + context.config.enable_super_powers && + p.is_int() && + !p.to_string().contains("e") + } { b.push_str(&c); for c in p.to_string().chars() { if c == '-' && t.len() != 0 { continue; } diff --git a/src/tests.rs b/src/tests.rs index 087b298..0f41c8d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -10,8 +10,10 @@ fn eval_to_str(s: &str) -> Result { }; //let out_str = g.print(); - return match evaluate(&mut Context::new(), &g) { - Ok(x) => Ok(x.to_string_outer()), + let mut c = Context::new(); + + return match evaluate(&mut c, &g) { + Ok(x) => Ok(x.display_outer(&c)), Err(_) => Err(()) }; }