diff --git a/Cargo.lock b/Cargo.lock index 2b49f5d4ce..d4508ba73c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2576,6 +2576,8 @@ dependencies = [ "anyhow", "clock", "collections", + "ctor", + "env_logger", "futures", "gpui", "lazy_static", diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 585f885d1a..d6121cc7bd 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -46,6 +46,8 @@ gpui = { path = "../gpui", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } text = { path = "../text", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } +ctor = "0.1" +env_logger = "0.8" rand = "0.8.3" tree-sitter-rust = "0.20.0" unindent = "0.1.7" diff --git a/crates/language/src/fragment_list.rs b/crates/language/src/fragment_list.rs index a1c27757de..e8312aeda0 100644 --- a/crates/language/src/fragment_list.rs +++ b/crates/language/src/fragment_list.rs @@ -157,21 +157,22 @@ impl FragmentList { for edit in patches[patch_ix].edits() { let edit_start = edit.new.start; let edit_end = edit.new.start + edit.old_len(); - if edit_end < new_range.start { + if edit_start > new_range.end { + break; + } else if edit_end < new_range.start { let delta = edit.new_len() as isize - edit.old_len() as isize; new_range.start = (new_range.start as isize + delta) as usize; new_range.end = (new_range.end as isize + delta) as usize; - } else if edit_start >= new_range.end { - break; } else { let mut new_range_len = new_range.len(); new_range_len -= cmp::min(new_range.end, edit_end) - cmp::max(new_range.start, edit_start); - if edit_start > new_range.start { + if edit_start < new_range.start { + new_range.start = edit.new.end; + } else { new_range_len += edit.new_len(); } - new_range.start = cmp::min(new_range.start, edit.new.end); new_range.end = new_range.start + new_range_len; } } @@ -362,11 +363,13 @@ impl Location { #[cfg(test)] mod tests { + use std::env; + use super::*; use crate::Buffer; use gpui::MutableAppContext; use rand::prelude::*; - use text::Point; + use text::{Point, RandomCharIter}; use util::test::sample_text; #[gpui::test] @@ -451,6 +454,77 @@ mod tests { ); } + #[gpui::test(iterations = 100)] + fn test_random(cx: &mut MutableAppContext, mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let mut buffers: Vec> = Vec::new(); + let list = cx.add_model(|_| FragmentList::new()); + let mut fragment_ids = Vec::new(); + let mut expected_fragments = Vec::new(); + + for _ in 0..operations { + match rng.gen_range(0..100) { + 0..=19 if !buffers.is_empty() => { + let buffer = buffers.choose(&mut rng).unwrap(); + buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx)); + } + _ => { + let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) { + let base_text = RandomCharIter::new(&mut rng).take(10).collect::(); + buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx))); + buffers.last().unwrap() + } else { + buffers.choose(&mut rng).unwrap() + }; + + let buffer = buffer_handle.read(cx); + let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right); + let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let header_height = rng.gen_range(0..=5); + let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix); + log::info!( + "Pushing fragment for buffer {}: {:?}[{:?}] = {:?}", + buffer_handle.id(), + buffer.text(), + start_ix..end_ix, + &buffer.text()[start_ix..end_ix] + ); + + let fragment_id = list.update(cx, |list, cx| { + list.push( + FragmentProperties { + buffer: &buffer_handle, + range: start_ix..end_ix, + header_height, + }, + cx, + ) + }); + fragment_ids.push(fragment_id); + expected_fragments.push((buffer_handle.clone(), anchor_range, header_height)); + } + } + + let snapshot = list.read(cx).snapshot(cx); + let mut expected_text = String::new(); + for (buffer, range, header_height) in &expected_fragments { + let buffer = buffer.read(cx); + if !expected_text.is_empty() { + expected_text.push('\n'); + } + + for _ in 0..*header_height { + expected_text.push('\n'); + } + expected_text.extend(buffer.text_for_range(range.clone())); + } + assert_eq!(snapshot.text(), expected_text); + } + } + #[gpui::test(iterations = 100)] fn test_location(mut rng: StdRng) { let mut lhs = Default::default(); diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index cff74af1e3..9a52322c22 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -12,6 +12,13 @@ use std::{ }; use unindent::Unindent as _; +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + // std::env::set_var("RUST_LOG", "info"); + env_logger::init(); +} + #[test] fn test_select_language() { let registry = LanguageRegistry { diff --git a/crates/text/src/random_char_iter.rs b/crates/text/src/random_char_iter.rs index 244665688d..94913150be 100644 --- a/crates/text/src/random_char_iter.rs +++ b/crates/text/src/random_char_iter.rs @@ -12,6 +12,14 @@ impl Iterator for RandomCharIter { type Item = char; fn next(&mut self) -> Option { + if std::env::var("SIMPLE_TEXT").map_or(false, |v| !v.is_empty()) { + return if self.0.gen_range(0..100) < 5 { + Some('\n') + } else { + Some(self.0.gen_range(b'a'..b'z' + 1).into()) + }; + } + match self.0.gen_range(0..100) { // whitespace 0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(),