Use chunk-wise DisplayMap iteration when laying out lines

This commit is contained in:
Max Brunsfeld 2021-05-17 20:45:14 -07:00 committed by Antonio Scandurra
parent a9583d0074
commit c62a679e12
4 changed files with 101 additions and 111 deletions

View file

@ -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)

View file

@ -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<Item = char> + '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::<DisplayPoint, TransformSummary>();
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<Take<rope::Chars<'a>>>,
}
impl<'a> Iterator for Chars<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
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>,

View file

@ -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<Item = char> + '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<Self::Item> {
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::<String>(),
&map.snapshot(app.as_ref())
.chunks_at(DisplayPoint::new(1, 0), app.as_ref())
.collect::<String>()[0..10],
" b bb"
);
assert_eq!(
map.snapshot(app.as_ref())
.chars_at(DisplayPoint::new(1, 2), app.as_ref())
.take(10)
.collect::<String>(),
&map.snapshot(app.as_ref())
.chunks_at(DisplayPoint::new(1, 2), app.as_ref())
.collect::<String>()[0..10],
" b bbbb"
);
assert_eq!(
map.snapshot(app.as_ref())
.chars_at(DisplayPoint::new(1, 6), app.as_ref())
.take(13)
.collect::<String>(),
&map.snapshot(app.as_ref())
.chunks_at(DisplayPoint::new(1, 6), app.as_ref())
.collect::<String>()[0..13],
" bbbbb\nc c"
);
}

View file

@ -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);
}