@@ -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::<String>();
+ 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) {
@@ -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<WrapMap>,
- cx: &mut gpui::TestAppContext,
- ) -> (ModelHandle<Self>, 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)
- }
- }
}
@@ -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<AppState> {
fs: Arc::new(RealFs),
})
}
+
+pub struct Observer<T>(PhantomData<T>);
+
+impl<T: 'static + Send + Sync> Entity for Observer<T> {
+ type Event = ();
+}
+
+impl<T: Entity> Observer<T> {
+ pub fn new(
+ handle: &ModelHandle<T>,
+ cx: &mut gpui::TestAppContext,
+ ) -> (ModelHandle<Self>, 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)
+ }
+}