use std::fmt;
use anstyle::Style;
use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete, UnicodeSegmentation};
use crate::cells::{cluster_width, is_space};
use crate::span::Span;
#[derive(Clone, Copy, Debug)]
pub(crate) struct Piece<'a> {
pub(crate) text: &'a str,
pub(crate) style: Style,
pub(crate) width: u16,
pub(crate) space: bool,
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct Line {
spans: Vec<Span>,
}
impl Line {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, span: Span) {
if span.text.is_empty() {
return;
}
match self.spans.last_mut() {
Some(last) if last.style == span.style => last.text.push_str(&span.text),
Some(_) | None => self.push_new(span),
}
}
#[must_use]
pub fn spans(&self) -> &[Span] {
&self.spans
}
#[must_use]
pub fn width(&self) -> u16 {
self.spans
.iter()
.map(Span::width)
.fold(0, u16::saturating_add)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.spans.iter().all(|span| span.text.is_empty())
}
pub fn trim_end(&mut self) {
while let Some(last) = self.spans.last_mut() {
let trailing = trailing_space_len(&last.text);
if trailing == last.text.len() {
self.spans.pop();
} else {
let kept = last.text.len() - trailing;
last.text.truncate(kept);
break;
}
}
}
pub(crate) fn append(&mut self, text: &str, style: Style) {
if text.is_empty() {
return;
}
match self.spans.last_mut() {
Some(last) if last.style == style => last.text.push_str(text),
Some(_) | None => self.push_new(Span {
text: text.to_owned(),
style,
}),
}
}
fn push_new(&mut self, span: Span) {
self.spans.push(span);
self.keep_clusters_whole();
}
fn keep_clusters_whole(&mut self) {
let [.., before, last] = self.spans.as_mut_slice() else {
return;
};
let moved = continuation_len(&before.text, &last.text);
if moved == 0 {
return;
}
before.text.extend(last.text.drain(..moved));
if last.text.is_empty() {
self.spans.pop();
}
}
pub(crate) fn pieces(&self) -> Vec<Piece<'_>> {
self.spans
.iter()
.flat_map(|span| {
span.text.graphemes(true).map(|cluster| Piece {
text: cluster,
style: span.style,
width: cluster_width(cluster),
space: is_space(cluster),
})
})
.collect()
}
pub(crate) fn from_pieces(pieces: &[Piece<'_>]) -> Self {
let mut line = Self::new();
for piece in pieces {
line.append(piece.text, piece.style);
}
line
}
}
fn continuation_len(before: &str, after: &str) -> usize {
let junction = before.len();
let mut cursor = GraphemeCursor::new(junction, junction + after.len(), true);
loop {
match cursor.is_boundary(after, junction) {
Ok(true)
| Err(
GraphemeIncomplete::PrevChunk
| GraphemeIncomplete::NextChunk
| GraphemeIncomplete::InvalidOffset,
) => return 0,
Ok(false) => break,
Err(GraphemeIncomplete::PreContext(end)) => cursor.provide_context(&before[..end], 0),
}
}
loop {
match cursor.next_boundary(after, junction) {
Ok(Some(end)) => return end.saturating_sub(junction),
Ok(None) => return after.len(),
Err(GraphemeIncomplete::PreContext(end)) => cursor.provide_context(&before[..end], 0),
Err(
GraphemeIncomplete::PrevChunk
| GraphemeIncomplete::NextChunk
| GraphemeIncomplete::InvalidOffset,
) => return 0,
}
}
}
fn trailing_space_len(text: &str) -> usize {
text.graphemes(true)
.rev()
.take_while(|cluster| is_space(cluster))
.map(str::len)
.sum()
}
impl From<Span> for Line {
fn from(span: Span) -> Self {
let mut line = Self::new();
line.push(span);
line
}
}
impl Extend<Span> for Line {
fn extend<I: IntoIterator<Item = Span>>(&mut self, spans: I) {
for span in spans {
self.push(span);
}
}
}
impl FromIterator<Span> for Line {
fn from_iter<I: IntoIterator<Item = Span>>(spans: I) -> Self {
let mut line = Self::new();
line.extend(spans);
line
}
}
impl fmt::Display for Line {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.spans
.iter()
.try_for_each(|span| formatter.write_str(&span.text))
}
}