mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Refactor spans and markup
We now use mostly indexed spans into a source string. Indexed Spans can still be resolved to a string slice when needed.
This commit is contained in:
parent
a9d9239fac
commit
39405ba1ec
@ -2,7 +2,7 @@ extern crate cursive;
|
||||
|
||||
use cursive::Cursive;
|
||||
#[cfg(feature = "markdown")]
|
||||
use cursive::utils::markup::MarkdownText;
|
||||
use cursive::utils::markup::markdown;
|
||||
use cursive::views::{Dialog, TextView};
|
||||
|
||||
// Make sure you compile with the `markdown` feature!
|
||||
@ -13,14 +13,13 @@ fn main() {
|
||||
let mut siv = Cursive::new();
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
let text = MarkdownText("Isn't *that* **cool**?");
|
||||
let text = markdown::parse("Isn't *that* **cool**?");
|
||||
|
||||
#[cfg(not(feature = "markdown"))]
|
||||
let text = "Rebuild with --features markdown ;)";
|
||||
|
||||
siv.add_layer(
|
||||
Dialog::around(TextView::styled(text).unwrap())
|
||||
.button("Hell yeah!", |s| s.quit()),
|
||||
Dialog::around(TextView::new(text)).button("Hell yeah!", |s| s.quit()),
|
||||
);
|
||||
|
||||
siv.run();
|
||||
|
@ -14,7 +14,7 @@ fn show_popup(siv: &mut Cursive) {
|
||||
// Look for a view tagged "text".
|
||||
// We _know_ it's there, so unwrap it.
|
||||
s.call_on_id("text", |view: &mut TextView| {
|
||||
let content = reverse(&view.get_content());
|
||||
let content = reverse(view.get_content().source());
|
||||
view.set_content(content);
|
||||
});
|
||||
})
|
||||
|
@ -136,10 +136,13 @@ impl<'a> Printer<'a> {
|
||||
|
||||
/// Call the given closure with a styled printer,
|
||||
/// that will apply the given style on prints.
|
||||
pub fn with_style<F>(&self, style: Style, f: F)
|
||||
pub fn with_style<F, T>(&self, style: T, f: F)
|
||||
where
|
||||
F: FnOnce(&Printer),
|
||||
T: Into<Style>,
|
||||
{
|
||||
let style = style.into();
|
||||
|
||||
let color = style.color;
|
||||
let effects = style.effects;
|
||||
|
||||
|
@ -17,6 +17,12 @@ pub struct Style {
|
||||
pub color: Option<ColorStyle>,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Returns a new `Style` that doesn't apply anything.
|
||||
pub fn none() -> Self {
|
||||
|
@ -22,11 +22,13 @@ impl<'a> Chunk<'a> {
|
||||
segment.seg.start += to_remove.length;
|
||||
segment.seg.width -= to_remove.width;
|
||||
segment.text = &segment.text[to_remove.length..];
|
||||
self.width -= to_remove.width;
|
||||
break;
|
||||
} else {
|
||||
// This segment is too small, so it'll disapear entirely.
|
||||
to_remove.length -= segment.seg.end - segment.seg.start;
|
||||
to_remove.width -= segment.seg.width;
|
||||
self.width -= segment.seg.width;
|
||||
|
||||
// Empty this segment
|
||||
segment.seg.start = segment.seg.end;
|
||||
|
@ -1,32 +1,34 @@
|
||||
use super::Span;
|
||||
use super::chunk::Chunk;
|
||||
use super::segment::{Segment, SegmentWithText};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use utils::span::SpannedString;
|
||||
use xi_unicode::LineBreakLeafIter;
|
||||
|
||||
/// Iterator that returns non-breakable chunks of text.
|
||||
///
|
||||
/// Works accross spans of text.
|
||||
pub struct ChunkIterator<'a, 'b>
|
||||
pub struct ChunkIterator<'a, T>
|
||||
where
|
||||
'a: 'b,
|
||||
T: 'a,
|
||||
{
|
||||
/// Input that we want to split
|
||||
spans: &'b [Span<'a>],
|
||||
/// Input that we want to chunk.
|
||||
source: &'a SpannedString<T>,
|
||||
|
||||
/// ID of the span we are processing.
|
||||
current_span: usize,
|
||||
|
||||
/// How much of the current span has been processed already.
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ChunkIterator<'a, 'b>
|
||||
impl<'a, T> ChunkIterator<'a, T>
|
||||
where
|
||||
'a: 'b,
|
||||
T: 'a,
|
||||
{
|
||||
pub fn new(spans: &'b [Span<'a>]) -> Self {
|
||||
/// Creates a new ChunkIterator on the given styled string.
|
||||
pub fn new(source: &'a SpannedString<T>) -> Self {
|
||||
ChunkIterator {
|
||||
spans,
|
||||
source,
|
||||
current_span: 0,
|
||||
offset: 0,
|
||||
}
|
||||
@ -37,30 +39,31 @@ where
|
||||
///
|
||||
/// These chunks may go accross spans (a single word may be broken into more
|
||||
/// than one span, for instance if parts of it are marked up differently).
|
||||
impl<'a, 'b> Iterator for ChunkIterator<'a, 'b>
|
||||
impl<'a, T> Iterator for ChunkIterator<'a, T>
|
||||
where
|
||||
'a: 'b,
|
||||
T: 'a,
|
||||
{
|
||||
type Item = Chunk<'b>;
|
||||
type Item = Chunk<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Protect agains empty spans
|
||||
while self.current_span < self.spans.len()
|
||||
&& self.spans[self.current_span].text.is_empty()
|
||||
{
|
||||
self.current_span += 1;
|
||||
}
|
||||
|
||||
if self.current_span >= self.spans.len() {
|
||||
if self.current_span >= self.source.spans_raw().len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut span: &Span<'a> = &self.spans[self.current_span];
|
||||
// Protect agains empty spans
|
||||
if self.source.spans_raw()[self.current_span].is_empty() {
|
||||
self.current_span += 1;
|
||||
return self.next();
|
||||
}
|
||||
|
||||
let mut span = &self.source.spans_raw()[self.current_span];
|
||||
let mut span_text = span.content.resolve(self.source.source());
|
||||
|
||||
let mut total_width = 0;
|
||||
|
||||
// We'll use an iterator from xi-unicode to detect possible breaks.
|
||||
let mut iter = LineBreakLeafIter::new(&span.text, self.offset);
|
||||
let text = span.content.resolve(self.source.source());
|
||||
let mut iter = LineBreakLeafIter::new(text, self.offset);
|
||||
|
||||
// We'll accumulate segments from spans.
|
||||
let mut segments = Vec::new();
|
||||
@ -74,7 +77,7 @@ where
|
||||
// Look at next possible break
|
||||
// `hard_stop = true` means that the break is non-optional,
|
||||
// like after a `\n`.
|
||||
let (pos, hard_stop) = iter.next(&span.text);
|
||||
let (pos, hard_stop) = iter.next(span_text);
|
||||
|
||||
// When xi-unicode reaches the end of a span, it returns a "fake"
|
||||
// break. To know if it's actually a true break, we need to give
|
||||
@ -85,8 +88,11 @@ where
|
||||
|
||||
let (width, ends_with_space) = if pos == 0 {
|
||||
// If pos = 0, we had a span before.
|
||||
let prev_span = &self.spans[self.current_span - 1];
|
||||
(0, prev_span.text.ends_with(' '))
|
||||
let prev_span =
|
||||
&self.source.spans_raw()[self.current_span - 1];
|
||||
let prev_text =
|
||||
prev_span.content.resolve(self.source.source());
|
||||
(0, prev_text.ends_with(' '))
|
||||
} else {
|
||||
// We actually got something.
|
||||
// Remember its width, and whether it ends with a space.
|
||||
@ -94,7 +100,7 @@ where
|
||||
// (When a chunk ends with a space, we may compress it a bit
|
||||
// near the end of a row, so this information will be useful
|
||||
// later.)
|
||||
let text = &span.text[self.offset..pos];
|
||||
let text = &span_text[self.offset..pos];
|
||||
|
||||
(text.width(), text.ends_with(' '))
|
||||
};
|
||||
@ -109,26 +115,30 @@ where
|
||||
end: pos,
|
||||
width,
|
||||
},
|
||||
text: &span.text[self.offset..pos],
|
||||
text: &span_text[self.offset..pos],
|
||||
});
|
||||
}
|
||||
|
||||
if pos == span.text.len() {
|
||||
if pos == span_text.len() {
|
||||
// If we reached the end of the slice,
|
||||
// we need to look at the next span first.
|
||||
self.current_span += 1;
|
||||
|
||||
// Skip empty spans
|
||||
while self.current_span < self.spans.len()
|
||||
&& self.spans[self.current_span].text.is_empty()
|
||||
{
|
||||
while let Some(true) = self.source
|
||||
.spans_raw()
|
||||
.get(self.current_span)
|
||||
.map(|span| {
|
||||
span.content.resolve(self.source.source()).is_empty()
|
||||
}) {
|
||||
self.current_span += 1;
|
||||
}
|
||||
|
||||
if self.current_span >= self.spans.len() {
|
||||
if self.current_span >= self.source.spans_raw().len() {
|
||||
// If this was the last chunk, return as is!
|
||||
// Well, make sure we don't end with a newline...
|
||||
let hard_stop = hard_stop || span.text.ends_with('\n');
|
||||
let text = span.content.resolve(self.source.source());
|
||||
let hard_stop = hard_stop || text.ends_with('\n');
|
||||
|
||||
return Some(Chunk {
|
||||
width: total_width,
|
||||
@ -138,7 +148,8 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
span = &self.spans[self.current_span];
|
||||
span = &self.source.spans_raw()[self.current_span];
|
||||
span_text = span.content.resolve(self.source.source());
|
||||
self.offset = 0;
|
||||
continue;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use super::Span;
|
||||
use super::chunk::{Chunk, ChunkPart};
|
||||
use super::chunk_iterator::ChunkIterator;
|
||||
use super::prefix::prefix;
|
||||
@ -8,15 +7,16 @@ use super::segment_merge_iterator::SegmentMergeIterator;
|
||||
use std::iter::Peekable;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use utils::span::SpannedString;
|
||||
|
||||
/// Generates rows of text in constrainted width.
|
||||
///
|
||||
/// Works on spans of text.
|
||||
pub struct SpanLinesIterator<'a, 'b>
|
||||
pub struct LinesIterator<'a, T>
|
||||
where
|
||||
'a: 'b,
|
||||
T: 'a,
|
||||
{
|
||||
iter: Peekable<ChunkIterator<'a, 'b>>,
|
||||
iter: Peekable<ChunkIterator<'a, T>>,
|
||||
|
||||
/// Available width
|
||||
width: usize,
|
||||
@ -26,24 +26,18 @@ where
|
||||
chunk_offset: ChunkPart,
|
||||
}
|
||||
|
||||
impl<'a, 'b> SpanLinesIterator<'a, 'b>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
impl<'a, T> LinesIterator<'a, T> {
|
||||
/// Creates a new iterator with the given content and width.
|
||||
pub fn new(spans: &'b [Span<'a>], width: usize) -> Self {
|
||||
SpanLinesIterator {
|
||||
iter: ChunkIterator::new(spans).peekable(),
|
||||
pub fn new(source: &'a SpannedString<T>, width: usize) -> Self {
|
||||
LinesIterator {
|
||||
iter: ChunkIterator::new(source).peekable(),
|
||||
width,
|
||||
chunk_offset: ChunkPart::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Iterator for SpanLinesIterator<'a, 'b>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
impl<'a, T> Iterator for LinesIterator<'a, T> {
|
||||
type Item = Row;
|
||||
|
||||
fn next(&mut self) -> Option<Row> {
|
||||
|
@ -15,24 +15,6 @@ mod segment;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use self::lines_iterator::SpanLinesIterator;
|
||||
pub use self::lines_iterator::LinesIterator;
|
||||
pub use self::row::Row;
|
||||
pub use self::segment::Segment;
|
||||
use std::borrow::Cow;
|
||||
use theme::Style;
|
||||
|
||||
/// Input to the algorithm
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Span<'a> {
|
||||
/// Text for this span.
|
||||
///
|
||||
/// It can be either a reference to some input text,
|
||||
/// or an owned string.
|
||||
///
|
||||
/// The owned string is mostly useful when parsing marked-up text that
|
||||
/// contains escape codes.
|
||||
pub text: Cow<'a, str>,
|
||||
|
||||
/// Style to apply to this span of text.
|
||||
pub style: Style,
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::{Segment, Span};
|
||||
use std::borrow::Cow;
|
||||
use super::Segment;
|
||||
use utils::span::{Span, SpannedString};
|
||||
|
||||
/// A list of segments representing a row of text
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -11,20 +11,13 @@ pub struct Row {
|
||||
}
|
||||
|
||||
impl Row {
|
||||
/// Resolve the row indices into styled spans.
|
||||
pub fn resolve<'a: 'b, 'b>(&self, spans: &'b [Span<'a>]) -> Vec<Span<'b>> {
|
||||
/// Resolve the row indices into string slices and attributes.
|
||||
pub fn resolve<'a, T>(
|
||||
&self, source: &'a SpannedString<T>
|
||||
) -> Vec<Span<'a, T>> {
|
||||
self.segments
|
||||
.iter()
|
||||
.map(|seg| {
|
||||
let span: &'b Span<'a> = &spans[seg.span_id];
|
||||
let text: &'b str = &span.text;
|
||||
let text: &'b str = &text[seg.start..seg.end];
|
||||
|
||||
Span {
|
||||
text: Cow::Borrowed(text),
|
||||
style: span.style,
|
||||
}
|
||||
})
|
||||
.map(|seg| seg.resolve(source))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use utils::span::{Span, SpannedString};
|
||||
|
||||
/// Refers to a part of a span
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Segment {
|
||||
@ -18,6 +20,19 @@ impl Segment {
|
||||
pub fn with_text<'a>(self, text: &'a str) -> SegmentWithText<'a> {
|
||||
SegmentWithText { text, seg: self }
|
||||
}
|
||||
|
||||
/// Resolve this segment to a string slice and an attribute.
|
||||
pub fn resolve<'a, T>(&self, source: &'a SpannedString<T>) -> Span<'a, T> {
|
||||
let span = &source.spans_raw()[self.span_id];
|
||||
|
||||
let content = span.content.resolve(source.source());
|
||||
let content = &content[self.start..self.end];
|
||||
|
||||
Span {
|
||||
content,
|
||||
attr: &span.attr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
@ -1,348 +1,67 @@
|
||||
use super::Span;
|
||||
use super::SpanLinesIterator;
|
||||
use super::chunk::Chunk;
|
||||
use super::chunk_iterator::ChunkIterator;
|
||||
use super::row::Row;
|
||||
use super::segment::Segment;
|
||||
use std::borrow::Cow;
|
||||
use theme::Style;
|
||||
use super::LinesIterator;
|
||||
use theme::{Effect, Style};
|
||||
use utils::markup::StyledString;
|
||||
use utils::span::Span;
|
||||
|
||||
fn input() -> Vec<Span<'static>> {
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("A beautiful "),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("boat"),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed(" isn't it?\nYes indeed, my "),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("Super"),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("Captain !"),
|
||||
style: Style::none(),
|
||||
},
|
||||
]
|
||||
}
|
||||
fn input() -> StyledString {
|
||||
let mut text = StyledString::plain("I ");
|
||||
text.append(StyledString::styled("didn't", Effect::Bold));
|
||||
text.append(StyledString::plain(" say "));
|
||||
text.append(StyledString::styled("half", Effect::Italic));
|
||||
text.append(StyledString::plain(" the things people say I did."));
|
||||
text.append(StyledString::plain("\n"));
|
||||
text.append(StyledString::plain(" - A. Einstein"));
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
#[test]
|
||||
fn test_lines_markdown() {
|
||||
let input = r"
|
||||
A beautiful *boat* isn't it?
|
||||
Yes indeed, my **Super**Captain !";
|
||||
|
||||
let input_spans = ::utils::markup::markdown::parse(input);
|
||||
let iter = SpanLinesIterator::new(&input_spans, 16);
|
||||
let rows: Vec<Row> = iter.collect();
|
||||
let output_spans: Vec<_> =
|
||||
rows.iter().map(|row| row.resolve(&input_spans)).collect();
|
||||
|
||||
assert_eq!(
|
||||
&output_spans[..],
|
||||
&[
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("A beautiful "),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("boat"),
|
||||
style: Style::from(Effect::Italic),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("isn\'t it?"),
|
||||
style: Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("Yes indeed, my "),
|
||||
style: Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("Super"),
|
||||
style: Style::from(Effect::Bold),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("Captain "),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("!"),
|
||||
style: Style::none(),
|
||||
},
|
||||
]
|
||||
]
|
||||
);
|
||||
text
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lines_iter() {
|
||||
fn test_line_breaks() {
|
||||
let input = input();
|
||||
|
||||
let iter = SpanLinesIterator::new(&input, 16);
|
||||
let rows: Vec<Row> = iter.collect();
|
||||
let spans: Vec<_> = rows.iter().map(|row| row.resolve(&input)).collect();
|
||||
let iter = LinesIterator::new(&input, 17);
|
||||
|
||||
assert_eq!(
|
||||
&spans[..],
|
||||
&[
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("A beautiful "),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("boat"),
|
||||
style: Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("isn\'t it?"),
|
||||
style: Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("Yes indeed, my "),
|
||||
style: Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed("Super"),
|
||||
style: Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("Captain !"),
|
||||
style: Style::none(),
|
||||
},
|
||||
]
|
||||
]
|
||||
);
|
||||
let rows: Vec<_> = iter.map(|row| row.resolve(&input)).collect();
|
||||
|
||||
assert_eq!(
|
||||
&rows[..],
|
||||
&[
|
||||
Row {
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 0,
|
||||
start: 0,
|
||||
end: 12,
|
||||
width: 12,
|
||||
},
|
||||
Segment {
|
||||
span_id: 1,
|
||||
start: 0,
|
||||
end: 4,
|
||||
width: 4,
|
||||
},
|
||||
],
|
||||
width: 16,
|
||||
},
|
||||
Row {
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 1,
|
||||
end: 10,
|
||||
width: 9,
|
||||
},
|
||||
],
|
||||
width: 9,
|
||||
},
|
||||
Row {
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 11,
|
||||
end: 26,
|
||||
width: 15,
|
||||
},
|
||||
],
|
||||
width: 15,
|
||||
},
|
||||
Row {
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 3,
|
||||
start: 0,
|
||||
end: 5,
|
||||
width: 5,
|
||||
},
|
||||
Segment {
|
||||
span_id: 4,
|
||||
start: 0,
|
||||
end: 9,
|
||||
width: 9,
|
||||
},
|
||||
],
|
||||
width: 14,
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunk_iter() {
|
||||
let input = input();
|
||||
|
||||
let iter = ChunkIterator::new(&input);
|
||||
let chunks: Vec<Chunk> = iter.collect();
|
||||
|
||||
assert_eq!(
|
||||
&chunks[..],
|
||||
&[
|
||||
Chunk {
|
||||
width: 2,
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 0,
|
||||
start: 0,
|
||||
end: 2,
|
||||
width: 2,
|
||||
}.with_text("A "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 10,
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 0,
|
||||
start: 2,
|
||||
end: 12,
|
||||
width: 10,
|
||||
}.with_text("beautiful "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 5,
|
||||
segments: vec![
|
||||
Segment {
|
||||
span_id: 1,
|
||||
start: 0,
|
||||
end: 4,
|
||||
width: 4,
|
||||
}.with_text("boat"),
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 0,
|
||||
end: 1,
|
||||
width: 1,
|
||||
}.with_text(" "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 6,
|
||||
segments: vec![
|
||||
// "isn't "
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 1,
|
||||
end: 7,
|
||||
width: 6,
|
||||
}.with_text("isn't "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 3,
|
||||
segments: vec![
|
||||
// "it?\n"
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 7,
|
||||
end: 11,
|
||||
width: 3,
|
||||
}.with_text("it?\n"),
|
||||
],
|
||||
hard_stop: true,
|
||||
ends_with_space: false,
|
||||
},
|
||||
Chunk {
|
||||
width: 4,
|
||||
segments: vec![
|
||||
// "Yes "
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 11,
|
||||
end: 15,
|
||||
width: 4,
|
||||
}.with_text("Yes "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 8,
|
||||
segments: vec![
|
||||
// "indeed, "
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 15,
|
||||
end: 23,
|
||||
width: 8,
|
||||
}.with_text("indeed, "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 3,
|
||||
segments: vec![
|
||||
// "my "
|
||||
Segment {
|
||||
span_id: 2,
|
||||
start: 23,
|
||||
end: 26,
|
||||
width: 3,
|
||||
}.with_text("my "),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: true,
|
||||
},
|
||||
Chunk {
|
||||
width: 14,
|
||||
segments: vec![
|
||||
// "Super"
|
||||
Segment {
|
||||
span_id: 3,
|
||||
start: 0,
|
||||
end: 5,
|
||||
width: 5,
|
||||
}.with_text("Super"),
|
||||
// "Captain !"
|
||||
Segment {
|
||||
span_id: 4,
|
||||
start: 0,
|
||||
end: 9,
|
||||
width: 9,
|
||||
}.with_text("Captain !"),
|
||||
],
|
||||
hard_stop: false,
|
||||
ends_with_space: false,
|
||||
}
|
||||
vec![
|
||||
Span {
|
||||
content: "I ",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
content: "didn't",
|
||||
attr: &Style::from(Effect::Bold),
|
||||
},
|
||||
Span {
|
||||
content: " say ",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
content: "half",
|
||||
attr: &Style::from(Effect::Italic),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
content: "the things people",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
content: "say I did.",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
Span {
|
||||
content: " - A. Einstein",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
89
src/utils/markup/.mod.rs.rustfmt
Normal file
89
src/utils/markup/.mod.rs.rustfmt
Normal file
@ -0,0 +1,89 @@
|
||||
//! Parse various text markup formats.
|
||||
//!
|
||||
//! Each module is optional and relies on a feature.
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
pub mod markdown;
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
pub use self::markdown::MarkdownText;
|
||||
use owning_ref::OwningHandle;
|
||||
use owning_ref::StringRef;
|
||||
use std::ops::Deref;
|
||||
use theme::Style;
|
||||
|
||||
use utils::span::{StSpannedString;
|
||||
|
||||
/// A parsed string with markup style.
|
||||
///
|
||||
/// Contains both the source string, and parsed information indicating the
|
||||
/// style to apply.
|
||||
pub type StyledString = SpannedString<Style>;
|
||||
|
||||
pub type StyledIndexedSpan = IndexedSpan<Style>;
|
||||
|
||||
impl SpannedString<Style> {
|
||||
/// 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::styled(content, Style::none())
|
||||
}
|
||||
|
||||
/// Creates a new `StyledString` using a single style for the entire text.
|
||||
pub fn styled<S, T>(content: S, style: T) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
T: Into<Style>,
|
||||
{
|
||||
let content = content.into();
|
||||
|
||||
let spans = vec![
|
||||
StyledIndexedSpan {
|
||||
content: IndexedCow::Borrowed {
|
||||
start: 0,
|
||||
end: content.len(),
|
||||
},
|
||||
style: style.into(),
|
||||
},
|
||||
];
|
||||
|
||||
Self::new(content, spans)
|
||||
}
|
||||
|
||||
/// Appends the given plain text to `self`.
|
||||
pub fn append_plain<S>(&mut self, text: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.append(Self::plain(text));
|
||||
}
|
||||
|
||||
/// Appends `text` to `self`, using `style`.
|
||||
pub fn append_styled<S, T>(&mut self, text: S, style: T)
|
||||
where
|
||||
S: Into<String>,
|
||||
T: Into<Style>,
|
||||
{
|
||||
self.append(Self::styled(text, style));
|
||||
}
|
||||
/// Appends the given plain text to `self`.
|
||||
pub fn append_plain<S>(&mut self, text: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.append(Self::plain(text));
|
||||
}
|
||||
|
||||
/// Appends `text` to `self`, using `style`.
|
||||
pub fn append_styled<S, T>(&mut self, text: S, style: T)
|
||||
where
|
||||
S: Into<String>,
|
||||
T: Into<Style>,
|
||||
{
|
||||
self.append(Self::styled(text, style));
|
||||
}
|
||||
}
|
@ -5,55 +5,27 @@
|
||||
extern crate pulldown_cmark;
|
||||
|
||||
use self::pulldown_cmark::{Event, Tag};
|
||||
use std::borrow::Cow;
|
||||
use theme::{Effect, Style};
|
||||
use utils::lines::spans::Span;
|
||||
use utils::markup::{StyledString, StyledIndexedSpan};
|
||||
use utils::span::IndexedCow;
|
||||
|
||||
/// `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>
|
||||
/// Parses the given string as markdown text.
|
||||
pub fn parse<S>(input: S) -> StyledString
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
type M = Markdown;
|
||||
let input = input.into();
|
||||
|
||||
fn to_string(self) -> String {
|
||||
self.0.into()
|
||||
}
|
||||
let spans = parse_spans(&input);
|
||||
|
||||
StyledString::new(input, spans)
|
||||
}
|
||||
|
||||
/// Iterator that parse a markdown text and outputs styled spans.
|
||||
pub struct Parser<'a> {
|
||||
first: bool,
|
||||
stack: Vec<Style>,
|
||||
input: &'a str,
|
||||
parser: pulldown_cmark::Parser<'a>,
|
||||
}
|
||||
|
||||
@ -61,23 +33,21 @@ impl<'a> Parser<'a> {
|
||||
/// Creates a new parser with the given input text.
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
Parser {
|
||||
input,
|
||||
first: true,
|
||||
parser: pulldown_cmark::Parser::new(input),
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn literal_string<'b>(&self, text: String) -> Span<'b> {
|
||||
Span {
|
||||
text: Cow::Owned(text),
|
||||
style: Style::merge(&self.stack),
|
||||
}
|
||||
}
|
||||
|
||||
fn literal<'b>(&self, text: &'b str) -> Span<'b> {
|
||||
Span {
|
||||
text: Cow::Borrowed(text),
|
||||
style: Style::merge(&self.stack),
|
||||
/// Creates a new span with the given value
|
||||
fn literal<S>(&self, text: S) -> StyledIndexedSpan
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
StyledIndexedSpan {
|
||||
content: IndexedCow::Owned(text.into()),
|
||||
attr: Style::merge(&self.stack),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,7 +57,7 @@ fn header(level: usize) -> &'static str {
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Parser<'a> {
|
||||
type Item = Span<'a>;
|
||||
type Item = StyledIndexedSpan;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
@ -103,7 +73,7 @@ impl<'a> Iterator for Parser<'a> {
|
||||
self.stack.push(Style::from(Effect::Italic))
|
||||
}
|
||||
Tag::Header(level) => {
|
||||
return Some(self.literal_string(format!(
|
||||
return Some(self.literal(format!(
|
||||
"{} ",
|
||||
header(level as usize)
|
||||
)))
|
||||
@ -123,10 +93,7 @@ impl<'a> Iterator for Parser<'a> {
|
||||
Tag::Paragraph if self.first => self.first = false,
|
||||
Tag::Header(_) => return Some(self.literal("\n\n")),
|
||||
Tag::Link(link, _) => {
|
||||
return Some(self.literal_string(format!(
|
||||
"]({})",
|
||||
link
|
||||
)))
|
||||
return Some(self.literal(format!("]({})", link)))
|
||||
}
|
||||
Tag::Code => return Some(self.literal("```")),
|
||||
Tag::Emphasis | Tag::Strong => {
|
||||
@ -142,9 +109,9 @@ impl<'a> Iterator for Parser<'a> {
|
||||
| Event::Html(text)
|
||||
| Event::Text(text) => {
|
||||
// Return something!
|
||||
return Some(Span {
|
||||
text,
|
||||
style: Style::merge(&self.stack),
|
||||
return Some(StyledIndexedSpan {
|
||||
content: IndexedCow::from_cow(text, self.input),
|
||||
attr: Style::merge(&self.stack),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -155,7 +122,7 @@ impl<'a> Iterator for Parser<'a> {
|
||||
/// Parse the given markdown text into a list of spans.
|
||||
///
|
||||
/// This is a shortcut for `Parser::new(input).collect()`.
|
||||
pub fn parse<'a>(input: &'a str) -> Vec<Span<'a>> {
|
||||
pub fn parse_spans<'a>(input: &'a str) -> Vec<StyledIndexedSpan> {
|
||||
Parser::new(input).collect()
|
||||
// Parser::new(input).inspect(|span| eprintln!("{:?}", span)).collect()
|
||||
}
|
||||
@ -163,6 +130,7 @@ pub fn parse<'a>(input: &'a str) -> Vec<Span<'a>> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use utils::span::Span;
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
@ -170,43 +138,44 @@ mod tests {
|
||||
Attention
|
||||
====
|
||||
I *really* love __Cursive__!";
|
||||
let spans = parse(input);
|
||||
let spans = parse_spans(input);
|
||||
let spans: Vec<_> = spans.iter().map(|span| span.resolve(input)).collect();
|
||||
|
||||
// println!("{:?}", spans);
|
||||
assert_eq!(
|
||||
&spans[..],
|
||||
&[
|
||||
Span {
|
||||
text: Cow::Borrowed("# "),
|
||||
style: Style::none(),
|
||||
content: "# ",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("Attention"),
|
||||
style: Style::none(),
|
||||
content: "Attention",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("\n\n"),
|
||||
style: Style::none(),
|
||||
content: "\n\n",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("I "),
|
||||
style: Style::none(),
|
||||
content: "I ",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("really"),
|
||||
style: Style::from(Effect::Italic),
|
||||
content: "really",
|
||||
attr: &Style::from(Effect::Italic),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed(" love "),
|
||||
style: Style::none(),
|
||||
content: " love ",
|
||||
attr: &Style::none(),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("Cursive"),
|
||||
style: Style::from(Effect::Bold),
|
||||
content: "Cursive",
|
||||
attr: &Style::from(Effect::Bold),
|
||||
},
|
||||
Span {
|
||||
text: Cow::Borrowed("!"),
|
||||
style: Style::none(),
|
||||
content: "!",
|
||||
attr: &Style::none(),
|
||||
}
|
||||
]
|
||||
);
|
||||
|
@ -5,109 +5,28 @@
|
||||
#[cfg(feature = "markdown")]
|
||||
pub mod markdown;
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
pub use self::markdown::MarkdownText;
|
||||
use owning_ref::OwningHandle;
|
||||
use owning_ref::StringRef;
|
||||
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 {
|
||||
/// Possible error happening when parsing.
|
||||
type Error;
|
||||
use utils::span::{SpannedString, IndexedSpan, Span};
|
||||
|
||||
/// Parses text and return the styled spans.
|
||||
fn parse<'a>(input: &'a str) -> Result<Vec<Span<'a>>, Self::Error>;
|
||||
|
||||
/// Returns a string and its parsed spans.
|
||||
///
|
||||
/// Generates a self-borrowing struct containing the source string, as well
|
||||
/// as the styled spans borrowing this string.
|
||||
fn make_handle<S>(input: S) -> Result<StyledHandle, Self::Error>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
let input = input.into();
|
||||
OwningHandle::try_new(StringRef::new(input), |input| {
|
||||
Self::parse(unsafe { &*input })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Thin wrapper around a string, with a markup format.
|
||||
/// A parsed string with markup style.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
impl Markup for Plain {
|
||||
type Error = ();
|
||||
|
||||
fn parse<'a>(input: &'a str) -> Result<Vec<Span<'a>>, Self::Error> {
|
||||
Ok(if input.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![
|
||||
Span {
|
||||
text: Cow::Borrowed(input),
|
||||
style: Style::none(),
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds both parsed spans, and the input string they borrow.
|
||||
/// Contains both the source string, and parsed information indicating the
|
||||
/// style to apply.
|
||||
///
|
||||
/// This is used to pass around a parsed string.
|
||||
pub type StyledHandle = OwningHandle<StringRef, Vec<Span<'static>>>;
|
||||
/// **Note**: due to limitations in rustdoc, you will need to read the
|
||||
/// documentation from the [`SpannedString`] page.
|
||||
///
|
||||
/// [`SpannedString`]: ../span/struct.SpannedString.html
|
||||
pub type StyledString = SpannedString<Style>;
|
||||
|
||||
/// A String that parses a markup language.
|
||||
pub struct StyledString {
|
||||
content: Option<StyledHandle>,
|
||||
}
|
||||
/// Indexes a span into a source string.
|
||||
pub type StyledIndexedSpan = IndexedSpan<Style>;
|
||||
|
||||
impl StyledString {
|
||||
/// Creates a new styled string, parsing the given content.
|
||||
///
|
||||
/// # 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
|
||||
T: MarkupText,
|
||||
{
|
||||
let content = content.to_string();
|
||||
|
||||
let content = Some(T::M::make_handle(content)?);
|
||||
|
||||
Ok(StyledString { content })
|
||||
}
|
||||
/// A resolved styled span borrowing its source string.
|
||||
pub type StyledSpan<'a> = Span<'a, Style>;
|
||||
|
||||
impl SpannedString<Style> {
|
||||
/// Returns a plain StyledString without any style.
|
||||
///
|
||||
/// > You got no style, Dutch. You know that.
|
||||
@ -115,81 +34,35 @@ impl StyledString {
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
Self::new(content).unwrap()
|
||||
Self::styled(content, Style::none())
|
||||
}
|
||||
|
||||
/// Sets the content of this string.
|
||||
///
|
||||
/// # 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>
|
||||
/// Creates a new `StyledString` using a single style for the entire text.
|
||||
pub fn styled<S, T>(content: S, style: T) -> Self
|
||||
where
|
||||
T: MarkupText,
|
||||
S: Into<String>,
|
||||
T: Into<Style>,
|
||||
{
|
||||
let content = content.to_string();
|
||||
let content = content.into();
|
||||
let style = style.into();
|
||||
|
||||
self.content = Some(T::M::make_handle(content)?);
|
||||
|
||||
Ok(())
|
||||
Self::single_span(content, style)
|
||||
}
|
||||
|
||||
/// Sets the content of this string to plain text.
|
||||
pub fn set_plain<S>(&mut self, content: S)
|
||||
/// Appends the given plain text to `self`.
|
||||
pub fn append_plain<S>(&mut self, text: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.set_content(content).unwrap();
|
||||
self.append(Self::plain(text));
|
||||
}
|
||||
|
||||
/// Append `content` to the end.
|
||||
///
|
||||
/// Re-parse everything after.
|
||||
pub fn append_content<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::Error>
|
||||
/// Appends `text` to `self`, using `style`.
|
||||
pub fn append_styled<S, T>(&mut self, text: S, style: T)
|
||||
where
|
||||
T: MarkupText,
|
||||
S: Into<String>,
|
||||
T: Into<Style>,
|
||||
{
|
||||
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.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StyledString {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
&self.content.as_ref().unwrap().owner()
|
||||
self.append(Self::styled(text, style));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Toolbox to make text layout easier.
|
||||
|
||||
mod reader;
|
||||
pub mod span;
|
||||
pub mod lines;
|
||||
pub mod markup;
|
||||
|
||||
|
219
src/utils/span.rs
Normal file
219
src/utils/span.rs
Normal file
@ -0,0 +1,219 @@
|
||||
//! Work with spans of text.
|
||||
//!
|
||||
//! This module defines various structs describing a span of text from a
|
||||
//! larger string.
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A string with associated spans.
|
||||
///
|
||||
/// Each span has an associated attribute `T`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SpannedString<T> {
|
||||
source: String,
|
||||
spans: Vec<IndexedSpan<T>>,
|
||||
}
|
||||
|
||||
impl<S, T> From<S> for SpannedString<T>
|
||||
where
|
||||
S: Into<String>,
|
||||
T: Default,
|
||||
{
|
||||
fn from(value: S) -> Self {
|
||||
Self::single_span(value.into(), T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SpannedString<T> {
|
||||
/// Creates a new `SpannedString` manually.
|
||||
///
|
||||
/// It is not recommended to use this directly.
|
||||
/// Instead, look for methods like `Markdown::parse`.
|
||||
pub fn new<S>(source: S, spans: Vec<IndexedSpan<T>>) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
let source = source.into();
|
||||
|
||||
// Make sure the spans are within bounds.
|
||||
// This should disapear when compiled in release mode.
|
||||
for span in &spans {
|
||||
if let IndexedCow::Borrowed { start: _, end } = span.content {
|
||||
assert!(end <= source.len());
|
||||
}
|
||||
}
|
||||
|
||||
SpannedString { source, spans }
|
||||
}
|
||||
|
||||
/// Returns a new SpannedString with a single span.
|
||||
pub fn single_span(source: String, attr: T) -> Self {
|
||||
let spans = vec![
|
||||
IndexedSpan {
|
||||
content: IndexedCow::Borrowed {
|
||||
start: 0,
|
||||
end: source.len(),
|
||||
},
|
||||
attr,
|
||||
},
|
||||
];
|
||||
|
||||
Self::new(source, spans)
|
||||
}
|
||||
|
||||
/// Appends the given `StyledString` to `self`.
|
||||
pub fn append<S>(&mut self, other: S)
|
||||
where
|
||||
S: Into<Self>,
|
||||
{
|
||||
let other = other.into();
|
||||
self.append_raw(other.source, other.spans);
|
||||
}
|
||||
|
||||
/// Appends `content` and its corresponding spans to the end.
|
||||
///
|
||||
/// It is not recommended to use this directly;
|
||||
/// instead, look at the `append` method.
|
||||
pub fn append_raw(&mut self, source: String, spans: Vec<IndexedSpan<T>>) {
|
||||
let offset = self.source.len();
|
||||
let mut spans = spans;
|
||||
|
||||
for span in &mut spans {
|
||||
span.content.offset(offset);
|
||||
}
|
||||
|
||||
self.source.push_str(&source);
|
||||
self.spans.append(&mut spans);
|
||||
}
|
||||
|
||||
/// Gives access to the parsed styled spans.
|
||||
pub fn spans<'a>(&'a self) -> Vec<Span<'a, T>> {
|
||||
self.spans
|
||||
.iter()
|
||||
.map(|span| span.resolve(&self.source))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a reference to the indexed spans.
|
||||
pub fn spans_raw(&self) -> &[IndexedSpan<T>] {
|
||||
&self.spans
|
||||
}
|
||||
|
||||
/// Returns a reference to the source string.
|
||||
///
|
||||
/// This is the non-parsed string.
|
||||
pub fn source(&self) -> &str {
|
||||
&self.source
|
||||
}
|
||||
|
||||
/// Returns `true` if self is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.source.is_empty() || self.spans.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// An indexed span with an associated attribute.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IndexedSpan<T> {
|
||||
/// Content of the span.
|
||||
pub content: IndexedCow,
|
||||
|
||||
/// Attribute applied to the span.
|
||||
pub attr: T,
|
||||
}
|
||||
|
||||
/// A resolved span borrowing its source string.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Span<'a, T: 'a> {
|
||||
/// Content of this span.
|
||||
pub content: &'a str,
|
||||
|
||||
/// Attribute associated to this span.
|
||||
pub attr: &'a T,
|
||||
}
|
||||
|
||||
impl<T> IndexedSpan<T> {
|
||||
/// Resolve the span to a string slice and an attribute.
|
||||
pub fn resolve<'a>(&'a self, source: &'a str) -> Span<'a, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
Span {
|
||||
content: self.content.resolve(source),
|
||||
attr: &self.attr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is an empty span.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.content.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A span of text that can be either owned, or indexed in another String.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum IndexedCow {
|
||||
/// Indexes content in a separate string.
|
||||
Borrowed {
|
||||
/// Byte offset of the beginning of the span (inclusive)
|
||||
start: usize,
|
||||
|
||||
/// Byte offset of the end of the span (exclusive)
|
||||
end: usize,
|
||||
},
|
||||
|
||||
/// Owns its content.
|
||||
Owned(String),
|
||||
}
|
||||
|
||||
impl IndexedCow {
|
||||
/// Resolve the span to a string slice.
|
||||
pub fn resolve<'a>(&'a self, source: &'a str) -> &'a str {
|
||||
match *self {
|
||||
IndexedCow::Borrowed { start, end } => &source[start..end],
|
||||
IndexedCow::Owned(ref content) => &content,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an indexed view of the given item.
|
||||
///
|
||||
/// **Note**: it is assumed `cow`, if borrowed, is a substring of `source`.
|
||||
pub fn from_cow(cow: Cow<str>, source: &str) -> Self {
|
||||
match cow {
|
||||
Cow::Owned(value) => IndexedCow::Owned(value),
|
||||
Cow::Borrowed(value) => {
|
||||
let source_pos = source.as_ptr() as usize;
|
||||
let value_pos = value.as_ptr() as usize;
|
||||
|
||||
// Make sure `value` is indeed a substring of `source`
|
||||
assert!(value_pos >= source_pos);
|
||||
assert!(value_pos + value.len() <= source_pos + source.len());
|
||||
let start = value_pos - source_pos;
|
||||
let end = start + value.len();
|
||||
|
||||
IndexedCow::Borrowed { start, end }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `ŧrue` if this represents an empty span.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match *self {
|
||||
IndexedCow::Borrowed { start, end } => start == end,
|
||||
IndexedCow::Owned(ref content) => content.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `self` is borrowed, offset its indices by the given value.
|
||||
///
|
||||
/// Useful to update spans when concatenating sources.
|
||||
pub fn offset(&mut self, offset: usize) {
|
||||
if let IndexedCow::Borrowed {
|
||||
ref mut start,
|
||||
ref mut end,
|
||||
} = *self
|
||||
{
|
||||
*start += offset;
|
||||
*end += offset;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,8 +9,9 @@ use std::ops::Deref;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use std::sync::Arc;
|
||||
use theme::Effect;
|
||||
use utils::lines::spans::{Row, SpanLinesIterator};
|
||||
use utils::markup::{Markup, MarkupText, StyledString};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use utils::lines::spans::{LinesIterator, Row};
|
||||
use utils::markup::StyledString;
|
||||
use vec::Vec2;
|
||||
use view::{ScrollBase, ScrollStrategy, SizeCache, View};
|
||||
|
||||
@ -29,7 +30,7 @@ use view::{ScrollBase, ScrollStrategy, SizeCache, View};
|
||||
///
|
||||
/// // Later, possibly in a different thread
|
||||
/// content.set_content("new content");
|
||||
/// assert!(content.get_content().contains("new"));
|
||||
/// assert!(content.get_content().source().contains("new"));
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct TextContent {
|
||||
@ -37,35 +38,29 @@ pub struct TextContent {
|
||||
}
|
||||
|
||||
impl TextContent {
|
||||
/// Creates a new text content around the given value.
|
||||
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>
|
||||
pub fn new<S>(content: S) -> Self
|
||||
where
|
||||
T: MarkupText,
|
||||
S: Into<StyledString>,
|
||||
{
|
||||
let content = StyledString::new(content)?;
|
||||
let content = content.into();
|
||||
|
||||
Ok(TextContent {
|
||||
TextContent {
|
||||
content: Arc::new(Mutex::new(TextContentInner {
|
||||
content,
|
||||
size_cache: None,
|
||||
})),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to the text content.
|
||||
///
|
||||
/// It implements `Deref<Target=str>`.
|
||||
/// This can be deref'ed into a [`StyledString`].
|
||||
///
|
||||
/// [`StyledString`]: ../utils/markup/type.StyledString.html
|
||||
///
|
||||
/// This keeps the content locked. Do not store this!
|
||||
pub struct TextContentRef {
|
||||
@ -76,42 +71,29 @@ pub struct TextContentRef {
|
||||
}
|
||||
|
||||
impl Deref for TextContentRef {
|
||||
type Target = str;
|
||||
type Target = StyledString;
|
||||
|
||||
fn deref(&self) -> &str {
|
||||
fn deref(&self) -> &StyledString {
|
||||
&self.handle.content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl TextContent {
|
||||
/// Replaces the content with the given value.
|
||||
pub fn set_content<S>(&mut self, content: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
S: Into<StyledString>,
|
||||
{
|
||||
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))
|
||||
self.with_content(|c| *c = content.into());
|
||||
}
|
||||
|
||||
/// Append `content` to the end of a `TextView`.
|
||||
pub fn append_content<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::Error>
|
||||
pub fn append<S>(&mut self, content: S)
|
||||
where
|
||||
T: MarkupText,
|
||||
S: Into<StyledString>,
|
||||
{
|
||||
self.with_content(|c| c.append_content(content))
|
||||
self.with_content(|c| c.append(content))
|
||||
}
|
||||
|
||||
/// Returns a reference to the content.
|
||||
@ -136,6 +118,11 @@ impl TextContent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Internel representation of the content for a TextView.
|
||||
///
|
||||
/// This is mostly just a StyledString.
|
||||
///
|
||||
/// Can be shared (through a `Arc<Mutex>`).
|
||||
struct TextContentInner {
|
||||
// content: String,
|
||||
content: StyledString,
|
||||
@ -145,6 +132,7 @@ struct TextContentInner {
|
||||
}
|
||||
|
||||
impl TextContentInner {
|
||||
/// From a shareable content (Arc + Mutex), return a
|
||||
fn get_content(content: &Arc<Mutex<TextContentInner>>) -> TextContentRef {
|
||||
let arc_ref: ArcRef<Mutex<TextContentInner>> =
|
||||
ArcRef::new(Arc::clone(content));
|
||||
@ -197,26 +185,9 @@ impl TextView {
|
||||
/// Creates a new TextView with the given content.
|
||||
pub fn new<S>(content: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
S: Into<StyledString>,
|
||||
{
|
||||
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)
|
||||
Self::new_with_content(TextContent::new(content))
|
||||
}
|
||||
|
||||
/// Creates a new TextView using the given `Arc<Mutex<String>>`.
|
||||
@ -233,7 +204,7 @@ impl TextView {
|
||||
///
|
||||
/// // Later, possibly in a different thread
|
||||
/// content.set_content("new content");
|
||||
/// assert!(content.get_content().contains("new"));
|
||||
/// assert!(content.get_content().source().contains("new"));
|
||||
/// ```
|
||||
pub fn new_with_content(content: TextContent) -> Self {
|
||||
TextView {
|
||||
@ -318,58 +289,25 @@ impl TextView {
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.markup(content).unwrap()
|
||||
}
|
||||
|
||||
/// Replace the text in this view.
|
||||
///
|
||||
/// 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))
|
||||
self.with(|s| s.set_content(content))
|
||||
}
|
||||
|
||||
/// Replace the text in this view.
|
||||
pub fn set_content<S>(&mut self, content: S)
|
||||
where
|
||||
S: Into<String>,
|
||||
S: Into<StyledString>,
|
||||
{
|
||||
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.content.lock().unwrap().content = content.into();
|
||||
self.invalidate();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Append `content` to the end of a `TextView`.
|
||||
pub fn append_content<T>(
|
||||
&mut self, content: T
|
||||
) -> Result<(), <T::M as Markup>::Error>
|
||||
pub fn append<S>(&mut self, content: S)
|
||||
where
|
||||
T: MarkupText,
|
||||
S: Into<StyledString>,
|
||||
{
|
||||
self.content
|
||||
.lock()
|
||||
.unwrap()
|
||||
.content
|
||||
.append_content(content)?;
|
||||
self.content.lock().unwrap().content.append(content.into());
|
||||
self.invalidate();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the current text in this view.
|
||||
@ -456,8 +394,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 =
|
||||
SpanLinesIterator::new(content.content.spans(), size.x).collect();
|
||||
self.rows = LinesIterator::new(&content.content, size.x).collect();
|
||||
|
||||
// Width taken by the scrollbar. Without a scrollbar, it's 0.
|
||||
let mut scrollbar_width = 0;
|
||||
@ -473,8 +410,7 @@ impl TextView {
|
||||
};
|
||||
|
||||
self.rows =
|
||||
SpanLinesIterator::new(content.content.spans(), available)
|
||||
.collect();
|
||||
LinesIterator::new(&content.content, available).collect();
|
||||
|
||||
if self.rows.is_empty() && !content.content.is_empty() {
|
||||
// We have some content, we we didn't find any row for it?
|
||||
@ -526,10 +462,10 @@ impl View for TextView {
|
||||
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();
|
||||
for span in row.resolve(&content.content) {
|
||||
printer.with_style(*span.attr, |printer| {
|
||||
printer.print((x, 0), &span.content);
|
||||
x += span.content.width();
|
||||
});
|
||||
}
|
||||
// let text = &content.content[row.start..row.end];
|
||||
|
Loading…
Reference in New Issue
Block a user