mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
TextView: print styled spans of text
This commit is contained in:
parent
ef7cfb2dd6
commit
890b3f13e1
22
examples/markup.rs
Normal file
22
examples/markup.rs
Normal file
@ -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();
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Vec<Span<'a>>, 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<S>(pub S)
|
||||
where
|
||||
S: Into<String>;
|
||||
|
||||
impl<S> super::MarkupText for MarkdownText<S>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<Span<'a>> {
|
||||
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<Vec<Span<'a>>, Self::Error> {
|
||||
Ok(parse(input))
|
||||
}
|
||||
// Parser::new(input).inspect(|span| eprintln!("{:?}", span)).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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<S: Into<String>> 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<Vec<Span<'a>>, 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<StringRef, Vec<Span<'static>>>;
|
||||
|
||||
/// A String that parses a markup language.
|
||||
pub struct StyledString {
|
||||
content: StyledHandle,
|
||||
content: Option<StyledHandle>,
|
||||
}
|
||||
|
||||
impl StyledString {
|
||||
|
||||
/// Creates a new styled string, parsing the given content.
|
||||
pub fn new<S, M>(content: S) -> Result<Self, M::Error>
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cursive::utils::markup::StyledString;
|
||||
/// let styled_string = StyledString::new("*plain* text");
|
||||
/// ```
|
||||
pub fn new<T>(content: T) -> Result<Self, <T::M as Markup>::Error>
|
||||
where
|
||||
S: Into<String>,
|
||||
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<S>(content: S) -> Self
|
||||
where S: Into<String>
|
||||
{
|
||||
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<S, M>(&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<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <<T as MarkupText>::M as Markup>::Error>
|
||||
where
|
||||
S: Into<String>,
|
||||
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<S>(&mut self, content: S) where S: Into<String> {
|
||||
self.set_content(content).unwrap();
|
||||
}
|
||||
|
||||
/// Append `content` to the end.
|
||||
///
|
||||
/// Re-parse everything after.
|
||||
pub fn append_content<T>(&mut self, content: T) -> Result<(), <T::M as Markup>::Error>
|
||||
where
|
||||
T: MarkupText
|
||||
{
|
||||
self.with_content::<T::M, _, _>(|c| c.push_str(&content.to_string()))
|
||||
}
|
||||
|
||||
/// Run a closure on the text content.
|
||||
///
|
||||
/// And re-parse everything after.
|
||||
pub fn with_content<M, F, O>(&mut self, f: F) -> Result<O, M::Error>
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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<S: Into<String>>(content: S) -> Self {
|
||||
TextContent {
|
||||
pub fn new<S>(content: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
Self::styled(content).unwrap()
|
||||
}
|
||||
|
||||
/// Creates a new text content around the given value.
|
||||
///
|
||||
/// Parses the given value.
|
||||
pub fn styled<T>(content: T) -> Result<Self, <T::M as Markup>::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<S: Into<String>>(&mut self, content: S) {
|
||||
self.with_content(|c| *c = content.into());
|
||||
pub fn set_content<S>(&mut self, content: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::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<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::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<F>(&mut self, f: F)
|
||||
fn with_content<F, O>(&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<XY<SizeCache>>,
|
||||
}
|
||||
|
||||
@ -164,8 +205,28 @@ fn strip_last_newline(content: &str) -> &str {
|
||||
|
||||
impl TextView {
|
||||
/// Creates a new TextView with the given content.
|
||||
pub fn new<S: Into<String>>(content: S) -> Self {
|
||||
TextView::new_with_content(TextContent::new(content))
|
||||
pub fn new<S>(content: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
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<T>(content: T) -> Result<Self, <T::M as Markup>::Error>
|
||||
where
|
||||
T: MarkupText,
|
||||
{
|
||||
TextContent::styled(content).map(TextView::new_with_content)
|
||||
}
|
||||
|
||||
/// Creates a new TextView using the given `Arc<Mutex<String>>`.
|
||||
@ -263,21 +324,62 @@ impl TextView {
|
||||
/// Replace the text in this view.
|
||||
///
|
||||
/// Chainable variant.
|
||||
pub fn content<S: Into<String>>(self, content: S) -> Self {
|
||||
self.with(|s| s.set_content(content))
|
||||
pub fn content<S>(self, content: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.markup(content).unwrap()
|
||||
}
|
||||
|
||||
/// Replace the text in this view.
|
||||
pub fn set_content<S: Into<String>>(&mut self, content: S) {
|
||||
let content = content.into();
|
||||
self.content.lock().unwrap().content = content;
|
||||
///
|
||||
/// Parse the given markup text.
|
||||
///
|
||||
/// Chainable variant.
|
||||
pub fn markup<T>(self, content: T) -> Result<Self, <T::M as Markup>::Error>
|
||||
where
|
||||
T: MarkupText,
|
||||
{
|
||||
self.try_with(|s| s.set_markup(content))
|
||||
}
|
||||
|
||||
/// Replace the text in this view.
|
||||
pub fn set_content<S>(&mut self, content: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.set_markup(content).unwrap();
|
||||
}
|
||||
|
||||
/// Replace the text in this view.
|
||||
///
|
||||
/// Parses the given markup text.
|
||||
pub fn set_markup<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::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<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -5,6 +5,15 @@ pub trait With: Sized {
|
||||
f(&mut self);
|
||||
self
|
||||
}
|
||||
|
||||
/// Calls the given closure on `self`.
|
||||
fn try_with<E, F>(mut self, f: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Result<(), E>,
|
||||
{
|
||||
f(&mut self)?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> With for T {}
|
||||
|
Loading…
Reference in New Issue
Block a user