diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index add0723e85..814cadb4fb 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -366,13 +366,26 @@ impl ChatPanel { }; let is_pending = message.is_pending(); - let text = self - .markdown_data - .entry(message.id) - .or_insert_with(|| rich_text::render_markdown(message.body, &self.languages, None)); + let theme = theme::current(cx); + let text = self.markdown_data.entry(message.id).or_insert_with(|| { + let mut markdown = + rich_text::render_markdown(message.body.clone(), &self.languages, None); + let self_client_id = self.client.id(); + for (mention_range, user_id) in message.mentions { + let is_current_user = self_client_id == user_id; + markdown + .add_mention( + mention_range, + is_current_user, + theme.chat_panel.mention_highlight.clone(), + ) + .log_err(); + } + markdown + }); let now = OffsetDateTime::now_utc(); - let theme = theme::current(cx); + let style = if is_pending { &theme.chat_panel.pending_message } else if is_continuation { @@ -400,6 +413,7 @@ impl ChatPanel { theme.editor.syntax.clone(), style.body.clone(), theme.editor.document_highlight_read_background, + theme.chat_panel.self_mention_background, cx, ) .flex(1., true), @@ -456,6 +470,7 @@ impl ChatPanel { theme.editor.syntax.clone(), style.body.clone(), theme.editor.document_highlight_read_background, + theme.chat_panel.self_mention_background, cx, ) .flex(1., true), diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 72c7bdf6c1..0b13050777 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -1,5 +1,6 @@ use std::{ops::Range, sync::Arc}; +use anyhow::bail; use futures::FutureExt; use gpui::{ color::Color, @@ -25,9 +26,15 @@ pub struct RichText { pub regions: Vec, } +#[derive(Clone, Copy, Debug, PartialEq)] +enum BackgroundKind { + Code, + Mention, +} + #[derive(Debug, Clone)] pub struct RenderedRegion { - code: bool, + background_kind: Option, link_url: Option, } @@ -37,6 +44,7 @@ impl RichText { syntax: Arc, style: TextStyle, code_span_background_color: Color, + self_mention_span_background_color: Color, cx: &mut ViewContext, ) -> AnyElement { let mut region_id = 0; @@ -73,18 +81,49 @@ impl RichText { }), ); } - if region.code { + if region.background_kind == Some(BackgroundKind::Code) { cx.scene().push_quad(gpui::Quad { bounds, background: Some(code_span_background_color), border: Default::default(), corner_radii: (2.0).into(), }); + } else if region.background_kind == Some(BackgroundKind::Mention) { + cx.scene().push_quad(gpui::Quad { + bounds, + background: Some(self_mention_span_background_color), + border: Default::default(), + corner_radii: (2.0).into(), + }); } }) .with_soft_wrap(true) .into_any() } + pub fn add_mention( + &mut self, + range: Range, + is_current_user: bool, + mention_style: HighlightStyle, + ) -> anyhow::Result<()> { + if range.end > self.text.len() { + bail!( + "Mention in range {range:?} is outside of bounds for a message of length {}", + self.text.len() + ); + } + + if is_current_user { + self.region_ranges.push(range.clone()); + self.regions.push(RenderedRegion { + background_kind: Some(BackgroundKind::Mention), + link_url: None, + }); + } + self.highlights + .push((range, Highlight::Highlight(mention_style))); + Ok(()) + } } pub fn render_markdown_mut( @@ -101,7 +140,11 @@ pub fn render_markdown_mut( let mut current_language = None; let mut list_stack = Vec::new(); - for event in Parser::new_ext(&block, Options::all()) { + // Smart Punctuation is disabled as that messes with offsets within the message. + let mut options = Options::all(); + options.remove(Options::ENABLE_SMART_PUNCTUATION); + + for event in Parser::new_ext(&block, options) { let prev_len = data.text.len(); match event { Event::Text(t) => { @@ -121,7 +164,7 @@ pub fn render_markdown_mut( data.region_ranges.push(prev_len..data.text.len()); data.regions.push(RenderedRegion { link_url: Some(link_url), - code: false, + background_kind: None, }); style.underline = Some(Underline { thickness: 1.0.into(), @@ -162,7 +205,7 @@ pub fn render_markdown_mut( )); } data.regions.push(RenderedRegion { - code: true, + background_kind: Some(BackgroundKind::Code), link_url: link_url.clone(), }); } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9e71b2de2f..7bedb26340 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -641,6 +641,7 @@ pub struct ChatPanel { pub avatar_container: ContainerStyle, pub message: ChatMessage, pub mention_highlight: HighlightStyle, + pub self_mention_background: Color, pub continuation_message: ChatMessage, pub last_message_bottom_spacing: f32, pub pending_message: ChatMessage, diff --git a/styles/src/style_tree/chat_panel.ts b/styles/src/style_tree/chat_panel.ts index 20a88ec929..6e67dd84e4 100644 --- a/styles/src/style_tree/chat_panel.ts +++ b/styles/src/style_tree/chat_panel.ts @@ -1,11 +1,8 @@ -import { - background, - border, - text, -} from "./components" +import { background, border, text } from "./components" import { icon_button } from "../component/icon_button" import { useTheme } from "../theme" import { interactive } from "../element" +import { Color } from "ayu/dist/color" export default function chat_panel(): any { const theme = useTheme() @@ -41,15 +38,13 @@ export default function chat_panel(): any { left: 2, top: 2, bottom: 2, - } - }, - list: { - + }, }, + list: {}, channel_select: { header: { ...channel_name, - border: border(layer, { bottom: true }) + border: border(layer, { bottom: true }), }, item: channel_name, active_item: { @@ -62,8 +57,8 @@ export default function chat_panel(): any { }, menu: { background: background(layer, "on"), - border: border(layer, { bottom: true }) - } + border: border(layer, { bottom: true }), + }, }, icon_button: icon_button({ variant: "ghost", @@ -91,7 +86,8 @@ export default function chat_panel(): any { top: 4, }, }, - mention_highlight: { weight: 'bold' }, + mention_highlight: { weight: "bold" }, + self_mention_background: background(layer, "active"), message: { ...interactive({ base: { @@ -101,7 +97,7 @@ export default function chat_panel(): any { bottom: 4, left: SPACING / 2, right: SPACING / 3, - } + }, }, state: { hovered: { @@ -135,7 +131,7 @@ export default function chat_panel(): any { bottom: 4, left: SPACING / 2, right: SPACING / 3, - } + }, }, state: { hovered: { @@ -160,7 +156,7 @@ export default function chat_panel(): any { bottom: 4, left: SPACING / 2, right: SPACING / 3, - } + }, }, state: { hovered: { @@ -171,6 +167,6 @@ export default function chat_panel(): any { }, sign_in_prompt: { default: text(layer, "sans", "base"), - } + }, } }