diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f2e986b91b8bca419f7cf990d91ad15f66358ecc..b95c9312c5ccdfb5c4a6f283f540b682b92c3635 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -66,7 +66,7 @@ use std::{ use sum_tree::{Bias, TreeMap}; use tab_map::{TabMap, TabSnapshot}; use text::LineIndent; -use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext}; +use ui::{px, SharedString, WindowContext}; use unicode_segmentation::UnicodeSegmentation; use wrap_map::{WrapMap, WrapSnapshot}; @@ -541,11 +541,17 @@ pub struct HighlightStyles { pub suggestion: Option, } +#[derive(Clone)] +pub enum ChunkReplacement { + Renderer(ChunkRenderer), + Str(SharedString), +} + pub struct HighlightedChunk<'a> { pub text: &'a str, pub style: Option, pub is_tab: bool, - pub renderer: Option, + pub replacement: Option, } impl<'a> HighlightedChunk<'a> { @@ -557,7 +563,7 @@ impl<'a> HighlightedChunk<'a> { let mut text = self.text; let style = self.style; let is_tab = self.is_tab; - let renderer = self.renderer; + let renderer = self.replacement; iter::from_fn(move || { let mut prefix_len = 0; while let Some(&ch) = chars.peek() { @@ -573,30 +579,33 @@ impl<'a> HighlightedChunk<'a> { text: prefix, style, is_tab, - renderer: renderer.clone(), + replacement: renderer.clone(), }); } chars.next(); let (prefix, suffix) = text.split_at(ch.len_utf8()); text = suffix; if let Some(replacement) = replacement(ch) { - let background = editor_style.status.hint_background; - let underline = editor_style.status.hint; + let invisible_highlight = HighlightStyle { + background_color: Some(editor_style.status.hint_background), + underline: Some(UnderlineStyle { + color: Some(editor_style.status.hint), + thickness: px(1.), + wavy: false, + }), + ..Default::default() + }; + let invisible_style = if let Some(mut style) = style { + style.highlight(invisible_highlight); + style + } else { + invisible_highlight + }; return Some(HighlightedChunk { text: prefix, - style: None, + style: Some(invisible_style), is_tab: false, - renderer: Some(ChunkRenderer { - render: Arc::new(move |_| { - div() - .child(replacement) - .bg(background) - .text_decoration_1() - .text_decoration_color(underline) - .into_any_element() - }), - constrain_width: false, - }), + replacement: Some(ChunkReplacement::Str(replacement.into())), }); } else { let invisible_highlight = HighlightStyle { @@ -619,7 +628,7 @@ impl<'a> HighlightedChunk<'a> { text: prefix, style: Some(invisible_style), is_tab: false, - renderer: renderer.clone(), + replacement: renderer.clone(), }); } } @@ -631,7 +640,7 @@ impl<'a> HighlightedChunk<'a> { text: remainder, style, is_tab, - renderer: renderer.clone(), + replacement: renderer.clone(), }) } else { None @@ -895,7 +904,7 @@ impl DisplaySnapshot { text: chunk.text, style: highlight_style, is_tab: chunk.is_tab, - renderer: chunk.renderer, + replacement: chunk.renderer.map(ChunkReplacement::Renderer), } .highlight_invisibles(editor_style) }) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 56322ff9f50284dc158ee3a73f06949938eb5a89..7702134409f70106dde8c9f7b9a28b79eb62e64b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -16,8 +16,8 @@ use crate::{ items::BufferSearchHighlights, mouse_context_menu::{self, MenuPosition, MouseContextMenu}, scroll::scroll_amount::ScrollAmount, - BlockId, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, DisplayRow, - DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, + BlockId, ChunkReplacement, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, + DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint, @@ -34,8 +34,8 @@ use gpui::{ FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, - StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, - ViewContext, WeakView, WindowContext, + StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, View, ViewContext, + WeakView, WindowContext, }; use gpui::{ClickEvent, Subscription}; use itertools::Itertools; @@ -2019,7 +2019,7 @@ impl EditorElement { let chunks = snapshot.highlighted_chunks(rows.clone(), true, style); LineWithInvisibles::from_chunks( chunks, - &style.text, + &style, MAX_LINE_LEN, rows.len(), snapshot.mode, @@ -4372,7 +4372,7 @@ impl LineWithInvisibles { #[allow(clippy::too_many_arguments)] fn from_chunks<'a>( chunks: impl Iterator>, - text_style: &TextStyle, + editor_style: &EditorStyle, max_line_len: usize, max_line_count: usize, editor_mode: EditorMode, @@ -4380,6 +4380,7 @@ impl LineWithInvisibles { is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, cx: &mut WindowContext, ) -> Vec { + let text_style = &editor_style.text; let mut layouts = Vec::with_capacity(max_line_count); let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new(); let mut line = String::new(); @@ -4398,9 +4399,9 @@ impl LineWithInvisibles { text: "\n", style: None, is_tab: false, - renderer: None, + replacement: None, }]) { - if let Some(renderer) = highlighted_chunk.renderer { + if let Some(replacement) = highlighted_chunk.replacement { if !line.is_empty() { let shaped_line = cx .text_system() @@ -4413,42 +4414,71 @@ impl LineWithInvisibles { styles.clear(); } - let available_width = if renderer.constrain_width { - let chunk = if highlighted_chunk.text == ellipsis.as_ref() { - ellipsis.clone() - } else { - SharedString::from(Arc::from(highlighted_chunk.text)) - }; - let shaped_line = cx - .text_system() - .shape_line( - chunk, - font_size, - &[text_style.to_run(highlighted_chunk.text.len())], - ) - .unwrap(); - AvailableSpace::Definite(shaped_line.width) - } else { - AvailableSpace::MinContent - }; + match replacement { + ChunkReplacement::Renderer(renderer) => { + let available_width = if renderer.constrain_width { + let chunk = if highlighted_chunk.text == ellipsis.as_ref() { + ellipsis.clone() + } else { + SharedString::from(Arc::from(highlighted_chunk.text)) + }; + let shaped_line = cx + .text_system() + .shape_line( + chunk, + font_size, + &[text_style.to_run(highlighted_chunk.text.len())], + ) + .unwrap(); + AvailableSpace::Definite(shaped_line.width) + } else { + AvailableSpace::MinContent + }; - let mut element = (renderer.render)(&mut ChunkRendererContext { - context: cx, - max_width: text_width, - }); - let line_height = text_style.line_height_in_pixels(cx.rem_size()); - let size = element.layout_as_root( - size(available_width, AvailableSpace::Definite(line_height)), - cx, - ); + let mut element = (renderer.render)(&mut ChunkRendererContext { + context: cx, + max_width: text_width, + }); + let line_height = text_style.line_height_in_pixels(cx.rem_size()); + let size = element.layout_as_root( + size(available_width, AvailableSpace::Definite(line_height)), + cx, + ); - width += size.width; - len += highlighted_chunk.text.len(); - fragments.push(LineFragment::Element { - element: Some(element), - size, - len: highlighted_chunk.text.len(), - }); + width += size.width; + len += highlighted_chunk.text.len(); + fragments.push(LineFragment::Element { + element: Some(element), + size, + len: highlighted_chunk.text.len(), + }); + } + ChunkReplacement::Str(x) => { + let text_style = if let Some(style) = highlighted_chunk.style { + Cow::Owned(text_style.clone().highlight(style)) + } else { + Cow::Borrowed(text_style) + }; + + let run = TextRun { + len: x.len(), + font: text_style.font(), + color: text_style.color, + background_color: text_style.background_color, + underline: text_style.underline, + strikethrough: text_style.strikethrough, + }; + let line_layout = cx + .text_system() + .shape_line(x, font_size, &[run]) + .unwrap() + .with_len(highlighted_chunk.text.len()); + + width += line_layout.width; + len += highlighted_chunk.text.len(); + fragments.push(LineFragment::Text(line_layout)) + } + } } else { for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() { if ix > 0 { @@ -5992,7 +6022,7 @@ fn layout_line( let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style); LineWithInvisibles::from_chunks( chunks, - &style.text, + &style, MAX_LINE_LEN, 1, snapshot.mode, diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index b8b698a0427a68cfe58a405075b593a5ac4c8ab3..7c18684cbc4a53f4147e658c033f3c2e34bccb12 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -44,6 +44,21 @@ impl ShapedLine { self.layout.len } + /// Override the len, useful if you're rendering text a + /// as text b (e.g. rendering invisibles). + pub fn with_len(mut self, len: usize) -> Self { + let layout = self.layout.as_ref(); + self.layout = Arc::new(LineLayout { + font_size: layout.font_size, + width: layout.width, + ascent: layout.ascent, + descent: layout.descent, + runs: layout.runs.clone(), + len, + }); + self + } + /// Paint the line of text to the window. pub fn paint( &self, diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 7e5a43dee881f4de6cfd52a94e10aeed56b1222f..66eb914a30780dcf6a637cb679235cb91814c7cf 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -29,7 +29,7 @@ pub struct LineLayout { } /// A run of text that has been shaped . -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ShapedRun { /// The font id for this run pub font_id: FontId,