chiark / gitweb /
Actually implement the highlighting methods!
authorSimon Tatham <anakin@pobox.com>
Thu, 4 Jan 2024 07:39:44 +0000 (07:39 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 4 Jan 2024 08:14:03 +0000 (08:14 +0000)
Everything up to here just added API. Now I'm actually providing
non-default implementations so that TextFragments that need to
highlight can be highlighted.

src/coloured_string.rs
src/text.rs
src/tui.rs

index e9ae2f4acd2c7e0d42cc766b5641c4e929b0cffd..185a16a17fdec14f6fc4a61a35e4c6218fab1d32 100644 (file)
@@ -47,6 +47,9 @@ impl ColouredString {
     pub fn width(&self) -> usize { UnicodeWidthStr::width(&self.text as &str) }
 
     pub fn text(&self) -> &str { &self.text }
+    pub fn recolour(&self, colour: char) -> Self {
+        Self::uniform(&self.text, colour)
+    }
 
     pub fn slice(&self) -> ColouredStringSlice {
         ColouredStringSlice {
index 1f53100d951c0929424c9ff93e882cb5606ae54a..80e47ef99ffe4ef8710b8fdafd22e088362a9c20 100644 (file)
@@ -318,12 +318,34 @@ impl TextFragment for UsernameHeader {
                           -> Vec<ColouredString>
     {
         let header = ColouredString::plain(&format!("{}: ", self.header));
+        let colour = match highlight {
+            Some(Highlight(HighlightType::User, 0)) => '*',
+            _ => self.colour,
+        };
         let body = ColouredString::uniform(
-            &format!("{} ({})", self.nameline, self.account), self.colour);
+            &format!("{} ({})", self.nameline, self.account), colour);
         vec! {
             header + body,
         }
     }
+
+    fn can_highlight(htype: HighlightType) -> bool where Self : Sized {
+        htype == HighlightType::User
+    }
+
+    fn count_highlightables(&self, htype: HighlightType) -> usize {
+        match htype {
+            HighlightType::User => 1,
+            _ => 0,
+        }
+    }
+
+    fn highlighted_id(&self, highlight: Option<Highlight>) -> Option<String> {
+        match highlight {
+            Some(Highlight(HighlightType::User, 0)) => Some(self.id.clone()),
+            _ => None,
+        }
+    }
 }
 
 #[test]
@@ -431,6 +453,13 @@ impl Paragraph {
         self.words.extend_from_slice(&para.words);
     }
 
+    pub fn push_para_recoloured(&mut self, para: &Paragraph, colour: char) {
+        self.end_word();
+        for word in &para.words {
+            self.words.push(word.recolour(colour));
+        }
+    }
+
     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('@')))
@@ -1005,10 +1034,20 @@ impl TextFragment for NotificationLog {
         let datestr = format_date(self.timestamp);
         full_para.push_text(&ColouredString::uniform(&datestr, ' '), false);
         full_para.end_word();
-        // FIXME: highlight account_desc if active
+
+        let user_colour = match highlight {
+            Some(Highlight(HighlightType::User, 0)) => '*',
+            _ => ' ',
+        };
         full_para.push_text(
-            &ColouredString::uniform(&self.account_desc, ' '), false);
-        full_para.push_para(&self.para);
+            &ColouredString::uniform(&self.account_desc, user_colour), false);
+
+        match (highlight, &self.status_id) {
+            (Some(Highlight(HighlightType::Status, 0)), Some(..)) =>
+                full_para.push_para_recoloured(&self.para, '*'),
+            _ => full_para.push_para(&self.para),
+        };
+
         let rendered_para = full_para.render(width);
         if rendered_para.len() > 2 {
             vec! {
@@ -1020,6 +1059,28 @@ impl TextFragment for NotificationLog {
             rendered_para
         }
     }
+
+    fn can_highlight(htype: HighlightType) -> bool where Self : Sized {
+        htype == HighlightType::User || htype == HighlightType::Status
+    }
+
+    fn count_highlightables(&self, htype: HighlightType) -> usize {
+        match htype {
+            HighlightType::User => 1,
+            HighlightType::Status => if self.status_id.is_some() {1} else {0},
+            _ => 0,
+        }
+    }
+
+    fn highlighted_id(&self, highlight: Option<Highlight>) -> Option<String> {
+        match highlight {
+            Some(Highlight(HighlightType::User, 0)) =>
+                Some(self.account_id.clone()),
+            Some(Highlight(HighlightType::Status, 0)) =>
+                self.status_id.clone(),
+            _ => None,
+        }
+    }
 }
 
 #[test]
@@ -1644,13 +1705,22 @@ impl TextFragment for StatusDisplay {
                           -> Vec<ColouredString>
     {
         let mut lines = Vec::new();
+        let mut highlight = highlight;
 
         push_fragment(&mut lines, self.sep.render(width));
-        push_fragment(&mut lines, self.from.render(width));
-        push_fragment(&mut lines, self.via.render(width));
+        push_fragment(&mut lines, self.from.render_highlighted_update(
+            width, &mut highlight));
+        push_fragment(&mut lines, self.via.render_highlighted_update(
+            width, &mut highlight));
         push_fragment(&mut lines, self.irt.render(width));
         push_fragment(&mut lines, self.blank.render(width));
-        let rendered_content = self.content.render(width);
+        let mut rendered_content = self.content.render(width);
+        if highlight.consume(HighlightType::Status, 1) == Some(0) ||
+            highlight.consume(HighlightType::WholeStatus, 1) == Some(0)
+        {
+            rendered_content = rendered_content.iter()
+                .map(|s| s.recolour('*')).collect();
+        }
         let content_empty = rendered_content.len() == 0;
         push_fragment(&mut lines, rendered_content);
         if !content_empty {
@@ -1663,6 +1733,42 @@ impl TextFragment for StatusDisplay {
 
         lines
     }
+
+    fn can_highlight(htype: HighlightType) -> bool where Self : Sized {
+        htype == HighlightType::User || htype == HighlightType::Status ||
+            htype == HighlightType::WholeStatus
+    }
+
+    fn count_highlightables(&self, htype: HighlightType) -> usize {
+        match htype {
+            HighlightType::User => {
+                self.from.count_highlightables(htype) +
+                    self.via.count_highlightables(htype)
+            }
+            HighlightType::Status => 1,
+            HighlightType::WholeStatus => 1,
+        }
+    }
+
+    fn highlighted_id(&self, highlight: Option<Highlight>) -> Option<String> {
+        match highlight {
+            Some(Highlight(HighlightType::User, _)) => {
+                let mut highlight = highlight;
+                match self.from.highlighted_id_update(&mut highlight) {
+                    result @ Some(..) => return result,
+                    _ => (),
+                }
+                match self.via.highlighted_id_update(&mut highlight) {
+                    result @ Some(..) => return result,
+                    _ => (),
+                }
+                None
+            }
+            Some(Highlight(HighlightType::WholeStatus, 0)) |
+            Some(Highlight(HighlightType::Status, 0)) => Some(self.id.clone()),
+            _ => None,
+        }
+    }
 }
 
 pub struct DetailedStatusDisplay {
@@ -1820,8 +1926,10 @@ impl TextFragment for DetailedStatusDisplay {
                           -> Vec<ColouredString>
     {
         let mut lines = Vec::new();
+        let mut highlight = highlight;
 
-        push_fragment(&mut lines, self.sd.render(width));
+        push_fragment(&mut lines, self.sd.render_highlighted_update(
+            width, &mut highlight));
         push_fragment(&mut lines, self.sep.render(width));
         push_fragment(&mut lines, self.blank.render(width));
 
@@ -1831,8 +1939,25 @@ impl TextFragment for DetailedStatusDisplay {
 
         push_fragment(&mut lines, self.creation.render(width));
         push_fragment(&mut lines, self.lastedit.render(width));
-        push_fragment(&mut lines, self.reply_to.render(width));
-        push_fragment(&mut lines, self.reply_to_user.render(width));
+
+        let mut reply_to = self.reply_to.render(width);
+        if self.reply_to_id.is_some() &&
+            highlight.consume(HighlightType::Status, 1) == Some(0)
+        {
+            reply_to = reply_to.iter().map(|s| s.recolour('*'))
+                .collect();
+        }
+        push_fragment(&mut lines, reply_to);
+
+        let mut reply_to_user = self.reply_to_user.render(width);
+        if self.reply_to_user_id.is_some() &&
+            highlight.consume(HighlightType::User, 1) == Some(0)
+        {
+            reply_to_user = reply_to_user.iter().map(|s| s.recolour('*'))
+                .collect();
+        }
+        push_fragment(&mut lines, reply_to_user);
+
         push_fragment(&mut lines, self.blank.render(width));
 
         push_fragment(&mut lines, self.language.render(width));
@@ -1850,6 +1975,10 @@ impl TextFragment for DetailedStatusDisplay {
             push_fragment(&mut lines, self.mentions_header.render(width));
             for (para, _id) in &self.mentions {
                 let mut rendered = para.render(width);
+                if highlight.consume(HighlightType::User, 1) == Some(0) {
+                    rendered = rendered.iter().map(|s| s.recolour('*'))
+                        .collect();
+                }
                 push_fragment(&mut lines, rendered);
             }
             push_fragment(&mut lines, self.blank.render(width));
@@ -1861,6 +1990,60 @@ impl TextFragment for DetailedStatusDisplay {
 
         lines
     }
+
+    fn can_highlight(htype: HighlightType) -> bool where Self : Sized {
+        htype == HighlightType::User || htype == HighlightType::Status
+    }
+
+    fn count_highlightables(&self, htype: HighlightType) -> usize {
+        let base = self.sd.count_highlightables(htype);
+        match htype {
+            HighlightType::User => {
+                base +
+                    (if self.reply_to_user_id.is_some() {1} else {0}) +
+                    self.mentions.len()
+            }
+            HighlightType::Status => {
+                base + (if self.reply_to_id.is_some() {1} else {0})
+            }
+            _ => base,
+        }
+    }
+
+    fn highlighted_id(&self, highlight: Option<Highlight>)
+                      -> Option<String>
+    {
+        let mut highlight = highlight;
+        match self.sd.highlighted_id_update(&mut highlight) {
+            result @ Some(..) => return result,
+            _ => (),
+        }
+
+        match highlight {
+            Some(Highlight(HighlightType::User, index)) => {
+                let mut index = index;
+                if let Some(ref id) = self.reply_to_user_id {
+                    if index == 0 {
+                        return Some(id.clone());
+                    } else {
+                        index -= 1;
+                    }
+                }
+                if index < self.mentions.len() {
+                    return Some(self.mentions[index].1.clone());
+                }
+            }
+            Some(Highlight(HighlightType::Status, index)) => {
+                if let Some(ref id) = self.reply_to_id {
+                    if index == 0 {
+                        return Some(id.clone());
+                    }
+                }
+            }
+            _ => (),
+        }
+        None
+    }
 }
 
 pub struct ExamineUserDisplay {
index 7b50db1920102520678f1d7917755e2273ffae2d..35bebb3e1a3fd704b8be0f205e72ba139fe6a8d7 100644 (file)
@@ -100,6 +100,11 @@ fn ratatui_style_from_colour(colour: char) -> Style {
         // # reverse-video > indicating a truncated too-long line
         '>' => Style::default().add_modifier(Modifier::REVERSED),
 
+        // a selected user or status you're about to operate on while
+        // viewing a file
+        '*' => Style::default().fg(Color::Cyan).bg(Color::Black)
+            .add_modifier(Modifier::REVERSED),
+
         // # error report, or by default any unrecognised colour character
         '!' | _ => Style::default().fg(Color::Red).bg(Color::Yellow).
             add_modifier(Modifier::REVERSED | Modifier::BOLD)