mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-03 00:39:51 +00:00
Use chunk-wise DisplayMap iteration when laying out lines
This commit is contained in:
parent
a9583d0074
commit
c62a679e12
4 changed files with 101 additions and 111 deletions
|
@ -2140,23 +2140,26 @@ impl BufferView {
|
||||||
|
|
||||||
let mut layouts = Vec::with_capacity(rows.len());
|
let mut layouts = Vec::with_capacity(rows.len());
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
let mut line_len = 0;
|
|
||||||
let mut row = rows.start;
|
let mut row = rows.start;
|
||||||
let snapshot = self.display_map.snapshot(ctx);
|
let snapshot = self.display_map.snapshot(ctx);
|
||||||
let chars = snapshot.chars_at(DisplayPoint::new(rows.start, 0), ctx);
|
let chunks = snapshot.chunks_at(DisplayPoint::new(rows.start, 0), ctx);
|
||||||
for char in chars.chain(Some('\n')) {
|
for (chunk_row, chunk_line) in chunks
|
||||||
if char == '\n' {
|
.chain(Some("\n"))
|
||||||
layouts.push(layout_cache.layout_str(&line, font_size, &[(0..line_len, font_id)]));
|
.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.clear();
|
||||||
line_len = 0;
|
|
||||||
row += 1;
|
row += 1;
|
||||||
if row == rows.end {
|
if row == rows.end {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
line_len += 1;
|
|
||||||
line.push(char);
|
|
||||||
}
|
}
|
||||||
|
line.push_str(chunk_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(layouts)
|
Ok(layouts)
|
||||||
|
|
|
@ -3,7 +3,7 @@ use super::{
|
||||||
Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset,
|
Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
editor::{buffer, rope},
|
editor::buffer,
|
||||||
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
|
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
|
||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,6 @@ use gpui::{AppContext, ModelHandle};
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
iter::Take,
|
|
||||||
ops::Range,
|
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 offset = self.to_display_offset(point, ctx);
|
||||||
let mut cursor = self.transforms.cursor();
|
self.chunks_at(offset, ctx).flat_map(str::chars)
|
||||||
cursor.seek(&offset, SeekBias::Right, &());
|
|
||||||
Chars {
|
|
||||||
cursor,
|
|
||||||
offset: offset.0,
|
|
||||||
buffer: self.buffer.read(ctx),
|
|
||||||
buffer_chars: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>();
|
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
|
||||||
cursor.seek(&point, SeekBias::Right, &());
|
cursor.seek(&point, SeekBias::Right, &());
|
||||||
let overshoot = point.0 - cursor.start().display.lines;
|
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> {
|
pub struct Chunks<'a> {
|
||||||
transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
|
transform_cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
|
||||||
buffer_chunks: buffer::ChunksIter<'a>,
|
buffer_chunks: buffer::ChunksIter<'a>,
|
||||||
|
|
|
@ -67,15 +67,24 @@ impl DisplayMap {
|
||||||
|
|
||||||
pub fn text(&self, ctx: &AppContext) -> String {
|
pub fn text(&self, ctx: &AppContext) -> String {
|
||||||
self.snapshot(ctx)
|
self.snapshot(ctx)
|
||||||
.chars_at(DisplayPoint::zero(), ctx)
|
.chunks_at(DisplayPoint::zero(), ctx)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line(&self, display_row: u32, ctx: &AppContext) -> String {
|
pub fn line(&self, display_row: u32, ctx: &AppContext) -> String {
|
||||||
self.snapshot(ctx)
|
let mut result = String::new();
|
||||||
.chars_at(DisplayPoint::new(display_row, 0), ctx)
|
for chunk in self
|
||||||
.take_while(|c| *c != '\n')
|
.snapshot(ctx)
|
||||||
.collect()
|
.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) {
|
pub fn line_indent(&self, display_row: u32, ctx: &AppContext) -> (u32, bool) {
|
||||||
|
@ -83,7 +92,8 @@ impl DisplayMap {
|
||||||
let mut is_blank = true;
|
let mut is_blank = true;
|
||||||
for c in self
|
for c in self
|
||||||
.snapshot(ctx)
|
.snapshot(ctx)
|
||||||
.chars_at(DisplayPoint::new(display_row, 0), ctx)
|
.chunks_at(DisplayPoint::new(display_row, 0), ctx)
|
||||||
|
.flat_map(str::chars)
|
||||||
{
|
{
|
||||||
if c == ' ' {
|
if c == ' ' {
|
||||||
indent += 1;
|
indent += 1;
|
||||||
|
@ -132,21 +142,28 @@ impl DisplayMapSnapshot {
|
||||||
self.folds_snapshot.buffer_rows(start_row)
|
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 column = point.column() as usize;
|
||||||
let (point, to_next_stop) = self.collapse_tabs(point, Bias::Left, app);
|
let (point, _) = self.collapse_tabs(point, Bias::Left, app);
|
||||||
let mut fold_chars = self.folds_snapshot.chars_at(point, app);
|
let fold_chunks = self
|
||||||
if to_next_stop > 0 {
|
.folds_snapshot
|
||||||
fold_chars.next();
|
.chunks_at(self.folds_snapshot.to_display_offset(point, app), app);
|
||||||
}
|
Chunks {
|
||||||
Chars {
|
fold_chunks,
|
||||||
fold_chars,
|
|
||||||
column,
|
column,
|
||||||
to_next_stop,
|
|
||||||
tab_size: self.tab_size,
|
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 {
|
fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint {
|
||||||
let chars = self
|
let chars = self
|
||||||
.folds_snapshot
|
.folds_snapshot
|
||||||
|
@ -238,38 +255,50 @@ impl Anchor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Chars<'a> {
|
pub struct Chunks<'a> {
|
||||||
fold_chars: fold_map::Chars<'a>,
|
fold_chunks: fold_map::Chunks<'a>,
|
||||||
|
chunk: &'a str,
|
||||||
column: usize,
|
column: usize,
|
||||||
to_next_stop: usize,
|
|
||||||
tab_size: usize,
|
tab_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Chars<'a> {
|
impl<'a> Iterator for Chunks<'a> {
|
||||||
type Item = char;
|
type Item = &'a str;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.to_next_stop > 0 {
|
// Handles a tab width <= 16
|
||||||
self.to_next_stop -= 1;
|
const SPACES: &'static str = " ";
|
||||||
self.column += 1;
|
|
||||||
Some(' ')
|
if self.chunk.is_empty() {
|
||||||
} else {
|
if let Some(chunk) = self.fold_chunks.next() {
|
||||||
self.fold_chars.next().map(|c| match c {
|
self.chunk = chunk;
|
||||||
'\t' => {
|
} else {
|
||||||
self.to_next_stop = self.tab_size - self.column % self.tab_size - 1;
|
return None;
|
||||||
self.column += 1;
|
}
|
||||||
' '
|
|
||||||
}
|
|
||||||
'\n' => {
|
|
||||||
self.column = 0;
|
|
||||||
c
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.column += 1;
|
|
||||||
c
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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::*;
|
use crate::test::*;
|
||||||
|
|
||||||
#[gpui::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 text = sample_text(6, 6);
|
||||||
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
|
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
|
||||||
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
|
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
|
||||||
|
@ -340,24 +369,21 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.snapshot(app.as_ref())
|
&map.snapshot(app.as_ref())
|
||||||
.chars_at(DisplayPoint::new(1, 0), app.as_ref())
|
.chunks_at(DisplayPoint::new(1, 0), app.as_ref())
|
||||||
.take(10)
|
.collect::<String>()[0..10],
|
||||||
.collect::<String>(),
|
|
||||||
" b bb"
|
" b bb"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.snapshot(app.as_ref())
|
&map.snapshot(app.as_ref())
|
||||||
.chars_at(DisplayPoint::new(1, 2), app.as_ref())
|
.chunks_at(DisplayPoint::new(1, 2), app.as_ref())
|
||||||
.take(10)
|
.collect::<String>()[0..10],
|
||||||
.collect::<String>(),
|
|
||||||
" b bbbb"
|
" b bbbb"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.snapshot(app.as_ref())
|
&map.snapshot(app.as_ref())
|
||||||
.chars_at(DisplayPoint::new(1, 6), app.as_ref())
|
.chunks_at(DisplayPoint::new(1, 6), app.as_ref())
|
||||||
.take(13)
|
.collect::<String>()[0..13],
|
||||||
.collect::<String>(),
|
|
||||||
" bbbbb\nc c"
|
" bbbbb\nc c"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ pub fn prev_word_boundary(
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_c = Some(c);
|
prev_c = Some(c);
|
||||||
column += 1;
|
column += c.len_utf8() as u32;
|
||||||
}
|
}
|
||||||
Ok(boundary)
|
Ok(boundary)
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ pub fn next_word_boundary(
|
||||||
*point.row_mut() += 1;
|
*point.row_mut() += 1;
|
||||||
*point.column_mut() = 0;
|
*point.column_mut() = 0;
|
||||||
} else {
|
} else {
|
||||||
*point.column_mut() += 1;
|
*point.column_mut() += c.len_utf8() as u32;
|
||||||
}
|
}
|
||||||
prev_c = Some(c);
|
prev_c = Some(c);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue