diff --git a/examples/hyperlink.rs b/examples/hyperlink.rs index abb284c..ac1e7ec 100644 --- a/examples/hyperlink.rs +++ b/examples/hyperlink.rs @@ -10,7 +10,7 @@ fn main() { let link = Color::Blue .underline() .paint("Link to example.com") - .hyperlink("https://example.com"); + .hyperlink_content("https://example.com"); println!("{}", link); sleep(sleep_ms); diff --git a/src/difference.rs b/src/difference.rs index 312992c..a1e70db 100644 --- a/src/difference.rs +++ b/src/difference.rs @@ -157,7 +157,7 @@ impl Style { } else { // No colors or formatting options were turned off. But there might // have been things turned on, or colors that have changed. This - // case handles that. + // case let turned_off_in_next = BoolStyle::turned_off(self.into(), next.into()); if turned_off_in_next.formats.is_empty() && turned_off_in_next.coloring.is_empty() { let turned_on_from_self = BoolStyle::turned_on(self.into(), next.into()); diff --git a/src/display.rs b/src/display.rs index 20538d7..5e784a1 100644 --- a/src/display.rs +++ b/src/display.rs @@ -4,6 +4,7 @@ use crate::style::{BasedOn, Color, Style}; use crate::write::{AnyWrite, Content, StrLike, WriteResult}; use crate::{fmt_write, io_write, write_fmt, write_str}; use std::borrow::Cow; +use std::cell::{Ref, RefCell, RefMut}; use std::fmt::{self, Debug}; use std::io; @@ -169,7 +170,11 @@ pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>; impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> { /// Create an [`AnsiByteString`] from the given data. - pub fn new(style: Style, content: Content<'a, S>, oscontrol: Option>) -> Self { + pub const fn new( + style: Style, + content: Content<'a, S>, + oscontrol: Option>, + ) -> Self { Self { style, content, @@ -188,13 +193,13 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> { } /// Get the (text) content in this generic string. - pub fn content(&self) -> &Content<'a, S> { + pub const fn content(&self) -> &Content<'a, S> { &self.content } /// Get the [`OSControl`] settings associated with this generic string, if /// any exist. - pub fn oscontrol(&self) -> &Option> { + pub const fn oscontrol(&self) -> &Option> { &self.oscontrol } @@ -213,17 +218,57 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> { /// println!("{}", title_string); /// ``` /// Should produce an empty line but set the terminal title. - pub fn title(s: I) -> Self + pub fn title_content(s: I) -> Self where I: Into>, { Self { - style: Style::default(), + style: Style::new(), content: s.into(), oscontrol: Some(OSControl::::Title), } } + /// Produce an ANSI string that changes the title shown + /// by the terminal emulator. This is a const function which can only accept + /// `&str` or `&[u8]`. + /// + /// # Examples + /// + /// ``` + /// use nu_ansi_term::AnsiGenericString; + /// let title_string = AnsiGenericString::title("My Title"); + /// println!("{}", title_string); + /// ``` + /// Should produce an empty line but set the terminal title. + pub const fn title(s: &'a S) -> Self { + Self { + style: Style::new(), + content: Content::StrLike(Cow::Borrowed(s)), + oscontrol: Some(OSControl::::Title), + } + } + + /// Produce an ANSI string that changes the title shown + /// by the terminal emulator. This is a const function which can only accept + /// a [`fmt::Argument`]. + /// + /// # Examples + /// + /// ``` + /// use nu_ansi_term::AnsiGenericString; + /// let title_string = AnsiGenericString::title("My Title"); + /// println!("{}", title_string); + /// ``` + /// Should produce an empty line but set the terminal title. + pub const fn title_fmt_arg(s: fmt::Arguments<'a>) -> Self { + Self { + style: Style::new(), + content: Content::FmtArgs(s), + oscontrol: Some(OSControl::::Title), + } + } + // // Annotations (OSC sequences that do more than wrap) // @@ -235,12 +280,12 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> { /// ``` /// use nu_ansi_term::Color::Red; /// - /// let link_string = Red.paint("a red string").hyperlink("https://www.example.com"); + /// let link_string = Red.paint("a red string").hyperlink_content(String::from("https://www.example.com")); /// println!("{}", link_string); /// ``` /// Should show a red-painted string which, on terminals /// that support it, is a clickable hyperlink. - pub fn hyperlink(mut self, url: I) -> Self + pub fn hyperlink_content(mut self, url: I) -> Self where I: Into>, { @@ -248,15 +293,40 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> { self } - /// Get any URL associated with the string - pub fn url_string(&self) -> Option<&Content<'a, S>> { - self.oscontrol.as_ref().and_then(|osc| { - if let OSControl::Link { url } = osc { - Some(url) - } else { - None + /// Cause the styled ANSI string to link to the given URL. This is a const + /// fn which can only accept `&str` or `&[u8]`. + /// + /// # Examples + /// + /// ``` + /// use nu_ansi_term::Color::Red; + /// + /// let link_string = Red.paint("a red string").hyperlink("https://www.example.com"); + /// println!("{}", link_string); + /// ``` + /// Should show a red-painted string which, on terminals + /// that support it, is a clickable hyperlink. + pub fn hyperlink(self, url: &'a S) -> Self { + Self { + style: self.style, + content: self.content, + oscontrol: Some(OSControl::Link { + url: Content::StrLike(Cow::Borrowed(url)), + }), + } + } + + /// Get the url content for this string's oscontrol. + pub const fn url_string(&self) -> Option<&Content<'a, S>> { + if let Some(osc) = &self.oscontrol { + match osc { + OSControl::Title => {} + OSControl::Link { url } => { + return Some(url); + } } - }) + } + None } } @@ -264,7 +334,7 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> { /// written with a minimum of control characters. pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized> { strings: Cow<'a, [AnsiGenericString<'a, S>]>, - style_updates: Cow<'a, [StyleUpdate]>, + style_updates: RefCell>, } impl<'a, S: 'a + ToOwned + ?Sized> From> for AnsiGenericStrings<'a, S> { @@ -272,10 +342,10 @@ impl<'a, S: 'a + ToOwned + ?Sized> From> for AnsiGeneri let style = value.style; Self { strings: Cow::Owned(vec![value]), - style_updates: Cow::Owned(vec![StyleUpdate { + style_updates: RefCell::new(Cow::Owned(vec![StyleUpdate { style_delta: StyleDelta::ExtraStyles(style), begins_at: 0, - }]), + }])), } } } @@ -283,7 +353,7 @@ impl<'a, S: 'a + ToOwned + ?Sized> From> for AnsiGeneri impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericStrings<'a, S> { fn clone(&self) -> Self { Self { - style_updates: self.style_updates.clone(), + style_updates: RefCell::new(self.style_updates.borrow_mut().clone()), strings: self.strings.clone(), } } @@ -298,17 +368,23 @@ where fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AnsiGenericStrings") .field("strings", &self.strings) - .field("style_updates", &self.style_updates) + .field("style_updates", &self.style_updates.borrow_mut()) .finish() } } impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { + pub const fn new(strings: &'a [AnsiGenericString<'a, S>]) -> Self { + Self { + strings: Cow::Borrowed(strings), + style_updates: RefCell::new(Cow::Borrowed(&[])), + } + } /// Create empty sequence with the given capacity. pub fn with_capacity(capacity: usize) -> Self { Self { strings: Vec::with_capacity(capacity).into(), - style_updates: Vec::with_capacity(capacity).into(), + style_updates: RefCell::new(Vec::with_capacity(capacity).into()), } } @@ -317,6 +393,34 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { self.strings.iter() } + fn calculate_style_updates(&self) { + let mut style_updates = Vec::with_capacity(self.strings.len()); + for (ix, string) in self.strings.iter().enumerate() { + Self::push_style_into(&mut style_updates, string.style, ix); + } + *self.style_updates.borrow_mut() = Cow::Owned(style_updates); + } + + /// Get the style updates required to build this string. + /// + /// If they are not yet computed, they will be computed, otherwise the cached updates will be returned. + fn style_updates(&self) -> Ref<'_, Cow<'_, [StyleUpdate]>> { + if self.strings.len() != self.style_updates.borrow().len() { + self.calculate_style_updates(); + } + self.style_updates.borrow() + } + + /// Get mutable access to the style updates required to build this string. + /// + /// If they are not yet computed, they will be computed, otherwise the cached updates will be returned. + fn style_updates_mut(&self) -> RefMut<'_, Cow<'a, [StyleUpdate]>> { + if self.strings.len() != self.style_updates.borrow().len() { + self.calculate_style_updates(); + } + self.style_updates.borrow_mut() + } + /// Update specific generic strings. /// /// Depending on where the updates are made, not all style deltas will be @@ -347,9 +451,9 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { } if min_changed_ix < original_len { + let unchanged_existing = &self.style_updates()[0..min_changed_ix]; let mut new_style_updates = Vec::with_capacity(new_strings.len()); - new_style_updates.extend(&self.style_updates[0..min_changed_ix]); - let mut new_style_updates = Cow::Owned(new_style_updates); + new_style_updates.extend(unchanged_existing); for (ix, style) in new_strings[min_changed_ix..] .iter() @@ -361,7 +465,7 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { Self { strings: Cow::Owned(new_strings), - style_updates: new_style_updates, + style_updates: RefCell::new(Cow::Owned(new_style_updates)), } } else { Self::from_iter(new_strings) @@ -370,8 +474,8 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { /// Rebase a nested string onto a parent's style. This is effectively an /// "OR" operation. - pub fn rebase_on(mut self, base: Style) -> Self { - for update in self.style_updates.to_mut() { + pub fn rebase_on(self, base: Style) -> Self { + for update in self.style_updates_mut().to_mut().iter_mut() { update.style_delta = match update.style_delta { StyleDelta::ExtraStyles(style) => { StyleDelta::ExtraStyles(if style.prefix_before_reset { @@ -395,7 +499,7 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { #[inline] fn push_style_into( - existing_style_updates: &mut Cow<'a, [StyleUpdate]>, + existing_style_updates: &mut Vec, next: Style, begins_at: usize, ) { @@ -406,22 +510,22 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { .style_delta .delta_next(next); - existing_style_updates.to_mut().push(StyleUpdate { + existing_style_updates.push(StyleUpdate { begins_at, style_delta: command, }); } #[inline] - fn push_style(&mut self, next: Style, begins_at: usize) { - Self::push_style_into(&mut self.style_updates, next, begins_at) + fn push_style(&self, next: Style, begins_at: usize) { + Self::push_style_into(self.style_updates.borrow_mut().to_mut(), next, begins_at) } fn write_iter(&self) -> WriteIter<'_, 'a, S> { WriteIter { style_iter: StyleIter { cursor: 0, - instructions: &self.style_updates, + instructions: self.style_updates.borrow(), next_update: None, current: None, }, @@ -436,7 +540,7 @@ impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericStrings<'a, S> { /// Iterator over the minimal styles (see [`StyleDelta`]) of an [`AnsiGenericStrings`] sequence. pub struct StyleIter<'b> { cursor: usize, - instructions: &'b [StyleUpdate], + instructions: Ref<'b, Cow<'b, [StyleUpdate]>>, next_update: Option, current: Option, } @@ -558,7 +662,7 @@ pub fn AnsiByteStrings<'a>( // ---- paint functions ---- impl Style { - /// Paints the given text with this style, returning an ANSI string. + /// Paints the given content with this style, returning an ANSI string. /// /// ``` /// use nu_ansi_term::Style; @@ -842,7 +946,7 @@ mod tests { fn hyperlink() { let styled = Red .paint("Link to example.com.") - .hyperlink("https://example.com"); + .hyperlink_content("https://example.com"); assert_eq!( styled.to_string(), "\x1B[31m\x1B]8;;https://example.com\x1B\\Link to example.com.\x1B]8;;\x1B\\\x1B[0m" @@ -854,7 +958,7 @@ mod tests { let link = Blue .underline() .paint("Link to example.com.") - .hyperlink("https://example.com"); + .hyperlink_content("https://example.com"); dbg!("link: {:?}", &link); // Assemble with link by itself let joined = AnsiStrings([link.clone()]).to_string(); @@ -869,7 +973,7 @@ mod tests { let link = Blue .underline() .paint("Link to example.com.") - .hyperlink("https://example.com"); + .hyperlink_content("https://example.com"); dbg!("link: {:?}", &link); let after = Green.paint(" After link."); // Assemble with link first @@ -886,7 +990,7 @@ mod tests { let link = Blue .underline() .paint("Link to example.com.") - .hyperlink("https://example.com"); + .hyperlink_content("https://example.com"); dbg!("link: {:?}", &link); // Assemble with link at the end let joined = AnsiStrings([before.clone(), link.clone()]).to_string(); @@ -902,7 +1006,7 @@ mod tests { let link = Blue .underline() .paint("Link to example.com.") - .hyperlink("https://example.com"); + .hyperlink_content("https://example.com"); dbg!("link: {:?}", &link); let after = Green.paint(" After link."); dbg!("link: {:?}", &link); diff --git a/src/style.rs b/src/style.rs index 80fae8d..ac12846 100644 --- a/src/style.rs +++ b/src/style.rs @@ -27,15 +27,13 @@ bitflags! { impl FormatFlags { #[inline] - pub fn set_flags(mut self, flags: FormatFlags) -> Self { - self.insert(flags); - self + pub const fn set_flags(self, flags: FormatFlags) -> Self { + self.union(flags) } #[inline] - pub fn unset_flags(mut self, flags: FormatFlags) -> Self { - self &= !flags; - self + pub fn unset_flags(self, flags: FormatFlags) -> Self { + self.intersection(flags.complement()) } } @@ -165,7 +163,7 @@ macro_rules! format_methods { #[doc = r"let style = Style::new()." [< $flag:lower >] r"();"] #[doc = r#"println!("{}", style.paint("hey"));"# ] #[doc = r"```"] - pub fn [< $flag:lower >](&self) -> Style { + pub const fn [< $flag:lower >](&self) -> Style { (*self).insert_formats(FormatFlags::$flag) } @@ -175,7 +173,7 @@ macro_rules! format_methods { } #[doc = r"Returns a copy of this style with the [`FormatFlags::`" $flag r"`] property unset."] - pub fn [< without_ $flag:lower >](&self) -> Style { + pub const fn [< without_ $flag:lower >](&self) -> Style { (*self).remove_formats(FormatFlags::$flag) } } @@ -197,15 +195,15 @@ macro_rules! style_color_methods { #[doc = r#"println!("{}", style.paint("hey"));"# ] #[doc = r"```"] #[inline] - pub fn [< set_ $ground >](mut self, color: Option) -> Self { + pub const fn [< set_ $ground >](mut self, color: Option) -> Self { self.coloring.[< $ground >] = color; self } #[doc = r"Set the " $ground " color of the style."] #[inline] - pub fn [< $ground >](self, color: Color) -> Self { - self.[< set_ $ground >](color.into()) + pub const fn [< $ground >](self, color: Color) -> Self { + self.[< set_ $ground >](Some(color)) } #[doc = r"Gets the corresponding " $ground " color if it exists."] @@ -234,35 +232,43 @@ impl Style { /// let style = Style::new(); /// println!("{}", style.paint("hi")); /// ``` - pub fn new() -> Style { - Style::default() + pub const fn new() -> Style { + Style { + prefix_before_reset: false, + formats: FormatFlags::empty(), + coloring: Coloring { fg: None, bg: None }, + } } /// Insert (turn on) style properties in this style that are true in given `formats`. - pub fn insert_formats(mut self, formats: FormatFlags) -> Self { - self.formats.insert(formats); - self + pub const fn insert_formats(self, formats: FormatFlags) -> Self { + Self { + prefix_before_reset: self.prefix_before_reset, + formats: self.formats.union(formats), + coloring: self.coloring, + } } /// Remove (turn off) the format properties specified by `formats`. - pub fn remove_formats(mut self, formats: FormatFlags) -> Self { - // We use &! instead of the `remove` operator on `flags`, because ! - // truncates any unknown bits. - self.formats &= !formats; - self + pub const fn remove_formats(self, formats: FormatFlags) -> Self { + Self { + prefix_before_reset: self.prefix_before_reset, + formats: self.formats.intersection(formats.complement()), + coloring: self.coloring, + } } /// Create a copy of this style, and insert into it any formats /// that are true in `flags`. #[inline] - pub fn with_flags(&self, flags: FormatFlags) -> Style { + pub const fn with_flags(&self, flags: FormatFlags) -> Style { (*self).insert_formats(flags) } /// Create a copy of this style, and remove from it any formats that are /// true in `flags`. #[inline] - pub fn without_flags(&self, flags: FormatFlags) -> Style { + pub const fn without_flags(&self, flags: FormatFlags) -> Style { (*self).remove_formats(flags) } @@ -297,26 +303,26 @@ impl Style { /// Check if style has no formatting or coloring (it might still have `reset_before_style`). #[inline] - pub fn has_no_styling(&self) -> bool { + pub const fn has_no_styling(&self) -> bool { !self.has_color() && !self.has_formatting() } /// Check if style has any coloring. #[inline] - pub fn has_color(&self) -> bool { + pub const fn has_color(self) -> bool { self.coloring.fg.is_some() || self.coloring.bg.is_some() } /// Get the formatting flags of this style. #[inline] - pub fn get_formats(&self) -> FormatFlags { + pub const fn get_formats(self) -> FormatFlags { self.formats } /// Check if the style contains some property which cannot be inverted, and /// thus must be followed by a reset flag in order turn off its effect. #[inline] - pub fn has_formatting(&self) -> bool { + pub const fn has_formatting(self) -> bool { !self.formats.is_empty() } @@ -326,7 +332,8 @@ impl Style { } /// Create a copy of this style, with the styling properties updated using - pub fn update_with(self, other: Self) -> Self { + /// the other style. + pub const fn update_with(self, other: Self) -> Self { Self { prefix_before_reset: !self.prefix_before_reset && other.prefix_before_reset, formats: self.formats.set_flags(other.formats), @@ -346,18 +353,18 @@ impl Style { } /// Return whether or not `reset_before_style` is set. - pub fn is_reset_before_style(&self) -> bool { + pub const fn is_reset_before_style(&self) -> bool { self.prefix_before_reset } /// Set `reset_before_style` to be `true`. - pub fn reset_before_style(mut self) -> Self { + pub const fn reset_before_style(mut self) -> Self { self.prefix_before_reset = true; self } ///Set `reset_before_style` to the specified value. - pub fn set_reset_before_style(mut self, value: bool) -> Self { + pub const fn set_reset_before_style(mut self, value: bool) -> Self { self.prefix_before_reset = value; self } @@ -365,7 +372,7 @@ impl Style { /// Sets the background color for this style. This is a shim for backwards /// compatibility, which ultimately calls [`Style::bg`](crate::style::Style::bg). #[inline] - pub fn on(self, color: Color) -> Self { + pub const fn on(self, color: Color) -> Self { self.bg(color) } } @@ -502,7 +509,7 @@ impl Color { /// let style = Color::Rgb(31, 31, 31).normal(); /// println!("{}", style.paint("eyyyy")); /// ``` - pub fn normal(self) -> Style { + pub const fn normal(self) -> Style { Style::new().fg(self) } @@ -516,7 +523,7 @@ impl Color { /// let style = Color::White.bg().fg(Color::Rgb(31, 31, 31)); /// println!("{}", style.paint("eyyyy")); /// ``` - pub fn bg(self) -> Style { + pub const fn bg(self) -> Style { Style::new().bg(self) } @@ -531,13 +538,13 @@ impl Color { /// let style = Color::Rgb(31, 31, 31).on(Color::White); /// println!("{}", style.paint("eyyyy")); /// ``` - pub fn on(self, bg: Self) -> Style { + pub const fn on(self, bg: Self) -> Style { Style::new().fg(self).bg(bg) } /// Returns a `Style` with the background color set to this color and the /// foreground color property set to the given color. - pub fn under(self, fg: Self) -> Style { + pub const fn under(self, fg: Self) -> Style { Style::new().bg(self).fg(fg) }