diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index 5e41412bf165d92aeb6f97d52c2a726013eae332..c8d896b0436ce489289812a3e62958d976931150 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -89,6 +89,11 @@ impl DisplayMap { self.wrap_map .update(cx, |map, cx| map.set_wrap_width(width, cx)) } + + #[cfg(test)] + pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool { + self.wrap_map.read(cx).is_rewrapping() + } } pub struct DisplayMapSnapshot { @@ -323,10 +328,139 @@ mod tests { language::{Language, LanguageConfig}, settings::Theme, test::*, + util::RandomCharIter, }; use buffer::History; use gpui::MutableAppContext; - use std::sync::Arc; + use rand::{prelude::StdRng, Rng}; + use std::{env, sync::Arc}; + use Bias::*; + + #[gpui::test(iterations = 100, seed = 495)] + async fn test_random(mut cx: gpui::TestAppContext, mut rng: StdRng) { + cx.foreground().set_block_on_ticks(0..=50); + cx.foreground().forbid_parking(); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let font_cache = cx.font_cache().clone(); + let settings = Settings { + tab_size: rng.gen_range(1..=4), + buffer_font_family: font_cache.load_family(&["Helvetica"]).unwrap(), + buffer_font_size: 14.0, + ..Settings::new(&font_cache).unwrap() + }; + let max_wrap_width = 300.0; + let mut wrap_width = if rng.gen_bool(0.1) { + None + } else { + Some(rng.gen_range(0.0..=max_wrap_width)) + }; + + log::info!("tab size: {}", settings.tab_size); + log::info!("wrap width: {:?}", wrap_width); + + let buffer = cx.add_model(|cx| { + let len = rng.gen_range(0..10); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + Buffer::new(0, text, cx) + }); + + let map = cx.add_model(|cx| DisplayMap::new(buffer.clone(), settings, wrap_width, cx)); + let (_observer, notifications) = Observer::new(&map, &mut cx); + + for _i in 0..operations { + match rng.gen_range(0..=100) { + 0..=19 => { + wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=max_wrap_width)) + }; + log::info!("setting wrap width to {:?}", wrap_width); + map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + } + 20..=39 => { + let mut ranges = Vec::new(); + for _ in 0..rng.gen_range(1..=3) { + buffer.read_with(&cx, |buffer, _| { + let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); + let start = buffer.clip_offset(rng.gen_range(0..=end), Left); + ranges.push(start..end); + }); + } + + if rng.gen() { + log::info!("unfolding ranges: {:?}", ranges); + map.update(&mut cx, |map, cx| { + map.unfold(ranges, cx); + }); + } else { + log::info!("folding ranges: {:?}", ranges); + map.update(&mut cx, |map, cx| { + map.fold(ranges, cx); + }); + } + } + _ => { + buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx)); + } + } + + if map.read_with(&cx, |map, cx| map.is_rewrapping(cx)) { + notifications.recv().await.unwrap(); + } + + let snapshot = map.update(&mut cx, |map, cx| map.snapshot(cx)); + log::info!("buffer text: {:?}", buffer.read_with(&cx, |b, _| b.text())); + log::info!("display text: {:?}", snapshot.text()); + + // line boundaries + for _ in 0..5 { + let row = rng.gen_range(0..=snapshot.max_point().row()); + let column = rng.gen_range(0..=snapshot.line_len(row)); + let point = snapshot.clip_point(DisplayPoint::new(row, column), Left); + + let (prev_display_bound, prev_buffer_bound) = snapshot.prev_row_boundary(point); + let (next_display_bound, next_buffer_bound) = snapshot.next_row_boundary(point); + + assert!(prev_display_bound <= point); + assert!(next_display_bound >= point); + assert_eq!(prev_buffer_bound.column, 0); + assert_eq!(prev_display_bound.column(), 0); + if next_display_bound < snapshot.max_point() { + assert_eq!(next_buffer_bound.column, 0); + assert_eq!(next_display_bound.column(), 0); + } + + assert_eq!( + prev_buffer_bound.to_display_point(&snapshot), + prev_display_bound, + "{:?} to display point", + prev_buffer_bound + ); + assert_eq!( + next_buffer_bound.to_display_point(&snapshot), + next_display_bound, + "{:?} to display point", + next_buffer_bound + ); + assert_eq!( + prev_display_bound.to_buffer_point(&snapshot, Left), + prev_buffer_bound, + "{:?} to buffer point", + prev_display_bound + ); + assert_eq!( + next_display_bound.to_buffer_point(&snapshot, Left), + next_buffer_bound, + "{:?} to buffer point", + next_display_bound + ); + } + } + } #[gpui::test] async fn test_soft_wraps(mut cx: gpui::TestAppContext) { diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index ec119af4d40fea700e56292184a362d9a243432f..be3f3238beed806f70e21dd325d55640e7c7dba7 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -875,11 +875,10 @@ mod tests { display_map::{fold_map::FoldMap, tab_map::TabMap}, Buffer, }, + test::Observer, util::RandomCharIter, }; - use gpui::ModelHandle; use rand::prelude::*; - use smol::channel; use std::env; #[gpui::test(iterations = 100)] @@ -1077,26 +1076,4 @@ mod tests { } } } - - struct Observer; - - impl Entity for Observer { - type Event = (); - } - - impl Observer { - fn new( - handle: &ModelHandle, - cx: &mut gpui::TestAppContext, - ) -> (ModelHandle, channel::Receiver<()>) { - let (notify_tx, notify_rx) = channel::unbounded(); - let observer = cx.add_model(|cx| { - cx.observe(handle, move |_, _, _| { - let _ = notify_tx.try_send(()); - }); - Observer - }); - (observer, notify_rx) - } - } } diff --git a/zed/src/test.rs b/zed/src/test.rs index adc75100a0e0be650614c0c73ad45265355f2b83..a3e5914b4ea94254a166b54a723b8da07795179b 100644 --- a/zed/src/test.rs +++ b/zed/src/test.rs @@ -1,6 +1,8 @@ use crate::{fs::RealFs, language::LanguageRegistry, rpc, settings, time::ReplicaId, AppState}; -use gpui::AppContext; +use gpui::{AppContext, Entity, ModelHandle}; +use smol::channel; use std::{ + marker::PhantomData, path::{Path, PathBuf}, sync::Arc, }; @@ -155,3 +157,25 @@ pub fn build_app_state(cx: &AppContext) -> Arc { fs: Arc::new(RealFs), }) } + +pub struct Observer(PhantomData); + +impl Entity for Observer { + type Event = (); +} + +impl Observer { + pub fn new( + handle: &ModelHandle, + cx: &mut gpui::TestAppContext, + ) -> (ModelHandle, channel::Receiver<()>) { + let (notify_tx, notify_rx) = channel::unbounded(); + let observer = cx.add_model(|cx| { + cx.observe(handle, move |_, _, _| { + let _ = notify_tx.try_send(()); + }); + Observer(PhantomData) + }); + (observer, notify_rx) + } +}