From c62a679e12f31fa62db5468fa9ea6b26345183bb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 17 May 2021 20:45:14 -0700 Subject: [PATCH] Use chunk-wise DisplayMap iteration when laying out lines --- zed/src/editor/buffer_view.rs | 21 ++-- zed/src/editor/display_map/fold_map.rs | 55 ++--------- zed/src/editor/display_map/mod.rs | 132 +++++++++++++++---------- zed/src/editor/movement.rs | 4 +- 4 files changed, 101 insertions(+), 111 deletions(-) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index ff8ea18cf0..84f15aa44b 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2140,23 +2140,26 @@ impl BufferView { let mut layouts = Vec::with_capacity(rows.len()); let mut line = String::new(); - let mut line_len = 0; let mut row = rows.start; let snapshot = self.display_map.snapshot(ctx); - let chars = snapshot.chars_at(DisplayPoint::new(rows.start, 0), ctx); - for char in chars.chain(Some('\n')) { - if char == '\n' { - layouts.push(layout_cache.layout_str(&line, font_size, &[(0..line_len, font_id)])); + let chunks = snapshot.chunks_at(DisplayPoint::new(rows.start, 0), ctx); + for (chunk_row, chunk_line) in chunks + .chain(Some("\n")) + .flat_map(|chunk| chunk.split("\n").enumerate()) + { + if chunk_row > 0 { + layouts.push(layout_cache.layout_str( + &line, + font_size, + &[(0..line.chars().count(), font_id)], + )); line.clear(); - line_len = 0; row += 1; if row == rows.end { break; } - } else { - line_len += 1; - line.push(char); } + line.push_str(chunk_line); } Ok(layouts) diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 4b9115a52b..0176da26de 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -3,7 +3,7 @@ use super::{ Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset, }; use crate::{ - editor::{buffer, rope}, + editor::buffer, sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree}, time, }; @@ -11,7 +11,6 @@ use gpui::{AppContext, ModelHandle}; use parking_lot::{Mutex, MutexGuard}; use std::{ cmp::{self, Ordering}, - iter::Take, ops::Range, }; @@ -442,19 +441,16 @@ impl FoldMapSnapshot { } } - pub fn chars_at<'a>(&'a self, point: DisplayPoint, ctx: &'a AppContext) -> Chars<'a> { + pub fn chars_at<'a>( + &'a self, + point: DisplayPoint, + ctx: &'a AppContext, + ) -> impl Iterator + 'a { let offset = self.to_display_offset(point, ctx); - let mut cursor = self.transforms.cursor(); - cursor.seek(&offset, SeekBias::Right, &()); - Chars { - cursor, - offset: offset.0, - buffer: self.buffer.read(ctx), - buffer_chars: None, - } + self.chunks_at(offset, ctx).flat_map(str::chars) } - fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset { + pub fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> DisplayOffset { let mut cursor = self.transforms.cursor::(); cursor.seek(&point, SeekBias::Right, &()); let overshoot = point.0 - cursor.start().display.lines; @@ -628,41 +624,6 @@ impl<'a> Iterator for BufferRows<'a> { } } -pub struct Chars<'a> { - cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, - offset: usize, - buffer: &'a Buffer, - buffer_chars: Option>>, -} - -impl<'a> Iterator for Chars<'a> { - type Item = char; - - fn next(&mut self) -> Option { - if let Some(c) = self.buffer_chars.as_mut().and_then(|chars| chars.next()) { - self.offset += c.len_utf8(); - return Some(c); - } - - while self.offset == self.cursor.end().display.bytes && self.cursor.item().is_some() { - self.cursor.next(); - } - - self.cursor.item().and_then(|transform| { - if let Some(c) = transform.display_text { - self.offset += c.len(); - Some(c.chars().next().unwrap()) - } else { - let overshoot = self.offset - self.cursor.start().display.bytes; - let buffer_start = self.cursor.start().buffer.bytes + overshoot; - let char_count = self.cursor.end().buffer.bytes - buffer_start; - self.buffer_chars = Some(self.buffer.chars_at(buffer_start).take(char_count)); - self.next() - } - }) - } -} - pub struct Chunks<'a> { transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, buffer_chunks: buffer::ChunksIter<'a>, diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 94b91301be..a0f3c9d34a 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -67,15 +67,24 @@ impl DisplayMap { pub fn text(&self, ctx: &AppContext) -> String { self.snapshot(ctx) - .chars_at(DisplayPoint::zero(), ctx) + .chunks_at(DisplayPoint::zero(), ctx) .collect() } pub fn line(&self, display_row: u32, ctx: &AppContext) -> String { - self.snapshot(ctx) - .chars_at(DisplayPoint::new(display_row, 0), ctx) - .take_while(|c| *c != '\n') - .collect() + let mut result = String::new(); + for chunk in self + .snapshot(ctx) + .chunks_at(DisplayPoint::new(display_row, 0), ctx) + { + if let Some(ix) = chunk.find('\n') { + result.push_str(&chunk[0..ix]); + break; + } else { + result.push_str(chunk); + } + } + result } pub fn line_indent(&self, display_row: u32, ctx: &AppContext) -> (u32, bool) { @@ -83,7 +92,8 @@ impl DisplayMap { let mut is_blank = true; for c in self .snapshot(ctx) - .chars_at(DisplayPoint::new(display_row, 0), ctx) + .chunks_at(DisplayPoint::new(display_row, 0), ctx) + .flat_map(str::chars) { if c == ' ' { indent += 1; @@ -132,21 +142,28 @@ impl DisplayMapSnapshot { self.folds_snapshot.buffer_rows(start_row) } - pub fn chars_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chars<'a> { + pub fn chunks_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Chunks<'a> { let column = point.column() as usize; - let (point, to_next_stop) = self.collapse_tabs(point, Bias::Left, app); - let mut fold_chars = self.folds_snapshot.chars_at(point, app); - if to_next_stop > 0 { - fold_chars.next(); - } - Chars { - fold_chars, + let (point, _) = self.collapse_tabs(point, Bias::Left, app); + let fold_chunks = self + .folds_snapshot + .chunks_at(self.folds_snapshot.to_display_offset(point, app), app); + Chunks { + fold_chunks, column, - to_next_stop, tab_size: self.tab_size, + chunk: "", } } + pub fn chars_at<'a>( + &'a self, + point: DisplayPoint, + app: &'a AppContext, + ) -> impl Iterator + 'a { + self.chunks_at(point, app).flat_map(str::chars) + } + fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint { let chars = self .folds_snapshot @@ -238,38 +255,50 @@ impl Anchor { } } -pub struct Chars<'a> { - fold_chars: fold_map::Chars<'a>, +pub struct Chunks<'a> { + fold_chunks: fold_map::Chunks<'a>, + chunk: &'a str, column: usize, - to_next_stop: usize, tab_size: usize, } -impl<'a> Iterator for Chars<'a> { - type Item = char; +impl<'a> Iterator for Chunks<'a> { + type Item = &'a str; fn next(&mut self) -> Option { - if self.to_next_stop > 0 { - self.to_next_stop -= 1; - self.column += 1; - Some(' ') - } else { - self.fold_chars.next().map(|c| match c { - '\t' => { - self.to_next_stop = self.tab_size - self.column % self.tab_size - 1; - self.column += 1; - ' ' - } - '\n' => { - self.column = 0; - c - } - _ => { - self.column += 1; - c - } - }) + // Handles a tab width <= 16 + const SPACES: &'static str = " "; + + if self.chunk.is_empty() { + if let Some(chunk) = self.fold_chunks.next() { + self.chunk = chunk; + } else { + return None; + } } + + for (ix, c) in self.chunk.char_indices() { + match c { + '\t' => { + if ix > 0 { + let (prefix, suffix) = self.chunk.split_at(ix); + self.chunk = suffix; + return Some(prefix); + } else { + self.chunk = &self.chunk[1..]; + let len = self.tab_size - self.column % self.tab_size; + self.column += len; + return Some(&SPACES[0..len]); + } + } + '\n' => self.column = 0, + _ => self.column += 1, + } + } + + let result = Some(self.chunk); + self.chunk = ""; + result } } @@ -321,7 +350,7 @@ mod tests { use crate::test::*; #[gpui::test] - fn test_chars_at(app: &mut gpui::MutableAppContext) { + fn test_chunks_at(app: &mut gpui::MutableAppContext) { let text = sample_text(6, 6); let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); let map = DisplayMap::new(buffer.clone(), 4, app.as_ref()); @@ -340,24 +369,21 @@ mod tests { .unwrap(); assert_eq!( - map.snapshot(app.as_ref()) - .chars_at(DisplayPoint::new(1, 0), app.as_ref()) - .take(10) - .collect::(), + &map.snapshot(app.as_ref()) + .chunks_at(DisplayPoint::new(1, 0), app.as_ref()) + .collect::()[0..10], " b bb" ); assert_eq!( - map.snapshot(app.as_ref()) - .chars_at(DisplayPoint::new(1, 2), app.as_ref()) - .take(10) - .collect::(), + &map.snapshot(app.as_ref()) + .chunks_at(DisplayPoint::new(1, 2), app.as_ref()) + .collect::()[0..10], " b bbbb" ); assert_eq!( - map.snapshot(app.as_ref()) - .chars_at(DisplayPoint::new(1, 6), app.as_ref()) - .take(13) - .collect::(), + &map.snapshot(app.as_ref()) + .chunks_at(DisplayPoint::new(1, 6), app.as_ref()) + .collect::()[0..13], " bbbbb\nc c" ); } diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 162d593ef6..3ac135d08c 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -114,7 +114,7 @@ pub fn prev_word_boundary( } prev_c = Some(c); - column += 1; + column += c.len_utf8() as u32; } Ok(boundary) } @@ -135,7 +135,7 @@ pub fn next_word_boundary( *point.row_mut() += 1; *point.column_mut() = 0; } else { - *point.column_mut() += 1; + *point.column_mut() += c.len_utf8() as u32; } prev_c = Some(c); }