From 890b3f13e1b9d70bf7fcfaf2cdd0c06f3faa2d83 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 10 Jan 2018 23:58:29 +0100 Subject: [PATCH] TextView: print styled spans of text --- examples/markup.rs | 22 +++++ src/printer.rs | 5 +- src/utils/markup/markdown.rs | 55 +++++++++--- src/utils/markup/mod.rs | 134 +++++++++++++++++++++++----- src/views/text_view.rs | 167 +++++++++++++++++++++++++++++------ src/with.rs | 9 ++ 6 files changed, 329 insertions(+), 63 deletions(-) create mode 100644 examples/markup.rs diff --git a/examples/markup.rs b/examples/markup.rs new file mode 100644 index 0000000..31dd7c1 --- /dev/null +++ b/examples/markup.rs @@ -0,0 +1,22 @@ +extern crate cursive; + +use cursive::Cursive; +use cursive::utils::markup::MarkdownText; +use cursive::views::{Dialog, TextView}; + +// Make sure you compile with the `markdown` feature! +// +// cargo run --example markup --features markdown + +fn main() { + let mut siv = Cursive::new(); + + let text = MarkdownText("Isn't *that* **cool**?"); + + siv.add_layer( + Dialog::around(TextView::styled(text).unwrap()) + .button("Hell yeah!", |s| s.quit()), + ); + + siv.run(); +} diff --git a/src/printer.rs b/src/printer.rs index 1cf6581..0fb2722 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -143,6 +143,8 @@ impl<'a> Printer<'a> { let color = style.color; let effects = style.effects; + // eprintln!("{:?}", effects); + if let Some(color) = color { self.with_color(color, |printer| { printer.with_effects(effects, f); @@ -172,7 +174,8 @@ impl<'a> Printer<'a> { Some(effect) => { let mut effects = effects; effects.remove(effect); - self.with_effects(effects, f); + + self.with_effect(effect, |s| s.with_effects(effects, f)); } } } diff --git a/src/utils/markup/markdown.rs b/src/utils/markup/markdown.rs index c49fdea..f1bc721 100644 --- a/src/utils/markup/markdown.rs +++ b/src/utils/markup/markdown.rs @@ -9,6 +9,47 @@ use std::borrow::Cow; use theme::{Effect, Style}; use utils::lines::spans::Span; +/// `Markup` trait implementation for markdown text. +/// +/// Requires the `markdown` feature. +pub struct Markdown; + +impl super::Markup for Markdown { + type Error = (); + + fn parse<'a>(input: &'a str) -> Result>, Self::Error> { + Ok(parse(input)) + } +} + +/// Thin wrapper around text that should be parsed as Markdown. +/// +/// This does not parse the text here, but indicates how it should be parsed. +/// +/// # Examples +/// +/// ```rust +/// // Can use `&str` +/// let text = MarkdownText("*Markdown* text!"); +/// +/// // Or `String` +/// let text = MarkdownText(String::from("*Right __here__!")); +/// ``` +pub struct MarkdownText(pub S) +where + S: Into; + +impl super::MarkupText for MarkdownText +where + S: Into, +{ + type M = Markdown; + + fn to_string(self) -> String { + self.0.into() + } +} + /// Iterator that parse a markdown text and outputs styled spans. pub struct Parser<'a> { first: bool, @@ -116,19 +157,7 @@ impl<'a> Iterator for Parser<'a> { /// This is a shortcut for `Parser::new(input).collect()`. pub fn parse<'a>(input: &'a str) -> Vec> { Parser::new(input).collect() -} - -/// `Markup` trait implementation for markdown text. -/// -/// Requires the `markdown` feature. -pub struct Markdown; - -impl super::Markup for Markdown { - type Error = (); - - fn parse<'a>(input: &'a str) -> Result>, Self::Error> { - Ok(parse(input)) - } + // Parser::new(input).inspect(|span| eprintln!("{:?}", span)).collect() } #[cfg(test)] diff --git a/src/utils/markup/mod.rs b/src/utils/markup/mod.rs index 6ded550..f7ec329 100644 --- a/src/utils/markup/mod.rs +++ b/src/utils/markup/mod.rs @@ -6,12 +6,14 @@ pub mod markdown; #[cfg(feature = "markdown")] -pub use self::markdown::Markdown; +pub use self::markdown::MarkdownText; + use owning_ref::OwningHandle; use owning_ref::StringRef; -use utils::lines::spans::Span; -use theme::Style; use std::borrow::Cow; +use std::ops::Deref; +use theme::Style; +use utils::lines::spans::Span; /// Trait for parsing text into styled spans. pub trait Markup { @@ -36,6 +38,28 @@ pub trait Markup { } } +/// Thin wrapper around a string, with a markup format. +/// +/// This only wraps the text and indicates how it should be parsed; +/// it does not parse the text itself. +pub trait MarkupText { + + /// Markup format to use to parse the string. + type M: Markup; + + /// Access the inner string. + fn to_string(self) -> String; +} + +/// Unwrapped text gets the "Plain" markup for free. +impl> MarkupText for S { + type M = Plain; + + fn to_string(self) -> String { + self.into() + } +} + /// Dummy `Markup` implementation that returns the text as-is. pub struct Plain; @@ -43,10 +67,12 @@ impl Markup for Plain { type Error = (); fn parse<'a>(input: &'a str) -> Result>, Self::Error> { - Ok(vec![Span { - text: Cow::Borrowed(input), - style: Style::none(), - }]) + Ok(vec![ + Span { + text: Cow::Borrowed(input), + style: Style::none(), + }, + ]) } } @@ -57,37 +83,105 @@ pub type StyledHandle = OwningHandle>>; /// A String that parses a markup language. pub struct StyledString { - content: StyledHandle, + content: Option, } impl StyledString { - /// Creates a new styled string, parsing the given content. - pub fn new(content: S) -> Result + /// + /// # Examples + /// + /// ```rust + /// # use cursive::utils::markup::StyledString; + /// let styled_string = StyledString::new("*plain* text"); + /// ``` + pub fn new(content: T) -> Result::Error> where - S: Into, - M: Markup, + T: MarkupText, { - let content = M::make_handle(content)?; + let content = content.to_string(); + + let content = Some(T::M::make_handle(content)?); + Ok(StyledString { content }) } + /// Returns a plain StyledString without any style. + /// + /// > You got no style, Dutch. You know that. + pub fn plain(content: S) -> Self + where S: Into + { + Self::new(content).unwrap() + } + /// Sets the content of this string. /// - /// The content will be parsed; if an error is found, - /// it will be returned here (and the content will be unchanged). - pub fn set_content(&mut self, content: S) -> Result<(), M::Error> + /// # Examples + /// + /// ```rust + /// # use cursive::utils::markup::StyledString; + /// # let mut styled_string = StyledString::new("").unwrap(); + /// styled_string.set_content("*plain* text").unwrap(); + /// ``` + pub fn set_content( + &mut self, content: T + ) -> Result<(), <::M as Markup>::Error> where - S: Into, - M: Markup, + T: MarkupText, { - self.content = M::make_handle(content)?; + let content = content.to_string(); + + self.content = Some(T::M::make_handle(content)?); Ok(()) } + /// Sets the content of this string to plain text. + pub fn set_plain(&mut self, content: S) where S: Into { + self.set_content(content).unwrap(); + } + + /// Append `content` to the end. + /// + /// Re-parse everything after. + pub fn append_content(&mut self, content: T) -> Result<(), ::Error> + where + T: MarkupText + { + self.with_content::(|c| c.push_str(&content.to_string())) + } + + /// Run a closure on the text content. + /// + /// And re-parse everything after. + pub fn with_content(&mut self, f: F) -> Result + where + M: Markup, + F: FnOnce(&mut String) -> O, + { + // Get hold of the StyledHandle + let content = self.content.take().unwrap(); + // Get the inner String + let mut content = content.into_inner().into_inner(); + // Do what we have to do + let out = f(&mut content); + // And re-parse everything + self.content = Some(M::make_handle(content)?); + + Ok(out) + } + /// Gives access to the parsed styled spans. pub fn spans<'a>(&'a self) -> &'a [Span<'a>] { - &self.content + &self.content.as_ref().unwrap() + } +} + +impl Deref for StyledString { + type Target = str; + + fn deref(&self) -> &str { + &self.content.as_ref().unwrap().owner() } } diff --git a/src/views/text_view.rs b/src/views/text_view.rs index 424644f..c312948 100644 --- a/src/views/text_view.rs +++ b/src/views/text_view.rs @@ -10,7 +10,8 @@ use std::sync::{Mutex, MutexGuard}; use std::sync::Arc; use theme::Effect; use unicode_width::UnicodeWidthStr; -use utils::lines::simple::{LinesIterator, Row}; +use utils::lines::spans::{Row, SpanLinesIterator}; +use utils::markup::{Markup, MarkupText, StyledString}; use vec::Vec2; use view::{ScrollBase, ScrollStrategy, SizeCache, View}; @@ -38,13 +39,28 @@ pub struct TextContent { impl TextContent { /// Creates a new text content around the given value. - pub fn new>(content: S) -> Self { - TextContent { + pub fn new(content: S) -> Self + where + S: Into, + { + Self::styled(content).unwrap() + } + + /// Creates a new text content around the given value. + /// + /// Parses the given value. + pub fn styled(content: T) -> Result::Error> + where + T: MarkupText, + { + let content = StyledString::new(content)?; + + Ok(TextContent { content: Arc::new(Mutex::new(TextContentInner { - content: content.into(), + content, size_cache: None, })), - } + }) } } @@ -70,13 +86,33 @@ impl Deref for TextContentRef { impl TextContent { /// Replaces the content with the given value. - pub fn set_content>(&mut self, content: S) { - self.with_content(|c| *c = content.into()); + pub fn set_content(&mut self, content: S) + where + S: Into, + { + self.with_content(|c| c.set_plain(content)); + } + + /// Replaces the content with the given value. + /// + /// The given markup text will be parsed. + pub fn set_markup( + &mut self, content: T + ) -> Result<(), ::Error> + where + T: MarkupText, + { + self.with_content(|c| c.set_content(content)) } /// Append `content` to the end of a `TextView`. - pub fn append_content(&mut self, content: &str) { - self.with_content(|c| c.push_str(content)); + pub fn append_content( + &mut self, content: T + ) -> Result<(), ::Error> + where + T: MarkupText, + { + self.with_content(|c| c.append_content(content)) } /// Returns a reference to the content. @@ -87,20 +123,25 @@ impl TextContent { TextContentInner::get_content(&self.content) } - fn with_content(&mut self, f: F) + fn with_content(&mut self, f: F) -> O where - F: FnOnce(&mut String), + F: FnOnce(&mut StyledString) -> O, { let mut lock = self.content.lock().unwrap(); - f(&mut lock.content); + let out = f(&mut lock.content); lock.size_cache = None; + + out } } struct TextContentInner { - content: String, + // content: String, + content: StyledString, + + // We keep the cache here so it can be busted when we change the content. size_cache: Option>, } @@ -164,8 +205,28 @@ fn strip_last_newline(content: &str) -> &str { impl TextView { /// Creates a new TextView with the given content. - pub fn new>(content: S) -> Self { - TextView::new_with_content(TextContent::new(content)) + pub fn new(content: S) -> Self + where + S: Into, + { + Self::styled(content).unwrap() + } + + /// Creates a new TextView by parsing the given content. + /// + /// # Examples + /// + /// ```rust,ignore + /// # use cursive::views::TextView; + /// use cursive::utils::markup::MarkdownText; + /// // This will require the `markdown` feature! + /// let view = TextView::styled(MarkdownText("**Bold** text")); + /// ``` + pub fn styled(content: T) -> Result::Error> + where + T: MarkupText, + { + TextContent::styled(content).map(TextView::new_with_content) } /// Creates a new TextView using the given `Arc>`. @@ -263,21 +324,62 @@ impl TextView { /// Replace the text in this view. /// /// Chainable variant. - pub fn content>(self, content: S) -> Self { - self.with(|s| s.set_content(content)) + pub fn content(self, content: S) -> Self + where + S: Into, + { + self.markup(content).unwrap() } /// Replace the text in this view. - pub fn set_content>(&mut self, content: S) { - let content = content.into(); - self.content.lock().unwrap().content = content; + /// + /// Parse the given markup text. + /// + /// Chainable variant. + pub fn markup(self, content: T) -> Result::Error> + where + T: MarkupText, + { + self.try_with(|s| s.set_markup(content)) + } + + /// Replace the text in this view. + pub fn set_content(&mut self, content: S) + where + S: Into, + { + self.set_markup(content).unwrap(); + } + + /// Replace the text in this view. + /// + /// Parses the given markup text. + pub fn set_markup( + &mut self, content: T + ) -> Result<(), ::Error> + where + T: MarkupText, + { + self.content.lock().unwrap().content.set_content(content)?; self.invalidate(); + + Ok(()) } /// Append `content` to the end of a `TextView`. - pub fn append_content(&mut self, content: &str) { - self.content.lock().unwrap().content.push_str(content); + pub fn append_content( + &mut self, content: T + ) -> Result<(), ::Error> + where + T: MarkupText, + { + self.content + .lock() + .unwrap() + .content + .append_content(content)?; self.invalidate(); + Ok(()) } /// Returns the current text in this view. @@ -365,8 +467,7 @@ impl TextView { // First attempt: naively hope that we won't need a scrollbar_width // (This means we try to use the entire available width for text). self.rows = - LinesIterator::new(strip_last_newline(&content.content), size.x) - .collect(); + SpanLinesIterator::new(content.content.spans(), size.x).collect(); // Width taken by the scrollbar. Without a scrollbar, it's 0. let mut scrollbar_width = 0; @@ -382,7 +483,8 @@ impl TextView { }; self.rows = - LinesIterator::new(&content.content, available).collect(); + SpanLinesIterator::new(content.content.spans(), available) + .collect(); if self.rows.is_empty() && !content.content.is_empty() { // We have some content, we we didn't find any row for it? @@ -431,10 +533,17 @@ impl View for TextView { printer.with_effect(self.effect, |printer| { self.scrollbase.draw(printer, |printer, i| { let row = &self.rows[i]; - let text = &content.content[row.start..row.end]; - let l = text.width(); - let x = self.align.h.get_offset(l, printer.size.x); - printer.print((x, 0), text); + let l = row.width; + let mut x = self.align.h.get_offset(l, printer.size.x); + + for span in row.resolve(content.content.spans()) { + printer.with_style(span.style, |printer| { + printer.print((x, 0), &span.text); + x += span.text.len(); + }); + } + // let text = &content.content[row.start..row.end]; + // printer.print((x, 0), text); }); }); } diff --git a/src/with.rs b/src/with.rs index da86497..ba6df23 100644 --- a/src/with.rs +++ b/src/with.rs @@ -5,6 +5,15 @@ pub trait With: Sized { f(&mut self); self } + + /// Calls the given closure on `self`. + fn try_with(mut self, f: F) -> Result + where + F: FnOnce(&mut Self) -> Result<(), E>, + { + f(&mut self)?; + Ok(self) + } } impl With for T {}