@@ -1026,337 +1026,334 @@ 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::{font, px, test::observe, Platform};
+ use rand::prelude::*;
+ use settings::SettingsStore;
+ use smol::stream::StreamExt;
+ use std::{cmp, env, num::NonZeroU32};
+ use text::Rope;
+ use theme::LoadThemes;
+
+ #[gpui::test(iterations = 100)]
+ async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
+ // todo!() this test is flaky
+ 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 text_system = cx.test_platform.text_system();
+ let mut wrap_width = if rng.gen_bool(0.1) {
+ None
+ } else {
+ Some(px(rng.gen_range(0.0..=1000.0)))
+ };
+ let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
+ let font = font("Helvetica");
+ let font_id = text_system.font_id(&font).unwrap();
+ let font_size = px(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, text_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, 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(px(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.update(|cx| {
+ let settings = SettingsStore::test(cx);
+ cx.set_global(settings);
+ theme::init(LoadThemes::JustBase, cx);
+ });
+ }
+
+ fn wrap_text(
+ unwrapped_text: &str,
+ wrap_width: Option<Pixels>,
+ 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,347 @@ 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) {
+ // todo!() this test is flaky
+ 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) {