sum_tree: Replace rayon with futures (#41586)
Release Notes: - N/A *or* Added/Fixed/Improved ... Co-authored by: Kate <kate@zed.dev>
This commit is contained in:
@@ -14,10 +14,10 @@ path = "src/rope.rs"
|
||||
[dependencies]
|
||||
arrayvec = "0.7.1"
|
||||
log.workspace = true
|
||||
rayon.workspace = true
|
||||
sum_tree.workspace = true
|
||||
unicode-segmentation.workspace = true
|
||||
util.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::ops::Range;
|
||||
use criterion::{
|
||||
BatchSize, BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main,
|
||||
};
|
||||
use gpui::{AsyncApp, TestAppContext};
|
||||
use rand::prelude::*;
|
||||
use rand::rngs::StdRng;
|
||||
use rope::{Point, Rope};
|
||||
@@ -26,10 +27,10 @@ fn generate_random_text(rng: &mut StdRng, len: usize) -> String {
|
||||
str
|
||||
}
|
||||
|
||||
fn generate_random_rope(rng: &mut StdRng, text_len: usize) -> Rope {
|
||||
fn generate_random_rope(rng: &mut StdRng, text_len: usize, cx: &AsyncApp) -> Rope {
|
||||
let text = generate_random_text(rng, text_len);
|
||||
let mut rope = Rope::new();
|
||||
rope.push(&text);
|
||||
rope.push(&text, cx.background_executor());
|
||||
rope
|
||||
}
|
||||
|
||||
@@ -82,11 +83,13 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let text = generate_random_text(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
|
||||
b.iter(|| {
|
||||
let mut rope = Rope::new();
|
||||
for _ in 0..10 {
|
||||
rope.push(&text);
|
||||
rope.push(&text, cx.background_executor());
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -99,8 +102,10 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let mut random_ropes = Vec::new();
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
for _ in 0..5 {
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
random_ropes.push(rope);
|
||||
}
|
||||
|
||||
@@ -119,7 +124,9 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
|
||||
b.iter_batched(
|
||||
|| generate_random_rope_ranges(&mut rng, &rope),
|
||||
@@ -139,7 +146,9 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
|
||||
b.iter_batched(
|
||||
|| generate_random_rope_ranges(&mut rng, &rope),
|
||||
@@ -160,7 +169,9 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
|
||||
b.iter(|| {
|
||||
let chars = rope.chars().count();
|
||||
@@ -175,7 +186,9 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
|
||||
b.iter_batched(
|
||||
|| generate_random_rope_points(&mut rng, &rope),
|
||||
@@ -196,7 +209,9 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
|
||||
b.iter_batched(
|
||||
|| generate_random_rope_points(&mut rng, &rope),
|
||||
@@ -216,7 +231,9 @@ fn rope_benchmarks(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut rng = StdRng::seed_from_u64(SEED);
|
||||
let rope = generate_random_rope(&mut rng, *size);
|
||||
let cx = TestAppContext::single();
|
||||
let cx = cx.to_async();
|
||||
let rope = generate_random_rope(&mut rng, *size, &cx);
|
||||
|
||||
b.iter_batched(
|
||||
|| {
|
||||
|
||||
+178
-98
@@ -5,7 +5,7 @@ mod point_utf16;
|
||||
mod unclipped;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator as _};
|
||||
use gpui::BackgroundExecutor;
|
||||
use std::{
|
||||
cmp, fmt, io, mem,
|
||||
ops::{self, AddAssign, Range},
|
||||
@@ -31,6 +31,41 @@ impl Rope {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create a new rope from a string without trying to parallelize the construction for large strings.
|
||||
pub fn from_str_small(text: &str) -> Self {
|
||||
let mut rope = Self::new();
|
||||
rope.push_small(text);
|
||||
rope
|
||||
}
|
||||
|
||||
/// Create a new rope from a string.
|
||||
pub fn from_str(text: &str, executor: &BackgroundExecutor) -> Self {
|
||||
let mut rope = Self::new();
|
||||
rope.push(text, executor);
|
||||
rope
|
||||
}
|
||||
|
||||
/// Create a new rope from a string without trying to parallelize the construction for large strings.
|
||||
pub fn from_iter_small<'a, T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
|
||||
let mut rope = Rope::new();
|
||||
for chunk in iter {
|
||||
rope.push_small(chunk);
|
||||
}
|
||||
rope
|
||||
}
|
||||
|
||||
/// Create a new rope from a string.
|
||||
pub fn from_iter<'a, T: IntoIterator<Item = &'a str>>(
|
||||
iter: T,
|
||||
executor: &BackgroundExecutor,
|
||||
) -> Self {
|
||||
let mut rope = Rope::new();
|
||||
for chunk in iter {
|
||||
rope.push(chunk, executor);
|
||||
}
|
||||
rope
|
||||
}
|
||||
|
||||
/// Checks that `index`-th byte is the first byte in a UTF-8 code point
|
||||
/// sequence or the end of the string.
|
||||
///
|
||||
@@ -145,12 +180,12 @@ impl Rope {
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, range: Range<usize>, text: &str) {
|
||||
pub fn replace(&mut self, range: Range<usize>, text: &str, executor: &BackgroundExecutor) {
|
||||
let mut new_rope = Rope::new();
|
||||
let mut cursor = self.cursor(0);
|
||||
new_rope.append(cursor.slice(range.start));
|
||||
cursor.seek_forward(range.end);
|
||||
new_rope.push(text);
|
||||
new_rope.push(text, executor);
|
||||
new_rope.append(cursor.suffix());
|
||||
*self = new_rope;
|
||||
}
|
||||
@@ -168,28 +203,12 @@ impl Rope {
|
||||
self.slice(start..end)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, mut text: &str) {
|
||||
self.chunks.update_last(
|
||||
|last_chunk| {
|
||||
let split_ix = if last_chunk.text.len() + text.len() <= chunk::MAX_BASE {
|
||||
text.len()
|
||||
} else {
|
||||
let mut split_ix = cmp::min(
|
||||
chunk::MIN_BASE.saturating_sub(last_chunk.text.len()),
|
||||
text.len(),
|
||||
);
|
||||
while !text.is_char_boundary(split_ix) {
|
||||
split_ix += 1;
|
||||
}
|
||||
split_ix
|
||||
};
|
||||
pub fn push(&mut self, mut text: &str, executor: &BackgroundExecutor) {
|
||||
self.fill_last_chunk(&mut text);
|
||||
|
||||
let (suffix, remainder) = text.split_at(split_ix);
|
||||
last_chunk.push_str(suffix);
|
||||
text = remainder;
|
||||
},
|
||||
(),
|
||||
);
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(rust_analyzer)))]
|
||||
const NUM_CHUNKS: usize = 16;
|
||||
@@ -200,7 +219,8 @@ impl Rope {
|
||||
// but given the chunk boundary can land within a character
|
||||
// we need to accommodate for the worst case where every chunk gets cut short by up to 4 bytes
|
||||
if text.len() > NUM_CHUNKS * chunk::MAX_BASE - NUM_CHUNKS * 4 {
|
||||
return self.push_large(text);
|
||||
let future = self.push_large(text, executor.clone());
|
||||
return executor.block(future);
|
||||
}
|
||||
// 16 is enough as otherwise we will hit the branch above
|
||||
let mut new_chunks = ArrayVec::<_, NUM_CHUNKS>::new();
|
||||
@@ -220,8 +240,57 @@ impl Rope {
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
/// Pushes a string into the rope. Unlike [`push`], this method does not parallelize the construction on large strings.
|
||||
pub fn push_small(&mut self, mut text: &str) {
|
||||
self.fill_last_chunk(&mut text);
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 16 is enough as otherwise we will hit the branch above
|
||||
let mut new_chunks = Vec::new();
|
||||
|
||||
while !text.is_empty() {
|
||||
let mut split_ix = cmp::min(chunk::MAX_BASE, text.len());
|
||||
while !text.is_char_boundary(split_ix) {
|
||||
split_ix -= 1;
|
||||
}
|
||||
let (chunk, remainder) = text.split_at(split_ix);
|
||||
new_chunks.push(chunk);
|
||||
text = remainder;
|
||||
}
|
||||
self.chunks
|
||||
.extend(new_chunks.into_iter().map(Chunk::new), ());
|
||||
|
||||
self.check_invariants();
|
||||
}
|
||||
|
||||
fn fill_last_chunk(&mut self, text: &mut &str) {
|
||||
self.chunks.update_last(
|
||||
|last_chunk| {
|
||||
let split_ix = if last_chunk.text.len() + text.len() <= chunk::MAX_BASE {
|
||||
text.len()
|
||||
} else {
|
||||
let mut split_ix = cmp::min(
|
||||
chunk::MIN_BASE.saturating_sub(last_chunk.text.len()),
|
||||
text.len(),
|
||||
);
|
||||
while !text.is_char_boundary(split_ix) {
|
||||
split_ix += 1;
|
||||
}
|
||||
split_ix
|
||||
};
|
||||
|
||||
let (suffix, remainder) = text.split_at(split_ix);
|
||||
last_chunk.push_str(suffix);
|
||||
*text = remainder;
|
||||
},
|
||||
(),
|
||||
);
|
||||
}
|
||||
|
||||
/// A copy of `push` specialized for working with large quantities of text.
|
||||
fn push_large(&mut self, mut text: &str) {
|
||||
async fn push_large(&mut self, mut text: &str, executor: BackgroundExecutor) {
|
||||
// To avoid frequent reallocs when loading large swaths of file contents,
|
||||
// we estimate worst-case `new_chunks` capacity;
|
||||
// Chunk is a fixed-capacity buffer. If a character falls on
|
||||
@@ -254,8 +323,22 @@ impl Rope {
|
||||
const PARALLEL_THRESHOLD: usize = 4 * (2 * sum_tree::TREE_BASE);
|
||||
|
||||
if new_chunks.len() >= PARALLEL_THRESHOLD {
|
||||
self.chunks
|
||||
.par_extend(new_chunks.into_par_iter().map(Chunk::new), ());
|
||||
let cx2 = executor.clone();
|
||||
executor
|
||||
.scoped(|scope| {
|
||||
// SAFETY: transmuting to 'static is safe because the future is scoped
|
||||
// and the underlying string data cannot go out of scope because dropping the scope
|
||||
// will wait for the task to finish
|
||||
let new_chunks =
|
||||
unsafe { std::mem::transmute::<Vec<&str>, Vec<&'static str>>(new_chunks) };
|
||||
|
||||
let async_extend = self
|
||||
.chunks
|
||||
.async_extend(new_chunks.into_iter().map(Chunk::new), cx2);
|
||||
|
||||
scope.spawn(async_extend);
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
self.chunks
|
||||
.extend(new_chunks.into_iter().map(Chunk::new), ());
|
||||
@@ -292,8 +375,13 @@ impl Rope {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_front(&mut self, text: &str) {
|
||||
let suffix = mem::replace(self, Rope::from(text));
|
||||
pub fn push_front(&mut self, text: &str, cx: &BackgroundExecutor) {
|
||||
let suffix = mem::replace(self, Rope::from_str(text, cx));
|
||||
self.append(suffix);
|
||||
}
|
||||
|
||||
pub fn push_front_small(&mut self, text: &str) {
|
||||
let suffix = mem::replace(self, Rope::from_str_small(text));
|
||||
self.append(suffix);
|
||||
}
|
||||
|
||||
@@ -577,37 +665,19 @@ impl Rope {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Rope {
|
||||
fn from(text: &'a str) -> Self {
|
||||
let mut rope = Self::new();
|
||||
rope.push(text);
|
||||
rope
|
||||
}
|
||||
}
|
||||
// impl From<String> for Rope {
|
||||
// #[inline(always)]
|
||||
// fn from(text: String) -> Self {
|
||||
// Rope::from(text.as_str())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<'a> FromIterator<&'a str> for Rope {
|
||||
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
|
||||
let mut rope = Rope::new();
|
||||
for chunk in iter {
|
||||
rope.push(chunk);
|
||||
}
|
||||
rope
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Rope {
|
||||
#[inline(always)]
|
||||
fn from(text: String) -> Self {
|
||||
Rope::from(text.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Rope {
|
||||
#[inline(always)]
|
||||
fn from(text: &String) -> Self {
|
||||
Rope::from(text.as_str())
|
||||
}
|
||||
}
|
||||
// impl From<&String> for Rope {
|
||||
// #[inline(always)]
|
||||
// fn from(text: &String) -> Self {
|
||||
// Rope::from(text.as_str())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl fmt::Display for Rope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@@ -1639,6 +1709,7 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use Bias::{Left, Right};
|
||||
use gpui::TestAppContext;
|
||||
use rand::prelude::*;
|
||||
use std::{cmp::Ordering, env, io::Read};
|
||||
use util::RandomCharIter;
|
||||
@@ -1648,17 +1719,17 @@ mod tests {
|
||||
zlog::init_test();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_4_byte_chars() {
|
||||
#[gpui::test]
|
||||
async fn test_all_4_byte_chars(cx: &mut TestAppContext) {
|
||||
let mut rope = Rope::new();
|
||||
let text = "🏀".repeat(256);
|
||||
rope.push(&text);
|
||||
rope.push(&text, cx.background_executor());
|
||||
assert_eq!(rope.text(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clip() {
|
||||
let rope = Rope::from("🧘");
|
||||
#[gpui::test]
|
||||
fn test_clip(cx: &mut TestAppContext) {
|
||||
let rope = Rope::from_str("🧘", cx.background_executor());
|
||||
|
||||
assert_eq!(rope.clip_offset(1, Bias::Left), 0);
|
||||
assert_eq!(rope.clip_offset(1, Bias::Right), 4);
|
||||
@@ -1704,9 +1775,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prev_next_line() {
|
||||
let rope = Rope::from("abc\ndef\nghi\njkl");
|
||||
#[gpui::test]
|
||||
fn test_prev_next_line(cx: &mut TestAppContext) {
|
||||
let rope = Rope::from_str("abc\ndef\nghi\njkl", cx.background_executor());
|
||||
|
||||
let mut chunks = rope.chunks();
|
||||
assert_eq!(chunks.peek().unwrap().chars().next().unwrap(), 'a');
|
||||
@@ -1748,16 +1819,16 @@ mod tests {
|
||||
assert_eq!(chunks.peek(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lines() {
|
||||
let rope = Rope::from("abc\ndefg\nhi");
|
||||
#[gpui::test]
|
||||
fn test_lines(cx: &mut TestAppContext) {
|
||||
let rope = Rope::from_str("abc\ndefg\nhi", cx.background_executor());
|
||||
let mut lines = rope.chunks().lines();
|
||||
assert_eq!(lines.next(), Some("abc"));
|
||||
assert_eq!(lines.next(), Some("defg"));
|
||||
assert_eq!(lines.next(), Some("hi"));
|
||||
assert_eq!(lines.next(), None);
|
||||
|
||||
let rope = Rope::from("abc\ndefg\nhi\n");
|
||||
let rope = Rope::from_str("abc\ndefg\nhi\n", cx.background_executor());
|
||||
let mut lines = rope.chunks().lines();
|
||||
assert_eq!(lines.next(), Some("abc"));
|
||||
assert_eq!(lines.next(), Some("defg"));
|
||||
@@ -1765,14 +1836,14 @@ mod tests {
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), None);
|
||||
|
||||
let rope = Rope::from("abc\ndefg\nhi");
|
||||
let rope = Rope::from_str("abc\ndefg\nhi", cx.background_executor());
|
||||
let mut lines = rope.reversed_chunks_in_range(0..rope.len()).lines();
|
||||
assert_eq!(lines.next(), Some("hi"));
|
||||
assert_eq!(lines.next(), Some("defg"));
|
||||
assert_eq!(lines.next(), Some("abc"));
|
||||
assert_eq!(lines.next(), None);
|
||||
|
||||
let rope = Rope::from("abc\ndefg\nhi\n");
|
||||
let rope = Rope::from_str("abc\ndefg\nhi\n", cx.background_executor());
|
||||
let mut lines = rope.reversed_chunks_in_range(0..rope.len()).lines();
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), Some("hi"));
|
||||
@@ -1780,14 +1851,14 @@ mod tests {
|
||||
assert_eq!(lines.next(), Some("abc"));
|
||||
assert_eq!(lines.next(), None);
|
||||
|
||||
let rope = Rope::from("abc\nlonger line test\nhi");
|
||||
let rope = Rope::from_str("abc\nlonger line test\nhi", cx.background_executor());
|
||||
let mut lines = rope.chunks().lines();
|
||||
assert_eq!(lines.next(), Some("abc"));
|
||||
assert_eq!(lines.next(), Some("longer line test"));
|
||||
assert_eq!(lines.next(), Some("hi"));
|
||||
assert_eq!(lines.next(), None);
|
||||
|
||||
let rope = Rope::from("abc\nlonger line test\nhi");
|
||||
let rope = Rope::from_str("abc\nlonger line test\nhi", cx.background_executor());
|
||||
let mut lines = rope.reversed_chunks_in_range(0..rope.len()).lines();
|
||||
assert_eq!(lines.next(), Some("hi"));
|
||||
assert_eq!(lines.next(), Some("longer line test"));
|
||||
@@ -1796,7 +1867,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_rope(mut rng: StdRng) {
|
||||
async fn test_random_rope(cx: &mut TestAppContext, mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
@@ -1812,7 +1883,7 @@ mod tests {
|
||||
let mut new_actual = Rope::new();
|
||||
let mut cursor = actual.cursor(0);
|
||||
new_actual.append(cursor.slice(start_ix));
|
||||
new_actual.push(&new_text);
|
||||
new_actual.push(&new_text, cx.background_executor());
|
||||
cursor.seek_forward(end_ix);
|
||||
new_actual.append(cursor.suffix());
|
||||
actual = new_actual;
|
||||
@@ -2112,10 +2183,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunks_equals_str() {
|
||||
#[gpui::test]
|
||||
fn test_chunks_equals_str(cx: &mut TestAppContext) {
|
||||
let text = "This is a multi-chunk\n& multi-line test string!";
|
||||
let rope = Rope::from(text);
|
||||
let rope = Rope::from_str(text, cx.background_executor());
|
||||
for start in 0..text.len() {
|
||||
for end in start..text.len() {
|
||||
let range = start..end;
|
||||
@@ -2158,34 +2229,37 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
let rope = Rope::from("");
|
||||
let rope = Rope::from_str("", cx.background_executor());
|
||||
assert!(rope.chunks_in_range(0..0).equals_str(""));
|
||||
assert!(rope.reversed_chunks_in_range(0..0).equals_str(""));
|
||||
assert!(!rope.chunks_in_range(0..0).equals_str("foo"));
|
||||
assert!(!rope.reversed_chunks_in_range(0..0).equals_str("foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_char_boundary() {
|
||||
#[gpui::test]
|
||||
fn test_is_char_boundary(cx: &mut TestAppContext) {
|
||||
let fixture = "地";
|
||||
let rope = Rope::from("地");
|
||||
let rope = Rope::from_str("地", cx.background_executor());
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(rope.is_char_boundary(b), fixture.is_char_boundary(b));
|
||||
}
|
||||
let fixture = "";
|
||||
let rope = Rope::from("");
|
||||
let rope = Rope::from_str("", cx.background_executor());
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(rope.is_char_boundary(b), fixture.is_char_boundary(b));
|
||||
}
|
||||
let fixture = "🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩";
|
||||
let rope = Rope::from("🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩");
|
||||
let rope = Rope::from_str(
|
||||
"🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩",
|
||||
cx.background_executor(),
|
||||
);
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(rope.is_char_boundary(b), fixture.is_char_boundary(b));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_floor_char_boundary() {
|
||||
#[gpui::test]
|
||||
fn test_floor_char_boundary(cx: &mut TestAppContext) {
|
||||
// polyfill of str::floor_char_boundary
|
||||
fn floor_char_boundary(str: &str, index: usize) -> usize {
|
||||
if index >= str.len() {
|
||||
@@ -2201,7 +2275,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let fixture = "地";
|
||||
let rope = Rope::from("地");
|
||||
let rope = Rope::from_str("地", cx.background_executor());
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(
|
||||
rope.floor_char_boundary(b),
|
||||
@@ -2210,7 +2284,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let fixture = "";
|
||||
let rope = Rope::from("");
|
||||
let rope = Rope::from_str("", cx.background_executor());
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(
|
||||
rope.floor_char_boundary(b),
|
||||
@@ -2219,7 +2293,10 @@ mod tests {
|
||||
}
|
||||
|
||||
let fixture = "🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩";
|
||||
let rope = Rope::from("🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩");
|
||||
let rope = Rope::from_str(
|
||||
"🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩",
|
||||
cx.background_executor(),
|
||||
);
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(
|
||||
rope.floor_char_boundary(b),
|
||||
@@ -2228,8 +2305,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ceil_char_boundary() {
|
||||
#[gpui::test]
|
||||
fn test_ceil_char_boundary(cx: &mut TestAppContext) {
|
||||
// polyfill of str::ceil_char_boundary
|
||||
fn ceil_char_boundary(str: &str, index: usize) -> usize {
|
||||
if index > str.len() {
|
||||
@@ -2244,19 +2321,22 @@ mod tests {
|
||||
}
|
||||
|
||||
let fixture = "地";
|
||||
let rope = Rope::from("地");
|
||||
let rope = Rope::from_str("地", cx.background_executor());
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(rope.ceil_char_boundary(b), ceil_char_boundary(&fixture, b));
|
||||
}
|
||||
|
||||
let fixture = "";
|
||||
let rope = Rope::from("");
|
||||
let rope = Rope::from_str("", cx.background_executor());
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(rope.ceil_char_boundary(b), ceil_char_boundary(&fixture, b));
|
||||
}
|
||||
|
||||
let fixture = "🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩";
|
||||
let rope = Rope::from("🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩");
|
||||
let rope = Rope::from_str(
|
||||
"🔴🟠🟡🟢🔵🟣⚫️⚪️🟤\n🏳️⚧️🏁🏳️🌈🏴☠️⛳️📬📭🏴🏳️🚩",
|
||||
cx.background_executor(),
|
||||
);
|
||||
for b in 0..=fixture.len() {
|
||||
assert_eq!(rope.ceil_char_boundary(b), ceil_char_boundary(&fixture, b));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user