chiark / gitweb /
Basic version of split.
authorSimon Tatham <anakin@pobox.com>
Sun, 24 Dec 2023 16:08:43 +0000 (16:08 +0000)
committerSimon Tatham <anakin@pobox.com>
Sun, 24 Dec 2023 16:11:59 +0000 (16:11 +0000)
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

index 6c6a8b7f623c42acce1ee54ebfd931ad1a813d1e..446bdc6e883af2a3ee8aa55b1162ef37873a0d4a 100644 (file)
@@ -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<Self::Item> {
+        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);
+}