From 2d13e1a2c1c113de9312e1666620d8360d5c9c2d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 4 Jan 2024 07:39:44 +0000 Subject: [PATCH] Actually implement the highlighting methods! 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 | 3 + src/text.rs | 203 +++++++++++++++++++++++++++++++++++++++-- src/tui.rs | 5 + 3 files changed, 201 insertions(+), 10 deletions(-) diff --git a/src/coloured_string.rs b/src/coloured_string.rs index e9ae2f4..185a16a 100644 --- a/src/coloured_string.rs +++ b/src/coloured_string.rs @@ -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 { diff --git a/src/text.rs b/src/text.rs index 1f53100..80e47ef 100644 --- a/src/text.rs +++ b/src/text.rs @@ -318,12 +318,34 @@ impl TextFragment for UsernameHeader { -> Vec { 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) -> Option { + match highlight { + Some(Highlight(HighlightType::User, 0)) => Some(self.id.clone()), + _ => None, + } + } } #[test] @@ -431,6 +453,13 @@ impl Paragraph { self.words.extend_from_slice(¶.words); } + pub fn push_para_recoloured(&mut self, para: &Paragraph, colour: char) { + self.end_word(); + for word in ¶.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) -> Option { + 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 { 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) -> Option { + 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 { 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) + -> Option + { + 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 { diff --git a/src/tui.rs b/src/tui.rs index 7b50db1..35bebb3 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -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) -- 2.30.2