Detailed changes
@@ -1026,337 +1026,337 @@ fn consolidate_wrap_edits(edits: &mut Vec<WrapEdit>) {
}
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{
- display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
- MultiBuffer,
- };
- use gpui::test::observe;
- use rand::prelude::*;
- use settings::SettingsStore;
- use smol::stream::StreamExt;
- use std::{cmp, env, num::NonZeroU32};
- use text::Rope;
-
- #[gpui::test(iterations = 100)]
- async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
- init_test(cx);
-
- cx.background_executor.set_block_on_ticks(0..=50);
- let operations = env::var("OPERATIONS")
- .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
- .unwrap_or(10);
-
- let font_cache = cx.read(|cx| cx.font_cache().clone());
- let font_system = cx.platform().fonts();
- let mut wrap_width = if rng.gen_bool(0.1) {
- None
- } else {
- Some(rng.gen_range(0.0..=1000.0))
- };
- let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
- let family_id = font_cache
- .load_family(&["Helvetica"], &Default::default())
- .unwrap();
- let font_id = font_cache
- .select_font(family_id, &Default::default())
- .unwrap();
- let font_size = 14.0;
-
- log::info!("Tab size: {}", tab_size);
- log::info!("Wrap width: {:?}", wrap_width);
-
- let buffer = cx.update(|cx| {
- if rng.gen() {
- MultiBuffer::build_random(&mut rng, cx)
- } else {
- let len = rng.gen_range(0..10);
- let text = util::RandomCharIter::new(&mut rng)
- .take(len)
- .collect::<String>();
- MultiBuffer::build_simple(&text, cx)
- }
- });
- let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
- log::info!("Buffer text: {:?}", buffer_snapshot.text());
- let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
- log::info!("InlayMap text: {:?}", inlay_snapshot.text());
- let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
- log::info!("FoldMap text: {:?}", fold_snapshot.text());
- let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
- let tabs_snapshot = tab_map.set_max_expansion_column(32);
- log::info!("TabMap text: {:?}", tabs_snapshot.text());
-
- let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system);
- let unwrapped_text = tabs_snapshot.text();
- let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
-
- let (wrap_map, _) =
- cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx));
- let mut notifications = observe(&wrap_map, cx);
-
- if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
- notifications.next().await.unwrap();
- }
-
- let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
- assert!(!map.is_rewrapping());
- map.sync(tabs_snapshot.clone(), Vec::new(), cx)
- });
-
- let actual_text = initial_snapshot.text();
- assert_eq!(
- actual_text, expected_text,
- "unwrapped text is: {:?}",
- unwrapped_text
- );
- log::info!("Wrapped text: {:?}", actual_text);
-
- let mut next_inlay_id = 0;
- let mut edits = Vec::new();
- for _i in 0..operations {
- log::info!("{} ==============================================", _i);
-
- let mut buffer_edits = Vec::new();
- match rng.gen_range(0..=100) {
- 0..=19 => {
- wrap_width = if rng.gen_bool(0.2) {
- None
- } else {
- Some(rng.gen_range(0.0..=1000.0))
- };
- log::info!("Setting wrap width to {:?}", wrap_width);
- wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
- }
- 20..=39 => {
- for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
- let (tabs_snapshot, tab_edits) =
- tab_map.sync(fold_snapshot, fold_edits, tab_size);
- let (mut snapshot, wrap_edits) =
- wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
- snapshot.check_invariants();
- snapshot.verify_chunks(&mut rng);
- edits.push((snapshot, wrap_edits));
- }
- }
- 40..=59 => {
- let (inlay_snapshot, inlay_edits) =
- inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
- let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
- let (tabs_snapshot, tab_edits) =
- tab_map.sync(fold_snapshot, fold_edits, tab_size);
- let (mut snapshot, wrap_edits) =
- wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
- snapshot.check_invariants();
- snapshot.verify_chunks(&mut rng);
- edits.push((snapshot, wrap_edits));
- }
- _ => {
- buffer.update(cx, |buffer, cx| {
- let subscription = buffer.subscribe();
- let edit_count = rng.gen_range(1..=5);
- buffer.randomly_mutate(&mut rng, edit_count, cx);
- buffer_snapshot = buffer.snapshot(cx);
- buffer_edits.extend(subscription.consume());
- });
- }
- }
-
- log::info!("Buffer text: {:?}", buffer_snapshot.text());
- let (inlay_snapshot, inlay_edits) =
- inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
- log::info!("InlayMap text: {:?}", inlay_snapshot.text());
- let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
- log::info!("FoldMap text: {:?}", fold_snapshot.text());
- let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
- log::info!("TabMap text: {:?}", tabs_snapshot.text());
-
- let unwrapped_text = tabs_snapshot.text();
- let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
- let (mut snapshot, wrap_edits) =
- wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
- snapshot.check_invariants();
- snapshot.verify_chunks(&mut rng);
- edits.push((snapshot, wrap_edits));
-
- if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
- log::info!("Waiting for wrapping to finish");
- while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
- notifications.next().await.unwrap();
- }
- wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
- }
-
- if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
- let (mut wrapped_snapshot, wrap_edits) =
- wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
- let actual_text = wrapped_snapshot.text();
- let actual_longest_row = wrapped_snapshot.longest_row();
- log::info!("Wrapping finished: {:?}", actual_text);
- wrapped_snapshot.check_invariants();
- wrapped_snapshot.verify_chunks(&mut rng);
- edits.push((wrapped_snapshot.clone(), wrap_edits));
- assert_eq!(
- actual_text, expected_text,
- "unwrapped text is: {:?}",
- unwrapped_text
- );
-
- let mut summary = TextSummary::default();
- for (ix, item) in wrapped_snapshot
- .transforms
- .items(&())
- .into_iter()
- .enumerate()
- {
- summary += &item.summary.output;
- log::info!("{} summary: {:?}", ix, item.summary.output,);
- }
-
- if tab_size.get() == 1
- || !wrapped_snapshot
- .tab_snapshot
- .fold_snapshot
- .text()
- .contains('\t')
- {
- let mut expected_longest_rows = Vec::new();
- let mut longest_line_len = -1;
- for (row, line) in expected_text.split('\n').enumerate() {
- let line_char_count = line.chars().count() as isize;
- if line_char_count > longest_line_len {
- expected_longest_rows.clear();
- longest_line_len = line_char_count;
- }
- if line_char_count >= longest_line_len {
- expected_longest_rows.push(row as u32);
- }
- }
-
- assert!(
- expected_longest_rows.contains(&actual_longest_row),
- "incorrect longest row {}. expected {:?} with length {}",
- actual_longest_row,
- expected_longest_rows,
- longest_line_len,
- )
- }
- }
- }
-
- let mut initial_text = Rope::from(initial_snapshot.text().as_str());
- for (snapshot, patch) in edits {
- let snapshot_text = Rope::from(snapshot.text().as_str());
- for edit in &patch {
- let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
- let old_end = initial_text.point_to_offset(cmp::min(
- Point::new(edit.new.start + edit.old.len() as u32, 0),
- initial_text.max_point(),
- ));
- let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
- let new_end = snapshot_text.point_to_offset(cmp::min(
- Point::new(edit.new.end, 0),
- snapshot_text.max_point(),
- ));
- let new_text = snapshot_text
- .chunks_in_range(new_start..new_end)
- .collect::<String>();
-
- initial_text.replace(old_start..old_end, &new_text);
- }
- assert_eq!(initial_text.to_string(), snapshot_text.to_string());
- }
-
- if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
- log::info!("Waiting for wrapping to finish");
- while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
- notifications.next().await.unwrap();
- }
- }
- wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
- }
-
- fn init_test(cx: &mut gpui::TestAppContext) {
- cx.foreground_executor().forbid_parking();
- cx.update(|cx| {
- cx.set_global(SettingsStore::test(cx));
- theme::init((), cx);
- });
- }
-
- fn wrap_text(
- unwrapped_text: &str,
- wrap_width: Option<f32>,
- line_wrapper: &mut LineWrapper,
- ) -> String {
- if let Some(wrap_width) = wrap_width {
- let mut wrapped_text = String::new();
- for (row, line) in unwrapped_text.split('\n').enumerate() {
- if row > 0 {
- wrapped_text.push('\n')
- }
-
- let mut prev_ix = 0;
- for boundary in line_wrapper.wrap_line(line, wrap_width) {
- wrapped_text.push_str(&line[prev_ix..boundary.ix]);
- wrapped_text.push('\n');
- wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
- prev_ix = boundary.ix;
- }
- wrapped_text.push_str(&line[prev_ix..]);
- }
- wrapped_text
- } else {
- unwrapped_text.to_string()
- }
- }
-
- impl WrapSnapshot {
- pub fn text(&self) -> String {
- self.text_chunks(0).collect()
- }
-
- pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
- self.chunks(
- wrap_row..self.max_point().row() + 1,
- false,
- Highlights::default(),
- )
- .map(|h| h.text)
- }
-
- fn verify_chunks(&mut self, rng: &mut impl Rng) {
- for _ in 0..5 {
- let mut end_row = rng.gen_range(0..=self.max_point().row());
- let start_row = rng.gen_range(0..=end_row);
- end_row += 1;
-
- let mut expected_text = self.text_chunks(start_row).collect::<String>();
- if expected_text.ends_with('\n') {
- expected_text.push('\n');
- }
- let mut expected_text = expected_text
- .lines()
- .take((end_row - start_row) as usize)
- .collect::<Vec<_>>()
- .join("\n");
- if end_row <= self.max_point().row() {
- expected_text.push('\n');
- }
-
- let actual_text = self
- .chunks(start_row..end_row, true, Highlights::default())
- .map(|c| c.text)
- .collect::<String>();
- assert_eq!(
- expected_text,
- actual_text,
- "chunks != highlighted_chunks for rows {:?}",
- start_row..end_row
- );
- }
- }
- }
-}
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+// use crate::{
+// display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
+// MultiBuffer,
+// };
+// use gpui::test::observe;
+// use rand::prelude::*;
+// use settings::SettingsStore;
+// use smol::stream::StreamExt;
+// use std::{cmp, env, num::NonZeroU32};
+// use text::Rope;
+
+// #[gpui::test(iterations = 100)]
+// async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
+// init_test(cx);
+
+// cx.background_executor.set_block_on_ticks(0..=50);
+// let operations = env::var("OPERATIONS")
+// .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+// .unwrap_or(10);
+
+// let font_cache = cx.read(|cx| cx.font_cache().clone());
+// let font_system = cx.platform().fonts();
+// let mut wrap_width = if rng.gen_bool(0.1) {
+// None
+// } else {
+// Some(rng.gen_range(0.0..=1000.0))
+// };
+// let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
+// let family_id = font_cache
+// .load_family(&["Helvetica"], &Default::default())
+// .unwrap();
+// let font_id = font_cache
+// .select_font(family_id, &Default::default())
+// .unwrap();
+// let font_size = 14.0;
+
+// log::info!("Tab size: {}", tab_size);
+// log::info!("Wrap width: {:?}", wrap_width);
+
+// let buffer = cx.update(|cx| {
+// if rng.gen() {
+// MultiBuffer::build_random(&mut rng, cx)
+// } else {
+// let len = rng.gen_range(0..10);
+// let text = util::RandomCharIter::new(&mut rng)
+// .take(len)
+// .collect::<String>();
+// MultiBuffer::build_simple(&text, cx)
+// }
+// });
+// let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
+// log::info!("Buffer text: {:?}", buffer_snapshot.text());
+// let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
+// log::info!("InlayMap text: {:?}", inlay_snapshot.text());
+// let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
+// log::info!("FoldMap text: {:?}", fold_snapshot.text());
+// let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
+// let tabs_snapshot = tab_map.set_max_expansion_column(32);
+// log::info!("TabMap text: {:?}", tabs_snapshot.text());
+
+// let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system);
+// let unwrapped_text = tabs_snapshot.text();
+// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
+
+// let (wrap_map, _) =
+// cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx));
+// let mut notifications = observe(&wrap_map, cx);
+
+// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
+// notifications.next().await.unwrap();
+// }
+
+// let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
+// assert!(!map.is_rewrapping());
+// map.sync(tabs_snapshot.clone(), Vec::new(), cx)
+// });
+
+// let actual_text = initial_snapshot.text();
+// assert_eq!(
+// actual_text, expected_text,
+// "unwrapped text is: {:?}",
+// unwrapped_text
+// );
+// log::info!("Wrapped text: {:?}", actual_text);
+
+// let mut next_inlay_id = 0;
+// let mut edits = Vec::new();
+// for _i in 0..operations {
+// log::info!("{} ==============================================", _i);
+
+// let mut buffer_edits = Vec::new();
+// match rng.gen_range(0..=100) {
+// 0..=19 => {
+// wrap_width = if rng.gen_bool(0.2) {
+// None
+// } else {
+// Some(rng.gen_range(0.0..=1000.0))
+// };
+// log::info!("Setting wrap width to {:?}", wrap_width);
+// wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
+// }
+// 20..=39 => {
+// for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
+// let (tabs_snapshot, tab_edits) =
+// tab_map.sync(fold_snapshot, fold_edits, tab_size);
+// let (mut snapshot, wrap_edits) =
+// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
+// snapshot.check_invariants();
+// snapshot.verify_chunks(&mut rng);
+// edits.push((snapshot, wrap_edits));
+// }
+// }
+// 40..=59 => {
+// let (inlay_snapshot, inlay_edits) =
+// inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
+// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
+// let (tabs_snapshot, tab_edits) =
+// tab_map.sync(fold_snapshot, fold_edits, tab_size);
+// let (mut snapshot, wrap_edits) =
+// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
+// snapshot.check_invariants();
+// snapshot.verify_chunks(&mut rng);
+// edits.push((snapshot, wrap_edits));
+// }
+// _ => {
+// buffer.update(cx, |buffer, cx| {
+// let subscription = buffer.subscribe();
+// let edit_count = rng.gen_range(1..=5);
+// buffer.randomly_mutate(&mut rng, edit_count, cx);
+// buffer_snapshot = buffer.snapshot(cx);
+// buffer_edits.extend(subscription.consume());
+// });
+// }
+// }
+
+// log::info!("Buffer text: {:?}", buffer_snapshot.text());
+// let (inlay_snapshot, inlay_edits) =
+// inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
+// log::info!("InlayMap text: {:?}", inlay_snapshot.text());
+// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
+// log::info!("FoldMap text: {:?}", fold_snapshot.text());
+// let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
+// log::info!("TabMap text: {:?}", tabs_snapshot.text());
+
+// let unwrapped_text = tabs_snapshot.text();
+// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
+// let (mut snapshot, wrap_edits) =
+// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
+// snapshot.check_invariants();
+// snapshot.verify_chunks(&mut rng);
+// edits.push((snapshot, wrap_edits));
+
+// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
+// log::info!("Waiting for wrapping to finish");
+// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
+// notifications.next().await.unwrap();
+// }
+// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
+// }
+
+// if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
+// let (mut wrapped_snapshot, wrap_edits) =
+// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
+// let actual_text = wrapped_snapshot.text();
+// let actual_longest_row = wrapped_snapshot.longest_row();
+// log::info!("Wrapping finished: {:?}", actual_text);
+// wrapped_snapshot.check_invariants();
+// wrapped_snapshot.verify_chunks(&mut rng);
+// edits.push((wrapped_snapshot.clone(), wrap_edits));
+// assert_eq!(
+// actual_text, expected_text,
+// "unwrapped text is: {:?}",
+// unwrapped_text
+// );
+
+// let mut summary = TextSummary::default();
+// for (ix, item) in wrapped_snapshot
+// .transforms
+// .items(&())
+// .into_iter()
+// .enumerate()
+// {
+// summary += &item.summary.output;
+// log::info!("{} summary: {:?}", ix, item.summary.output,);
+// }
+
+// if tab_size.get() == 1
+// || !wrapped_snapshot
+// .tab_snapshot
+// .fold_snapshot
+// .text()
+// .contains('\t')
+// {
+// let mut expected_longest_rows = Vec::new();
+// let mut longest_line_len = -1;
+// for (row, line) in expected_text.split('\n').enumerate() {
+// let line_char_count = line.chars().count() as isize;
+// if line_char_count > longest_line_len {
+// expected_longest_rows.clear();
+// longest_line_len = line_char_count;
+// }
+// if line_char_count >= longest_line_len {
+// expected_longest_rows.push(row as u32);
+// }
+// }
+
+// assert!(
+// expected_longest_rows.contains(&actual_longest_row),
+// "incorrect longest row {}. expected {:?} with length {}",
+// actual_longest_row,
+// expected_longest_rows,
+// longest_line_len,
+// )
+// }
+// }
+// }
+
+// let mut initial_text = Rope::from(initial_snapshot.text().as_str());
+// for (snapshot, patch) in edits {
+// let snapshot_text = Rope::from(snapshot.text().as_str());
+// for edit in &patch {
+// let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
+// let old_end = initial_text.point_to_offset(cmp::min(
+// Point::new(edit.new.start + edit.old.len() as u32, 0),
+// initial_text.max_point(),
+// ));
+// let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
+// let new_end = snapshot_text.point_to_offset(cmp::min(
+// Point::new(edit.new.end, 0),
+// snapshot_text.max_point(),
+// ));
+// let new_text = snapshot_text
+// .chunks_in_range(new_start..new_end)
+// .collect::<String>();
+
+// initial_text.replace(old_start..old_end, &new_text);
+// }
+// assert_eq!(initial_text.to_string(), snapshot_text.to_string());
+// }
+
+// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
+// log::info!("Waiting for wrapping to finish");
+// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
+// notifications.next().await.unwrap();
+// }
+// }
+// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
+// }
+
+// fn init_test(cx: &mut gpui::TestAppContext) {
+// cx.foreground_executor().forbid_parking();
+// cx.update(|cx| {
+// cx.set_global(SettingsStore::test(cx));
+// theme::init((), cx);
+// });
+// }
+
+// fn wrap_text(
+// unwrapped_text: &str,
+// wrap_width: Option<f32>,
+// line_wrapper: &mut LineWrapper,
+// ) -> String {
+// if let Some(wrap_width) = wrap_width {
+// let mut wrapped_text = String::new();
+// for (row, line) in unwrapped_text.split('\n').enumerate() {
+// if row > 0 {
+// wrapped_text.push('\n')
+// }
+
+// let mut prev_ix = 0;
+// for boundary in line_wrapper.wrap_line(line, wrap_width) {
+// wrapped_text.push_str(&line[prev_ix..boundary.ix]);
+// wrapped_text.push('\n');
+// wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
+// prev_ix = boundary.ix;
+// }
+// wrapped_text.push_str(&line[prev_ix..]);
+// }
+// wrapped_text
+// } else {
+// unwrapped_text.to_string()
+// }
+// }
+
+// impl WrapSnapshot {
+// pub fn text(&self) -> String {
+// self.text_chunks(0).collect()
+// }
+
+// pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
+// self.chunks(
+// wrap_row..self.max_point().row() + 1,
+// false,
+// Highlights::default(),
+// )
+// .map(|h| h.text)
+// }
+
+// fn verify_chunks(&mut self, rng: &mut impl Rng) {
+// for _ in 0..5 {
+// let mut end_row = rng.gen_range(0..=self.max_point().row());
+// let start_row = rng.gen_range(0..=end_row);
+// end_row += 1;
+
+// let mut expected_text = self.text_chunks(start_row).collect::<String>();
+// if expected_text.ends_with('\n') {
+// expected_text.push('\n');
+// }
+// let mut expected_text = expected_text
+// .lines()
+// .take((end_row - start_row) as usize)
+// .collect::<Vec<_>>()
+// .join("\n");
+// if end_row <= self.max_point().row() {
+// expected_text.push('\n');
+// }
+
+// let actual_text = self
+// .chunks(start_row..end_row, true, Highlights::default())
+// .map(|c| c.text)
+// .collect::<String>();
+// assert_eq!(
+// expected_text,
+// actual_text,
+// "chunks != highlighted_chunks for rows {:?}",
+// start_row..end_row
+// );
+// }
+// }
+// }
+// }
@@ -2401,346 +2401,346 @@ pub mod tests {
});
}
- #[gpui::test(iterations = 10)]
- async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
- init_test(cx, |settings| {
- settings.defaults.inlay_hints = Some(InlayHintSettings {
- enabled: true,
- show_type_hints: true,
- show_parameter_hints: true,
- show_other_hints: true,
- })
- });
-
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- inlay_hint_provider: Some(lsp::OneOf::Left(true)),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- let language = Arc::new(language);
- let fs = FakeFs::new(cx.background_executor.clone());
- fs.insert_tree(
- "/a",
- json!({
- "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
- "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
- }),
- )
- .await;
- let project = Project::test(fs, ["/a".as_ref()], cx).await;
- project.update(cx, |project, _| {
- project.languages().add(Arc::clone(&language))
- });
- let worktree_id = project.update(cx, |project, cx| {
- project.worktrees().next().unwrap().read(cx).id()
- });
-
- let buffer_1 = project
- .update(cx, |project, cx| {
- project.open_buffer((worktree_id, "main.rs"), cx)
- })
- .await
- .unwrap();
- let buffer_2 = project
- .update(cx, |project, cx| {
- project.open_buffer((worktree_id, "other.rs"), cx)
- })
- .await
- .unwrap();
- let multibuffer = cx.build_model(|cx| {
- let mut multibuffer = MultiBuffer::new(0);
- multibuffer.push_excerpts(
- buffer_1.clone(),
- [
- ExcerptRange {
- context: Point::new(0, 0)..Point::new(2, 0),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(4, 0)..Point::new(11, 0),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(22, 0)..Point::new(33, 0),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(44, 0)..Point::new(55, 0),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(56, 0)..Point::new(66, 0),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(67, 0)..Point::new(77, 0),
- primary: None,
- },
- ],
- cx,
- );
- multibuffer.push_excerpts(
- buffer_2.clone(),
- [
- ExcerptRange {
- context: Point::new(0, 1)..Point::new(2, 1),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(4, 1)..Point::new(11, 1),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(22, 1)..Point::new(33, 1),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(44, 1)..Point::new(55, 1),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(56, 1)..Point::new(66, 1),
- primary: None,
- },
- ExcerptRange {
- context: Point::new(67, 1)..Point::new(77, 1),
- primary: None,
- },
- ],
- cx,
- );
- multibuffer
- });
-
- cx.executor().run_until_parked();
- let editor =
- cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
- let editor_edited = Arc::new(AtomicBool::new(false));
- let fake_server = fake_servers.next().await.unwrap();
- let closure_editor_edited = Arc::clone(&editor_edited);
- fake_server
- .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
- let task_editor_edited = Arc::clone(&closure_editor_edited);
- async move {
- let hint_text = if params.text_document.uri
- == lsp::Url::from_file_path("/a/main.rs").unwrap()
- {
- "main hint"
- } else if params.text_document.uri
- == lsp::Url::from_file_path("/a/other.rs").unwrap()
- {
- "other hint"
- } else {
- panic!("unexpected uri: {:?}", params.text_document.uri);
- };
-
- // one hint per excerpt
- let positions = [
- lsp::Position::new(0, 2),
- lsp::Position::new(4, 2),
- lsp::Position::new(22, 2),
- lsp::Position::new(44, 2),
- lsp::Position::new(56, 2),
- lsp::Position::new(67, 2),
- ];
- let out_of_range_hint = lsp::InlayHint {
- position: lsp::Position::new(
- params.range.start.line + 99,
- params.range.start.character + 99,
- ),
- label: lsp::InlayHintLabel::String(
- "out of excerpt range, should be ignored".to_string(),
- ),
- kind: None,
- text_edits: None,
- tooltip: None,
- padding_left: None,
- padding_right: None,
- data: None,
- };
-
- let edited = task_editor_edited.load(Ordering::Acquire);
- Ok(Some(
- std::iter::once(out_of_range_hint)
- .chain(positions.into_iter().enumerate().map(|(i, position)| {
- lsp::InlayHint {
- position,
- label: lsp::InlayHintLabel::String(format!(
- "{hint_text}{} #{i}",
- if edited { "(edited)" } else { "" },
- )),
- kind: None,
- text_edits: None,
- tooltip: None,
- padding_left: None,
- padding_right: None,
- data: None,
- }
- }))
- .collect(),
- ))
- }
- })
- .next()
- .await;
- cx.executor().run_until_parked();
-
- editor.update(cx, |editor, cx| {
- let expected_hints = vec![
- "main hint #0".to_string(),
- "main hint #1".to_string(),
- "main hint #2".to_string(),
- "main hint #3".to_string(),
- // todo!() there used to be no these hints, but new gpui2 presumably scrolls a bit farther
- // (or renders less?) note that tests below pass
- "main hint #4".to_string(),
- "main hint #5".to_string(),
- ];
- assert_eq!(
- expected_hints,
- cached_hint_labels(editor),
- "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
- );
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
- });
-
- editor.update(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::Next), cx, |s| {
- s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
- });
- editor.change_selections(Some(Autoscroll::Next), cx, |s| {
- s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
- });
- editor.change_selections(Some(Autoscroll::Next), cx, |s| {
- s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
- });
- });
- cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
- let expected_hints = vec![
- "main hint #0".to_string(),
- "main hint #1".to_string(),
- "main hint #2".to_string(),
- "main hint #3".to_string(),
- "main hint #4".to_string(),
- "main hint #5".to_string(),
- "other hint #0".to_string(),
- "other hint #1".to_string(),
- "other hint #2".to_string(),
- ];
- assert_eq!(expected_hints, cached_hint_labels(editor),
- "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
- "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
- });
-
- editor.update(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::Next), cx, |s| {
- s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
- });
- });
- cx.executor().advance_clock(Duration::from_millis(
- INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
- ));
- cx.executor().run_until_parked();
- let last_scroll_update_version = editor.update(cx, |editor, cx| {
- let expected_hints = vec![
- "main hint #0".to_string(),
- "main hint #1".to_string(),
- "main hint #2".to_string(),
- "main hint #3".to_string(),
- "main hint #4".to_string(),
- "main hint #5".to_string(),
- "other hint #0".to_string(),
- "other hint #1".to_string(),
- "other hint #2".to_string(),
- "other hint #3".to_string(),
- "other hint #4".to_string(),
- "other hint #5".to_string(),
- ];
- assert_eq!(expected_hints, cached_hint_labels(editor),
- "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
- expected_hints.len()
- }).unwrap();
-
- editor.update(cx, |editor, cx| {
- editor.change_selections(Some(Autoscroll::Next), cx, |s| {
- s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
- });
- });
- cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
- let expected_hints = vec![
- "main hint #0".to_string(),
- "main hint #1".to_string(),
- "main hint #2".to_string(),
- "main hint #3".to_string(),
- "main hint #4".to_string(),
- "main hint #5".to_string(),
- "other hint #0".to_string(),
- "other hint #1".to_string(),
- "other hint #2".to_string(),
- "other hint #3".to_string(),
- "other hint #4".to_string(),
- "other hint #5".to_string(),
- ];
- assert_eq!(expected_hints, cached_hint_labels(editor),
- "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
- });
-
- editor_edited.store(true, Ordering::Release);
- editor.update(cx, |editor, cx| {
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
- });
- editor.handle_input("++++more text++++", cx);
- });
- cx.executor().run_until_parked();
- editor.update(cx, |editor, cx| {
- let expected_hints = vec![
- "main hint(edited) #0".to_string(),
- "main hint(edited) #1".to_string(),
- "main hint(edited) #2".to_string(),
- "main hint(edited) #3".to_string(),
- "main hint(edited) #4".to_string(),
- "main hint(edited) #5".to_string(),
- "other hint(edited) #0".to_string(),
- "other hint(edited) #1".to_string(),
- ];
- assert_eq!(
- expected_hints,
- cached_hint_labels(editor),
- "After multibuffer edit, editor gets scolled back to the last selection; \
-all hints should be invalidated and requeried for all of its visible excerpts"
- );
- assert_eq!(expected_hints, visible_hint_labels(editor, cx));
-
- let current_cache_version = editor.inlay_hint_cache().version;
- let minimum_expected_version = last_scroll_update_version + expected_hints.len();
- assert!(
- current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1,
- "Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update"
- );
- });
- }
+ // #[gpui::test(iterations = 10)]
+ // async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
+ // init_test(cx, |settings| {
+ // settings.defaults.inlay_hints = Some(InlayHintSettings {
+ // enabled: true,
+ // show_type_hints: true,
+ // show_parameter_hints: true,
+ // show_other_hints: true,
+ // })
+ // });
+
+ // let mut language = Language::new(
+ // LanguageConfig {
+ // name: "Rust".into(),
+ // path_suffixes: vec!["rs".to_string()],
+ // ..Default::default()
+ // },
+ // Some(tree_sitter_rust::language()),
+ // );
+ // let mut fake_servers = language
+ // .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+ // capabilities: lsp::ServerCapabilities {
+ // inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+ // ..Default::default()
+ // },
+ // ..Default::default()
+ // }))
+ // .await;
+ // let language = Arc::new(language);
+ // let fs = FakeFs::new(cx.background_executor.clone());
+ // fs.insert_tree(
+ // "/a",
+ // json!({
+ // "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
+ // "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
+ // }),
+ // )
+ // .await;
+ // let project = Project::test(fs, ["/a".as_ref()], cx).await;
+ // project.update(cx, |project, _| {
+ // project.languages().add(Arc::clone(&language))
+ // });
+ // let worktree_id = project.update(cx, |project, cx| {
+ // project.worktrees().next().unwrap().read(cx).id()
+ // });
+
+ // let buffer_1 = project
+ // .update(cx, |project, cx| {
+ // project.open_buffer((worktree_id, "main.rs"), cx)
+ // })
+ // .await
+ // .unwrap();
+ // let buffer_2 = project
+ // .update(cx, |project, cx| {
+ // project.open_buffer((worktree_id, "other.rs"), cx)
+ // })
+ // .await
+ // .unwrap();
+ // let multibuffer = cx.build_model(|cx| {
+ // let mut multibuffer = MultiBuffer::new(0);
+ // multibuffer.push_excerpts(
+ // buffer_1.clone(),
+ // [
+ // ExcerptRange {
+ // context: Point::new(0, 0)..Point::new(2, 0),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(4, 0)..Point::new(11, 0),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(22, 0)..Point::new(33, 0),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(44, 0)..Point::new(55, 0),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(56, 0)..Point::new(66, 0),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(67, 0)..Point::new(77, 0),
+ // primary: None,
+ // },
+ // ],
+ // cx,
+ // );
+ // multibuffer.push_excerpts(
+ // buffer_2.clone(),
+ // [
+ // ExcerptRange {
+ // context: Point::new(0, 1)..Point::new(2, 1),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(4, 1)..Point::new(11, 1),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(22, 1)..Point::new(33, 1),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(44, 1)..Point::new(55, 1),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(56, 1)..Point::new(66, 1),
+ // primary: None,
+ // },
+ // ExcerptRange {
+ // context: Point::new(67, 1)..Point::new(77, 1),
+ // primary: None,
+ // },
+ // ],
+ // cx,
+ // );
+ // multibuffer
+ // });
+
+ // cx.executor().run_until_parked();
+ // let editor =
+ // cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+ // let editor_edited = Arc::new(AtomicBool::new(false));
+ // let fake_server = fake_servers.next().await.unwrap();
+ // let closure_editor_edited = Arc::clone(&editor_edited);
+ // fake_server
+ // .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+ // let task_editor_edited = Arc::clone(&closure_editor_edited);
+ // async move {
+ // let hint_text = if params.text_document.uri
+ // == lsp::Url::from_file_path("/a/main.rs").unwrap()
+ // {
+ // "main hint"
+ // } else if params.text_document.uri
+ // == lsp::Url::from_file_path("/a/other.rs").unwrap()
+ // {
+ // "other hint"
+ // } else {
+ // panic!("unexpected uri: {:?}", params.text_document.uri);
+ // };
+
+ // // one hint per excerpt
+ // let positions = [
+ // lsp::Position::new(0, 2),
+ // lsp::Position::new(4, 2),
+ // lsp::Position::new(22, 2),
+ // lsp::Position::new(44, 2),
+ // lsp::Position::new(56, 2),
+ // lsp::Position::new(67, 2),
+ // ];
+ // let out_of_range_hint = lsp::InlayHint {
+ // position: lsp::Position::new(
+ // params.range.start.line + 99,
+ // params.range.start.character + 99,
+ // ),
+ // label: lsp::InlayHintLabel::String(
+ // "out of excerpt range, should be ignored".to_string(),
+ // ),
+ // kind: None,
+ // text_edits: None,
+ // tooltip: None,
+ // padding_left: None,
+ // padding_right: None,
+ // data: None,
+ // };
+
+ // let edited = task_editor_edited.load(Ordering::Acquire);
+ // Ok(Some(
+ // std::iter::once(out_of_range_hint)
+ // .chain(positions.into_iter().enumerate().map(|(i, position)| {
+ // lsp::InlayHint {
+ // position,
+ // label: lsp::InlayHintLabel::String(format!(
+ // "{hint_text}{} #{i}",
+ // if edited { "(edited)" } else { "" },
+ // )),
+ // kind: None,
+ // text_edits: None,
+ // tooltip: None,
+ // padding_left: None,
+ // padding_right: None,
+ // data: None,
+ // }
+ // }))
+ // .collect(),
+ // ))
+ // }
+ // })
+ // .next()
+ // .await;
+ // cx.executor().run_until_parked();
+
+ // editor.update(cx, |editor, cx| {
+ // let expected_hints = vec![
+ // "main hint #0".to_string(),
+ // "main hint #1".to_string(),
+ // "main hint #2".to_string(),
+ // "main hint #3".to_string(),
+ // // todo!() there used to be no these hints, but new gpui2 presumably scrolls a bit farther
+ // // (or renders less?) note that tests below pass
+ // "main hint #4".to_string(),
+ // "main hint #5".to_string(),
+ // ];
+ // assert_eq!(
+ // expected_hints,
+ // cached_hint_labels(editor),
+ // "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
+ // );
+ // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ // assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
+ // });
+
+ // editor.update(cx, |editor, cx| {
+ // editor.change_selections(Some(Autoscroll::Next), cx, |s| {
+ // s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
+ // });
+ // editor.change_selections(Some(Autoscroll::Next), cx, |s| {
+ // s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
+ // });
+ // editor.change_selections(Some(Autoscroll::Next), cx, |s| {
+ // s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
+ // });
+ // });
+ // cx.executor().run_until_parked();
+ // editor.update(cx, |editor, cx| {
+ // let expected_hints = vec![
+ // "main hint #0".to_string(),
+ // "main hint #1".to_string(),
+ // "main hint #2".to_string(),
+ // "main hint #3".to_string(),
+ // "main hint #4".to_string(),
+ // "main hint #5".to_string(),
+ // "other hint #0".to_string(),
+ // "other hint #1".to_string(),
+ // "other hint #2".to_string(),
+ // ];
+ // assert_eq!(expected_hints, cached_hint_labels(editor),
+ // "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
+ // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ // assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
+ // "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
+ // });
+
+ // editor.update(cx, |editor, cx| {
+ // editor.change_selections(Some(Autoscroll::Next), cx, |s| {
+ // s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
+ // });
+ // });
+ // cx.executor().advance_clock(Duration::from_millis(
+ // INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
+ // ));
+ // cx.executor().run_until_parked();
+ // let last_scroll_update_version = editor.update(cx, |editor, cx| {
+ // let expected_hints = vec![
+ // "main hint #0".to_string(),
+ // "main hint #1".to_string(),
+ // "main hint #2".to_string(),
+ // "main hint #3".to_string(),
+ // "main hint #4".to_string(),
+ // "main hint #5".to_string(),
+ // "other hint #0".to_string(),
+ // "other hint #1".to_string(),
+ // "other hint #2".to_string(),
+ // "other hint #3".to_string(),
+ // "other hint #4".to_string(),
+ // "other hint #5".to_string(),
+ // ];
+ // assert_eq!(expected_hints, cached_hint_labels(editor),
+ // "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
+ // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ // assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
+ // expected_hints.len()
+ // }).unwrap();
+
+ // editor.update(cx, |editor, cx| {
+ // editor.change_selections(Some(Autoscroll::Next), cx, |s| {
+ // s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
+ // });
+ // });
+ // cx.executor().run_until_parked();
+ // editor.update(cx, |editor, cx| {
+ // let expected_hints = vec![
+ // "main hint #0".to_string(),
+ // "main hint #1".to_string(),
+ // "main hint #2".to_string(),
+ // "main hint #3".to_string(),
+ // "main hint #4".to_string(),
+ // "main hint #5".to_string(),
+ // "other hint #0".to_string(),
+ // "other hint #1".to_string(),
+ // "other hint #2".to_string(),
+ // "other hint #3".to_string(),
+ // "other hint #4".to_string(),
+ // "other hint #5".to_string(),
+ // ];
+ // assert_eq!(expected_hints, cached_hint_labels(editor),
+ // "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
+ // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+ // assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
+ // });
+
+ // editor_edited.store(true, Ordering::Release);
+ // editor.update(cx, |editor, cx| {
+ // editor.change_selections(None, cx, |s| {
+ // s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
+ // });
+ // editor.handle_input("++++more text++++", cx);
+ // });
+ // cx.executor().run_until_parked();
+ // editor.update(cx, |editor, cx| {
+ // let expected_hints = vec![
+ // "main hint(edited) #0".to_string(),
+ // "main hint(edited) #1".to_string(),
+ // "main hint(edited) #2".to_string(),
+ // "main hint(edited) #3".to_string(),
+ // "main hint(edited) #4".to_string(),
+ // "main hint(edited) #5".to_string(),
+ // "other hint(edited) #0".to_string(),
+ // "other hint(edited) #1".to_string(),
+ // ];
+ // assert_eq!(
+ // expected_hints,
+ // cached_hint_labels(editor),
+ // "After multibuffer edit, editor gets scolled back to the last selection; \
+ // all hints should be invalidated and requeried for all of its visible excerpts"
+ // );
+ // assert_eq!(expected_hints, visible_hint_labels(editor, cx));
+
+ // let current_cache_version = editor.inlay_hint_cache().version;
+ // let minimum_expected_version = last_scroll_update_version + expected_hints.len();
+ // assert!(
+ // current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1,
+ // "Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update"
+ // );
+ // });
+ // }
#[gpui::test]
async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {
@@ -452,483 +452,475 @@ pub fn split_display_range_by_lines(
result
}
-// #[cfg(test)]
-// mod tests {
-// use super::*;
-// use crate::{
-// display_map::Inlay,
-// test::{},
-// Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
-// };
-// use project::Project;
-// use settings::SettingsStore;
-// use util::post_inc;
-
-// #[gpui::test]
-// fn test_previous_word_start(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// previous_word_start(&snapshot, display_points[1]),
-// display_points[0]
-// );
-// }
-
-// assert("\nˇ ˇlorem", cx);
-// assert("ˇ\nˇ lorem", cx);
-// assert(" ˇloremˇ", cx);
-// assert("ˇ ˇlorem", cx);
-// assert(" ˇlorˇem", cx);
-// assert("\nlorem\nˇ ˇipsum", cx);
-// assert("\n\nˇ\nˇ", cx);
-// assert(" ˇlorem ˇipsum", cx);
-// assert("loremˇ-ˇipsum", cx);
-// assert("loremˇ-#$@ˇipsum", cx);
-// assert("ˇlorem_ˇipsum", cx);
-// assert(" ˇdefγˇ", cx);
-// assert(" ˇbcΔˇ", cx);
-// assert(" abˇ——ˇcd", cx);
-// }
-
-// #[gpui::test]
-// fn test_previous_subword_start(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// previous_subword_start(&snapshot, display_points[1]),
-// display_points[0]
-// );
-// }
-
-// // Subword boundaries are respected
-// assert("lorem_ˇipˇsum", cx);
-// assert("lorem_ˇipsumˇ", cx);
-// assert("ˇlorem_ˇipsum", cx);
-// assert("lorem_ˇipsum_ˇdolor", cx);
-// assert("loremˇIpˇsum", cx);
-// assert("loremˇIpsumˇ", cx);
-
-// // Word boundaries are still respected
-// assert("\nˇ ˇlorem", cx);
-// assert(" ˇloremˇ", cx);
-// assert(" ˇlorˇem", cx);
-// assert("\nlorem\nˇ ˇipsum", cx);
-// assert("\n\nˇ\nˇ", cx);
-// assert(" ˇlorem ˇipsum", cx);
-// assert("loremˇ-ˇipsum", cx);
-// assert("loremˇ-#$@ˇipsum", cx);
-// assert(" ˇdefγˇ", cx);
-// assert(" bcˇΔˇ", cx);
-// assert(" ˇbcδˇ", cx);
-// assert(" abˇ——ˇcd", cx);
-// }
-
-// #[gpui::test]
-// fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(
-// marked_text: &str,
-// cx: &mut gpui::AppContext,
-// is_boundary: impl FnMut(char, char) -> bool,
-// ) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// find_preceding_boundary(
-// &snapshot,
-// display_points[1],
-// FindRange::MultiLine,
-// is_boundary
-// ),
-// display_points[0]
-// );
-// }
-
-// assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
-// left == 'c' && right == 'd'
-// });
-// assert("abcdef\nˇgh\nijˇk", cx, |left, right| {
-// left == '\n' && right == 'g'
-// });
-// let mut line_count = 0;
-// assert("abcdef\nˇgh\nijˇk", cx, |left, _| {
-// if left == '\n' {
-// line_count += 1;
-// line_count == 2
-// } else {
-// false
-// }
-// });
-// }
-
-// #[gpui::test]
-// fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// let input_text = "abcdefghijklmnopqrstuvwxys";
-// let family_id = cx
-// .font_cache()
-// .load_family(&["Helvetica"], &Default::default())
-// .unwrap();
-// let font_id = cx
-// .font_cache()
-// .select_font(family_id, &Default::default())
-// .unwrap();
-// let font_size = 14.0;
-// let buffer = MultiBuffer::build_simple(input_text, cx);
-// let buffer_snapshot = buffer.read(cx).snapshot(cx);
-// let display_map =
-// cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
-
-// // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
-// let mut id = 0;
-// let inlays = (0..buffer_snapshot.len())
-// .map(|offset| {
-// [
-// Inlay {
-// id: InlayId::Suggestion(post_inc(&mut id)),
-// position: buffer_snapshot.anchor_at(offset, Bias::Left),
-// text: format!("test").into(),
-// },
-// Inlay {
-// id: InlayId::Suggestion(post_inc(&mut id)),
-// position: buffer_snapshot.anchor_at(offset, Bias::Right),
-// text: format!("test").into(),
-// },
-// Inlay {
-// id: InlayId::Hint(post_inc(&mut id)),
-// position: buffer_snapshot.anchor_at(offset, Bias::Left),
-// text: format!("test").into(),
-// },
-// Inlay {
-// id: InlayId::Hint(post_inc(&mut id)),
-// position: buffer_snapshot.anchor_at(offset, Bias::Right),
-// text: format!("test").into(),
-// },
-// ]
-// })
-// .flatten()
-// .collect();
-// let snapshot = display_map.update(cx, |map, cx| {
-// map.splice_inlays(Vec::new(), inlays, cx);
-// map.snapshot(cx)
-// });
-
-// assert_eq!(
-// find_preceding_boundary(
-// &snapshot,
-// buffer_snapshot.len().to_display_point(&snapshot),
-// FindRange::MultiLine,
-// |left, _| left == 'e',
-// ),
-// snapshot
-// .buffer_snapshot
-// .offset_to_point(5)
-// .to_display_point(&snapshot),
-// "Should not stop at inlays when looking for boundaries"
-// );
-// }
-
-// #[gpui::test]
-// fn test_next_word_end(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// next_word_end(&snapshot, display_points[0]),
-// display_points[1]
-// );
-// }
-
-// assert("\nˇ loremˇ", cx);
-// assert(" ˇloremˇ", cx);
-// assert(" lorˇemˇ", cx);
-// assert(" loremˇ ˇ\nipsum\n", cx);
-// assert("\nˇ\nˇ\n\n", cx);
-// assert("loremˇ ipsumˇ ", cx);
-// assert("loremˇ-ˇipsum", cx);
-// assert("loremˇ#$@-ˇipsum", cx);
-// assert("loremˇ_ipsumˇ", cx);
-// assert(" ˇbcΔˇ", cx);
-// assert(" abˇ——ˇcd", cx);
-// }
-
-// #[gpui::test]
-// fn test_next_subword_end(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// next_subword_end(&snapshot, display_points[0]),
-// display_points[1]
-// );
-// }
-
-// // Subword boundaries are respected
-// assert("loˇremˇ_ipsum", cx);
-// assert("ˇloremˇ_ipsum", cx);
-// assert("loremˇ_ipsumˇ", cx);
-// assert("loremˇ_ipsumˇ_dolor", cx);
-// assert("loˇremˇIpsum", cx);
-// assert("loremˇIpsumˇDolor", cx);
-
-// // Word boundaries are still respected
-// assert("\nˇ loremˇ", cx);
-// assert(" ˇloremˇ", cx);
-// assert(" lorˇemˇ", cx);
-// assert(" loremˇ ˇ\nipsum\n", cx);
-// assert("\nˇ\nˇ\n\n", cx);
-// assert("loremˇ ipsumˇ ", cx);
-// assert("loremˇ-ˇipsum", cx);
-// assert("loremˇ#$@-ˇipsum", cx);
-// assert("loremˇ_ipsumˇ", cx);
-// assert(" ˇbcˇΔ", cx);
-// assert(" abˇ——ˇcd", cx);
-// }
-
-// #[gpui::test]
-// fn test_find_boundary(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(
-// marked_text: &str,
-// cx: &mut gpui::AppContext,
-// is_boundary: impl FnMut(char, char) -> bool,
-// ) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// find_boundary(
-// &snapshot,
-// display_points[0],
-// FindRange::MultiLine,
-// is_boundary
-// ),
-// display_points[1]
-// );
-// }
-
-// assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
-// left == 'j' && right == 'k'
-// });
-// assert("abˇcdef\ngh\nˇijk", cx, |left, right| {
-// left == '\n' && right == 'i'
-// });
-// let mut line_count = 0;
-// assert("abcˇdef\ngh\nˇijk", cx, |left, _| {
-// if left == '\n' {
-// line_count += 1;
-// line_count == 2
-// } else {
-// false
-// }
-// });
-// }
-
-// #[gpui::test]
-// fn test_surrounding_word(cx: &mut gpui::AppContext) {
-// init_test(cx);
-
-// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
-// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
-// assert_eq!(
-// surrounding_word(&snapshot, display_points[1]),
-// display_points[0]..display_points[2],
-// "{}",
-// marked_text.to_string()
-// );
-// }
-
-// assert("ˇˇloremˇ ipsum", cx);
-// assert("ˇloˇremˇ ipsum", cx);
-// assert("ˇloremˇˇ ipsum", cx);
-// assert("loremˇ ˇ ˇipsum", cx);
-// assert("lorem\nˇˇˇ\nipsum", cx);
-// assert("lorem\nˇˇipsumˇ", cx);
-// assert("loremˇ,ˇˇ ipsum", cx);
-// assert("ˇloremˇˇ, ipsum", cx);
-// }
-
-// #[gpui::test]
-// async fn test_move_up_and_down_with_excerpts(cx: &mut gpui::TestAppContext) {
-// cx.update(|cx| {
-// init_test(cx);
-// });
-
-// let mut cx = EditorTestContext::new(cx).await;
-// let editor = cx.editor.clone();
-// let window = cx.window.clone();
-// cx.update_window(window, |cx| {
-// let text_layout_details =
-// editor.read_with(cx, |editor, cx| editor.text_layout_details(cx));
-
-// let family_id = cx
-// .font_cache()
-// .load_family(&["Helvetica"], &Default::default())
-// .unwrap();
-// let font_id = cx
-// .font_cache()
-// .select_font(family_id, &Default::default())
-// .unwrap();
-
-// let buffer =
-// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abc\ndefg\nhijkl\nmn"));
-// let multibuffer = cx.add_model(|cx| {
-// let mut multibuffer = MultiBuffer::new(0);
-// multibuffer.push_excerpts(
-// buffer.clone(),
-// [
-// ExcerptRange {
-// context: Point::new(0, 0)..Point::new(1, 4),
-// primary: None,
-// },
-// ExcerptRange {
-// context: Point::new(2, 0)..Point::new(3, 2),
-// primary: None,
-// },
-// ],
-// cx,
-// );
-// multibuffer
-// });
-// let display_map =
-// cx.add_model(|cx| DisplayMap::new(multibuffer, font_id, 14.0, None, 2, 2, cx));
-// let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
-
-// assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
-
-// let col_2_x = snapshot.x_for_point(DisplayPoint::new(2, 2), &text_layout_details);
-
-// // Can't move up into the first excerpt's header
-// assert_eq!(
-// up(
-// &snapshot,
-// DisplayPoint::new(2, 2),
-// SelectionGoal::HorizontalPosition(col_2_x),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(2, 0),
-// SelectionGoal::HorizontalPosition(0.0)
-// ),
-// );
-// assert_eq!(
-// up(
-// &snapshot,
-// DisplayPoint::new(2, 0),
-// SelectionGoal::None,
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(2, 0),
-// SelectionGoal::HorizontalPosition(0.0)
-// ),
-// );
-
-// let col_4_x = snapshot.x_for_point(DisplayPoint::new(3, 4), &text_layout_details);
-
-// // Move up and down within first excerpt
-// assert_eq!(
-// up(
-// &snapshot,
-// DisplayPoint::new(3, 4),
-// SelectionGoal::HorizontalPosition(col_4_x),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(2, 3),
-// SelectionGoal::HorizontalPosition(col_4_x)
-// ),
-// );
-// assert_eq!(
-// down(
-// &snapshot,
-// DisplayPoint::new(2, 3),
-// SelectionGoal::HorizontalPosition(col_4_x),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(3, 4),
-// SelectionGoal::HorizontalPosition(col_4_x)
-// ),
-// );
-
-// let col_5_x = snapshot.x_for_point(DisplayPoint::new(6, 5), &text_layout_details);
-
-// // Move up and down across second excerpt's header
-// assert_eq!(
-// up(
-// &snapshot,
-// DisplayPoint::new(6, 5),
-// SelectionGoal::HorizontalPosition(col_5_x),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(3, 4),
-// SelectionGoal::HorizontalPosition(col_5_x)
-// ),
-// );
-// assert_eq!(
-// down(
-// &snapshot,
-// DisplayPoint::new(3, 4),
-// SelectionGoal::HorizontalPosition(col_5_x),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(6, 5),
-// SelectionGoal::HorizontalPosition(col_5_x)
-// ),
-// );
-
-// let max_point_x = snapshot.x_for_point(DisplayPoint::new(7, 2), &text_layout_details);
-
-// // Can't move down off the end
-// assert_eq!(
-// down(
-// &snapshot,
-// DisplayPoint::new(7, 0),
-// SelectionGoal::HorizontalPosition(0.0),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(7, 2),
-// SelectionGoal::HorizontalPosition(max_point_x)
-// ),
-// );
-// assert_eq!(
-// down(
-// &snapshot,
-// DisplayPoint::new(7, 2),
-// SelectionGoal::HorizontalPosition(max_point_x),
-// false,
-// &text_layout_details
-// ),
-// (
-// DisplayPoint::new(7, 2),
-// SelectionGoal::HorizontalPosition(max_point_x)
-// ),
-// );
-// });
-// }
-
-// fn init_test(cx: &mut gpui::AppContext) {
-// cx.set_global(SettingsStore::test(cx));
-// theme::init(cx);
-// language::init(cx);
-// crate::init(cx);
-// Project::init_settings(cx);
-// }
-// }
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ display_map::Inlay,
+ test::{editor_test_context::EditorTestContext, marked_display_snapshot},
+ Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
+ };
+ use gpui::{font, Context as _};
+ use project::Project;
+ use settings::SettingsStore;
+ use util::post_inc;
+
+ #[gpui::test]
+ fn test_previous_word_start(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ previous_word_start(&snapshot, display_points[1]),
+ display_points[0]
+ );
+ }
+
+ assert("\nˇ ˇlorem", cx);
+ assert("ˇ\nˇ lorem", cx);
+ assert(" ˇloremˇ", cx);
+ assert("ˇ ˇlorem", cx);
+ assert(" ˇlorˇem", cx);
+ assert("\nlorem\nˇ ˇipsum", cx);
+ assert("\n\nˇ\nˇ", cx);
+ assert(" ˇlorem ˇipsum", cx);
+ assert("loremˇ-ˇipsum", cx);
+ assert("loremˇ-#$@ˇipsum", cx);
+ assert("ˇlorem_ˇipsum", cx);
+ assert(" ˇdefγˇ", cx);
+ assert(" ˇbcΔˇ", cx);
+ assert(" abˇ——ˇcd", cx);
+ }
+
+ #[gpui::test]
+ fn test_previous_subword_start(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ previous_subword_start(&snapshot, display_points[1]),
+ display_points[0]
+ );
+ }
+
+ // Subword boundaries are respected
+ assert("lorem_ˇipˇsum", cx);
+ assert("lorem_ˇipsumˇ", cx);
+ assert("ˇlorem_ˇipsum", cx);
+ assert("lorem_ˇipsum_ˇdolor", cx);
+ assert("loremˇIpˇsum", cx);
+ assert("loremˇIpsumˇ", cx);
+
+ // Word boundaries are still respected
+ assert("\nˇ ˇlorem", cx);
+ assert(" ˇloremˇ", cx);
+ assert(" ˇlorˇem", cx);
+ assert("\nlorem\nˇ ˇipsum", cx);
+ assert("\n\nˇ\nˇ", cx);
+ assert(" ˇlorem ˇipsum", cx);
+ assert("loremˇ-ˇipsum", cx);
+ assert("loremˇ-#$@ˇipsum", cx);
+ assert(" ˇdefγˇ", cx);
+ assert(" bcˇΔˇ", cx);
+ assert(" ˇbcδˇ", cx);
+ assert(" abˇ——ˇcd", cx);
+ }
+
+ #[gpui::test]
+ fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(
+ marked_text: &str,
+ cx: &mut gpui::AppContext,
+ is_boundary: impl FnMut(char, char) -> bool,
+ ) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ find_preceding_boundary(
+ &snapshot,
+ display_points[1],
+ FindRange::MultiLine,
+ is_boundary
+ ),
+ display_points[0]
+ );
+ }
+
+ assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
+ left == 'c' && right == 'd'
+ });
+ assert("abcdef\nˇgh\nijˇk", cx, |left, right| {
+ left == '\n' && right == 'g'
+ });
+ let mut line_count = 0;
+ assert("abcdef\nˇgh\nijˇk", cx, |left, _| {
+ if left == '\n' {
+ line_count += 1;
+ line_count == 2
+ } else {
+ false
+ }
+ });
+ }
+
+ #[gpui::test]
+ fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ let input_text = "abcdefghijklmnopqrstuvwxys";
+ let font = font("Helvetica");
+ let font_size = px(14.0);
+ let buffer = MultiBuffer::build_simple(input_text, cx);
+ let buffer_snapshot = buffer.read(cx).snapshot(cx);
+ let display_map =
+ cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
+
+ // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
+ let mut id = 0;
+ let inlays = (0..buffer_snapshot.len())
+ .map(|offset| {
+ [
+ Inlay {
+ id: InlayId::Suggestion(post_inc(&mut id)),
+ position: buffer_snapshot.anchor_at(offset, Bias::Left),
+ text: format!("test").into(),
+ },
+ Inlay {
+ id: InlayId::Suggestion(post_inc(&mut id)),
+ position: buffer_snapshot.anchor_at(offset, Bias::Right),
+ text: format!("test").into(),
+ },
+ Inlay {
+ id: InlayId::Hint(post_inc(&mut id)),
+ position: buffer_snapshot.anchor_at(offset, Bias::Left),
+ text: format!("test").into(),
+ },
+ Inlay {
+ id: InlayId::Hint(post_inc(&mut id)),
+ position: buffer_snapshot.anchor_at(offset, Bias::Right),
+ text: format!("test").into(),
+ },
+ ]
+ })
+ .flatten()
+ .collect();
+ let snapshot = display_map.update(cx, |map, cx| {
+ map.splice_inlays(Vec::new(), inlays, cx);
+ map.snapshot(cx)
+ });
+
+ assert_eq!(
+ find_preceding_boundary(
+ &snapshot,
+ buffer_snapshot.len().to_display_point(&snapshot),
+ FindRange::MultiLine,
+ |left, _| left == 'e',
+ ),
+ snapshot
+ .buffer_snapshot
+ .offset_to_point(5)
+ .to_display_point(&snapshot),
+ "Should not stop at inlays when looking for boundaries"
+ );
+ }
+
+ #[gpui::test]
+ fn test_next_word_end(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ next_word_end(&snapshot, display_points[0]),
+ display_points[1]
+ );
+ }
+
+ assert("\nˇ loremˇ", cx);
+ assert(" ˇloremˇ", cx);
+ assert(" lorˇemˇ", cx);
+ assert(" loremˇ ˇ\nipsum\n", cx);
+ assert("\nˇ\nˇ\n\n", cx);
+ assert("loremˇ ipsumˇ ", cx);
+ assert("loremˇ-ˇipsum", cx);
+ assert("loremˇ#$@-ˇipsum", cx);
+ assert("loremˇ_ipsumˇ", cx);
+ assert(" ˇbcΔˇ", cx);
+ assert(" abˇ——ˇcd", cx);
+ }
+
+ #[gpui::test]
+ fn test_next_subword_end(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ next_subword_end(&snapshot, display_points[0]),
+ display_points[1]
+ );
+ }
+
+ // Subword boundaries are respected
+ assert("loˇremˇ_ipsum", cx);
+ assert("ˇloremˇ_ipsum", cx);
+ assert("loremˇ_ipsumˇ", cx);
+ assert("loremˇ_ipsumˇ_dolor", cx);
+ assert("loˇremˇIpsum", cx);
+ assert("loremˇIpsumˇDolor", cx);
+
+ // Word boundaries are still respected
+ assert("\nˇ loremˇ", cx);
+ assert(" ˇloremˇ", cx);
+ assert(" lorˇemˇ", cx);
+ assert(" loremˇ ˇ\nipsum\n", cx);
+ assert("\nˇ\nˇ\n\n", cx);
+ assert("loremˇ ipsumˇ ", cx);
+ assert("loremˇ-ˇipsum", cx);
+ assert("loremˇ#$@-ˇipsum", cx);
+ assert("loremˇ_ipsumˇ", cx);
+ assert(" ˇbcˇΔ", cx);
+ assert(" abˇ——ˇcd", cx);
+ }
+
+ #[gpui::test]
+ fn test_find_boundary(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(
+ marked_text: &str,
+ cx: &mut gpui::AppContext,
+ is_boundary: impl FnMut(char, char) -> bool,
+ ) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ find_boundary(
+ &snapshot,
+ display_points[0],
+ FindRange::MultiLine,
+ is_boundary
+ ),
+ display_points[1]
+ );
+ }
+
+ assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
+ left == 'j' && right == 'k'
+ });
+ assert("abˇcdef\ngh\nˇijk", cx, |left, right| {
+ left == '\n' && right == 'i'
+ });
+ let mut line_count = 0;
+ assert("abcˇdef\ngh\nˇijk", cx, |left, _| {
+ if left == '\n' {
+ line_count += 1;
+ line_count == 2
+ } else {
+ false
+ }
+ });
+ }
+
+ #[gpui::test]
+ fn test_surrounding_word(cx: &mut gpui::AppContext) {
+ init_test(cx);
+
+ fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
+ let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
+ assert_eq!(
+ surrounding_word(&snapshot, display_points[1]),
+ display_points[0]..display_points[2],
+ "{}",
+ marked_text.to_string()
+ );
+ }
+
+ assert("ˇˇloremˇ ipsum", cx);
+ assert("ˇloˇremˇ ipsum", cx);
+ assert("ˇloremˇˇ ipsum", cx);
+ assert("loremˇ ˇ ˇipsum", cx);
+ assert("lorem\nˇˇˇ\nipsum", cx);
+ assert("lorem\nˇˇipsumˇ", cx);
+ assert("loremˇ,ˇˇ ipsum", cx);
+ assert("ˇloremˇˇ, ipsum", cx);
+ }
+
+ #[gpui::test]
+ async fn test_move_up_and_down_with_excerpts(cx: &mut gpui::TestAppContext) {
+ cx.update(|cx| {
+ init_test(cx);
+ });
+
+ let mut cx = EditorTestContext::new(cx).await;
+ let editor = cx.editor.clone();
+ let window = cx.window.clone();
+ cx.update_window(window, |_, cx| {
+ let text_layout_details =
+ editor.update(cx, |editor, cx| editor.text_layout_details(cx));
+
+ let font = font("Helvetica");
+
+ let buffer = cx
+ .build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn"));
+ let multibuffer = cx.build_model(|cx| {
+ let mut multibuffer = MultiBuffer::new(0);
+ multibuffer.push_excerpts(
+ buffer.clone(),
+ [
+ ExcerptRange {
+ context: Point::new(0, 0)..Point::new(1, 4),
+ primary: None,
+ },
+ ExcerptRange {
+ context: Point::new(2, 0)..Point::new(3, 2),
+ primary: None,
+ },
+ ],
+ cx,
+ );
+ multibuffer
+ });
+ let display_map =
+ cx.build_model(|cx| DisplayMap::new(multibuffer, font, px(14.0), None, 2, 2, cx));
+ let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
+
+ assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
+
+ let col_2_x =
+ snapshot.x_for_display_point(DisplayPoint::new(2, 2), &text_layout_details);
+
+ // Can't move up into the first excerpt's header
+ assert_eq!(
+ up(
+ &snapshot,
+ DisplayPoint::new(2, 2),
+ SelectionGoal::HorizontalPosition(col_2_x.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(2, 0),
+ SelectionGoal::HorizontalPosition(0.0)
+ ),
+ );
+ assert_eq!(
+ up(
+ &snapshot,
+ DisplayPoint::new(2, 0),
+ SelectionGoal::None,
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(2, 0),
+ SelectionGoal::HorizontalPosition(0.0)
+ ),
+ );
+
+ let col_4_x =
+ snapshot.x_for_display_point(DisplayPoint::new(3, 4), &text_layout_details);
+
+ // Move up and down within first excerpt
+ assert_eq!(
+ up(
+ &snapshot,
+ DisplayPoint::new(3, 4),
+ SelectionGoal::HorizontalPosition(col_4_x.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(2, 3),
+ SelectionGoal::HorizontalPosition(col_4_x.0)
+ ),
+ );
+ assert_eq!(
+ down(
+ &snapshot,
+ DisplayPoint::new(2, 3),
+ SelectionGoal::HorizontalPosition(col_4_x.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(3, 4),
+ SelectionGoal::HorizontalPosition(col_4_x.0)
+ ),
+ );
+
+ let col_5_x =
+ snapshot.x_for_display_point(DisplayPoint::new(6, 5), &text_layout_details);
+
+ // Move up and down across second excerpt's header
+ assert_eq!(
+ up(
+ &snapshot,
+ DisplayPoint::new(6, 5),
+ SelectionGoal::HorizontalPosition(col_5_x.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(3, 4),
+ SelectionGoal::HorizontalPosition(col_5_x.0)
+ ),
+ );
+ assert_eq!(
+ down(
+ &snapshot,
+ DisplayPoint::new(3, 4),
+ SelectionGoal::HorizontalPosition(col_5_x.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(6, 5),
+ SelectionGoal::HorizontalPosition(col_5_x.0)
+ ),
+ );
+
+ let max_point_x =
+ snapshot.x_for_display_point(DisplayPoint::new(7, 2), &text_layout_details);
+
+ // Can't move down off the end
+ assert_eq!(
+ down(
+ &snapshot,
+ DisplayPoint::new(7, 0),
+ SelectionGoal::HorizontalPosition(0.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(7, 2),
+ SelectionGoal::HorizontalPosition(max_point_x.0)
+ ),
+ );
+ assert_eq!(
+ down(
+ &snapshot,
+ DisplayPoint::new(7, 2),
+ SelectionGoal::HorizontalPosition(max_point_x.0),
+ false,
+ &text_layout_details
+ ),
+ (
+ DisplayPoint::new(7, 2),
+ SelectionGoal::HorizontalPosition(max_point_x.0)
+ ),
+ );
+ });
+ }
+
+ fn init_test(cx: &mut gpui::AppContext) {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ theme::init(theme::LoadThemes::JustBase, cx);
+ language::init(cx);
+ crate::init(cx);
+ Project::init_settings(cx);
+ }
+}