diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 6dabbbc537..58384f5ee5 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -531,6 +531,8 @@ impl ChatPanel { &self.languages, self.client.id(), &message, + self.local_timezone, + cx, ) }); el.child( @@ -744,6 +746,8 @@ impl ChatPanel { language_registry: &Arc, current_user_id: u64, message: &channel::ChannelMessage, + local_timezone: UtcOffset, + cx: &AppContext, ) -> RichText { let mentions = message .mentions @@ -754,24 +758,39 @@ impl ChatPanel { }) .collect::>(); - const MESSAGE_UPDATED: &str = " (edited)"; + const MESSAGE_EDITED: &str = " (edited)"; let mut body = message.body.clone(); if message.edited_at.is_some() { - body.push_str(MESSAGE_UPDATED); + body.push_str(MESSAGE_EDITED); } let mut rich_text = rich_text::render_rich_text(body, &mentions, language_registry, None); if message.edited_at.is_some() { + let range = (rich_text.text.len() - MESSAGE_EDITED.len())..rich_text.text.len(); rich_text.highlights.push(( - (rich_text.text.len() - MESSAGE_UPDATED.len())..rich_text.text.len(), + range.clone(), Highlight::Highlight(HighlightStyle { - fade_out: Some(0.8), + color: Some(cx.theme().colors().text_muted), ..Default::default() }), )); + + if let Some(edit_timestamp) = message.edited_at { + let edit_timestamp_text = time_format::format_localized_timestamp( + edit_timestamp, + OffsetDateTime::now_utc(), + local_timezone, + time_format::TimestampFormat::Absolute, + ); + + rich_text.custom_ranges.push(range); + rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, cx| { + Some(Tooltip::text(edit_timestamp_text.clone(), cx)) + }) + } } rich_text } @@ -1176,7 +1195,13 @@ mod tests { edited_at: None, }; - let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message); + let message = ChatPanel::render_markdown_with_mentions( + &language_registry, + 102, + &message, + UtcOffset::UTC, + cx, + ); // Note that the "'" was replaced with ’ due to smart punctuation. let (body, ranges) = marked_text_ranges("«hi», «@abc», let’s «call» «@fgh»", false); @@ -1224,7 +1249,13 @@ mod tests { edited_at: None, }; - let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message); + let message = ChatPanel::render_markdown_with_mentions( + &language_registry, + 102, + &message, + UtcOffset::UTC, + cx, + ); // Note that the "'" was replaced with ’ due to smart punctuation. let (body, ranges) = @@ -1265,7 +1296,13 @@ mod tests { edited_at: None, }; - let message = ChatPanel::render_markdown_with_mentions(&language_registry, 102, &message); + let message = ChatPanel::render_markdown_with_mentions( + &language_registry, + 102, + &message, + UtcOffset::UTC, + cx, + ); // Note that the "'" was replaced with ’ due to smart punctuation. let (body, ranges) = marked_text_ranges( diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 40ae70608e..78dabe0ca3 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -1,7 +1,7 @@ use futures::FutureExt; use gpui::{ - AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, - SharedString, StrikethroughStyle, StyledText, UnderlineStyle, WindowContext, + AnyElement, AnyView, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, + IntoElement, SharedString, StrikethroughStyle, StyledText, UnderlineStyle, WindowContext, }; use language::{HighlightId, Language, LanguageRegistry}; use std::{ops::Range, sync::Arc}; @@ -31,12 +31,16 @@ impl From for Highlight { } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct RichText { pub text: SharedString, pub highlights: Vec<(Range, Highlight)>, pub link_ranges: Vec>, pub link_urls: Arc<[String]>, + + pub custom_ranges: Vec>, + custom_ranges_tooltip_fn: + Option, &mut WindowContext) -> Option>>, } /// Allows one to specify extra links to the rendered markdown, which can be used @@ -48,7 +52,14 @@ pub struct Mention { } impl RichText { - pub fn element(&self, id: ElementId, cx: &WindowContext) -> AnyElement { + pub fn set_tooltip_builder_for_custom_ranges( + &mut self, + f: impl Fn(usize, Range, &mut WindowContext) -> Option + 'static, + ) { + self.custom_ranges_tooltip_fn = Some(Arc::new(f)); + } + + pub fn element(&self, id: ElementId, cx: &mut WindowContext) -> AnyElement { let theme = cx.theme(); let code_background = theme.colors().surface_background; @@ -111,12 +122,21 @@ impl RichText { .tooltip({ let link_ranges = self.link_ranges.clone(); let link_urls = self.link_urls.clone(); + let custom_tooltip_ranges = self.custom_ranges.clone(); + let custom_tooltip_fn = self.custom_ranges_tooltip_fn.clone(); move |idx, cx| { for (ix, range) in link_ranges.iter().enumerate() { if range.contains(&idx) { return Some(LinkPreview::new(&link_urls[ix], cx)); } } + for range in &custom_tooltip_ranges { + if range.contains(&idx) { + if let Some(f) = &custom_tooltip_fn { + return f(idx, range.clone(), cx); + } + } + } None } }) @@ -354,6 +374,8 @@ pub fn render_rich_text( link_urls: link_urls.into(), link_ranges, highlights, + custom_ranges: Vec::new(), + custom_ranges_tooltip_fn: None, } }