diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ce3edfa459..dc448aa17f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -29,7 +29,7 @@ use gpui::{ }, json::{self, ToJson}, platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent}, - text_layout::{self, Invisible, Line, RunStyle, TextLayoutCache}, + text_layout::{self, Line, RunStyle, TextLayoutCache}, AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext, MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, }; @@ -1696,12 +1696,28 @@ struct HighlightedChunk<'a> { is_tab: bool, } +#[derive(Debug)] pub struct LineWithInvisibles { pub line: Line, invisibles: Vec, } -// TODO kb deduplicate? + tests +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Invisible { + Tab { line_start_offset: usize }, + Whitespace { line_offset: usize }, +} + +impl Invisible { + #[cfg(test)] + fn offset(&self) -> usize { + *match self { + Self::Tab { line_start_offset } => line_start_offset, + Self::Whitespace { line_offset } => line_offset, + } + } +} + fn layout_highlighted_chunks<'a>( chunks: impl Iterator>, text_style: &TextStyle, @@ -1769,7 +1785,7 @@ fn layout_highlighted_chunks<'a>( )); // Line wrap pads its contents with fake whitespaces, - // avoid printing them. + // avoid printing them let inside_wrapped_string = ix > 0; if highlighted_chunk.is_tab { if non_whitespace_added || !inside_wrapped_string { @@ -2753,7 +2769,7 @@ mod tests { }; use gpui::TestAppContext; use settings::Settings; - use std::sync::Arc; + use std::{num::NonZeroU32, sync::Arc}; use util::test::sample_text; #[gpui::test] @@ -2833,4 +2849,87 @@ mod tests { element.paint(&mut scene, bounds, bounds, &mut state, editor, cx); }); } + + #[gpui::test] + fn test_invisible_drawing(cx: &mut TestAppContext) { + let tab_size = 4; + let initial_str = "\t\t\t\t\t\t\t\t| | a b c d "; + let (input_text, expected_invisibles) = { + let mut input_text = String::new(); + let mut expected_invisibles = Vec::new(); + let mut offset = 0; + + let mut push_char = |char_symbol| { + input_text.push(char_symbol); + let new_offset = match char_symbol { + '\t' => { + expected_invisibles.push(Invisible::Tab { + line_start_offset: offset, + }); + tab_size as usize + } + ' ' => { + expected_invisibles.push(Invisible::Whitespace { + line_offset: offset, + }); + 1 + } + _ => 1, + }; + offset += new_offset; + }; + + for input_char in initial_str.chars() { + push_char(input_char) + } + + (input_text, expected_invisibles) + }; + assert_eq!( + expected_invisibles.len(), + initial_str + .chars() + .filter(|initial_char| initial_char.is_whitespace()) + .count() + ); + + cx.update(|cx| { + let mut test_settings = Settings::test(cx); + test_settings.editor_defaults.show_invisibles = Some(ShowInvisibles::All); + test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap()); + cx.set_global(test_settings); + }); + let (_, editor) = cx.add_window(|cx| { + let buffer = MultiBuffer::build_simple(&input_text, cx); + Editor::new(EditorMode::Full, buffer, None, None, cx) + }); + + let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx))); + let (_, layout_state) = editor.update(cx, |editor, cx| { + let mut new_parents = Default::default(); + let mut notify_views_if_parents_change = Default::default(); + let mut layout_cx = LayoutContext::new( + cx, + &mut new_parents, + &mut notify_views_if_parents_change, + false, + ); + element.layout( + SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), + editor, + &mut layout_cx, + ) + }); + + let line_layouts = &layout_state.position_map.line_layouts; + let actual_invisibles = line_layouts + .iter() + .map(|line_with_invisibles| &line_with_invisibles.invisibles) + .flatten() + .sorted_by(|invisible_1, invisible_2| invisible_1.offset().cmp(&invisible_2.offset())) + .cloned() + .collect::>(); + + assert_eq!(expected_invisibles, actual_invisibles); + } } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 97424e08ed..25b5c3f430 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -211,12 +211,6 @@ pub struct Glyph { pub is_emoji: bool, } -#[derive(Debug, Clone)] -pub enum Invisible { - Tab { line_start_offset: usize }, - Whitespace { line_offset: usize }, -} - impl Line { fn new(layout: Arc, runs: &[(usize, RunStyle)]) -> Self { let mut style_runs = SmallVec::new();