From ad731ea6d2f6744584cf7a6255f824d1dc515bd1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 4 May 2023 13:53:33 +0300 Subject: [PATCH] Draft invisibles' tabs display --- crates/editor/src/element.rs | 6 ++++- crates/gpui/src/elements/text.rs | 45 ++++++++++++++++++++++++++------ crates/gpui/src/text_layout.rs | 35 +++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index f2e8aca81d..a2b160b256 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1359,7 +1359,11 @@ impl EditorElement { highlight_style = Some(diagnostic_highlight); } - (chunk.text, highlight_style) + HighlightedChunk { + chunk: chunk.text, + style: highlight_style, + is_tab: chunk.is_tab, + } }); layout_highlighted_chunks( chunks, diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 829511c62f..b71c5a2809 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -6,7 +6,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{ToJson, Value}, - text_layout::{Line, RunStyle, ShapedBoundary}, + text_layout::{Invisible, Line, RunStyle, ShapedBoundary}, AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext, }; @@ -114,7 +114,11 @@ impl Element for Text { } else { result = None; } - result + result.map(|(chunk, style)| HighlightedChunk { + chunk, + style, + is_tab: false, + }) }); // Perform shaping on these highlighted chunks @@ -337,9 +341,25 @@ impl Element for Text { } } +pub struct HighlightedChunk<'a> { + pub chunk: &'a str, + pub style: Option, + pub is_tab: bool, +} + +impl<'a> HighlightedChunk<'a> { + fn plain_str(str_symbols: &'a str) -> Self { + Self { + chunk: str_symbols, + style: None, + is_tab: str_symbols == "\t", + } + } +} + /// Perform text layout on a series of highlighted chunks of text. pub fn layout_highlighted_chunks<'a>( - chunks: impl Iterator)>, + chunks: impl Iterator>, text_style: &TextStyle, text_layout_cache: &TextLayoutCache, font_cache: &Arc, @@ -348,13 +368,17 @@ pub fn layout_highlighted_chunks<'a>( ) -> Vec { let mut layouts = Vec::with_capacity(max_line_count); let mut line = String::new(); + let mut invisibles = Vec::new(); let mut styles = Vec::new(); let mut row = 0; let mut line_exceeded_max_len = false; - for (chunk, highlight_style) in chunks.chain([("\n", Default::default())]) { - for (ix, mut line_chunk) in chunk.split('\n').enumerate() { + for highlighted_chunk in chunks.chain(std::iter::once(HighlightedChunk::plain_str("\n"))) { + for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() { if ix > 0 { - layouts.push(text_layout_cache.layout_str(&line, text_style.font_size, &styles)); + let mut laid_out_line = + text_layout_cache.layout_str(&line, text_style.font_size, &styles); + laid_out_line.invisibles.extend(invisibles.drain(..)); + layouts.push(laid_out_line); line.clear(); styles.clear(); row += 1; @@ -365,7 +389,7 @@ pub fn layout_highlighted_chunks<'a>( } if !line_chunk.is_empty() && !line_exceeded_max_len { - let text_style = if let Some(style) = highlight_style { + let text_style = if let Some(style) = highlighted_chunk.style { text_style .clone() .highlight(style, font_cache) @@ -384,7 +408,6 @@ pub fn layout_highlighted_chunks<'a>( line_exceeded_max_len = true; } - line.push_str(line_chunk); styles.push(( line_chunk.len(), RunStyle { @@ -393,6 +416,12 @@ pub fn layout_highlighted_chunks<'a>( underline: text_style.underline, }, )); + if highlighted_chunk.is_tab { + invisibles.push(Invisible::Tab { + range: line.len()..line.len() + line_chunk.len(), + }); + } + line.push_str(line_chunk); } } } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 25b5c3f430..9e3f21a084 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -11,6 +11,7 @@ use crate::{ window::WindowContext, SceneBuilder, }; +use itertools::Itertools; use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; @@ -178,6 +179,7 @@ impl<'a> Hash for CacheKeyRef<'a> { pub struct Line { layout: Arc, style_runs: SmallVec<[StyleRun; 32]>, + pub invisibles: SmallVec<[Invisible; 32]>, } #[derive(Debug, Clone, Copy)] @@ -211,6 +213,12 @@ pub struct Glyph { pub is_emoji: bool, } +#[derive(Debug, Clone)] +pub enum Invisible { + Tab { range: std::ops::Range }, + Whitespace { range: std::ops::Range }, +} + impl Line { fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { let mut style_runs = SmallVec::new(); @@ -221,7 +229,11 @@ impl Line { underline: style.underline, }); } - Self { layout, style_runs } + Self { + layout, + style_runs, + invisibles: SmallVec::new(), + } } pub fn runs(&self) -> &[Run] { @@ -298,6 +310,16 @@ impl Line { let mut color = Color::black(); let mut underline = None; + let tab_ranges = self + .invisibles + .iter() + .filter_map(|invisible| match invisible { + Invisible::Tab { range } => Some(range), + Invisible::Whitespace { .. } => None, + }) + .sorted_by(|tab_range_1, tab_range_2| tab_range_1.start.cmp(&tab_range_2.start)) + .collect::>(); + for run in &self.layout.runs { let max_glyph_width = cx .font_cache @@ -364,10 +386,19 @@ impl Line { origin: glyph_origin, }); } else { + let id = if tab_ranges.iter().any(|tab_range| { + tab_range.start <= glyph.index && glyph.index < tab_range.end + }) { + // TODO kb get a proper (cached) glyph + glyph.id + 100 + } else { + glyph.id + }; + scene.push_glyph(scene::Glyph { font_id: run.font_id, font_size: self.layout.font_size, - id: glyph.id, + id, origin: glyph_origin, color, });