mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Preserve indentation when soft-wrapping
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
2f1a5c48d3
commit
164cafa57d
@ -111,22 +111,26 @@ impl DisplayMapSnapshot {
|
||||
DisplayPoint(self.wraps_snapshot.max_point())
|
||||
}
|
||||
|
||||
pub fn chunks_at(&self, point: DisplayPoint) -> wrap_map::Chunks {
|
||||
self.wraps_snapshot.chunks_at(point.0)
|
||||
pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks {
|
||||
self.wraps_snapshot.chunks_at(display_row)
|
||||
}
|
||||
|
||||
pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> wrap_map::HighlightedChunks {
|
||||
self.wraps_snapshot.highlighted_chunks_for_rows(rows)
|
||||
pub fn highlighted_chunks_for_rows(
|
||||
&mut self,
|
||||
display_rows: Range<u32>,
|
||||
) -> wrap_map::HighlightedChunks {
|
||||
self.wraps_snapshot
|
||||
.highlighted_chunks_for_rows(display_rows)
|
||||
}
|
||||
|
||||
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
|
||||
self.chunks_at(point).flat_map(str::chars)
|
||||
pub fn chars_at<'a>(&'a self, display_row: u32) -> impl Iterator<Item = char> + 'a {
|
||||
self.chunks_at(display_row).flat_map(str::chars)
|
||||
}
|
||||
|
||||
pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
|
||||
let mut count = 0;
|
||||
let mut column = 0;
|
||||
for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
|
||||
for c in self.chars_at(display_row) {
|
||||
if column >= target {
|
||||
break;
|
||||
}
|
||||
@ -139,7 +143,7 @@ impl DisplayMapSnapshot {
|
||||
pub fn column_from_chars(&self, display_row: u32, char_count: u32) -> u32 {
|
||||
let mut count = 0;
|
||||
let mut column = 0;
|
||||
for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
|
||||
for c in self.chars_at(display_row) {
|
||||
if c == '\n' || count >= char_count {
|
||||
break;
|
||||
}
|
||||
@ -174,12 +178,12 @@ impl DisplayMapSnapshot {
|
||||
}
|
||||
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks_at(DisplayPoint::zero()).collect()
|
||||
self.chunks_at(0).collect()
|
||||
}
|
||||
|
||||
pub fn line(&self, display_row: u32) -> String {
|
||||
let mut result = String::new();
|
||||
for chunk in self.chunks_at(DisplayPoint::new(display_row, 0)) {
|
||||
for chunk in self.chunks_at(display_row) {
|
||||
if let Some(ix) = chunk.find('\n') {
|
||||
result.push_str(&chunk[0..ix]);
|
||||
break;
|
||||
@ -193,7 +197,7 @@ impl DisplayMapSnapshot {
|
||||
pub fn line_indent(&self, display_row: u32) -> (u32, bool) {
|
||||
let mut indent = 0;
|
||||
let mut is_blank = true;
|
||||
for c in self.chars_at(DisplayPoint::new(display_row, 0)) {
|
||||
for c in self.chars_at(display_row) {
|
||||
if c == ' ' {
|
||||
indent += 1;
|
||||
} else {
|
||||
@ -378,10 +382,8 @@ mod tests {
|
||||
|
||||
let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.chunks_at(DisplayPoint::new(0, 3))
|
||||
.collect::<String>(),
|
||||
" two \nthree four \nfive\nsix seven \neight"
|
||||
snapshot.chunks_at(0).collect::<String>(),
|
||||
"one two \nthree four \nfive\nsix seven \neight"
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot.clip_point(DisplayPoint::new(0, 8), Bias::Left),
|
||||
@ -399,9 +401,7 @@ mod tests {
|
||||
|
||||
let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.chunks_at(DisplayPoint::new(1, 0))
|
||||
.collect::<String>(),
|
||||
snapshot.chunks_at(1).collect::<String>(),
|
||||
"three four \nfive\nsix and \nseven eight"
|
||||
);
|
||||
}
|
||||
@ -431,22 +431,20 @@ mod tests {
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
&map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(DisplayPoint::new(1, 0))
|
||||
.collect::<String>()[0..10],
|
||||
" b bb"
|
||||
map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(1)
|
||||
.collect::<String>()
|
||||
.lines()
|
||||
.next(),
|
||||
Some(" b bbbbb")
|
||||
);
|
||||
assert_eq!(
|
||||
&map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(DisplayPoint::new(1, 2))
|
||||
.collect::<String>()[0..10],
|
||||
" b bbbb"
|
||||
);
|
||||
assert_eq!(
|
||||
&map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(DisplayPoint::new(1, 6))
|
||||
.collect::<String>()[0..13],
|
||||
" bbbbb\nc c"
|
||||
map.update(cx, |map, cx| map.snapshot(cx))
|
||||
.chunks_at(2)
|
||||
.collect::<String>()
|
||||
.lines()
|
||||
.next(),
|
||||
Some("c ccccc")
|
||||
);
|
||||
}
|
||||
|
||||
@ -676,6 +674,12 @@ mod tests {
|
||||
});
|
||||
let map = map.update(cx, |map, cx| map.snapshot(cx));
|
||||
assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
|
||||
assert_eq!(
|
||||
map.chunks_at(0).collect::<String>(),
|
||||
"✅ α\nβ \n🏀β γ"
|
||||
);
|
||||
assert_eq!(map.chunks_at(1).collect::<String>(), "β \n🏀β γ");
|
||||
assert_eq!(map.chunks_at(2).collect::<String>(), "🏀β γ");
|
||||
|
||||
let point = Point::new(0, "✅\t\t".len() as u32);
|
||||
let display_point = DisplayPoint::new(0, "✅ ".len() as u32);
|
||||
@ -701,11 +705,6 @@ mod tests {
|
||||
DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Left),
|
||||
Point::new(0, "✅\t".len() as u32),
|
||||
);
|
||||
assert_eq!(
|
||||
map.chunks_at(DisplayPoint::new(0, "✅ ".len() as u32))
|
||||
.collect::<String>(),
|
||||
" α\nβ \n🏀β γ"
|
||||
);
|
||||
assert_eq!(
|
||||
DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Right),
|
||||
Point::new(0, "✅\t".len() as u32),
|
||||
@ -714,11 +713,6 @@ mod tests {
|
||||
DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Bias::Left),
|
||||
Point::new(0, "✅".len() as u32),
|
||||
);
|
||||
assert_eq!(
|
||||
map.chunks_at(DisplayPoint::new(0, "✅ ".len() as u32))
|
||||
.collect::<String>(),
|
||||
" α\nβ \n🏀β γ"
|
||||
);
|
||||
|
||||
// Clipping display points inside of multi-byte characters
|
||||
assert_eq!(
|
||||
|
@ -3,6 +3,7 @@ use gpui::{fonts::FontId, FontCache, FontSystem};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
iter,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
@ -11,6 +12,18 @@ thread_local! {
|
||||
static WRAPPERS: RefCell<Vec<LineWrapper>> = Default::default();
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Boundary {
|
||||
pub ix: usize,
|
||||
pub next_indent: u32,
|
||||
}
|
||||
|
||||
impl Boundary {
|
||||
fn new(ix: usize, next_indent: u32) -> Self {
|
||||
Self { ix, next_indent }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LineWrapper {
|
||||
font_system: Arc<dyn FontSystem>,
|
||||
font_id: FontId,
|
||||
@ -20,6 +33,8 @@ pub struct LineWrapper {
|
||||
}
|
||||
|
||||
impl LineWrapper {
|
||||
pub const MAX_INDENT: u32 = 256;
|
||||
|
||||
pub fn thread_local(
|
||||
font_system: Arc<dyn FontSystem>,
|
||||
font_cache: &FontCache,
|
||||
@ -60,33 +75,44 @@ impl LineWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn wrap_line_with_shaping(&self, line: &str, wrap_width: f32) -> Vec<usize> {
|
||||
self.font_system
|
||||
.wrap_line(line, self.font_id, self.font_size, wrap_width)
|
||||
}
|
||||
|
||||
pub fn wrap_line<'a>(
|
||||
&'a mut self,
|
||||
line: &'a str,
|
||||
wrap_width: f32,
|
||||
) -> impl Iterator<Item = usize> + 'a {
|
||||
) -> impl Iterator<Item = Boundary> + 'a {
|
||||
let mut width = 0.0;
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut indent = None;
|
||||
let mut last_candidate_ix = 0;
|
||||
let mut last_candidate_width = 0.0;
|
||||
let mut last_wrap_ix = 0;
|
||||
let mut prev_c = '\0';
|
||||
let char_indices = line.char_indices();
|
||||
char_indices.filter_map(move |(ix, c)| {
|
||||
if c != '\n' {
|
||||
if self.is_boundary(prev_c, c) {
|
||||
let mut char_indices = line.char_indices();
|
||||
iter::from_fn(move || {
|
||||
while let Some((ix, c)) = char_indices.next() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let char_width = self.width_for_char(c);
|
||||
width += char_width;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
|
||||
{
|
||||
indent = Some(
|
||||
Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
|
||||
);
|
||||
}
|
||||
|
||||
if last_candidate_ix > 0 {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
width -= last_candidate_width;
|
||||
@ -95,7 +121,12 @@ impl LineWrapper {
|
||||
last_wrap_ix = ix;
|
||||
width = char_width;
|
||||
}
|
||||
return Some(last_wrap_ix);
|
||||
|
||||
let indent_width =
|
||||
indent.map(|indent| indent as f32 * self.width_for_char(' '));
|
||||
width += indent_width.unwrap_or(0.);
|
||||
|
||||
return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
@ -105,10 +136,7 @@ impl LineWrapper {
|
||||
}
|
||||
|
||||
fn is_boundary(&self, prev: char, next: char) -> bool {
|
||||
if prev == ' ' || next == ' ' {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
(prev == ' ') && (next != ' ')
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -184,27 +212,54 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut wrapper = LineWrapper::new(font_system, &font_cache, settings);
|
||||
|
||||
assert_eq!(
|
||||
wrapper.wrap_line_with_shaping("aa bbb cccc ddddd eeee", 72.0),
|
||||
&[7, 12, 18],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line("aa bbb cccc ddddd eeee", 72.0)
|
||||
.collect::<Vec<_>>(),
|
||||
&[7, 12, 18],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wrapper.wrap_line_with_shaping("aaa aaaaaaaaaaaaaaaaaa", 72.0),
|
||||
&[4, 11, 18],
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(12, 0),
|
||||
Boundary::new(18, 0)
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
|
||||
.collect::<Vec<_>>(),
|
||||
&[4, 11, 18],
|
||||
&[
|
||||
Boundary::new(4, 0),
|
||||
Boundary::new(11, 0),
|
||||
Boundary::new(18, 0)
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper.wrap_line(" aaaaaaa", 72.).collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 5),
|
||||
Boundary::new(9, 5),
|
||||
Boundary::new(11, 5),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" ", 72.)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(14, 0),
|
||||
Boundary::new(21, 0)
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_line(" aaaaaaaaaaaaaa", 72.)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Boundary::new(7, 0),
|
||||
Boundary::new(14, 3),
|
||||
Boundary::new(18, 3),
|
||||
Boundary::new(22, 3),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ use crate::{
|
||||
Settings,
|
||||
};
|
||||
use gpui::{Entity, ModelContext, Task};
|
||||
use lazy_static::lazy_static;
|
||||
use smol::future::yield_now;
|
||||
use std::{collections::VecDeque, ops::Range, time::Duration};
|
||||
|
||||
@ -391,11 +392,11 @@ impl Snapshot {
|
||||
}
|
||||
|
||||
let mut prev_boundary_ix = 0;
|
||||
for boundary_ix in line_wrapper.wrap_line(&line, wrap_width) {
|
||||
let wrapped = &line[prev_boundary_ix..boundary_ix];
|
||||
for boundary in line_wrapper.wrap_line(&line, wrap_width) {
|
||||
let wrapped = &line[prev_boundary_ix..boundary.ix];
|
||||
push_isomorphic(&mut edit_transforms, TextSummary::from(wrapped));
|
||||
edit_transforms.push(Transform::newline());
|
||||
prev_boundary_ix = boundary_ix;
|
||||
edit_transforms.push(Transform::wrap(boundary.next_indent));
|
||||
prev_boundary_ix = boundary.ix;
|
||||
}
|
||||
|
||||
if prev_boundary_ix < line.len() {
|
||||
@ -453,11 +454,14 @@ impl Snapshot {
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
pub fn chunks_at(&self, point: WrapPoint) -> Chunks {
|
||||
pub fn chunks_at(&self, wrap_row: u32) -> Chunks {
|
||||
let point = WrapPoint::new(wrap_row, 0);
|
||||
let mut transforms = self.transforms.cursor::<WrapPoint, TabPoint>();
|
||||
transforms.seek(&point, Bias::Right, &());
|
||||
let input_position =
|
||||
TabPoint(transforms.sum_start().0 + (point.0 - transforms.seek_start().0));
|
||||
let mut input_position = TabPoint(transforms.sum_start().0);
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_position.0 += point.0 - transforms.seek_start().0;
|
||||
}
|
||||
let input_chunks = self.tab_snapshot.chunks_at(input_position);
|
||||
Chunks {
|
||||
input_chunks,
|
||||
@ -472,8 +476,10 @@ impl Snapshot {
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
let mut transforms = self.transforms.cursor::<WrapPoint, TabPoint>();
|
||||
transforms.seek(&output_start, Bias::Right, &());
|
||||
let input_start =
|
||||
TabPoint(transforms.sum_start().0 + (output_start.0 - transforms.seek_start().0));
|
||||
let mut input_start = TabPoint(transforms.sum_start().0);
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_start.0 += output_start.0 - transforms.seek_start().0;
|
||||
}
|
||||
let input_end = self
|
||||
.to_tab_point(output_end)
|
||||
.min(self.tab_snapshot.max_point());
|
||||
@ -493,7 +499,7 @@ impl Snapshot {
|
||||
|
||||
pub fn line_len(&self, row: u32) -> u32 {
|
||||
let mut len = 0;
|
||||
for chunk in self.chunks_at(WrapPoint::new(row, 0)) {
|
||||
for chunk in self.chunks_at(row) {
|
||||
if let Some(newline_ix) = chunk.find('\n') {
|
||||
len += newline_ix;
|
||||
break;
|
||||
@ -511,7 +517,10 @@ impl Snapshot {
|
||||
pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
|
||||
let mut transforms = self.transforms.cursor::<WrapPoint, TabPoint>();
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Right, &());
|
||||
let input_row = transforms.sum_start().row() + (start_row - transforms.seek_start().row());
|
||||
let mut input_row = transforms.sum_start().row();
|
||||
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
input_row += start_row - transforms.seek_start().row();
|
||||
}
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
|
||||
let input_buffer_row = input_buffer_rows.next().unwrap();
|
||||
BufferRows {
|
||||
@ -526,7 +535,11 @@ impl Snapshot {
|
||||
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint, TabPoint>();
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
TabPoint(cursor.sum_start().0 + (point.0 - cursor.seek_start().0))
|
||||
let mut tab_point = cursor.sum_start().0;
|
||||
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
|
||||
tab_point += point.0 - cursor.seek_start().0;
|
||||
}
|
||||
TabPoint(tab_point)
|
||||
}
|
||||
|
||||
pub fn to_wrap_point(&self, point: TabPoint) -> WrapPoint {
|
||||
@ -539,8 +552,8 @@ impl Snapshot {
|
||||
if bias == Bias::Left {
|
||||
let mut cursor = self.transforms.cursor::<WrapPoint, ()>();
|
||||
cursor.seek(&point, Bias::Right, &());
|
||||
let transform = cursor.item().expect("invalid point");
|
||||
if !transform.is_isomorphic() {
|
||||
if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
|
||||
point = *cursor.seek_start();
|
||||
*point.column_mut() -= 1;
|
||||
}
|
||||
}
|
||||
@ -559,11 +572,9 @@ impl Snapshot {
|
||||
{
|
||||
let mut transforms = self.transforms.cursor::<(), ()>().peekable();
|
||||
while let Some(transform) = transforms.next() {
|
||||
let next_transform = transforms.peek();
|
||||
assert!(
|
||||
!transform.is_isomorphic()
|
||||
|| next_transform.map_or(true, |t| !t.is_isomorphic())
|
||||
);
|
||||
if let Some(next_transform) = transforms.peek() {
|
||||
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -576,9 +587,15 @@ impl<'a> Iterator for Chunks<'a> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let transform = self.transforms.item()?;
|
||||
if let Some(display_text) = transform.display_text {
|
||||
self.output_position.0 += transform.summary.output.lines;
|
||||
self.transforms.next(&());
|
||||
return Some(display_text);
|
||||
if self.output_position > *self.transforms.seek_start() {
|
||||
self.output_position.0.column += transform.summary.output.lines.column;
|
||||
self.transforms.next(&());
|
||||
return Some(&display_text[1..]);
|
||||
} else {
|
||||
self.output_position.0 += transform.summary.output.lines;
|
||||
self.transforms.next(&());
|
||||
return Some(display_text);
|
||||
}
|
||||
}
|
||||
|
||||
if self.input_chunk.is_empty() {
|
||||
@ -619,9 +636,23 @@ impl<'a> Iterator for HighlightedChunks<'a> {
|
||||
|
||||
let transform = self.transforms.item()?;
|
||||
if let Some(display_text) = transform.display_text {
|
||||
self.output_position.0 += transform.summary.output.lines;
|
||||
let mut start_ix = 0;
|
||||
let mut end_ix = display_text.len();
|
||||
let mut summary = transform.summary.output.lines;
|
||||
|
||||
if self.output_position > *self.transforms.seek_start() {
|
||||
// Exclude newline starting prior to the desired row.
|
||||
start_ix = 1;
|
||||
summary.row = 0;
|
||||
} else if self.output_position.row() + 1 >= self.max_output_row {
|
||||
// Exclude soft indentation ending after the desired row.
|
||||
end_ix = 1;
|
||||
summary.column = 0;
|
||||
}
|
||||
|
||||
self.output_position.0 += summary;
|
||||
self.transforms.next(&());
|
||||
return Some((display_text, self.style_id));
|
||||
return Some((&display_text[start_ix..end_ix], self.style_id));
|
||||
}
|
||||
|
||||
if self.input_chunk.is_empty() {
|
||||
@ -688,19 +719,28 @@ impl Transform {
|
||||
}
|
||||
}
|
||||
|
||||
fn newline() -> Self {
|
||||
fn wrap(indent: u32) -> Self {
|
||||
lazy_static! {
|
||||
static ref WRAP_TEXT: String = {
|
||||
let mut wrap_text = String::new();
|
||||
wrap_text.push('\n');
|
||||
wrap_text.extend((0..LineWrapper::MAX_INDENT as usize).map(|_| ' '));
|
||||
wrap_text
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
summary: TransformSummary {
|
||||
input: TextSummary::default(),
|
||||
output: TextSummary {
|
||||
lines: Point::new(1, 0),
|
||||
lines: Point::new(1, indent),
|
||||
first_line_chars: 0,
|
||||
last_line_chars: 0,
|
||||
longest_row: 0,
|
||||
longest_row_chars: 0,
|
||||
last_line_chars: indent,
|
||||
longest_row: 1,
|
||||
longest_row_chars: indent,
|
||||
},
|
||||
},
|
||||
display_text: Some("\n"),
|
||||
display_text: Some(&WRAP_TEXT[..1 + indent as usize]),
|
||||
}
|
||||
}
|
||||
|
||||
@ -753,11 +793,6 @@ impl WrapPoint {
|
||||
Self(super::Point::new(row, column))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn zero() -> Self {
|
||||
Self::new(0, 0)
|
||||
}
|
||||
|
||||
pub fn row(self) -> u32 {
|
||||
self.0.row
|
||||
}
|
||||
@ -918,7 +953,7 @@ mod tests {
|
||||
map.sync(tabs_snapshot.clone(), edits, cx)
|
||||
});
|
||||
snapshot.check_invariants();
|
||||
interpolated_snapshot.verify_chunks(&mut rng);
|
||||
snapshot.verify_chunks(&mut rng);
|
||||
|
||||
if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
|
||||
log::info!("Waiting for wrapping to finish");
|
||||
@ -928,18 +963,17 @@ mod tests {
|
||||
}
|
||||
|
||||
if !wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) {
|
||||
log::info!("Wrapping finished");
|
||||
snapshot =
|
||||
wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
|
||||
snapshot.check_invariants();
|
||||
interpolated_snapshot.verify_chunks(&mut rng);
|
||||
let actual_text = snapshot.text();
|
||||
log::info!("Wrapping finished: {:?}", actual_text);
|
||||
snapshot.check_invariants();
|
||||
snapshot.verify_chunks(&mut rng);
|
||||
assert_eq!(
|
||||
actual_text, expected_text,
|
||||
"unwrapped text is: {:?}",
|
||||
unwrapped_text
|
||||
);
|
||||
log::info!("New wrapped text: {:?}", actual_text);
|
||||
interpolated_snapshot = snapshot.clone();
|
||||
}
|
||||
}
|
||||
@ -959,10 +993,11 @@ mod tests {
|
||||
}
|
||||
|
||||
let mut prev_ix = 0;
|
||||
for ix in line_wrapper.wrap_line(line, wrap_width) {
|
||||
wrapped_text.push_str(&line[prev_ix..ix]);
|
||||
for boundary in line_wrapper.wrap_line(line, wrap_width) {
|
||||
wrapped_text.push_str(&line[prev_ix..boundary.ix]);
|
||||
wrapped_text.push('\n');
|
||||
prev_ix = ix;
|
||||
wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
|
||||
prev_ix = boundary.ix;
|
||||
}
|
||||
wrapped_text.push_str(&line[prev_ix..]);
|
||||
}
|
||||
@ -974,7 +1009,7 @@ mod tests {
|
||||
|
||||
impl Snapshot {
|
||||
fn text(&self) -> String {
|
||||
self.chunks_at(WrapPoint::zero()).collect()
|
||||
self.chunks_at(0).collect()
|
||||
}
|
||||
|
||||
fn verify_chunks(&mut self, rng: &mut impl Rng) {
|
||||
@ -983,9 +1018,7 @@ mod tests {
|
||||
let start_row = rng.gen_range(0..=end_row);
|
||||
end_row += 1;
|
||||
|
||||
let mut expected_text = self
|
||||
.chunks_at(WrapPoint::new(start_row, 0))
|
||||
.collect::<String>();
|
||||
let mut expected_text = self.chunks_at(start_row).collect::<String>();
|
||||
if expected_text.ends_with("\n") {
|
||||
expected_text.push('\n');
|
||||
}
|
||||
@ -997,6 +1030,7 @@ mod tests {
|
||||
if end_row <= self.max_point().row() {
|
||||
expected_text.push('\n');
|
||||
}
|
||||
|
||||
let actual_text = self
|
||||
.highlighted_chunks_for_rows(start_row..end_row)
|
||||
.map(|c| c.0)
|
||||
|
@ -94,7 +94,7 @@ pub fn prev_word_boundary(map: &DisplayMapSnapshot, point: DisplayPoint) -> Resu
|
||||
let mut boundary = DisplayPoint::new(point.row(), 0);
|
||||
let mut column = 0;
|
||||
let mut prev_c = None;
|
||||
for c in map.chars_at(boundary) {
|
||||
for c in map.chars_at(point.row()) {
|
||||
if column >= point.column() {
|
||||
break;
|
||||
}
|
||||
@ -115,7 +115,7 @@ pub fn next_word_boundary(
|
||||
mut point: DisplayPoint,
|
||||
) -> Result<DisplayPoint> {
|
||||
let mut prev_c = None;
|
||||
for c in map.chars_at(point) {
|
||||
for c in map.chars_at(point.row()).skip(point.column() as usize) {
|
||||
if prev_c.is_some() && (c == '\n' || char_kind(prev_c.unwrap()) != char_kind(c)) {
|
||||
break;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user