Detailed changes
@@ -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"
- );
- assert_eq!(
- &map.update(cx, |map, cx| map.snapshot(cx))
- .chunks_at(DisplayPoint::new(1, 2))
- .collect::<String>()[0..10],
- " b bbbb"
+ 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, 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],
+ wrapper
+ .wrap_line("aa bbb cccc ddddd eeee", 72.0)
+ .collect::<Vec<_>>(),
+ &[
+ Boundary::new(7, 0),
+ Boundary::new(12, 0),
+ Boundary::new(18, 0)
+ ],
);
assert_eq!(
wrapper
- .wrap_line("aa bbb cccc ddddd eeee", 72.0)
+ .wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
.collect::<Vec<_>>(),
- &[7, 12, 18],
+ &[
+ Boundary::new(4, 0),
+ Boundary::new(11, 0),
+ Boundary::new(18, 0)
+ ],
);
-
assert_eq!(
- wrapper.wrap_line_with_shaping("aaa aaaaaaaaaaaaaaaaaa", 72.0),
- &[4, 11, 18],
+ wrapper.wrap_line(" aaaaaaa", 72.).collect::<Vec<_>>(),
+ &[
+ Boundary::new(7, 5),
+ Boundary::new(9, 5),
+ Boundary::new(11, 5),
+ ]
);
assert_eq!(
wrapper
- .wrap_line("aaa aaaaaaaaaaaaaaaaaa", 72.0)
+ .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<_>>(),
- &[4, 11, 18],
+ &[
+ 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;
}