From 9fe63a576184e96f59ce01cbbb067f4f0554fc34 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 24 Dec 2023 16:08:43 +0000 Subject: [PATCH] Basic version of split. Returns a newly allocated ColouredString. Perhaps it would be nice to be able to return a lighter-weight thing containing a pair of &str? --- src/coloured_string.rs | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/coloured_string.rs b/src/coloured_string.rs index 6c6a8b7..446bdc6 100644 --- a/src/coloured_string.rs +++ b/src/coloured_string.rs @@ -1,4 +1,5 @@ use unicode_width::UnicodeWidthStr; +use unicode_width::UnicodeWidthChar; #[derive(Debug, Clone)] pub struct ColouredString { @@ -85,6 +86,13 @@ pub struct ColouredStringFragIterator<'a> { colourpos: usize, } +pub struct ColouredStringSplitIterator<'a> { + cs: &'a ColouredString, + width: usize, + textpos: usize, + colourpos: usize, +} + impl<'a> ColouredString { pub fn frags(&'a self) -> ColouredStringFragIterator<'a> { ColouredStringFragIterator { @@ -93,6 +101,14 @@ impl<'a> ColouredString { colourpos: 0, } } + pub fn split(&'a self, width: usize) -> ColouredStringSplitIterator<'a> { + ColouredStringSplitIterator { + cs: self, + width: width, + textpos: 0, + colourpos: 0, + } + } } impl<'a> Iterator for ColouredStringFragIterator<'a> { @@ -126,6 +142,55 @@ impl<'a> Iterator for ColouredStringFragIterator<'a> { } } +fn char_width_infallible(c: char) -> usize { + match UnicodeWidthChar::width(c) { + Some(w) => w, + None => 0, + } +} + +impl<'a> Iterator for ColouredStringSplitIterator<'a> { + type Item = ColouredString; + fn next(&mut self) -> Option { + let textslice = &self.cs.text[self.textpos..]; + let mut textit = textslice.char_indices(); + let colourslice = &self.cs.colour[self.colourpos..]; + let mut colourit = colourslice.char_indices(); + match (textit.next(), colourit.next()) { + (None, None) => None, + (Some((_, tc)), Some(_)) => { + let mut tpos: usize = 0; + let mut cpos: usize = 0; + let mut width: usize = 0; + let mut last_tc = tc; + let (textend, colourend) = loop { + let wc = char_width_infallible(last_tc); + if width + wc > self.width { + break (tpos, cpos); + } + width += wc; + match (textit.next(), colourit.next()) { + (None, None) => { + break (textslice.len(), colourslice.len()); + }, + (Some((ti, tc)), Some((ci, _))) => { + tpos = ti; + cpos = ci; + last_tc = tc; + }, + _ => panic!("length mismatch in CSSI"), + }; + }; + self.textpos += textend; + self.colourpos += colourend; + Some(ColouredString::general(&textslice[..textend], + &colourslice[..colourend])) + }, + _ => panic!("length mismatch in CSSI"), + } + } +} + #[test] fn test_constructors() { assert_eq!(ColouredString::plain("hello"), @@ -169,3 +234,25 @@ fn test_frags() { assert_eq!(frags.next(), Some(("wèasël", 'v'))); assert_eq!(frags.next(), None); } + +#[test] +fn test_split() { + let cs = ColouredString::general("abcdefgh", "mnopqrst"); + let mut lines = cs.split(3); + assert_eq!(lines.next(), Some(ColouredString::general("abc", "mno"))); + assert_eq!(lines.next(), Some(ColouredString::general("def", "pqr"))); + assert_eq!(lines.next(), Some(ColouredString::general("gh", "st"))); + assert_eq!(lines.next(), None); + let mut lines = cs.split(4); + assert_eq!(lines.next(), Some(ColouredString::general("abcd", "mnop"))); + assert_eq!(lines.next(), Some(ColouredString::general("efgh", "qrst"))); + assert_eq!(lines.next(), None); + + let cs = ColouredString::general("ab\u{4567}defgh", "mnopqrst"); + let mut lines = cs.split(3); + assert_eq!(lines.next(), Some(ColouredString::general("ab", "mn"))); + assert_eq!(lines.next(), Some(ColouredString::general("\u{4567}d", "op"))); + assert_eq!(lines.next(), Some(ColouredString::general("efg", "qrs"))); + assert_eq!(lines.next(), Some(ColouredString::general("h", "t"))); + assert_eq!(lines.next(), None); +} -- 2.30.2