From 3703a86778acb0fea809f5c01b7fc53b0fb3c40e Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Sun, 29 Sep 2024 11:58:50 -0400 Subject: [PATCH 01/10] Initial implementation of copy to clipboard (no copy, just config) --- numbat-cli/src/config.rs | 4 + numbat-cli/src/copy_to_clipboard.rs | 153 ++++++++++++++++++++++++++++ numbat-cli/src/main.rs | 77 ++++++++------ numbat/src/command.rs | 8 ++ numbat/src/interpreter/mod.rs | 3 + numbat/src/lib.rs | 5 + numbat/src/number.rs | 51 ++++++---- numbat/src/quantity.rs | 15 ++- 8 files changed, 264 insertions(+), 52 deletions(-) create mode 100644 numbat-cli/src/copy_to_clipboard.rs diff --git a/numbat-cli/src/config.rs b/numbat-cli/src/config.rs index 870da62f4..159fd84a4 100644 --- a/numbat-cli/src/config.rs +++ b/numbat-cli/src/config.rs @@ -1,3 +1,4 @@ +use crate::copy_to_clipboard::NumericDisplayConfig; use clap::ValueEnum; use serde::{Deserialize, Serialize}; @@ -65,6 +66,8 @@ pub struct Config { #[serde(skip_serializing)] pub load_user_init: bool, pub exchange_rates: ExchangeRateConfig, + + pub copy_output_config: NumericDisplayConfig, } impl Default for Config { @@ -78,6 +81,7 @@ impl Default for Config { load_user_init: true, exchange_rates: Default::default(), enter_repl: true, + copy_output_config: Default::default(), } } } diff --git a/numbat-cli/src/copy_to_clipboard.rs b/numbat-cli/src/copy_to_clipboard.rs new file mode 100644 index 000000000..d5db36042 --- /dev/null +++ b/numbat-cli/src/copy_to_clipboard.rs @@ -0,0 +1,153 @@ +use numbat::{ + markup::Markup, num_format::CustomFormat, num_format::Grouping, pretty_dtoa::FmtFloatConfig, + pretty_print::PrettyPrint, value::Value, FloatDisplayConfigSource, RuntimeError, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Default, Debug, Clone)] +#[serde(remote = "Grouping", rename_all = "kebab-case")] +pub enum IntGroupingConfig { + #[default] + Standard, + Indian, + #[serde(rename = "none")] + Posix, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct IntDisplayConfig { + #[serde(with = "IntGroupingConfig")] + pub grouping: Grouping, + pub separator: String, + pub minus_sign: String, +} + +impl Default for IntDisplayConfig { + fn default() -> Self { + Self { + grouping: Grouping::Standard, + separator: ",".into(), + minus_sign: "-".into(), + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct FloatDisplayConfig { + /// 0 means this value will be ignored + pub max_sig_digits: u8, + /// 0 means this value will be ignored + pub min_sig_digits: u8, + /// 0 means this value will be ignored + pub max_width: u8, + pub decimal: char, + pub capitalize_e: bool, +} + +impl Default for FloatDisplayConfig { + fn default() -> Self { + Self { + max_sig_digits: 0, + min_sig_digits: 0, + max_width: 0, + decimal: '.', + capitalize_e: false, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Default, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct NumericDisplayConfig { + #[serde(serialize_with = "serialize_option_config")] + pub int_config: Option, + #[serde(serialize_with = "serialize_option_config")] + pub float_config: Option, +} + +fn serialize_option_config( + value: &Option, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + match value { + Some(o) => o.serialize(serializer), + None => T::default().serialize(serializer), + } +} + +pub(crate) fn pretty_print_value( + v: &Value, + config: &NumericDisplayConfig, +) -> Result> { + Ok(match &v { + Value::Quantity(q) => { + let NumericDisplayConfig { + int_config, + float_config, + } = config; + + let float_options = float_config.as_ref().map(|c| { + let FloatDisplayConfig { + max_sig_digits, + min_sig_digits, + max_width, + decimal, + capitalize_e, + } = *c; + + let mut opts = FmtFloatConfig::default() + .radix_point(decimal) + .capitalize_e(capitalize_e); + if max_sig_digits > 0 { + opts = opts.max_significant_digits(max_sig_digits); + } + if min_sig_digits > 0 { + opts = opts.min_significant_digits(min_sig_digits); + } + if max_width > 0 { + opts = opts.max_width(max_width); + } + + opts + }); + + let int_options = int_config + .as_ref() + .map(|c| { + let int_config @ IntDisplayConfig { + grouping, + separator, + minus_sign, + } = c; + + CustomFormat::builder() + .grouping(*grouping) + .separator(separator) + .minus_sign(minus_sign) + .build() + .map_err(|err| { + Box::new(RuntimeError::IntegerDisplayConfig( + format!("{int_config:?}"), + err.to_string(), + )) + }) + }) + .transpose()?; + + q.pretty_print_with_options( + match float_options { + Some(o) => FloatDisplayConfigSource::UserConfig(o), + None => FloatDisplayConfigSource::Numbat(None), + }, + int_options, + ) + } + + v => v.pretty_print(), + }) +} diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index ff5fa868d..5efd41d61 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -1,25 +1,28 @@ mod ansi_formatter; mod completer; mod config; +mod copy_to_clipboard; mod highlighter; use ansi_formatter::ansi_format; use colored::control::SHOULD_COLORIZE; use completer::NumbatCompleter; use config::{ColorMode, Config, ExchangeRateFetchingPolicy, IntroBanner, PrettyPrintMode}; +use copy_to_clipboard::pretty_print_value; use highlighter::NumbatHighlighter; use itertools::Itertools; use numbat::command::{self, CommandParser, SourcelessCommandParser}; use numbat::diagnostic::ErrorDiagnostic; use numbat::help::help_markup; -use numbat::markup as m; use numbat::module_importer::{BuiltinModuleImporter, ChainedImporter, FileSystemImporter}; use numbat::pretty_print::PrettyPrint; use numbat::resolver::CodeSource; -use numbat::session_history::{ParseEvaluationResult, SessionHistory, SessionHistoryOptions}; +use numbat::session_history::{SessionHistory, SessionHistoryOptions}; +use numbat::value::Value; +use numbat::InterpreterSettings; +use numbat::{markup as m, InterpreterResult}; use numbat::{Context, NumbatError}; -use numbat::{InterpreterSettings, NameResolutionError}; use anyhow::{bail, Context as AnyhowContext, Result}; use clap::Parser; @@ -97,7 +100,7 @@ struct Args { struct ParseEvaluationOutcome { control_flow: ControlFlow, - result: ParseEvaluationResult, + result: Result, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -348,9 +351,11 @@ impl Cli { interactive: bool, ) -> Result<()> { let mut session_history = SessionHistory::default(); + let mut last_value = None::; loop { let readline = rl.readline(&self.config.prompt); + match readline { Ok(line) => { if !line.trim().is_empty() { @@ -404,6 +409,22 @@ impl Cli { }; println!("{}", ansi_format(&m, false)); } + command::Command::Copy => match &last_value { + Some(v) => { + let m = match pretty_print_value( + v, + &self.config.copy_output_config, + ) { + Ok(m) => m, + Err(err) => { + self.print_diagnostic(*err); + continue; + } + }; + println!("{}", ansi_format(&m, false)); + } + None => println!("error: no value to copy"), + }, command::Command::Clear => rl.clear_screen()?, command::Command::Save { dst } => { let save_result = session_history.save( @@ -462,7 +483,15 @@ impl Cli { } } - session_history.push(line, result); + match result { + Ok(result) => { + session_history.push(line, Ok(())); + if let InterpreterResult::Value(value) = result { + last_value = Some(value); + } + } + Err(_) => session_history.push(line, Err(())), + } } } Err(ReadlineError::Interrupted) => {} @@ -506,12 +535,7 @@ impl Cli { PrettyPrintMode::Auto => interactive, }; - let parse_eval_result = match &interpretation_result { - Ok(_) => Ok(()), - Err(_) => Err(()), - }; - - let control_flow = match interpretation_result.map_err(|b| *b) { + let (control_flow, result) = match interpretation_result.map_err(|b| *b) { Ok((statements, interpreter_result)) => { if interactive || pretty_print { println!(); @@ -547,32 +571,23 @@ impl Cli { println!(); } - ControlFlow::Continue(()) - } - Err(NumbatError::ResolverError(e)) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() - } - Err(NumbatError::NameResolutionError( - e @ (NameResolutionError::IdentifierClash { .. } - | NameResolutionError::ReservedIdentifier(_)), - )) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() + (ControlFlow::Continue(()), Ok(interpreter_result)) } - Err(NumbatError::TypeCheckError(e)) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() - } - Err(NumbatError::RuntimeError(e)) => { - self.print_diagnostic(e); - execution_mode.exit_status_in_case_of_error() + Err(err) => { + match err { + NumbatError::ResolverError(e) => self.print_diagnostic(e), + NumbatError::NameResolutionError(e) => self.print_diagnostic(e), + NumbatError::TypeCheckError(e) => self.print_diagnostic(e), + NumbatError::RuntimeError(e) => self.print_diagnostic(e), + } + + (execution_mode.exit_status_in_case_of_error(), Err(())) } }; ParseEvaluationOutcome { control_flow, - result: parse_eval_result, + result, } } diff --git a/numbat/src/command.rs b/numbat/src/command.rs index 197a71d9b..3dc5ba0a2 100644 --- a/numbat/src/command.rs +++ b/numbat/src/command.rs @@ -23,6 +23,7 @@ enum CommandKind { Help, Info, List, + Copy, Clear, Save, Quit(QuitAlias), @@ -37,6 +38,7 @@ impl FromStr for CommandKind { "help" | "?" => Help, "info" => Info, "list" => List, + "copy" => Copy, "clear" => Clear, "save" => Save, "quit" => Quit(QuitAlias::Quit), @@ -51,6 +53,7 @@ pub enum Command<'a> { Help, Info { item: &'a str }, List { items: Option }, + Copy, Clear, Save { dst: &'a str }, Quit, @@ -212,6 +215,10 @@ impl<'a> CommandParser<'a> { Command::Quit } + CommandKind::Copy => { + self.ensure_zero_args("copy", "")?; + Command::Copy + } CommandKind::Info => { let err_msg = "`info` requires exactly one argument, the item to get info on"; let Some(item) = self.inner.args.next() else { @@ -248,6 +255,7 @@ impl<'a> CommandParser<'a> { Command::List { items } } + CommandKind::Save => { let Some(dst) = self.inner.args.next() else { return Ok(Command::Save { dst: "history.nbt" }); diff --git a/numbat/src/interpreter/mod.rs b/numbat/src/interpreter/mod.rs index 998f2f394..51be294f0 100644 --- a/numbat/src/interpreter/mod.rs +++ b/numbat/src/interpreter/mod.rs @@ -63,6 +63,9 @@ pub enum RuntimeError { #[error("Could not write to file: {0:?}")] FileWrite(std::path::PathBuf), + + #[error("Invalid integer display config: {0}. Originated from: {1}")] + IntegerDisplayConfig(String, String), } #[derive(Debug, PartialEq, Eq)] diff --git a/numbat/src/lib.rs b/numbat/src/lib.rs index ec56a233f..36cac197b 100644 --- a/numbat/src/lib.rs +++ b/numbat/src/lib.rs @@ -82,6 +82,11 @@ use unit_registry::UnitMetadata; use crate::prefix_parser::PrefixParserResult; use crate::unicode_input::UNICODE_INPUT; +pub use number::FloatDisplayConfigSource; + +pub use num_format; +pub use pretty_dtoa; + #[derive(Debug, Clone, Error)] pub enum NumbatError { #[error("{0}")] diff --git a/numbat/src/number.rs b/numbat/src/number.rs index 0102b7629..de9d63d07 100644 --- a/numbat/src/number.rs +++ b/numbat/src/number.rs @@ -32,13 +32,17 @@ impl Number { /// Pretty prints with default options pub fn pretty_print(self) -> String { - self.pretty_print_with_options(None) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(None), None) } /// Pretty prints with the given options if options is not None. /// If options is None, default options will be used. /// If options is not None, float-based format handling is used and integer-based format handling is skipped. - pub fn pretty_print_with_options(self, options: Option) -> String { + pub fn pretty_print_with_options( + self, + float_options: FloatDisplayConfigSource, + int_options: Option, + ) -> String { let number = self.0; // 64-bit floats can accurately represent integers up to 2^52 [1], @@ -47,19 +51,24 @@ impl Number { // [1] https://stackoverflow.com/a/43656339 // // Skip special format handling for integers if options is not None. - if options.is_none() && self.is_integer() && self.0.abs() < 1e15 { + if !matches!(float_options, FloatDisplayConfigSource::Numbat(_)) + && self.is_integer() + && self.0.abs() < 1e15 + { use num_format::{CustomFormat, Grouping, ToFormattedString}; - let format = CustomFormat::builder() - .grouping(if self.0.abs() >= 100_000.0 { - Grouping::Standard - } else { - Grouping::Posix - }) - .minus_sign("-") - .separator("_") - .build() - .unwrap(); + let format = int_options.unwrap_or_else(|| { + CustomFormat::builder() + .grouping(if self.0.abs() >= 100_000.0 { + Grouping::Standard + } else { + Grouping::Posix + }) + .minus_sign("-") + .separator("_") + .build() + .unwrap() + }); number .to_i64() @@ -68,16 +77,19 @@ impl Number { } else { use pretty_dtoa::dtoa; - let config = if let Some(options) = options { - options - } else { + let float_options = match float_options { + FloatDisplayConfigSource::Numbat(opts) => opts, + FloatDisplayConfigSource::UserConfig(opts) => Some(opts), + }; + + let config = float_options.unwrap_or_else(|| { FmtFloatConfig::default() .max_significant_digits(6) .add_point_zero(false) .lower_e_break(-6) .upper_e_break(6) .round() - }; + }); let formatted_number = dtoa(number, config); @@ -154,6 +166,11 @@ impl std::iter::Product for Number { } } +pub enum FloatDisplayConfigSource { + Numbat(Option), + UserConfig(FmtFloatConfig), +} + #[test] fn test_pretty_print() { assert_eq!(Number::from_f64(1.).pretty_print(), "1"); diff --git a/numbat/src/quantity.rs b/numbat/src/quantity.rs index eb3afcb32..ca431a867 100644 --- a/numbat/src/quantity.rs +++ b/numbat/src/quantity.rs @@ -2,6 +2,7 @@ use crate::arithmetic::{Exponent, Power, Rational}; use crate::number::Number; use crate::pretty_print::PrettyPrint; use crate::unit::{is_multiple_of, Unit, UnitFactor}; +use crate::FloatDisplayConfigSource; use itertools::Itertools; use num_rational::Ratio; @@ -341,17 +342,23 @@ impl PartialOrd for Quantity { impl PrettyPrint for Quantity { fn pretty_print(&self) -> crate::markup::Markup { - self.pretty_print_with_options(None) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(None), None) } } impl Quantity { /// Pretty prints with the given options. /// If options is None, default options will be used. - fn pretty_print_with_options(&self, options: Option) -> crate::markup::Markup { + pub fn pretty_print_with_options( + &self, + float_options: FloatDisplayConfigSource, + int_options: Option, + ) -> crate::markup::Markup { use crate::markup; - let formatted_number = self.unsafe_value().pretty_print_with_options(options); + let formatted_number = self + .unsafe_value() + .pretty_print_with_options(float_options, int_options); let unit_str = format!("{}", self.unit()); @@ -373,7 +380,7 @@ impl Quantity { .add_point_zero(false) .force_no_e_notation() .round(); - self.pretty_print_with_options(Some(options)) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(Some(options)), None) } pub fn unsafe_value_as_string(&self) -> String { From 862c7a59bc804200f26e21ae5a69cfa9fd495269 Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Sun, 29 Sep 2024 17:24:40 -0400 Subject: [PATCH 02/10] Added support for unit config --- numbat-cli/src/copy_to_clipboard.rs | 40 +++++++++++++++++++++++++++++ numbat/src/arithmetic.rs | 16 +++++++++--- numbat/src/lib.rs | 3 +++ numbat/src/product.rs | 8 +++++- numbat/src/quantity.rs | 40 ++++++++++++++++++++++++++--- numbat/src/registry.rs | 9 ++++++- numbat/src/unit.rs | 22 +++++++++++++++- 7 files changed, 129 insertions(+), 9 deletions(-) diff --git a/numbat-cli/src/copy_to_clipboard.rs b/numbat-cli/src/copy_to_clipboard.rs index d5db36042..98734a94b 100644 --- a/numbat-cli/src/copy_to_clipboard.rs +++ b/numbat-cli/src/copy_to_clipboard.rs @@ -1,6 +1,7 @@ use numbat::{ markup::Markup, num_format::CustomFormat, num_format::Grouping, pretty_dtoa::FmtFloatConfig, pretty_print::PrettyPrint, value::Value, FloatDisplayConfigSource, RuntimeError, + UnitDisplayOptions, }; use serde::{Deserialize, Serialize}; @@ -58,6 +59,26 @@ impl Default for FloatDisplayConfig { } } +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct UnitDisplayConfig { + fancy_exponents: bool, + multiplication_operator: char, + division_operator: char, + space_btwn_operators: bool, +} + +impl Default for UnitDisplayConfig { + fn default() -> Self { + Self { + fancy_exponents: false, + multiplication_operator: '·', + division_operator: '/', + space_btwn_operators: false, + } + } +} + #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Debug, Clone)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct NumericDisplayConfig { @@ -65,6 +86,8 @@ pub struct NumericDisplayConfig { pub int_config: Option, #[serde(serialize_with = "serialize_option_config")] pub float_config: Option, + #[serde(serialize_with = "serialize_option_config")] + pub unit_config: Option, } fn serialize_option_config( @@ -89,6 +112,7 @@ pub(crate) fn pretty_print_value( let NumericDisplayConfig { int_config, float_config, + unit_config, } = config; let float_options = float_config.as_ref().map(|c| { @@ -139,12 +163,28 @@ pub(crate) fn pretty_print_value( }) .transpose()?; + let unit_options = unit_config.as_ref().map(|c| { + let &UnitDisplayConfig { + fancy_exponents, + multiplication_operator, + division_operator, + space_btwn_operators, + } = c; + UnitDisplayOptions { + fancy_exponents, + multiplication_operator, + division_operator, + space_btwn_operators, + } + }); + q.pretty_print_with_options( match float_options { Some(o) => FloatDisplayConfigSource::UserConfig(o), None => FloatDisplayConfigSource::Numbat(None), }, int_options, + unit_options, ) } diff --git a/numbat/src/arithmetic.rs b/numbat/src/arithmetic.rs index 95cbc355e..fb9fbed31 100644 --- a/numbat/src/arithmetic.rs +++ b/numbat/src/arithmetic.rs @@ -24,9 +24,9 @@ pub fn pretty_exponent(e: &Exponent) -> String { "³".into() } else if e == &Ratio::from_integer(2) { "²".into() - } else if e == &Ratio::from_integer(1) { - "".into() - } else if e == &Ratio::from_integer(-1) { + } + // 1 handled by ugly exponent + else if e == &Ratio::from_integer(-1) { "⁻¹".into() } else if e == &Ratio::from_integer(-2) { "⁻²".into() @@ -36,9 +36,19 @@ pub fn pretty_exponent(e: &Exponent) -> String { "⁻⁴".into() } else if e == &Ratio::from_integer(-5) { "⁻⁵".into() + } else { + ugly_exponent(e) + } +} + +pub fn ugly_exponent(e: &Exponent) -> String { + if e == &Ratio::from_integer(1) { + "".into() } else if e.is_positive() && e.is_integer() { format!("^{e}") } else { format!("^({e})") } } + +// pub trait diff --git a/numbat/src/lib.rs b/numbat/src/lib.rs index 36cac197b..d964a3a3f 100644 --- a/numbat/src/lib.rs +++ b/numbat/src/lib.rs @@ -77,12 +77,14 @@ pub use registry::BaseRepresentationFactor; pub use typed_ast::Statement; pub use typed_ast::Type; use unit::BaseUnitAndFactor; +use unit::UnitFactor; use unit_registry::UnitMetadata; use crate::prefix_parser::PrefixParserResult; use crate::unicode_input::UNICODE_INPUT; pub use number::FloatDisplayConfigSource; +pub use quantity::UnitDisplayOptions; pub use num_format; pub use pretty_dtoa; @@ -409,6 +411,7 @@ impl Context { 'x', '/', true, + None:: String>, Some(m::FormatType::Unit), ); } else { diff --git a/numbat/src/product.rs b/numbat/src/product.rs index 2e907dc50..17001b027 100644 --- a/numbat/src/product.rs +++ b/numbat/src/product.rs @@ -34,6 +34,7 @@ impl String>, format_type: Option, ) -> m::Markup where @@ -54,7 +55,11 @@ impl String>, // need to specify type None, ), false, diff --git a/numbat/src/quantity.rs b/numbat/src/quantity.rs index ca431a867..0d0a57d4f 100644 --- a/numbat/src/quantity.rs +++ b/numbat/src/quantity.rs @@ -1,4 +1,5 @@ use crate::arithmetic::{Exponent, Power, Rational}; +use crate::markup::{Formatter, PlainTextFormatter}; use crate::number::Number; use crate::pretty_print::PrettyPrint; use crate::unit::{is_multiple_of, Unit, UnitFactor}; @@ -342,7 +343,7 @@ impl PartialOrd for Quantity { impl PrettyPrint for Quantity { fn pretty_print(&self) -> crate::markup::Markup { - self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(None), None) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(None), None, None) } } @@ -353,6 +354,7 @@ impl Quantity { &self, float_options: FloatDisplayConfigSource, int_options: Option, + unit_options: Option, ) -> crate::markup::Markup { use crate::markup; @@ -360,7 +362,32 @@ impl Quantity { .unsafe_value() .pretty_print_with_options(float_options, int_options); - let unit_str = format!("{}", self.unit()); + let unit_str = if let Some(UnitDisplayOptions { + fancy_exponents, + multiplication_operator, + division_operator, + space_btwn_operators, + }) = unit_options + { + let m = self.unit().pretty_print_with( + |f| f.exponent, + multiplication_operator, + division_operator, + space_btwn_operators, + Some(|unit: &UnitFactor| { + if fancy_exponents { + unit.to_string() + } else { + unit.to_ugly_string() + } + }), + None, + ); + + PlainTextFormatter.format(&m, false) + } else { + format!("{}", self.unit()) + }; markup::value(formatted_number) + if unit_str == "°" || unit_str == "′" || unit_str == "″" || unit_str.is_empty() { @@ -380,7 +407,7 @@ impl Quantity { .add_point_zero(false) .force_no_e_notation() .round(); - self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(Some(options)), None) + self.pretty_print_with_options(FloatDisplayConfigSource::Numbat(Some(options)), None, None) } pub fn unsafe_value_as_string(&self) -> String { @@ -398,6 +425,13 @@ impl std::fmt::Display for Quantity { } } +pub struct UnitDisplayOptions { + pub fancy_exponents: bool, + pub multiplication_operator: char, + pub division_operator: char, + pub space_btwn_operators: bool, +} + #[cfg(test)] mod tests { use crate::{prefix::Prefix, prefix_parser::AcceptsPrefix, unit::CanonicalName}; diff --git a/numbat/src/registry.rs b/numbat/src/registry.rs index 7ef0e24f1..55a36d481 100644 --- a/numbat/src/registry.rs +++ b/numbat/src/registry.rs @@ -77,7 +77,14 @@ impl PrettyPrint for BaseRepresentation { if self.iter().count() == 0 { crate::markup::type_identifier("Scalar") } else { - self.pretty_print_with(|f| f.1, '×', '/', true, None) + self.pretty_print_with( + |f| f.1, + '×', + '/', + true, + None:: String>, + None, + ) } } } diff --git a/numbat/src/unit.rs b/numbat/src/unit.rs index b94cd74d1..1c015559f 100644 --- a/numbat/src/unit.rs +++ b/numbat/src/unit.rs @@ -4,7 +4,7 @@ use itertools::Itertools; use num_traits::{ToPrimitive, Zero}; use crate::{ - arithmetic::{pretty_exponent, Exponent, Power, Rational}, + arithmetic::{pretty_exponent, ugly_exponent, Exponent, Power, Rational}, number::Number, prefix::Prefix, prefix_parser::AcceptsPrefix, @@ -172,6 +172,26 @@ pub struct UnitFactor { pub exponent: Exponent, } +impl UnitFactor { + // TODO: unify this implementation with `Display::fmt` (they're nearly identical) + /// Get this unit as a String, but without fancy Unicode formatting of exponents. + /// All exponents look like `^1`, `^-2`, etc. + pub fn to_ugly_string(&self) -> String { + let prefix = if self.unit_id.canonical_name.accepts_prefix.short { + self.prefix.as_string_short() + } else { + self.prefix.as_string_long() + }; + + format!( + "{}{}{}", + prefix, + self.unit_id.canonical_name.name, + ugly_exponent(&self.exponent) + ) + } +} + impl Canonicalize for UnitFactor { type MergeKey = (Prefix, UnitIdentifier); From c4e22ad2dfb26f655b13614e2a6f97b0fafcc057 Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Sun, 29 Sep 2024 23:12:37 -0400 Subject: [PATCH 03/10] Actually added copy functionality --- Cargo.lock | 342 +++++++++++++++++++++++++++++++++- numbat-cli/Cargo.toml | 1 + numbat-cli/src/main.rs | 22 ++- numbat/src/interpreter/mod.rs | 2 + 4 files changed, 360 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e1e170e2..a38986080 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -109,6 +115,24 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arboard" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +dependencies = [ + "clipboard-win", + "core-graphics", + "image", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "windows-sys 0.48.0", + "x11rb", +] + [[package]] name = "arrayvec" version = "0.7.4" @@ -164,6 +188,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -179,6 +209,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + [[package]] name = "bstr" version = "1.9.1" @@ -196,6 +235,18 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.7.1" @@ -348,12 +399,46 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.12" @@ -363,6 +448,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.5.1" @@ -595,6 +689,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fdeflate" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -610,6 +723,33 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -629,6 +769,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -772,6 +922,19 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -874,6 +1037,12 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.69" @@ -907,7 +1076,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.6.0", "libc", ] @@ -976,6 +1145,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -991,7 +1170,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "libc", ] @@ -1107,6 +1286,7 @@ name = "numbat-cli" version = "1.13.0" dependencies = [ "anyhow", + "arboard", "assert_cmd", "clap", "colored", @@ -1129,6 +1309,105 @@ dependencies = [ "quick-xml", ] +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -1246,6 +1525,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1390,7 +1682,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1540,7 +1832,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1584,7 +1876,7 @@ version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "clipboard-win", "fd-lock", @@ -1740,6 +2032,12 @@ dependencies = [ "dirs", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.5.0" @@ -1838,6 +2136,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.36" @@ -2115,6 +2424,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -2303,6 +2618,23 @@ dependencies = [ "memchr", ] +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/numbat-cli/Cargo.toml b/numbat-cli/Cargo.toml index fb6b80786..e471a09e2 100644 --- a/numbat-cli/Cargo.toml +++ b/numbat-cli/Cargo.toml @@ -23,6 +23,7 @@ toml = { version = "0.8.8", features = ["parse"] } serde = { version = "1.0.195", features = ["derive"] } terminal_size = "0.3.0" jiff = "0.1" +arboard = "3.4.1" [dependencies.clap] version = "4" diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index 5efd41d61..c0efcd54c 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -15,14 +15,15 @@ use itertools::Itertools; use numbat::command::{self, CommandParser, SourcelessCommandParser}; use numbat::diagnostic::ErrorDiagnostic; use numbat::help::help_markup; +use numbat::markup::{Formatter, PlainTextFormatter}; use numbat::module_importer::{BuiltinModuleImporter, ChainedImporter, FileSystemImporter}; use numbat::pretty_print::PrettyPrint; use numbat::resolver::CodeSource; use numbat::session_history::{SessionHistory, SessionHistoryOptions}; use numbat::value::Value; -use numbat::InterpreterSettings; use numbat::{markup as m, InterpreterResult}; use numbat::{Context, NumbatError}; +use numbat::{InterpreterSettings, RuntimeError}; use anyhow::{bail, Context as AnyhowContext, Result}; use clap::Parser; @@ -421,7 +422,24 @@ impl Cli { continue; } }; - println!("{}", ansi_format(&m, false)); + + if let Err(e) = + arboard::Clipboard::new().and_then(|mut cb| { + cb.set_text( + PlainTextFormatter.format(&m, false), + ) + }) + { + self.print_diagnostic( + RuntimeError::ClipboardError(e.to_string()), + ); + continue; + } + + println!( + "{} was copied to the clipboard", + ansi_format(&m, false) + ); } None => println!("error: no value to copy"), }, diff --git a/numbat/src/interpreter/mod.rs b/numbat/src/interpreter/mod.rs index 51be294f0..ae1675d50 100644 --- a/numbat/src/interpreter/mod.rs +++ b/numbat/src/interpreter/mod.rs @@ -66,6 +66,8 @@ pub enum RuntimeError { #[error("Invalid integer display config: {0}. Originated from: {1}")] IntegerDisplayConfig(String, String), + #[error("Could not access the clipboard. Original error: {0}")] + ClipboardError(String), } #[derive(Debug, PartialEq, Eq)] From ec80035f9f789c9e0554d3ea06210290a9a5fe6a Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Sun, 29 Sep 2024 23:22:11 -0400 Subject: [PATCH 04/10] Fixed too-lax pattern match --- numbat/src/number.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/numbat/src/number.rs b/numbat/src/number.rs index de9d63d07..95ea319c0 100644 --- a/numbat/src/number.rs +++ b/numbat/src/number.rs @@ -51,10 +51,11 @@ impl Number { // [1] https://stackoverflow.com/a/43656339 // // Skip special format handling for integers if options is not None. - if !matches!(float_options, FloatDisplayConfigSource::Numbat(_)) + if !matches!(float_options, FloatDisplayConfigSource::Numbat(Some(_))) && self.is_integer() && self.0.abs() < 1e15 { + println!("{int_options:?}"); use num_format::{CustomFormat, Grouping, ToFormattedString}; let format = int_options.unwrap_or_else(|| { From c7b4f89c0f62d2da41f74db597b6dce2791ecbfc Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Tue, 1 Oct 2024 17:29:18 -0400 Subject: [PATCH 05/10] Removed print statement --- numbat/src/number.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/numbat/src/number.rs b/numbat/src/number.rs index 95ea319c0..79b4a786f 100644 --- a/numbat/src/number.rs +++ b/numbat/src/number.rs @@ -55,7 +55,6 @@ impl Number { && self.is_integer() && self.0.abs() < 1e15 { - println!("{int_options:?}"); use num_format::{CustomFormat, Grouping, ToFormattedString}; let format = int_options.unwrap_or_else(|| { From 2998d1f4e4fdc20aef67ac849a838078bbbead05 Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Wed, 9 Oct 2024 11:47:47 -0400 Subject: [PATCH 06/10] Fixed (?) clipboard being dropped on linux, resulting in nothing copied --- numbat-cli/src/main.rs | 80 +++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index 0116fe431..b18094564 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -353,6 +353,16 @@ impl Cli { ) -> Result<()> { let mut session_history = SessionHistory::default(); let mut last_value = None::; + let mut clipboard = match arboard::Clipboard::new() { + Ok(cb) => Some(cb), + Err(_) => { + println!( + "error: could not initialize the clipboard, so + `copy` functionality will be disabled this session" + ); + None + } + }; loop { let readline = rl.readline(&self.config.prompt); @@ -410,39 +420,53 @@ impl Cli { }; println!("{}", ansi_format(&m, false)); } - command::Command::Copy => match &last_value { - Some(v) => { - let m = match pretty_print_value( - v, - &self.config.copy_output_config, - ) { - Ok(m) => m, - Err(err) => { - self.print_diagnostic(*err); + command::Command::Copy => { + let Some(clipboard) = &mut clipboard else { + println!( + "error: as the clipboard could not \ + be initialized, `copy` functionality is \ + disabled for this session" + ); + continue; + }; + match &last_value { + Some(v) => { + let m = match pretty_print_value( + v, + &self.config.copy_output_config, + ) { + Ok(m) => m, + Err(err) => { + self.print_diagnostic(*err); + continue; + } + }; + + let text = PlainTextFormatter.format(&m, false); + if let Err(e) = { + #[cfg(target_os = "linux")] + { + clipboard.set().wait().text(text) + } + #[cfg(not(target_os = "linux"))] + { + clipboard.set_text(text) + } + } { + self.print_diagnostic( + RuntimeError::ClipboardError(e.to_string()), + ); continue; } - }; - - if let Err(e) = - arboard::Clipboard::new().and_then(|mut cb| { - cb.set_text( - PlainTextFormatter.format(&m, false), - ) - }) - { - self.print_diagnostic( - RuntimeError::ClipboardError(e.to_string()), + + println!( + "{} was copied to the clipboard", + ansi_format(&m, false) ); - continue; } - - println!( - "{} was copied to the clipboard", - ansi_format(&m, false) - ); + None => println!("error: no value to copy"), } - None => println!("error: no value to copy"), - }, + } command::Command::Clear => rl.clear_screen()?, command::Command::Save { dst } => { let save_result = session_history.save( From 4b20f6f76aed18996f0b8ed026d889aeefdc916a Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Thu, 10 Oct 2024 10:02:03 -0400 Subject: [PATCH 07/10] Maybe fixed clipboard issue on linux --- numbat-cli/src/main.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index b18094564..bf6a37794 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -443,16 +443,7 @@ impl Cli { }; let text = PlainTextFormatter.format(&m, false); - if let Err(e) = { - #[cfg(target_os = "linux")] - { - clipboard.set().wait().text(text) - } - #[cfg(not(target_os = "linux"))] - { - clipboard.set_text(text) - } - } { + if let Err(e) = clipboard.set_text(text) { self.print_diagnostic( RuntimeError::ClipboardError(e.to_string()), ); From 4b5448bc34dc11422da87e7cd8a2f23465c45f05 Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Thu, 24 Oct 2024 13:07:33 -0400 Subject: [PATCH 08/10] Remove some pointless float config options --- numbat-cli/src/copy_to_clipboard.rs | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/numbat-cli/src/copy_to_clipboard.rs b/numbat-cli/src/copy_to_clipboard.rs index 98734a94b..59555e429 100644 --- a/numbat-cli/src/copy_to_clipboard.rs +++ b/numbat-cli/src/copy_to_clipboard.rs @@ -37,12 +37,6 @@ impl Default for IntDisplayConfig { #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct FloatDisplayConfig { - /// 0 means this value will be ignored - pub max_sig_digits: u8, - /// 0 means this value will be ignored - pub min_sig_digits: u8, - /// 0 means this value will be ignored - pub max_width: u8, pub decimal: char, pub capitalize_e: bool, } @@ -50,9 +44,6 @@ pub struct FloatDisplayConfig { impl Default for FloatDisplayConfig { fn default() -> Self { Self { - max_sig_digits: 0, - min_sig_digits: 0, - max_width: 0, decimal: '.', capitalize_e: false, } @@ -117,27 +108,13 @@ pub(crate) fn pretty_print_value( let float_options = float_config.as_ref().map(|c| { let FloatDisplayConfig { - max_sig_digits, - min_sig_digits, - max_width, decimal, capitalize_e, } = *c; - let mut opts = FmtFloatConfig::default() + FmtFloatConfig::default() .radix_point(decimal) - .capitalize_e(capitalize_e); - if max_sig_digits > 0 { - opts = opts.max_significant_digits(max_sig_digits); - } - if min_sig_digits > 0 { - opts = opts.min_significant_digits(min_sig_digits); - } - if max_width > 0 { - opts = opts.max_width(max_width); - } - - opts + .capitalize_e(capitalize_e) }); let int_options = int_config From b859ceb463a992990e93d4dad6029782e6fc2f06 Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Thu, 24 Oct 2024 13:09:46 -0400 Subject: [PATCH 09/10] Renamed `copy_output_config` to `copy_result` --- numbat-cli/src/config.rs | 4 ++-- numbat-cli/src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/numbat-cli/src/config.rs b/numbat-cli/src/config.rs index 1fc0b2493..7514601c0 100644 --- a/numbat-cli/src/config.rs +++ b/numbat-cli/src/config.rs @@ -68,7 +68,7 @@ pub struct Config { pub load_user_init: bool, pub exchange_rates: ExchangeRateConfig, - pub copy_output_config: NumericDisplayConfig, + pub copy_result: NumericDisplayConfig, } impl Default for Config { @@ -82,7 +82,7 @@ impl Default for Config { load_user_init: true, exchange_rates: Default::default(), enter_repl: true, - copy_output_config: Default::default(), + copy_result: Default::default(), } } } diff --git a/numbat-cli/src/main.rs b/numbat-cli/src/main.rs index 6189b3f4a..08be81448 100644 --- a/numbat-cli/src/main.rs +++ b/numbat-cli/src/main.rs @@ -437,7 +437,7 @@ impl Cli { Some(v) => { let m = match pretty_print_value( v, - &self.config.copy_output_config, + &self.config.copy_result, ) { Ok(m) => m, Err(err) => { From 441d9fcbf5dddc4e65c4eaa9d114f660ff63a83d Mon Sep 17 00:00:00 2001 From: Robert Bennett Date: Thu, 24 Oct 2024 13:11:44 -0400 Subject: [PATCH 10/10] Factored out some `CompactString::const_new`s --- numbat/src/arithmetic.rs | 24 +++--- numbat/src/prefix.rs | 164 +++++++++++++++++++-------------------- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/numbat/src/arithmetic.rs b/numbat/src/arithmetic.rs index b1ba1252b..798402f8e 100644 --- a/numbat/src/arithmetic.rs +++ b/numbat/src/arithmetic.rs @@ -17,29 +17,29 @@ pub trait Power { } pub fn pretty_exponent(e: &Exponent) -> CompactString { - if e == &Ratio::from_integer(5) { - CompactString::const_new("⁵") + CompactString::const_new(if e == &Ratio::from_integer(5) { + "⁵" } else if e == &Ratio::from_integer(4) { - CompactString::const_new("⁴") + "⁴" } else if e == &Ratio::from_integer(3) { - CompactString::const_new("³") + "³" } else if e == &Ratio::from_integer(2) { - CompactString::const_new("²") + "²" } // 1 handled by ugly_exponent else if e == &Ratio::from_integer(-1) { - CompactString::const_new("⁻¹") + "⁻¹" } else if e == &Ratio::from_integer(-2) { - CompactString::const_new("⁻²") + "⁻²" } else if e == &Ratio::from_integer(-3) { - CompactString::const_new("⁻³") + "⁻³" } else if e == &Ratio::from_integer(-4) { - CompactString::const_new("⁻⁴") + "⁻⁴" } else if e == &Ratio::from_integer(-5) { - CompactString::const_new("⁻⁵") + "⁻⁵" } else { - ugly_exponent(e) - } + return ugly_exponent(e); + }) } pub fn ugly_exponent(e: &Exponent) -> CompactString { diff --git a/numbat/src/prefix.rs b/numbat/src/prefix.rs index 53020d39a..76e375385 100644 --- a/numbat/src/prefix.rs +++ b/numbat/src/prefix.rs @@ -105,90 +105,90 @@ impl Prefix { } pub fn as_string_short(&self) -> CompactString { - match self { - Prefix::Metric(-30) => CompactString::const_new("q"), - Prefix::Metric(-27) => CompactString::const_new("r"), - Prefix::Metric(-24) => CompactString::const_new("y"), - Prefix::Metric(-21) => CompactString::const_new("z"), - Prefix::Metric(-18) => CompactString::const_new("a"), - Prefix::Metric(-15) => CompactString::const_new("f"), - Prefix::Metric(-12) => CompactString::const_new("p"), - Prefix::Metric(-9) => CompactString::const_new("n"), - Prefix::Metric(-6) => CompactString::const_new("µ"), - Prefix::Metric(-3) => CompactString::const_new("m"), - Prefix::Metric(-2) => CompactString::const_new("c"), - Prefix::Metric(-1) => CompactString::const_new("d"), - Prefix::Metric(0) => CompactString::const_new(""), - Prefix::Metric(1) => CompactString::const_new("da"), - Prefix::Metric(2) => CompactString::const_new("h"), - Prefix::Metric(3) => CompactString::const_new("k"), - Prefix::Metric(6) => CompactString::const_new("M"), - Prefix::Metric(9) => CompactString::const_new("G"), - Prefix::Metric(12) => CompactString::const_new("T"), - Prefix::Metric(15) => CompactString::const_new("P"), - Prefix::Metric(18) => CompactString::const_new("E"), - Prefix::Metric(21) => CompactString::const_new("Z"), - Prefix::Metric(24) => CompactString::const_new("Y"), - Prefix::Metric(27) => CompactString::const_new("R"), - Prefix::Metric(30) => CompactString::const_new("Q"), - - Prefix::Metric(n) => format_compact!(""), - - Prefix::Binary(0) => CompactString::const_new(""), - Prefix::Binary(10) => CompactString::const_new("Ki"), - Prefix::Binary(20) => CompactString::const_new("Mi"), - Prefix::Binary(30) => CompactString::const_new("Gi"), - Prefix::Binary(40) => CompactString::const_new("Ti"), - Prefix::Binary(50) => CompactString::const_new("Pi"), - Prefix::Binary(60) => CompactString::const_new("Ei"), - Prefix::Binary(70) => CompactString::const_new("Zi"), - Prefix::Binary(80) => CompactString::const_new("Yi"), - - Prefix::Binary(n) => format_compact!(""), - } + CompactString::const_new(match self { + Prefix::Metric(-30) => "q", + Prefix::Metric(-27) => "r", + Prefix::Metric(-24) => "y", + Prefix::Metric(-21) => "z", + Prefix::Metric(-18) => "a", + Prefix::Metric(-15) => "f", + Prefix::Metric(-12) => "p", + Prefix::Metric(-9) => "n", + Prefix::Metric(-6) => "µ", + Prefix::Metric(-3) => "m", + Prefix::Metric(-2) => "c", + Prefix::Metric(-1) => "d", + Prefix::Metric(0) => "", + Prefix::Metric(1) => "da", + Prefix::Metric(2) => "h", + Prefix::Metric(3) => "k", + Prefix::Metric(6) => "M", + Prefix::Metric(9) => "G", + Prefix::Metric(12) => "T", + Prefix::Metric(15) => "P", + Prefix::Metric(18) => "E", + Prefix::Metric(21) => "Z", + Prefix::Metric(24) => "Y", + Prefix::Metric(27) => "R", + Prefix::Metric(30) => "Q", + + Prefix::Metric(n) => return format_compact!(""), + + Prefix::Binary(0) => "", + Prefix::Binary(10) => "Ki", + Prefix::Binary(20) => "Mi", + Prefix::Binary(30) => "Gi", + Prefix::Binary(40) => "Ti", + Prefix::Binary(50) => "Pi", + Prefix::Binary(60) => "Ei", + Prefix::Binary(70) => "Zi", + Prefix::Binary(80) => "Yi", + + Prefix::Binary(n) => return format_compact!(""), + }) } pub fn as_string_long(&self) -> CompactString { - match self { - Prefix::Metric(-30) => CompactString::const_new("quecto"), - Prefix::Metric(-27) => CompactString::const_new("ronto"), - Prefix::Metric(-24) => CompactString::const_new("yocto"), - Prefix::Metric(-21) => CompactString::const_new("zepto"), - Prefix::Metric(-18) => CompactString::const_new("atto"), - Prefix::Metric(-15) => CompactString::const_new("femto"), - Prefix::Metric(-12) => CompactString::const_new("pico"), - Prefix::Metric(-9) => CompactString::const_new("nano"), - Prefix::Metric(-6) => CompactString::const_new("micro"), - Prefix::Metric(-3) => CompactString::const_new("milli"), - Prefix::Metric(-2) => CompactString::const_new("centi"), - Prefix::Metric(-1) => CompactString::const_new("deci"), - Prefix::Metric(0) => CompactString::const_new(""), - Prefix::Metric(1) => CompactString::const_new("deca"), - Prefix::Metric(2) => CompactString::const_new("hecto"), - Prefix::Metric(3) => CompactString::const_new("kilo"), - Prefix::Metric(6) => CompactString::const_new("mega"), - Prefix::Metric(9) => CompactString::const_new("giga"), - Prefix::Metric(12) => CompactString::const_new("tera"), - Prefix::Metric(15) => CompactString::const_new("peta"), - Prefix::Metric(18) => CompactString::const_new("exa"), - Prefix::Metric(21) => CompactString::const_new("zetta"), - Prefix::Metric(24) => CompactString::const_new("yotta"), - Prefix::Metric(27) => CompactString::const_new("ronna"), - Prefix::Metric(30) => CompactString::const_new("quetta"), - - Prefix::Metric(n) => format_compact!(""), - - Prefix::Binary(0) => CompactString::const_new(""), - Prefix::Binary(10) => CompactString::const_new("kibi"), - Prefix::Binary(20) => CompactString::const_new("mebi"), - Prefix::Binary(30) => CompactString::const_new("gibi"), - Prefix::Binary(40) => CompactString::const_new("tebi"), - Prefix::Binary(50) => CompactString::const_new("pebi"), - Prefix::Binary(60) => CompactString::const_new("exbi"), - Prefix::Binary(70) => CompactString::const_new("zebi"), - Prefix::Binary(80) => CompactString::const_new("yobi"), - - Prefix::Binary(n) => format_compact!(""), - } + CompactString::const_new(match self { + Prefix::Metric(-30) => "quecto", + Prefix::Metric(-27) => "ronto", + Prefix::Metric(-24) => "yocto", + Prefix::Metric(-21) => "zepto", + Prefix::Metric(-18) => "atto", + Prefix::Metric(-15) => "femto", + Prefix::Metric(-12) => "pico", + Prefix::Metric(-9) => "nano", + Prefix::Metric(-6) => "micro", + Prefix::Metric(-3) => "milli", + Prefix::Metric(-2) => "centi", + Prefix::Metric(-1) => "deci", + Prefix::Metric(0) => "", + Prefix::Metric(1) => "deca", + Prefix::Metric(2) => "hecto", + Prefix::Metric(3) => "kilo", + Prefix::Metric(6) => "mega", + Prefix::Metric(9) => "giga", + Prefix::Metric(12) => "tera", + Prefix::Metric(15) => "peta", + Prefix::Metric(18) => "exa", + Prefix::Metric(21) => "zetta", + Prefix::Metric(24) => "yotta", + Prefix::Metric(27) => "ronna", + Prefix::Metric(30) => "quetta", + + Prefix::Metric(n) => return format_compact!(""), + + Prefix::Binary(0) => "", + Prefix::Binary(10) => "kibi", + Prefix::Binary(20) => "mebi", + Prefix::Binary(30) => "gibi", + Prefix::Binary(40) => "tebi", + Prefix::Binary(50) => "pebi", + Prefix::Binary(60) => "exbi", + Prefix::Binary(70) => "zebi", + Prefix::Binary(80) => "yobi", + + Prefix::Binary(n) => return format_compact!(""), + }) } }