use crate::buffer::Buffer;
use crate::geom::{Pos, Rect, Size};
use crate::style::Style;
#[cfg(test)]
use crate::style::Color;
#[derive(Debug, Clone)]
pub struct Span {
pub text: String,
pub style: Style,
}
impl Span {
pub fn new(text: impl Into<String>) -> Self {
Self { text: text.into(), style: Style::default() }
}
pub fn styled(text: impl Into<String>, style: Style) -> Self {
Self { text: text.into(), style }
}
pub fn width(&self) -> u16 {
self.text.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16)
.sum()
}
}
impl From<String> for Span {
fn from(s: String) -> Self { Self { text: s, style: Style::default() } }
}
#[derive(Debug, Clone)]
pub struct Line {
pub spans: Vec<Span>,
}
impl Line {
pub fn new(spans: impl IntoIterator<Item = Span>) -> Self {
Self { spans: spans.into_iter().collect() }
}
pub fn width(&self) -> u16 {
self.spans.iter().map(Span::width).sum()
}
pub fn render(&self, buf: &mut Buffer, pos: Pos, clip: Rect) -> u16 {
let mut x = pos.x;
for span in &self.spans {
if span.text.is_empty() { continue; }
buf.write_text(Pos { x, y: pos.y }, clip, &span.text, &span.style);
x = x.saturating_add(span.width());
}
x.saturating_sub(pos.x)
}
}
impl From<Span> for Line {
fn from(span: Span) -> Self { Self { spans: vec![span] } }
}
impl From<Vec<Span>> for Line {
fn from(spans: Vec<Span>) -> Self { Self { spans } }
}
#[derive(Debug, Clone)]
pub struct Text {
pub lines: Vec<Line>,
}
impl Text {
pub fn new(lines: impl IntoIterator<Item = Line>) -> Self {
Self { lines: lines.into_iter().collect() }
}
pub fn height(&self) -> usize { self.lines.len() }
pub fn max_width(&self) -> u16 {
self.lines.iter().map(Line::width).max().unwrap_or(0)
}
pub fn first_text(&self) -> &str {
self.lines.first().and_then(|l| l.spans.first()).map(|s| s.text.as_str()).unwrap_or("")
}
pub fn render(&self, buf: &mut Buffer, rect: Rect) -> Size {
for (i, line) in self.lines.iter().enumerate() {
let y = rect.y.saturating_add(i as u16);
if y >= rect.y.saturating_add(rect.height) { break; }
line.render(buf, Pos { x: rect.x, y }, rect);
}
Size { width: self.max_width(), height: self.lines.len() as u16 }
}
}
impl From<&str> for Text {
fn from(s: &str) -> Self {
if s.is_empty() { return Self { lines: Vec::new() }; }
let lines: Vec<Line> = s.split('\n').map(|seg| Line::from(Span::new(seg))).collect();
Self { lines }
}
}
impl From<String> for Text {
fn from(s: String) -> Self { Text::from(s.as_str()) }
}
impl From<Line> for Text {
fn from(l: Line) -> Self { Self { lines: vec![l] } }
}
impl From<Vec<Line>> for Text {
fn from(lines: Vec<Line>) -> Self { Self { lines } }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stylize::Stylize;
#[test]
fn test_span_new() {
let s = Span::new("hello");
assert_eq!(s.text, "hello");
}
#[test]
fn test_span_width() {
assert_eq!(Span::new("abc").width(), 3);
}
#[test]
fn test_line_from_span() {
let l = Line::from(Span::new("hello"));
assert_eq!(l.spans.len(), 1);
assert_eq!(l.spans[0].text, "hello");
}
#[test]
fn test_line_from_spans() {
let l = Line::from(vec![
Span::styled("A", Style::default().fg(Color::Red)),
Span::new("B"),
]);
assert_eq!(l.spans.len(), 2);
assert_eq!(l.width(), 2);
}
#[test]
fn test_line_render() {
let mut buf = Buffer::new(Size { width: 10, height: 1 });
let line = Line::from(Span::new("hi"));
let w = line.render(&mut buf, Pos::default(), Rect { x: 0, y: 0, width: 10, height: 1 });
assert_eq!(w, 2);
assert_eq!(buf.cells[0].symbol, "h");
}
#[test]
fn test_text_from_str() {
let t = Text::from("hello");
assert_eq!(t.lines.len(), 1);
}
#[test]
fn test_text_from_line() {
let t = Text::from(Line::from(Span::new("hello")));
assert_eq!(t.lines.len(), 1);
}
#[test]
fn test_text_from_vec() {
let t = Text::from(vec![
Line::from(Span::new("line1")),
Line::from(Span::new("line2")),
]);
assert_eq!(t.height(), 2);
}
#[test]
fn test_text_with_stylize() {
let t = Text::from(Line::from(vec![
"Error: ".red().bold(),
Span::new("not found"),
]));
assert_eq!(t.lines[0].spans[0].style.fg, Some(crate::style::Color::Red));
assert!(t.lines[0].spans[0].style.bold);
}
#[test]
fn test_text_render() {
let mut buf = Buffer::new(Size { width: 20, height: 2 });
let t = Text::from(vec![
Line::from(Span::new("hello")),
Line::from(Span::new("world")),
]);
t.render(&mut buf, Rect { x: 0, y: 0, width: 20, height: 2 });
assert_eq!(buf.cells[0].symbol, "h");
assert_eq!(&buf.cells[20].symbol, "w"); }
#[test]
fn test_line_width_multi_style() {
let line = Line::from(vec![
Span::styled("AB", Style::default().fg(Color::Red)),
Span::new("CD"),
]);
assert_eq!(line.width(), 4);
}
}