chiark / gitweb /
Re: header.
authorSimon Tatham <anakin@pobox.com>
Mon, 25 Dec 2023 21:16:03 +0000 (21:16 +0000)
committerSimon Tatham <anakin@pobox.com>
Mon, 25 Dec 2023 21:38:17 +0000 (21:38 +0000)
src/text.rs

index 9b59666aa29b5c020d52647d9d6fffbfaea92f5d..96067dc6f9674c6834f2dad053ff3a7de13732ad 100644 (file)
@@ -1,5 +1,5 @@
 use chrono::{DateTime, Utc, Local};
-use core::cmp::{max, min};
+use core::cmp::min;
 use std::collections::{HashMap, BTreeSet};
 
 use super::html;
@@ -15,7 +15,7 @@ pub trait TextFragment {
     fn as_extendable_indicator(&self) -> Option<&ExtendableIndicator> { None }
 }
 
-pub struct BlankLine {}
+struct BlankLine {}
 
 pub fn blank() -> Box<dyn TextFragment> {
     Box::new(BlankLine{})
@@ -36,7 +36,7 @@ fn test_blank() {
         });
 }
 
-pub struct SeparatorLine {
+struct SeparatorLine {
     timestamp: Option<DateTime<Utc>>,
     favourited: bool,
     boosted: bool,
@@ -101,7 +101,7 @@ fn test_separator() {
         });
 }
 
-pub struct EditorHeaderSeparator {}
+struct EditorHeaderSeparator {}
 
 pub fn editorsep() -> Box<dyn TextFragment> {
     Box::new(EditorHeaderSeparator{})
@@ -111,7 +111,7 @@ impl TextFragment for EditorHeaderSeparator {
     fn render(&self, width: usize) -> Vec<ColouredString> {
         vec! {
             ColouredString::uniform(
-                &((&"-".repeat(max(0, width - 2))).to_string() + "|"),
+                &((&"-".repeat(width - min(2, width))).to_string() + "|"),
                 '-',
             ).truncate(width).to_owned(),
         }
@@ -128,7 +128,7 @@ fn test_editorsep() {
         });
 }
 
-pub struct UsernameHeader {
+struct UsernameHeader {
     header: String,
     colour: char,
     account: String,
@@ -197,6 +197,10 @@ impl ColouredString {
             false
         }
     }
+
+    fn is_colour(&self, colour: char) -> bool {
+        self.frags().all(|(_, c)| c == colour)
+    }
 }
 
 impl<'a> ColouredStringSlice<'a> {
@@ -251,18 +255,20 @@ impl Paragraph {
         self
     }
 
-    pub fn push_para(mut self, para: &Paragraph) -> Self {
-        let tail = if let Some(last_existing_word) = self.words.last_mut() {
-            if let Some(first_new_word) = para.words.first() {
-                if last_existing_word.is_space() ==
-                    first_new_word.is_space() {
-                    last_existing_word.push_str(&first_new_word.slice());
-                    1
-                } else { 0 }
-            } else { 0 }
-        } else { 0 };
-        self.words.extend_from_slice(&para.words[tail..]);
-        self
+    pub fn push_para(&mut self, para: &Paragraph) {
+        if let Some(word) = self.words.last() {
+            if !word.is_space() {
+                self.push_text(&ColouredString::plain(" "), false);
+            }
+        }
+        self.words.extend_from_slice(&para.words);
+    }
+
+    pub fn delete_mention_words_from(&mut self, start: usize) {
+        let first_non_mention = start + self.words[start..].iter()
+            .position(|word| !(word.is_space() || word.is_colour('@')))
+            .unwrap_or(self.words.len() - start);
+        self.words.splice(start..first_non_mention, vec!{});
     }
 
     pub fn into_box(self) -> Box<dyn TextFragment> { Box::new(self) }
@@ -433,7 +439,7 @@ fn test_para_wrap() {
         });
 }
 
-pub struct FileHeader {
+struct FileHeader {
     text: ColouredString,
 }
 
@@ -775,8 +781,67 @@ fn test_extendable() {
         });
 }
 
+pub struct InReplyToLine {
+    para: Paragraph,
+}
+
+pub fn in_reply_to_line(post: &Vec<Paragraph>) -> Box<dyn TextFragment> {
+    let mut para = Paragraph::new().add(&ColouredString::plain("Re: "));
+    let currlen = para.words.len();
+    for cpara in post {
+        para.push_para(cpara);
+    }
+    para.delete_mention_words_from(currlen);
+    Box::new(InReplyToLine {
+            para: para
+        })
+}
+
+impl TextFragment for InReplyToLine {
+    fn render(&self, width: usize) -> Vec<ColouredString> {
+        let rendered_para = self.para.render(width - min(width, 4));
+        let mut it = rendered_para.iter();
+        // "Re:" guarantees the first line must exist at least
+        let first_line = it.next().unwrap();
+        let result = if it.next().is_some() {
+            first_line + ColouredString::plain("...")
+        } else {
+            first_line.clone()
+        };
+        vec! { result.truncate(width).to_owned() }
+    }
+}
+
+#[test]
+fn test_in_reply_to() {
+    let post = vec! {
+        Paragraph::new().add(&ColouredString::general(
+            "@stoat @weasel take a look at this otter!",
+            "@@@@@@ @@@@@@@                           ")),
+        Paragraph::new().add(&ColouredString::general(
+            "@badger might also like it",
+            "@@@@@@@                   ")),
+    };
+
+    let irt = in_reply_to_line(&post);
+    assert_eq!(irt.render(48), vec!{
+            ColouredString::general(
+                "Re: take a look at this otter! @badger might...",
+                "                               @@@@@@@         "),
+            });
+    assert_eq!(irt.render(47), vec!{
+            ColouredString::general(
+                "Re: take a look at this otter! @badger...",
+                "                               @@@@@@@   "),
+            });
+    assert_eq!(irt.render(80), vec!{
+            ColouredString::general(
+                "Re: take a look at this otter! @badger might also like it",
+                "                               @@@@@@@                   "),
+            });
+}
+
 // TODO:
-// InReplyToLine, with first line of included paragraph
 // NotificationLog, also with included toot
 // UserListEntry
 // Media