From 467059e467d4100197a9cda19f88f2ae2cd36e73 Mon Sep 17 00:00:00 2001 From: reya Date: Tue, 3 Mar 2026 15:39:01 +0700 Subject: [PATCH] replace token with user's profile --- crates/chat_ui/src/lib.rs | 5 +- crates/chat_ui/src/text.rs | 319 ++++++++++++++++++------------------- 2 files changed, 163 insertions(+), 161 deletions(-) diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index de98cec..37ef7dd 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -699,10 +699,13 @@ impl ChatPanel { if let Some(message) = self.messages.iter().nth(ix) { match message { Message::User(rendered) => { + let persons = PersonRegistry::global(cx); let text = self .rendered_texts_by_id .entry(rendered.id) - .or_insert_with(|| RenderedText::new(&rendered.content, &rendered.mentions)) + .or_insert_with(|| { + RenderedText::new(&rendered.content, &rendered.mentions, &persons, cx) + }) .element(ix.into(), window, cx); self.render_text_message(ix, rendered, text, cx) diff --git a/crates/chat_ui/src/text.rs b/crates/chat_ui/src/text.rs index 32814ba..982ca11 100644 --- a/crates/chat_ui/src/text.rs +++ b/crates/chat_ui/src/text.rs @@ -4,9 +4,10 @@ use std::sync::Arc; use chat::Mention; use common::RangeExt; use gpui::{ - AnyElement, App, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, + AnyElement, App, ElementId, Entity, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, SharedString, StrikethroughStyle, StyledText, UnderlineStyle, Window, }; +use person::PersonRegistry; use theme::ActiveTheme; #[allow(clippy::enum_variant_names)] @@ -34,7 +35,12 @@ pub struct RenderedText { } impl RenderedText { - pub fn new(content: &str, mentions: &[Mention]) -> Self { + pub fn new( + content: &str, + mentions: &[Mention], + persons: &Entity, + cx: &App, + ) -> Self { let mut text = String::new(); let mut highlights = Vec::new(); let mut link_ranges = Vec::new(); @@ -47,6 +53,8 @@ impl RenderedText { &mut highlights, &mut link_ranges, &mut link_urls, + persons, + cx, ); text.truncate(text.trim_end().len()); @@ -117,6 +125,7 @@ impl RenderedText { } } +#[allow(clippy::too_many_arguments)] fn render_plain_text_mut( block: &str, mut mentions: &[Mention], @@ -124,6 +133,8 @@ fn render_plain_text_mut( highlights: &mut Vec<(Range, Highlight)>, link_ranges: &mut Vec>, link_urls: &mut Vec, + persons: &Entity, + cx: &App, ) { use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; @@ -141,90 +152,65 @@ fn render_plain_text_mut( match event { Event::Text(t) => { + // Process text with mention replacements + let t_str = t.as_ref(); + let mut last_processed = 0; + while let Some(mention) = mentions.first() { if !source_range.contains_inclusive(&mention.range) { break; } + + // Calculate positions within the current text + let mention_start_in_text = mention.range.start - source_range.start; + let mention_end_in_text = mention.range.end - source_range.start; + + // Add text before this mention + if mention_start_in_text > last_processed { + let before_mention = &t_str[last_processed..mention_start_in_text]; + process_text_segment( + before_mention, + prev_len + last_processed, + bold_depth, + italic_depth, + strikethrough_depth, + link_url.clone(), + text, + highlights, + link_ranges, + link_urls, + ); + } + + // Process the mention replacement + let profile = persons.read(cx).get(&mention.public_key, cx); + let replacement_text = format!("@{}", profile.name()); + + let replacement_start = text.len(); + text.push_str(&replacement_text); + let replacement_end = text.len(); + + highlights.push((replacement_start..replacement_end, Highlight::Mention)); + + last_processed = mention_end_in_text; mentions = &mentions[1..]; - let range = (prev_len + mention.range.start - source_range.start) - ..(prev_len + mention.range.end - source_range.start); - highlights.push((range.clone(), Highlight::Mention)); } - text.push_str(t.as_ref()); - - let mut style = HighlightStyle::default(); - - if bold_depth > 0 { - style.font_weight = Some(FontWeight::BOLD); - } - - if italic_depth > 0 { - style.font_style = Some(FontStyle::Italic); - } - - if strikethrough_depth > 0 { - style.strikethrough = Some(StrikethroughStyle { - thickness: 1.0.into(), - ..Default::default() - }); - } - - let last_run_len = if let Some(link_url) = link_url.clone() { - link_ranges.push(prev_len..text.len()); - link_urls.push(link_url); - style.underline = Some(UnderlineStyle { - thickness: 1.0.into(), - ..Default::default() - }); - prev_len - } else { - // Manually scan for links - let mut finder = linkify::LinkFinder::new(); - finder.kinds(&[linkify::LinkKind::Url]); - let mut last_link_len = prev_len; - for link in finder.links(&t) { - let start = link.start(); - let end = link.end(); - let range = (prev_len + start)..(prev_len + end); - link_ranges.push(range.clone()); - link_urls.push(link.as_str().to_string()); - - // If there is a style before we match a link, we have to add this to the highlighted ranges - if style != HighlightStyle::default() && last_link_len < link.start() { - highlights - .push((last_link_len..link.start(), Highlight::Highlight(style))); - } - - highlights.push(( - range, - Highlight::Highlight(HighlightStyle { - underline: Some(UnderlineStyle { - thickness: 1.0.into(), - ..Default::default() - }), - ..style - }), - )); - - last_link_len = end; - } - last_link_len - }; - - if style != HighlightStyle::default() && last_run_len < text.len() { - let mut new_highlight = true; - if let Some((last_range, last_style)) = highlights.last_mut() { - if last_range.end == last_run_len - && last_style == &Highlight::Highlight(style) - { - last_range.end = text.len(); - new_highlight = false; - } - } - if new_highlight { - highlights.push((last_run_len..text.len(), Highlight::Highlight(style))); - } + // Add any remaining text after the last mention + if last_processed < t_str.len() { + let remaining_text = &t_str[last_processed..]; + process_text_segment( + remaining_text, + prev_len + last_processed, + bold_depth, + italic_depth, + strikethrough_depth, + link_url.clone(), + text, + highlights, + link_ranges, + link_urls, + ); } } Event::Code(t) => { @@ -291,6 +277,100 @@ fn render_plain_text_mut( } } +#[allow(clippy::too_many_arguments)] +fn process_text_segment( + segment: &str, + segment_start: usize, + bold_depth: i32, + italic_depth: i32, + strikethrough_depth: i32, + link_url: Option, + text: &mut String, + highlights: &mut Vec<(Range, Highlight)>, + link_ranges: &mut Vec>, + link_urls: &mut Vec, +) { + // Build the style for this segment + let mut style = HighlightStyle::default(); + if bold_depth > 0 { + style.font_weight = Some(FontWeight::BOLD); + } + if italic_depth > 0 { + style.font_style = Some(FontStyle::Italic); + } + if strikethrough_depth > 0 { + style.strikethrough = Some(StrikethroughStyle { + thickness: 1.0.into(), + ..Default::default() + }); + } + + // Add the text + text.push_str(segment); + let text_end = text.len(); + + if let Some(link_url) = link_url { + // Handle as a markdown link + link_ranges.push(segment_start..text_end); + link_urls.push(link_url); + style.underline = Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }); + + // Add highlight for the entire linked segment + if style != HighlightStyle::default() { + highlights.push((segment_start..text_end, Highlight::Highlight(style))); + } + } else { + // Handle link detection within the segment + let mut finder = linkify::LinkFinder::new(); + finder.kinds(&[linkify::LinkKind::Url]); + let mut last_link_pos = 0; + + for link in finder.links(segment) { + let start = link.start(); + let end = link.end(); + + // Add non-link text before this link + if start > last_link_pos { + let non_link_start = segment_start + last_link_pos; + let non_link_end = segment_start + start; + + if style != HighlightStyle::default() { + highlights.push((non_link_start..non_link_end, Highlight::Highlight(style))); + } + } + + // Add the link + let range = (segment_start + start)..(segment_start + end); + link_ranges.push(range.clone()); + link_urls.push(link.as_str().to_string()); + + // Apply link styling (underline + existing style) + let mut link_style = style; + link_style.underline = Some(UnderlineStyle { + thickness: 1.0.into(), + ..Default::default() + }); + + highlights.push((range, Highlight::Highlight(link_style))); + + last_link_pos = end; + } + + // Add any remaining text after the last link + if last_link_pos < segment.len() { + let remaining_start = segment_start + last_link_pos; + let remaining_end = segment_start + segment.len(); + + if style != HighlightStyle::default() { + highlights.push((remaining_start..remaining_end, Highlight::Highlight(style))); + } + } + } +} + fn new_paragraph(text: &mut String, list_stack: &mut [(Option, bool)]) { let mut is_subsequent_paragraph_of_list = false; if let Some((_, has_content)) = list_stack.last_mut() { @@ -315,84 +395,3 @@ fn new_paragraph(text: &mut String, list_stack: &mut [(Option, bool)]) { text.push_str(" "); } } - -/* -fn render_pubkey( - public_key: PublicKey, - text: &mut String, - range: &Range, - highlights: &mut Vec<(Range, Highlight)>, - link_ranges: &mut Vec>, - link_urls: &mut Vec, - cx: &App, -) { - let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get(&public_key, cx); - let display_name = format!("@{}", profile.name()); - - text.replace_range(range.clone(), &display_name); - - let new_length = display_name.len(); - let length_diff = new_length as isize - (range.end - range.start) as isize; - let new_range = range.start..(range.start + new_length); - - highlights.push((new_range.clone(), Highlight::Nostr)); - link_ranges.push(new_range); - link_urls.push(format!("nostr:{}", profile.public_key().to_hex())); - - if length_diff != 0 { - adjust_ranges(highlights, link_ranges, range.end, length_diff); - } -} - -fn render_bech32( - bech32: String, - text: &mut String, - range: &Range, - highlights: &mut Vec<(Range, Highlight)>, - link_ranges: &mut Vec>, - link_urls: &mut Vec, -) { - let njump_url = format!("https://njump.me/{bech32}"); - let shortened_entity = format_shortened_entity(&bech32); - let display_text = format!("https://njump.me/{shortened_entity}"); - - text.replace_range(range.clone(), &display_text); - - let new_length = display_text.len(); - let length_diff = new_length as isize - (range.end - range.start) as isize; - let new_range = range.start..(range.start + new_length); - - highlights.push((new_range.clone(), Highlight::Link)); - link_ranges.push(new_range); - link_urls.push(njump_url); - - if length_diff != 0 { - adjust_ranges(highlights, link_ranges, range.end, length_diff); - } -} - -// Helper function to adjust ranges when text length changes -fn adjust_ranges( - highlights: &mut [(Range, Highlight)], - link_ranges: &mut [Range], - position: usize, - length_diff: isize, -) { - // Adjust highlight ranges - for (range, _) in highlights.iter_mut() { - if range.start > position { - range.start = (range.start as isize + length_diff) as usize; - range.end = (range.end as isize + length_diff) as usize; - } - } - - // Adjust link ranges - for range in link_ranges.iter_mut() { - if range.start > position { - range.start = (range.start as isize + length_diff) as usize; - range.end = (range.end as isize + length_diff) as usize; - } - } -} -*/