Detailed changes
@@ -12711,6 +12711,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
+[[package]]
+name = "pollster"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
+
[[package]]
name = "portable-atomic"
version = "1.11.1"
@@ -12759,7 +12765,7 @@ dependencies = [
"log",
"parking_lot",
"pin-project",
- "pollster",
+ "pollster 0.2.5",
"static_assertions",
"thiserror 1.0.69",
]
@@ -14311,7 +14317,6 @@ dependencies = [
"gpui",
"log",
"rand 0.9.2",
- "rayon",
"sum_tree",
"unicode-segmentation",
"util",
@@ -16237,6 +16242,7 @@ checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
name = "streaming_diff"
version = "0.1.0"
dependencies = [
+ "gpui",
"ordered-float 2.10.1",
"rand 0.9.2",
"rope",
@@ -16355,9 +16361,11 @@ version = "0.1.0"
dependencies = [
"arrayvec",
"ctor",
+ "futures 0.3.31",
+ "itertools 0.14.0",
"log",
+ "pollster 0.4.0",
"rand 0.9.2",
- "rayon",
"zlog",
]
@@ -361,10 +361,12 @@ async fn build_buffer_diff(
) -> Result<Entity<BufferDiff>> {
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
+ let executor = cx.background_executor().clone();
let old_text_rope = cx
.background_spawn({
let old_text = old_text.clone();
- async move { Rope::from(old_text.as_str()) }
+ let executor = executor.clone();
+ async move { Rope::from_str(old_text.as_str(), &executor) }
})
.await;
let base_buffer = cx
@@ -3,7 +3,9 @@ use buffer_diff::BufferDiff;
use clock;
use collections::BTreeMap;
use futures::{FutureExt, StreamExt, channel::mpsc};
-use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};
+use gpui::{
+ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, Subscription, Task, WeakEntity,
+};
use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
use std::{cmp, ops::Range, sync::Arc};
@@ -321,6 +323,7 @@ impl ActionLog {
let unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
let mut has_user_changes = false;
+ let executor = cx.background_executor().clone();
async move {
if let ChangeAuthor::User = author {
has_user_changes = apply_non_conflicting_edits(
@@ -328,6 +331,7 @@ impl ActionLog {
edits,
&mut base_text,
new_snapshot.as_rope(),
+ &executor,
);
}
@@ -382,6 +386,7 @@ impl ActionLog {
let agent_diff_base = tracked_buffer.diff_base.clone();
let git_diff_base = git_diff.read(cx).base_text().as_rope().clone();
let buffer_text = tracked_buffer.snapshot.as_rope().clone();
+ let executor = cx.background_executor().clone();
anyhow::Ok(cx.background_spawn(async move {
let mut old_unreviewed_edits = old_unreviewed_edits.into_iter().peekable();
let committed_edits = language::line_diff(
@@ -416,8 +421,11 @@ impl ActionLog {
),
new_agent_diff_base.max_point(),
));
- new_agent_diff_base
- .replace(old_byte_start..old_byte_end, &unreviewed_new);
+ new_agent_diff_base.replace(
+ old_byte_start..old_byte_end,
+ &unreviewed_new,
+ &executor,
+ );
row_delta +=
unreviewed.new_len() as i32 - unreviewed.old_len() as i32;
}
@@ -611,6 +619,7 @@ impl ActionLog {
.snapshot
.text_for_range(new_range)
.collect::<String>(),
+ cx.background_executor(),
);
delta += edit.new_len() as i32 - edit.old_len() as i32;
false
@@ -824,6 +833,7 @@ fn apply_non_conflicting_edits(
edits: Vec<Edit<u32>>,
old_text: &mut Rope,
new_text: &Rope,
+ executor: &BackgroundExecutor,
) -> bool {
let mut old_edits = patch.edits().iter().cloned().peekable();
let mut new_edits = edits.into_iter().peekable();
@@ -877,6 +887,7 @@ fn apply_non_conflicting_edits(
old_text.replace(
old_bytes,
&new_text.chunks_in_range(new_bytes).collect::<String>(),
+ executor,
);
applied_delta += new_edit.new_len() as i32 - new_edit.old_len() as i32;
has_made_changes = true;
@@ -2282,6 +2293,7 @@ mod tests {
old_text.replace(
old_start..old_end,
&new_text.slice_rows(edit.new.clone()).to_string(),
+ cx.background_executor(),
);
}
pretty_assertions::assert_eq!(old_text.to_string(), new_text.to_string());
@@ -305,18 +305,20 @@ impl SearchMatrix {
#[cfg(test)]
mod tests {
use super::*;
+ use gpui::TestAppContext;
use indoc::indoc;
use language::{BufferId, TextBuffer};
use rand::prelude::*;
use text::ReplicaId;
use util::test::{generate_marked_text, marked_text_ranges};
- #[test]
- fn test_empty_query() {
+ #[gpui::test]
+ fn test_empty_query(cx: &mut gpui::TestAppContext) {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
@@ -325,12 +327,13 @@ mod tests {
assert_eq!(finish(finder), None);
}
- #[test]
- fn test_streaming_exact_match() {
+ #[gpui::test]
+ fn test_streaming_exact_match(cx: &mut gpui::TestAppContext) {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
@@ -349,8 +352,8 @@ mod tests {
assert_eq!(finish(finder), Some("This is a test".to_string()));
}
- #[test]
- fn test_streaming_fuzzy_match() {
+ #[gpui::test]
+ fn test_streaming_fuzzy_match(cx: &mut gpui::TestAppContext) {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
@@ -363,6 +366,7 @@ mod tests {
return x * y;
}
"},
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
@@ -383,12 +387,13 @@ mod tests {
);
}
- #[test]
- fn test_incremental_improvement() {
+ #[gpui::test]
+ fn test_incremental_improvement(cx: &mut gpui::TestAppContext) {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
@@ -408,8 +413,8 @@ mod tests {
assert_eq!(finish(finder), Some("Line 3\nLine 4".to_string()));
}
- #[test]
- fn test_incomplete_lines_buffering() {
+ #[gpui::test]
+ fn test_incomplete_lines_buffering(cx: &mut gpui::TestAppContext) {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
@@ -418,6 +423,7 @@ mod tests {
jumps over the lazy dog
Pack my box with five dozen liquor jugs
"},
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
@@ -435,8 +441,8 @@ mod tests {
);
}
- #[test]
- fn test_multiline_fuzzy_match() {
+ #[gpui::test]
+ fn test_multiline_fuzzy_match(cx: &mut gpui::TestAppContext) {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
@@ -456,6 +462,7 @@ mod tests {
}
}
"#},
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
@@ -509,7 +516,7 @@ mod tests {
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_single_line(mut rng: StdRng) {
+ fn test_resolve_location_single_line(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
concat!(
" Lorem\n",
@@ -519,11 +526,12 @@ mod tests {
),
"ipsum",
&mut rng,
+ cx,
);
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_multiline(mut rng: StdRng) {
+ fn test_resolve_location_multiline(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
concat!(
" Lorem\n",
@@ -533,11 +541,12 @@ mod tests {
),
"ipsum\ndolor sit amet",
&mut rng,
+ cx,
);
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_function_with_typo(mut rng: StdRng) {
+ fn test_resolve_location_function_with_typo(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
indoc! {"
Β«fn foo1(a: usize) -> usize {
@@ -550,11 +559,12 @@ mod tests {
"},
"fn foo1(a: usize) -> u32 {\n40\n}",
&mut rng,
+ cx,
);
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_class_methods(mut rng: StdRng) {
+ fn test_resolve_location_class_methods(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
indoc! {"
class Something {
@@ -575,11 +585,12 @@ mod tests {
six() { return 6666; }
"},
&mut rng,
+ cx,
);
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_imports_no_match(mut rng: StdRng) {
+ fn test_resolve_location_imports_no_match(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
indoc! {"
use std::ops::Range;
@@ -609,11 +620,12 @@ mod tests {
use std::sync::Arc;
"},
&mut rng,
+ cx,
);
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_nested_closure(mut rng: StdRng) {
+ fn test_resolve_location_nested_closure(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
indoc! {"
impl Foo {
@@ -641,11 +653,12 @@ mod tests {
" });",
),
&mut rng,
+ cx,
);
}
#[gpui::test(iterations = 100)]
- fn test_resolve_location_tool_invocation(mut rng: StdRng) {
+ fn test_resolve_location_tool_invocation(mut rng: StdRng, cx: &mut TestAppContext) {
assert_location_resolution(
indoc! {r#"
let tool = cx
@@ -673,11 +686,12 @@ mod tests {
" .output;",
),
&mut rng,
+ cx,
);
}
#[gpui::test]
- fn test_line_hint_selection() {
+ fn test_line_hint_selection(cx: &mut TestAppContext) {
let text = indoc! {r#"
fn first_function() {
return 42;
@@ -696,6 +710,7 @@ mod tests {
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
text.to_string(),
+ cx.background_executor(),
);
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
@@ -727,9 +742,19 @@ mod tests {
}
#[track_caller]
- fn assert_location_resolution(text_with_expected_range: &str, query: &str, rng: &mut StdRng) {
+ fn assert_location_resolution(
+ text_with_expected_range: &str,
+ query: &str,
+ rng: &mut StdRng,
+ cx: &mut TestAppContext,
+ ) {
let (text, expected_ranges) = marked_text_ranges(text_with_expected_range, false);
- let buffer = TextBuffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text.clone());
+ let buffer = TextBuffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ text.clone(),
+ cx.background_executor(),
+ );
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot);
@@ -569,6 +569,7 @@ mod tests {
use prompt_store::ProjectContext;
use serde_json::json;
use settings::SettingsStore;
+ use text::Rope;
use util::{path, rel_path::rel_path};
#[gpui::test]
@@ -741,7 +742,7 @@ mod tests {
// Create the file
fs.save(
path!("/root/src/main.rs").as_ref(),
- &"initial content".into(),
+ &Rope::from_str_small("initial content"),
language::LineEnding::Unix,
)
.await
@@ -908,7 +909,7 @@ mod tests {
// Create a simple file with trailing whitespace
fs.save(
path!("/root/src/main.rs").as_ref(),
- &"initial content".into(),
+ &Rope::from_str_small("initial content"),
language::LineEnding::Unix,
)
.await
@@ -28,6 +28,7 @@ use project::{
agent_server_store::{AgentServerStore, CLAUDE_CODE_NAME, CODEX_NAME, GEMINI_NAME},
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
};
+use rope::Rope;
use settings::{SettingsStore, update_settings_file};
use ui::{
Chip, CommonAnimationExt, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex,
@@ -1114,8 +1115,11 @@ async fn open_new_agent_servers_entry_in_settings_editor(
) -> Result<()> {
let settings_editor = workspace
.update_in(cx, |_, window, cx| {
- create_and_open_local_file(paths::settings_file(), window, cx, || {
- settings::initial_user_settings_content().as_ref().into()
+ create_and_open_local_file(paths::settings_file(), window, cx, |cx| {
+ Rope::from_str(
+ &settings::initial_user_settings_content(),
+ cx.background_executor(),
+ )
})
})?
.await?
@@ -487,9 +487,10 @@ impl CodegenAlternative {
) {
let start_time = Instant::now();
let snapshot = self.snapshot.clone();
- let selected_text = snapshot
- .text_for_range(self.range.start..self.range.end)
- .collect::<Rope>();
+ let selected_text = Rope::from_iter(
+ snapshot.text_for_range(self.range.start..self.range.end),
+ cx.background_executor(),
+ );
let selection_start = self.range.start.to_point(&snapshot);
@@ -744,12 +744,13 @@ impl TextThread {
telemetry: Option<Arc<Telemetry>>,
cx: &mut Context<Self>,
) -> Self {
- let buffer = cx.new(|_cx| {
+ let buffer = cx.new(|cx| {
let buffer = Buffer::remote(
language::BufferId::new(1).unwrap(),
replica_id,
capability,
"",
+ cx.background_executor(),
);
buffer.set_language_registry(language_registry.clone());
buffer
@@ -1,6 +1,9 @@
use futures::channel::oneshot;
use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
-use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};
+use gpui::{
+ App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task,
+ TaskLabel,
+};
use language::{Language, LanguageRegistry};
use rope::Rope;
use std::{
@@ -191,7 +194,7 @@ impl BufferDiffSnapshot {
let base_text_exists;
let base_text_snapshot;
if let Some(text) = &base_text {
- let base_text_rope = Rope::from(text.as_str());
+ let base_text_rope = Rope::from_str(text.as_str(), cx.background_executor());
base_text_pair = Some((text.clone(), base_text_rope.clone()));
let snapshot =
language::Buffer::build_snapshot(base_text_rope, language, language_registry, cx);
@@ -311,6 +314,7 @@ impl BufferDiffInner {
hunks: &[DiffHunk],
buffer: &text::BufferSnapshot,
file_exists: bool,
+ cx: &BackgroundExecutor,
) -> Option<Rope> {
let head_text = self
.base_text_exists
@@ -505,7 +509,7 @@ impl BufferDiffInner {
for (old_range, replacement_text) in edits {
new_index_text.append(index_cursor.slice(old_range.start));
index_cursor.seek_forward(old_range.end);
- new_index_text.push(&replacement_text);
+ new_index_text.push(&replacement_text, cx);
}
new_index_text.append(index_cursor.suffix());
Some(new_index_text)
@@ -962,6 +966,7 @@ impl BufferDiff {
hunks,
buffer,
file_exists,
+ cx.background_executor(),
);
cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
@@ -1385,7 +1390,12 @@ mod tests {
"
.unindent();
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text,
+ cx.background_executor(),
+ );
let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
assert_hunks(
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
@@ -1394,7 +1404,7 @@ mod tests {
&[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
);
- buffer.edit([(0..0, "point five\n")]);
+ buffer.edit([(0..0, "point five\n")], cx.background_executor());
diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
assert_hunks(
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
@@ -1459,7 +1469,12 @@ mod tests {
"
.unindent();
- let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text,
+ cx.background_executor(),
+ );
let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
let mut uncommitted_diff =
BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
@@ -1528,7 +1543,12 @@ mod tests {
"
.unindent();
- let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text,
+ cx.background_executor(),
+ );
let diff = cx
.update(|cx| {
BufferDiffSnapshot::new_with_base_text(
@@ -1791,7 +1811,12 @@ mod tests {
for example in table {
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
- let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text,
+ cx.background_executor(),
+ );
let hunk_range =
buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
@@ -1868,6 +1893,7 @@ mod tests {
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
buffer_text.clone(),
+ cx.background_executor(),
);
let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
@@ -1941,7 +1967,12 @@ mod tests {
"
.unindent();
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text_1,
+ cx.background_executor(),
+ );
let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
@@ -1961,6 +1992,7 @@ mod tests {
NINE
"
.unindent(),
+ cx.background_executor(),
);
let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer));
@@ -1978,6 +2010,7 @@ mod tests {
NINE
"
.unindent(),
+ cx.background_executor(),
);
let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
let range = diff_3.inner.compare(&diff_2.inner, &buffer).unwrap();
@@ -1995,6 +2028,7 @@ mod tests {
NINE
"
.unindent(),
+ cx.background_executor(),
);
let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
let range = diff_4.inner.compare(&diff_3.inner, &buffer).unwrap();
@@ -2013,6 +2047,7 @@ mod tests {
NINE
"
.unindent(),
+ cx.background_executor(),
);
let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
let range = diff_5.inner.compare(&diff_4.inner, &buffer).unwrap();
@@ -2031,6 +2066,7 @@ mod tests {
Β«nineΒ»
"
.unindent(),
+ cx.background_executor(),
);
let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
let range = diff_6.inner.compare(&diff_5.inner, &buffer).unwrap();
@@ -2140,14 +2176,14 @@ mod tests {
let working_copy = gen_working_copy(rng, &head_text);
let working_copy = cx.new(|cx| {
language::Buffer::local_normalized(
- Rope::from(working_copy.as_str()),
+ Rope::from_str(working_copy.as_str(), cx.background_executor()),
text::LineEnding::default(),
cx,
)
});
let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
let mut index_text = if rng.random() {
- Rope::from(head_text.as_str())
+ Rope::from_str(head_text.as_str(), cx.background_executor())
} else {
working_copy.as_rope().clone()
};
@@ -70,6 +70,7 @@ impl ChannelBuffer {
ReplicaId::new(response.replica_id as u16),
capability,
base_text,
+ cx.background_executor(),
)
})?;
buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
@@ -701,12 +701,12 @@ impl Database {
return Ok(());
}
- let mut text_buffer = text::Buffer::new(
+ let mut text_buffer = text::Buffer::new_slow(
clock::ReplicaId::LOCAL,
text::BufferId::new(1).unwrap(),
base_text,
);
- text_buffer.apply_ops(operations.into_iter().filter_map(operation_from_wire));
+ text_buffer.apply_ops(operations.into_iter().filter_map(operation_from_wire), None);
let base_text = text_buffer.text();
let epoch = buffer.epoch + 1;
@@ -74,11 +74,21 @@ async fn test_channel_buffers(db: &Arc<Database>) {
ReplicaId::new(0),
text::BufferId::new(1).unwrap(),
"".to_string(),
+ &db.test_options.as_ref().unwrap().executor,
);
let operations = vec![
- buffer_a.edit([(0..0, "hello world")]),
- buffer_a.edit([(5..5, ", cruel")]),
- buffer_a.edit([(0..5, "goodbye")]),
+ buffer_a.edit(
+ [(0..0, "hello world")],
+ &db.test_options.as_ref().unwrap().executor,
+ ),
+ buffer_a.edit(
+ [(5..5, ", cruel")],
+ &db.test_options.as_ref().unwrap().executor,
+ ),
+ buffer_a.edit(
+ [(0..5, "goodbye")],
+ &db.test_options.as_ref().unwrap().executor,
+ ),
buffer_a.undo().unwrap().1,
];
assert_eq!(buffer_a.text(), "hello, cruel world");
@@ -102,15 +112,19 @@ async fn test_channel_buffers(db: &Arc<Database>) {
ReplicaId::new(0),
text::BufferId::new(1).unwrap(),
buffer_response_b.base_text,
+ &db.test_options.as_ref().unwrap().executor,
+ );
+ buffer_b.apply_ops(
+ buffer_response_b.operations.into_iter().map(|operation| {
+ let operation = proto::deserialize_operation(operation).unwrap();
+ if let language::Operation::Buffer(operation) = operation {
+ operation
+ } else {
+ unreachable!()
+ }
+ }),
+ None,
);
- buffer_b.apply_ops(buffer_response_b.operations.into_iter().map(|operation| {
- let operation = proto::deserialize_operation(operation).unwrap();
- if let language::Operation::Buffer(operation) = operation {
- operation
- } else {
- unreachable!()
- }
- }));
assert_eq!(buffer_b.text(), "hello, cruel world");
@@ -247,6 +261,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
ReplicaId::new(res.replica_id as u16),
text::BufferId::new(1).unwrap(),
"".to_string(),
+ &db.test_options.as_ref().unwrap().executor,
));
}
@@ -255,9 +270,9 @@ async fn test_channel_buffers_last_operations(db: &Database) {
user_id,
db,
vec![
- text_buffers[0].edit([(0..0, "a")]),
- text_buffers[0].edit([(0..0, "b")]),
- text_buffers[0].edit([(0..0, "c")]),
+ text_buffers[0].edit([(0..0, "a")], &db.test_options.as_ref().unwrap().executor),
+ text_buffers[0].edit([(0..0, "b")], &db.test_options.as_ref().unwrap().executor),
+ text_buffers[0].edit([(0..0, "c")], &db.test_options.as_ref().unwrap().executor),
],
)
.await;
@@ -267,9 +282,9 @@ async fn test_channel_buffers_last_operations(db: &Database) {
user_id,
db,
vec![
- text_buffers[1].edit([(0..0, "d")]),
- text_buffers[1].edit([(1..1, "e")]),
- text_buffers[1].edit([(2..2, "f")]),
+ text_buffers[1].edit([(0..0, "d")], &db.test_options.as_ref().unwrap().executor),
+ text_buffers[1].edit([(1..1, "e")], &db.test_options.as_ref().unwrap().executor),
+ text_buffers[1].edit([(2..2, "f")], &db.test_options.as_ref().unwrap().executor),
],
)
.await;
@@ -286,14 +301,15 @@ async fn test_channel_buffers_last_operations(db: &Database) {
replica_id,
text::BufferId::new(1).unwrap(),
"def".to_string(),
+ &db.test_options.as_ref().unwrap().executor,
);
update_buffer(
buffers[1].channel_id,
user_id,
db,
vec![
- text_buffers[1].edit([(0..0, "g")]),
- text_buffers[1].edit([(0..0, "h")]),
+ text_buffers[1].edit([(0..0, "g")], &db.test_options.as_ref().unwrap().executor),
+ text_buffers[1].edit([(0..0, "h")], &db.test_options.as_ref().unwrap().executor),
],
)
.await;
@@ -302,7 +318,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
buffers[2].channel_id,
user_id,
db,
- vec![text_buffers[2].edit([(0..0, "i")])],
+ vec![text_buffers[2].edit([(0..0, "i")], &db.test_options.as_ref().unwrap().executor)],
)
.await;
@@ -3694,7 +3694,7 @@ async fn test_buffer_reloading(
assert_eq!(buf.line_ending(), LineEnding::Unix);
});
- let new_contents = Rope::from("d\ne\nf");
+ let new_contents = Rope::from_str_small("d\ne\nf");
client_a
.fs()
.save(
@@ -4479,7 +4479,7 @@ async fn test_reloading_buffer_manually(
.fs()
.save(
path!("/a/a.rs").as_ref(),
- &Rope::from("let seven = 7;"),
+ &Rope::from_str_small("let seven = 7;"),
LineEnding::Unix,
)
.await
@@ -27,6 +27,7 @@ use std::{
rc::Rc,
sync::Arc,
};
+use text::Rope;
use util::{
ResultExt, path,
paths::PathStyle,
@@ -938,7 +939,11 @@ impl RandomizedTest for ProjectCollaborationTest {
client
.fs()
- .save(&path, &content.as_str().into(), text::LineEnding::Unix)
+ .save(
+ &path,
+ &Rope::from_str_small(content.as_str()),
+ text::LineEnding::Unix,
+ )
.await
.unwrap();
}
@@ -877,7 +877,7 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
vec![Inlay::edit_prediction(
post_inc(&mut next_inlay_id),
snapshot.buffer_snapshot().anchor_before(position),
- Rope::from_iter(["Test inlay ", "next_inlay_id"]),
+ Rope::from_iter_small(["Test inlay ", "next_inlay_id"]),
)],
cx,
);
@@ -2070,7 +2070,7 @@ fn random_lsp_diagnostic(
const ERROR_MARGIN: usize = 10;
let file_content = fs.read_file_sync(path).unwrap();
- let file_text = Rope::from(String::from_utf8_lossy(&file_content).as_ref());
+ let file_text = Rope::from_str_small(String::from_utf8_lossy(&file_content).as_ref());
let start = rng.random_range(0..file_text.len().saturating_add(ERROR_MARGIN));
let end = rng.random_range(start..file_text.len().saturating_add(ERROR_MARGIN));
@@ -13,7 +13,7 @@ use gpui::{
};
use indoc::indoc;
use language::{
- EditPredictionsMode, File, Language,
+ EditPredictionsMode, File, Language, Rope,
language_settings::{self, AllLanguageSettings, EditPredictionProvider, all_language_settings},
};
use project::DisableAiSettings;
@@ -1056,8 +1056,11 @@ async fn open_disabled_globs_setting_in_editor(
) -> Result<()> {
let settings_editor = workspace
.update_in(cx, |_, window, cx| {
- create_and_open_local_file(paths::settings_file(), window, cx, || {
- settings::initial_user_settings_content().as_ref().into()
+ create_and_open_local_file(paths::settings_file(), window, cx, |cx| {
+ Rope::from_str(
+ settings::initial_user_settings_content().as_ref(),
+ cx.background_executor(),
+ )
})
})?
.await?
@@ -1569,6 +1569,7 @@ pub mod tests {
use lsp::LanguageServerId;
use project::Project;
use rand::{Rng, prelude::*};
+ use rope::Rope;
use settings::{SettingsContent, SettingsStore};
use smol::stream::StreamExt;
use std::{env, sync::Arc};
@@ -2074,7 +2075,7 @@ pub mod tests {
vec![Inlay::edit_prediction(
0,
buffer_snapshot.anchor_after(0),
- "\n",
+ Rope::from_str_small("\n"),
)],
cx,
);
@@ -700,16 +700,20 @@ impl InlayMap {
.collect::<String>();
let next_inlay = if i % 2 == 0 {
+ use rope::Rope;
+
Inlay::mock_hint(
post_inc(next_inlay_id),
snapshot.buffer.anchor_at(position, bias),
- &text,
+ Rope::from_str_small(&text),
)
} else {
+ use rope::Rope;
+
Inlay::edit_prediction(
post_inc(next_inlay_id),
snapshot.buffer.anchor_at(position, bias),
- &text,
+ Rope::from_str_small(&text),
)
};
let inlay_id = next_inlay.id;
@@ -1301,7 +1305,7 @@ mod tests {
vec![Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_after(3),
- "|123|",
+ Rope::from_str_small("|123|"),
)],
);
assert_eq!(inlay_snapshot.text(), "abc|123|defghi");
@@ -1378,12 +1382,12 @@ mod tests {
Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(3),
- "|123|",
+ Rope::from_str_small("|123|"),
),
Inlay::edit_prediction(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_after(3),
- "|456|",
+ Rope::from_str_small("|456|"),
),
],
);
@@ -1593,17 +1597,17 @@ mod tests {
Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(0),
- "|123|\n",
+ Rope::from_str_small("|123|\n"),
),
Inlay::mock_hint(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(4),
- "|456|",
+ Rope::from_str_small("|456|"),
),
Inlay::edit_prediction(
post_inc(&mut next_inlay_id),
buffer.read(cx).snapshot(cx).anchor_before(7),
- "\n|567|\n",
+ Rope::from_str_small("\n|567|\n"),
),
],
);
@@ -1677,9 +1681,14 @@ mod tests {
(offset, inlay.clone())
})
.collect::<Vec<_>>();
- let mut expected_text = Rope::from(&buffer_snapshot.text());
+ let mut expected_text =
+ Rope::from_str(&buffer_snapshot.text(), cx.background_executor());
for (offset, inlay) in inlays.iter().rev() {
- expected_text.replace(*offset..*offset, &inlay.text().to_string());
+ expected_text.replace(
+ *offset..*offset,
+ &inlay.text().to_string(),
+ cx.background_executor(),
+ );
}
assert_eq!(inlay_snapshot.text(), expected_text.to_string());
@@ -2067,7 +2076,7 @@ mod tests {
let inlay = Inlay {
id: InlayId::Hint(0),
position,
- content: InlayContent::Text(text::Rope::from(inlay_text)),
+ content: InlayContent::Text(text::Rope::from_str(inlay_text, cx.background_executor())),
};
let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
@@ -2181,7 +2190,10 @@ mod tests {
let inlay = Inlay {
id: InlayId::Hint(0),
position,
- content: InlayContent::Text(text::Rope::from(test_case.inlay_text)),
+ content: InlayContent::Text(text::Rope::from_str(
+ test_case.inlay_text,
+ cx.background_executor(),
+ )),
};
let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
@@ -1042,7 +1042,7 @@ mod tests {
let (mut tab_map, _) = TabMap::new(fold_snapshot, tab_size);
let tabs_snapshot = tab_map.set_max_expansion_column(32);
- let text = text::Rope::from(tabs_snapshot.text().as_str());
+ let text = text::Rope::from_str(tabs_snapshot.text().as_str(), cx.background_executor());
log::info!(
"TabMap text (tab size: {}): {:?}",
tab_size,
@@ -863,7 +863,7 @@ impl WrapSnapshot {
}
}
- let text = language::Rope::from(self.text().as_str());
+ let text = language::Rope::from_str_small(self.text().as_str());
let mut input_buffer_rows = self.tab_snapshot.rows(0);
let mut expected_buffer_rows = Vec::new();
let mut prev_tab_row = 0;
@@ -1413,9 +1413,10 @@ mod tests {
}
}
- let mut initial_text = Rope::from(initial_snapshot.text().as_str());
+ let mut initial_text =
+ Rope::from_str(initial_snapshot.text().as_str(), cx.background_executor());
for (snapshot, patch) in edits {
- let snapshot_text = Rope::from(snapshot.text().as_str());
+ let snapshot_text = Rope::from_str(snapshot.text().as_str(), cx.background_executor());
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(
@@ -1431,7 +1432,7 @@ mod tests {
.chunks_in_range(new_start..new_end)
.collect::<String>();
- initial_text.replace(old_start..old_end, &new_text);
+ initial_text.replace(old_start..old_end, &new_text, cx.background_executor());
}
assert_eq!(initial_text.to_string(), snapshot_text.to_string());
}
@@ -7852,7 +7852,7 @@ impl Editor {
let inlay = Inlay::edit_prediction(
post_inc(&mut self.next_inlay_id),
range.start,
- new_text.as_str(),
+ Rope::from_str_small(new_text.as_str()),
);
inlay_ids.push(inlay.id);
inlays.push(inlay);
@@ -1115,18 +1115,19 @@ mod tests {
let fs = FakeFs::new(cx.executor());
let buffer_initial_text_len = rng.random_range(5..15);
- let mut buffer_initial_text = Rope::from(
+ let mut buffer_initial_text = Rope::from_str(
RandomCharIter::new(&mut rng)
.take(buffer_initial_text_len)
.collect::<String>()
.as_str(),
+ cx.background_executor(),
);
let mut newline_ixs = (0..buffer_initial_text_len).choose_multiple(&mut rng, 5);
newline_ixs.sort_unstable();
for newline_ix in newline_ixs.into_iter().rev() {
let newline_ix = buffer_initial_text.clip_offset(newline_ix, Bias::Right);
- buffer_initial_text.replace(newline_ix..newline_ix, "\n");
+ buffer_initial_text.replace(newline_ix..newline_ix, "\n", cx.background_executor());
}
log::info!("initial buffer text: {:?}", buffer_initial_text);
@@ -59,10 +59,10 @@ impl Inlay {
pub fn hint(id: InlayId, position: Anchor, hint: &InlayHint) -> Self {
let mut text = hint.text();
if hint.padding_right && text.reversed_chars_at(text.len()).next() != Some(' ') {
- text.push(" ");
+ text.push_small(" ");
}
if hint.padding_left && text.chars_at(0).next() != Some(' ') {
- text.push_front(" ");
+ text.push_front_small(" ");
}
Self {
id,
@@ -72,11 +72,11 @@ impl Inlay {
}
#[cfg(any(test, feature = "test-support"))]
- pub fn mock_hint(id: usize, position: Anchor, text: impl Into<Rope>) -> Self {
+ pub fn mock_hint(id: usize, position: Anchor, text: Rope) -> Self {
Self {
id: InlayId::Hint(id),
position,
- content: InlayContent::Text(text.into()),
+ content: InlayContent::Text(text),
}
}
@@ -88,19 +88,19 @@ impl Inlay {
}
}
- pub fn edit_prediction<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
+ pub fn edit_prediction(id: usize, position: Anchor, text: Rope) -> Self {
Self {
id: InlayId::EditPrediction(id),
position,
- content: InlayContent::Text(text.into()),
+ content: InlayContent::Text(text),
}
}
- pub fn debugger<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
+ pub fn debugger(id: usize, position: Anchor, text: Rope) -> Self {
Self {
id: InlayId::DebuggerValue(id),
position,
- content: InlayContent::Text(text.into()),
+ content: InlayContent::Text(text),
}
}
@@ -108,7 +108,7 @@ impl Inlay {
static COLOR_TEXT: OnceLock<Rope> = OnceLock::new();
match &self.content {
InlayContent::Text(text) => text,
- InlayContent::Color(_) => COLOR_TEXT.get_or_init(|| Rope::from("βΌ")),
+ InlayContent::Color(_) => COLOR_TEXT.get_or_init(|| Rope::from_str_small("βΌ")),
}
}
@@ -878,6 +878,7 @@ mod tests {
use gpui::{AppContext as _, font, px};
use language::Capability;
use project::{Project, project_settings::DiagnosticSeverity};
+ use rope::Rope;
use settings::SettingsStore;
use util::post_inc;
@@ -1024,22 +1025,22 @@ mod tests {
Inlay::edit_prediction(
post_inc(&mut id),
buffer_snapshot.anchor_before(offset),
- "test",
+ Rope::from_str_small("test"),
),
Inlay::edit_prediction(
post_inc(&mut id),
buffer_snapshot.anchor_after(offset),
- "test",
+ Rope::from_str_small("test"),
),
Inlay::mock_hint(
post_inc(&mut id),
buffer_snapshot.anchor_before(offset),
- "test",
+ Rope::from_str_small("test"),
),
Inlay::mock_hint(
post_inc(&mut id),
buffer_snapshot.anchor_after(offset),
- "test",
+ Rope::from_str_small("test"),
),
]
})
@@ -193,7 +193,7 @@ impl Editor {
if let Some(language) = language {
for signature in &mut signature_help.signatures {
- let text = Rope::from(signature.label.as_ref());
+ let text = Rope::from_str_small(signature.label.as_ref());
let highlights = language
.highlight_text(&text, 0..signature.label.len())
.into_iter()
@@ -1468,6 +1468,7 @@ impl ExtensionStore {
let extensions_dir = self.installed_dir.clone();
let index_path = self.index_path.clone();
let proxy = self.proxy.clone();
+ let executor = cx.background_executor().clone();
cx.background_spawn(async move {
let start_time = Instant::now();
let mut index = ExtensionIndex::default();
@@ -1501,10 +1502,14 @@ impl ExtensionStore {
}
if let Ok(index_json) = serde_json::to_string_pretty(&index) {
- fs.save(&index_path, &index_json.as_str().into(), Default::default())
- .await
- .context("failed to save extension index")
- .log_err();
+ fs.save(
+ &index_path,
+ &Rope::from_str(&index_json, &executor),
+ Default::default(),
+ )
+ .await
+ .context("failed to save extension index")
+ .log_err();
}
log::info!("rebuilt extension index in {:?}", start_time.elapsed());
@@ -1671,7 +1676,7 @@ impl ExtensionStore {
let manifest_toml = toml::to_string(&loaded_extension.manifest)?;
fs.save(
&tmp_dir.join(EXTENSION_TOML),
- &Rope::from(manifest_toml),
+ &Rope::from_str_small(&manifest_toml),
language::LineEnding::Unix,
)
.await?;
@@ -170,7 +170,10 @@ impl CommitView {
ReplicaId::LOCAL,
cx.entity_id().as_non_zero_u64().into(),
LineEnding::default(),
- format_commit(&commit, stash.is_some()).into(),
+ Rope::from_str(
+ &format_commit(&commit, stash.is_some()),
+ cx.background_executor(),
+ ),
);
metadata_buffer_id = Some(buffer.remote_id());
Buffer::build(buffer, Some(file.clone()), Capability::ReadWrite)
@@ -336,7 +339,7 @@ async fn build_buffer(
) -> Result<Entity<Buffer>> {
let line_ending = LineEnding::detect(&text);
LineEnding::normalize(&mut text);
- let text = Rope::from(text);
+ let text = Rope::from_str(&text, cx.background_executor());
let language = cx.update(|cx| language_registry.language_for_file(&blob, Some(&text), cx))?;
let language = if let Some(language) = language {
language_registry
@@ -376,7 +379,7 @@ async fn build_buffer_diff(
let base_buffer = cx
.update(|cx| {
Buffer::build_snapshot(
- old_text.as_deref().unwrap_or("").into(),
+ Rope::from_str(old_text.as_deref().unwrap_or(""), cx.background_executor()),
buffer.language().cloned(),
Some(language_registry.clone()),
cx,
@@ -359,6 +359,7 @@ mod tests {
use super::*;
use editor::test::editor_test_context::assert_state_with_diff;
use gpui::TestAppContext;
+ use language::Rope;
use project::{FakeFs, Fs, Project};
use settings::SettingsStore;
use std::path::PathBuf;
@@ -429,7 +430,7 @@ mod tests {
// Modify the new file on disk
fs.save(
path!("/test/new_file.txt").as_ref(),
- &unindent(
+ &Rope::from_str_small(&unindent(
"
new line 1
line 2
@@ -437,8 +438,7 @@ mod tests {
line 4
new line 5
",
- )
- .into(),
+ )),
Default::default(),
)
.await
@@ -465,15 +465,14 @@ mod tests {
// Modify the old file on disk
fs.save(
path!("/test/old_file.txt").as_ref(),
- &unindent(
+ &Rope::from_str_small(&unindent(
"
new line 1
line 2
old line 3
line 4
",
- )
- .into(),
+ )),
Default::default(),
)
.await
@@ -260,6 +260,19 @@ impl AsyncApp {
}
}
+impl sum_tree::BackgroundSpawn for BackgroundExecutor {
+ type Task<R>
+ = Task<R>
+ where
+ R: Send + Sync;
+ fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Self::Task<R>
+ where
+ R: Send + Sync + 'static,
+ {
+ self.spawn(future)
+ }
+}
+
/// A cloneable, owned handle to the application context,
/// composed with the window associated with the current task.
#[derive(Clone, Deref, DerefMut)]
@@ -393,6 +393,11 @@ impl TestAppContext {
}
}
+ /// Returns the background executor for this context.
+ pub fn background_executor(&self) -> &BackgroundExecutor {
+ &self.background_executor
+ }
+
/// Wait until there are no more pending tasks.
pub fn run_until_parked(&mut self) {
self.background_executor.run_until_parked()
@@ -342,7 +342,7 @@ impl BackgroundExecutor {
/// for all of them to complete before returning.
pub async fn scoped<'scope, F>(&self, scheduler: F)
where
- F: FnOnce(&mut Scope<'scope>),
+ F: for<'a> FnOnce(&'a mut Scope<'scope>),
{
let mut scope = Scope::new(self.clone());
(scheduler)(&mut scope);
@@ -22,7 +22,7 @@ use gpui::{
ScrollWheelEvent, Stateful, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity,
actions, anchored, deferred, div,
};
-use language::{Language, LanguageConfig, ToOffset as _};
+use language::{Language, LanguageConfig, Rope, ToOffset as _};
use notifications::status_toast::{StatusToast, ToastIcon};
use project::{CompletionDisplayOptions, Project};
use settings::{
@@ -2119,7 +2119,7 @@ impl RenderOnce for SyntaxHighlightedText {
let highlights = self
.language
- .highlight_text(&text.as_ref().into(), 0..text.len());
+ .highlight_text(&Rope::from_str_small(text.as_ref()), 0..text.len());
let mut runs = Vec::with_capacity(highlights.len());
let mut offset = 0;
@@ -24,8 +24,8 @@ use collections::HashMap;
use fs::MTime;
use futures::channel::oneshot;
use gpui::{
- App, AppContext as _, Context, Entity, EventEmitter, HighlightStyle, SharedString, StyledText,
- Task, TaskLabel, TextStyle,
+ App, AppContext as _, BackgroundExecutor, Context, Entity, EventEmitter, HighlightStyle,
+ SharedString, StyledText, Task, TaskLabel, TextStyle,
};
use lsp::{LanguageServerId, NumberOrString};
@@ -832,6 +832,7 @@ impl Buffer {
ReplicaId::LOCAL,
cx.entity_id().as_non_zero_u64().into(),
base_text.into(),
+ &cx.background_executor(),
),
None,
Capability::ReadWrite,
@@ -862,9 +863,10 @@ impl Buffer {
replica_id: ReplicaId,
capability: Capability,
base_text: impl Into<String>,
+ cx: &BackgroundExecutor,
) -> Self {
Self::build(
- TextBuffer::new(replica_id, remote_id, base_text.into()),
+ TextBuffer::new(replica_id, remote_id, base_text.into(), cx),
None,
capability,
)
@@ -877,9 +879,10 @@ impl Buffer {
capability: Capability,
message: proto::BufferState,
file: Option<Arc<dyn File>>,
+ cx: &BackgroundExecutor,
) -> Result<Self> {
let buffer_id = BufferId::new(message.id).context("Could not deserialize buffer_id")?;
- let buffer = TextBuffer::new(replica_id, buffer_id, message.base_text);
+ let buffer = TextBuffer::new(replica_id, buffer_id, message.base_text, cx);
let mut this = Self::build(buffer, file, capability);
this.text.set_line_ending(proto::deserialize_line_ending(
rpc::proto::LineEnding::from_i32(message.line_ending).context("missing line_ending")?,
@@ -1138,13 +1141,14 @@ impl Buffer {
let old_snapshot = self.text.snapshot();
let mut branch_buffer = self.text.branch();
let mut syntax_snapshot = self.syntax_map.lock().snapshot();
+ let executor = cx.background_executor().clone();
cx.background_spawn(async move {
if !edits.is_empty() {
if let Some(language) = language.clone() {
syntax_snapshot.reparse(&old_snapshot, registry.clone(), language);
}
- branch_buffer.edit(edits.iter().cloned());
+ branch_buffer.edit(edits.iter().cloned(), &executor);
let snapshot = branch_buffer.snapshot();
syntax_snapshot.interpolate(&snapshot);
@@ -2361,7 +2365,9 @@ impl Buffer {
let autoindent_request = autoindent_mode
.and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode)));
- let edit_operation = self.text.edit(edits.iter().cloned());
+ let edit_operation = self
+ .text
+ .edit(edits.iter().cloned(), cx.background_executor());
let edit_id = edit_operation.timestamp();
if let Some((before_edit, mode)) = autoindent_request {
@@ -2592,7 +2598,8 @@ impl Buffer {
for operation in buffer_ops.iter() {
self.send_operation(Operation::Buffer(operation.clone()), false, cx);
}
- self.text.apply_ops(buffer_ops);
+ self.text
+ .apply_ops(buffer_ops, Some(cx.background_executor()));
self.deferred_ops.insert(deferred_ops);
self.flush_deferred_ops(cx);
self.did_edit(&old_version, was_dirty, cx);
@@ -75,6 +75,7 @@ fn test_set_line_ending(cx: &mut TestAppContext) {
Capability::ReadWrite,
base.read(cx).to_proto(cx),
None,
+ cx.background_executor(),
)
.unwrap()
});
@@ -255,14 +256,18 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
.is_none()
);
assert!(
- cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
- .is_none()
+ cx.read(|cx| languages.language_for_file(
+ &file("the/script"),
+ Some(&Rope::from_str("nothing", cx.background_executor())),
+ cx
+ ))
+ .is_none()
);
assert_eq!(
cx.read(|cx| languages.language_for_file(
&file("the/script"),
- Some(&"#!/bin/env node".into()),
+ Some(&Rope::from_str("#!/bin/env node", cx.background_executor())),
cx
))
.unwrap()
@@ -406,6 +411,7 @@ fn test_edit_events(cx: &mut gpui::App) {
ReplicaId::new(1),
Capability::ReadWrite,
"abcdef",
+ cx.background_executor(),
)
});
let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
@@ -2781,8 +2787,14 @@ fn test_serialization(cx: &mut gpui::App) {
.background_executor()
.block(buffer1.read(cx).serialize_ops(None, cx));
let buffer2 = cx.new(|cx| {
- let mut buffer =
- Buffer::from_proto(ReplicaId::new(1), Capability::ReadWrite, state, None).unwrap();
+ let mut buffer = Buffer::from_proto(
+ ReplicaId::new(1),
+ Capability::ReadWrite,
+ state,
+ None,
+ cx.background_executor(),
+ )
+ .unwrap();
buffer.apply_ops(
ops.into_iter()
.map(|op| proto::deserialize_operation(op).unwrap()),
@@ -2806,6 +2818,7 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
Capability::ReadWrite,
base.read(cx).to_proto(cx),
None,
+ cx.background_executor(),
)
.unwrap()
});
@@ -3120,9 +3133,14 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
let ops = cx
.background_executor()
.block(base_buffer.read(cx).serialize_ops(None, cx));
- let mut buffer =
- Buffer::from_proto(ReplicaId::new(i as u16), Capability::ReadWrite, state, None)
- .unwrap();
+ let mut buffer = Buffer::from_proto(
+ ReplicaId::new(i as u16),
+ Capability::ReadWrite,
+ state,
+ None,
+ cx.background_executor(),
+ )
+ .unwrap();
buffer.apply_ops(
ops.into_iter()
.map(|op| proto::deserialize_operation(op).unwrap()),
@@ -3251,6 +3269,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
Capability::ReadWrite,
old_buffer_state,
None,
+ cx.background_executor(),
)
.unwrap();
new_buffer.apply_ops(
@@ -3414,7 +3433,7 @@ fn test_contiguous_ranges() {
}
#[gpui::test(iterations = 500)]
-fn test_trailing_whitespace_ranges(mut rng: StdRng) {
+fn test_trailing_whitespace_ranges(mut rng: StdRng, cx: &mut TestAppContext) {
// Generate a random multi-line string containing
// some lines with trailing whitespace.
let mut text = String::new();
@@ -3438,7 +3457,7 @@ fn test_trailing_whitespace_ranges(mut rng: StdRng) {
_ => {}
}
- let rope = Rope::from(text.as_str());
+ let rope = Rope::from_str(text.as_str(), cx.background_executor());
let actual_ranges = trailing_whitespace_ranges(&rope);
let expected_ranges = TRAILING_WHITESPACE_REGEX
.find_iter(&text)
@@ -100,6 +100,7 @@ fn test_syntax_map_layers_for_range(cx: &mut App) {
}
"#
.unindent(),
+ cx.background_executor(),
);
let mut syntax_map = SyntaxMap::new(&buffer);
@@ -147,7 +148,7 @@ fn test_syntax_map_layers_for_range(cx: &mut App) {
// Replace a vec! macro invocation with a plain slice, removing a syntactic layer.
let macro_name_range = range_for_text(&buffer, "vec!");
- buffer.edit([(macro_name_range, "&")]);
+ buffer.edit([(macro_name_range, "&")], cx.background_executor());
syntax_map.interpolate(&buffer);
syntax_map.reparse(language.clone(), &buffer);
@@ -199,6 +200,7 @@ fn test_dynamic_language_injection(cx: &mut App) {
```
"#
.unindent(),
+ cx.background_executor(),
);
let mut syntax_map = SyntaxMap::new(&buffer);
@@ -218,7 +220,10 @@ fn test_dynamic_language_injection(cx: &mut App) {
// Replace `rs` with a path to ending in `.rb` in code block.
let macro_name_range = range_for_text(&buffer, "rs");
- buffer.edit([(macro_name_range, "foo/bar/baz.rb")]);
+ buffer.edit(
+ [(macro_name_range, "foo/bar/baz.rb")],
+ cx.background_executor(),
+ );
syntax_map.interpolate(&buffer);
syntax_map.reparse(markdown.clone(), &buffer);
syntax_map.reparse(markdown_inline.clone(), &buffer);
@@ -235,7 +240,7 @@ fn test_dynamic_language_injection(cx: &mut App) {
// Replace Ruby with a language that hasn't been loaded yet.
let macro_name_range = range_for_text(&buffer, "foo/bar/baz.rb");
- buffer.edit([(macro_name_range, "html")]);
+ buffer.edit([(macro_name_range, "html")], cx.background_executor());
syntax_map.interpolate(&buffer);
syntax_map.reparse(markdown.clone(), &buffer);
syntax_map.reparse(markdown_inline.clone(), &buffer);
@@ -811,7 +816,12 @@ fn test_syntax_map_languages_loading_with_erb(cx: &mut App) {
.unindent();
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ text,
+ cx.background_executor(),
+ );
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
@@ -859,7 +869,7 @@ fn test_syntax_map_languages_loading_with_erb(cx: &mut App) {
.unindent();
log::info!("editing");
- buffer.edit_via_marked_text(&text);
+ buffer.edit_via_marked_text(&text, cx.background_executor());
syntax_map.interpolate(&buffer);
syntax_map.reparse(language, &buffer);
@@ -903,7 +913,7 @@ fn test_random_syntax_map_edits_rust_macros(rng: StdRng, cx: &mut App) {
let language = Arc::new(rust_lang());
registry.add(language.clone());
- test_random_edits(text, registry, language, rng);
+ test_random_edits(text, registry, language, rng, cx);
}
#[gpui::test(iterations = 50)]
@@ -932,7 +942,7 @@ fn test_random_syntax_map_edits_with_erb(rng: StdRng, cx: &mut App) {
registry.add(Arc::new(ruby_lang()));
registry.add(Arc::new(html_lang()));
- test_random_edits(text, registry, language, rng);
+ test_random_edits(text, registry, language, rng, cx);
}
#[gpui::test(iterations = 50)]
@@ -965,7 +975,7 @@ fn test_random_syntax_map_edits_with_heex(rng: StdRng, cx: &mut App) {
registry.add(Arc::new(heex_lang()));
registry.add(Arc::new(html_lang()));
- test_random_edits(text, registry, language, rng);
+ test_random_edits(text, registry, language, rng, cx);
}
fn test_random_edits(
@@ -973,12 +983,18 @@ fn test_random_edits(
registry: Arc<LanguageRegistry>,
language: Arc<Language>,
mut rng: StdRng,
+ cx: &mut App,
) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ text,
+ cx.background_executor(),
+ );
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
@@ -993,7 +1009,7 @@ fn test_random_edits(
let prev_buffer = buffer.snapshot();
let prev_syntax_map = syntax_map.snapshot();
- buffer.randomly_edit(&mut rng, 3);
+ buffer.randomly_edit(&mut rng, 3, cx.background_executor());
log::info!("text:\n{}", buffer.text());
syntax_map.interpolate(&buffer);
@@ -1159,7 +1175,12 @@ fn test_edit_sequence(language_name: &str, steps: &[&str], cx: &mut App) -> (Buf
.now_or_never()
.unwrap()
.unwrap();
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "",
+ cx.background_executor(),
+ );
let mut mutated_syntax_map = SyntaxMap::new(&buffer);
mutated_syntax_map.set_language_registry(registry.clone());
@@ -1168,7 +1189,7 @@ fn test_edit_sequence(language_name: &str, steps: &[&str], cx: &mut App) -> (Buf
for (i, marked_string) in steps.iter().enumerate() {
let marked_string = marked_string.unindent();
log::info!("incremental parse {i}: {marked_string:?}");
- buffer.edit_via_marked_text(&marked_string);
+ buffer.edit_via_marked_text(&marked_string, cx.background_executor());
// Reparse the syntax map
mutated_syntax_map.interpolate(&buffer);
@@ -11,7 +11,7 @@ use futures::{Future, FutureExt, future::join_all};
use gpui::{App, AppContext, AsyncApp, Task};
use language::{
BinaryStatus, CodeLabel, DynLspInstaller, HighlightId, Language, LanguageName, LspAdapter,
- LspAdapterDelegate, Toolchain,
+ LspAdapterDelegate, Rope, Toolchain,
};
use lsp::{
CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName,
@@ -403,7 +403,10 @@ fn labels_from_extension(
let runs = if label.code.is_empty() {
Vec::new()
} else {
- language.highlight_text(&label.code.as_str().into(), 0..label.code.len())
+ language.highlight_text(
+ &Rope::from_str_small(label.code.as_str()),
+ 0..label.code.len(),
+ )
};
build_code_label(&label, &runs, language)
})
@@ -189,7 +189,7 @@ impl super::LspAdapter for CLspAdapter {
Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
- let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
+ let source = Rope::from_str_small(format!("struct S {{ {} }}", text).as_str());
let runs = language.highlight_text(&source, 11..11 + text.len());
let filter_range = completion
.filter_text
@@ -206,7 +206,8 @@ impl super::LspAdapter for CLspAdapter {
{
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
- let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
+ let runs =
+ language.highlight_text(&Rope::from_str_small(text.as_str()), 0..text.len());
let filter_range = completion
.filter_text
.as_deref()
@@ -222,7 +223,8 @@ impl super::LspAdapter for CLspAdapter {
{
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
- let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
+ let runs =
+ language.highlight_text(&Rope::from_str_small(text.as_str()), 0..text.len());
let filter_range = completion
.filter_text
.as_deref()
@@ -326,7 +328,7 @@ impl super::LspAdapter for CLspAdapter {
Some(CodeLabel::new(
text[display_range.clone()].to_string(),
filter_range,
- language.highlight_text(&text.as_str().into(), display_range),
+ language.highlight_text(&Rope::from_str_small(text.as_str()), display_range),
))
}
@@ -221,7 +221,7 @@ impl LspAdapter for GoLspAdapter {
match completion.kind.zip(completion.detail.as_ref()) {
Some((lsp::CompletionItemKind::MODULE, detail)) => {
let text = format!("{label} {detail}");
- let source = Rope::from(format!("import {text}").as_str());
+ let source = Rope::from_str_small(format!("import {text}").as_str());
let runs = language.highlight_text(&source, 7..7 + text[name_offset..].len());
let filter_range = completion
.filter_text
@@ -238,8 +238,9 @@ impl LspAdapter for GoLspAdapter {
detail,
)) => {
let text = format!("{label} {detail}");
- let source =
- Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
+ let source = Rope::from_str_small(
+ format!("var {} {}", &text[name_offset..], detail).as_str(),
+ );
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 4..4 + text[name_offset..].len()),
@@ -256,7 +257,8 @@ impl LspAdapter for GoLspAdapter {
}
Some((lsp::CompletionItemKind::STRUCT, _)) => {
let text = format!("{label} struct {{}}");
- let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
+ let source =
+ Rope::from_str_small(format!("type {}", &text[name_offset..]).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
@@ -273,7 +275,8 @@ impl LspAdapter for GoLspAdapter {
}
Some((lsp::CompletionItemKind::INTERFACE, _)) => {
let text = format!("{label} interface {{}}");
- let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
+ let source =
+ Rope::from_str_small(format!("type {}", &text[name_offset..]).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
@@ -290,8 +293,9 @@ impl LspAdapter for GoLspAdapter {
}
Some((lsp::CompletionItemKind::FIELD, detail)) => {
let text = format!("{label} {detail}");
- let source =
- Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
+ let source = Rope::from_str_small(
+ format!("type T struct {{ {} }}", &text[name_offset..]).as_str(),
+ );
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 16..16 + text[name_offset..].len()),
@@ -309,7 +313,9 @@ impl LspAdapter for GoLspAdapter {
Some((lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD, detail)) => {
if let Some(signature) = detail.strip_prefix("func") {
let text = format!("{label}{signature}");
- let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
+ let source = Rope::from_str_small(
+ format!("func {} {{}}", &text[name_offset..]).as_str(),
+ );
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 5..5 + text[name_offset..].len()),
@@ -385,7 +391,7 @@ impl LspAdapter for GoLspAdapter {
Some(CodeLabel::new(
text[display_range.clone()].to_string(),
filter_range,
- language.highlight_text(&text.as_str().into(), display_range),
+ language.highlight_text(&Rope::from_str_small(text.as_str()), display_range),
))
}
@@ -19,6 +19,7 @@ use pet_core::python_environment::{PythonEnvironment, PythonEnvironmentKind};
use pet_virtualenv::is_virtualenv_dir;
use project::Fs;
use project::lsp_store::language_server_settings;
+use rope::Rope;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use smol::lock::OnceCell;
@@ -466,7 +467,7 @@ impl LspAdapter for PyrightLspAdapter {
Some(language::CodeLabel::new(
text[display_range.clone()].to_string(),
filter_range,
- language.highlight_text(&text.as_str().into(), display_range),
+ language.highlight_text(&Rope::from_str_small(text.as_str()), display_range),
))
}
@@ -1511,7 +1512,7 @@ impl LspAdapter for PyLspAdapter {
Some(language::CodeLabel::new(
text[display_range.clone()].to_string(),
filter_range,
- language.highlight_text(&text.as_str().into(), display_range),
+ language.highlight_text(&Rope::from_str_small(text.as_str()), display_range),
))
}
@@ -1800,7 +1801,7 @@ impl LspAdapter for BasedPyrightLspAdapter {
Some(language::CodeLabel::new(
text[display_range.clone()].to_string(),
filter_range,
- language.highlight_text(&text.as_str().into(), display_range),
+ language.highlight_text(&Rope::from_str_small(text.as_str()), display_range),
))
}
@@ -252,7 +252,7 @@ impl LspAdapter for RustLspAdapter {
let name = &completion.label;
let text = format!("{name}: {signature}");
let prefix = "struct S { ";
- let source = Rope::from_iter([prefix, &text, " }"]);
+ let source = Rope::from_iter_small([prefix, &text, " }"]);
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
mk_label(text, &|| 0..completion.label.len(), runs)
@@ -264,7 +264,7 @@ impl LspAdapter for RustLspAdapter {
let name = &completion.label;
let text = format!("{name}: {signature}",);
let prefix = "let ";
- let source = Rope::from_iter([prefix, &text, " = ();"]);
+ let source = Rope::from_iter_small([prefix, &text, " = ();"]);
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
mk_label(text, &|| 0..completion.label.len(), runs)
@@ -302,7 +302,7 @@ impl LspAdapter for RustLspAdapter {
.filter(|it| it.contains(&label))
.and_then(|it| Some((it, FULL_SIGNATURE_REGEX.find(it)?)))
{
- let source = Rope::from(function_signature);
+ let source = Rope::from_str_small(function_signature);
let runs = language.highlight_text(&source, 0..function_signature.len());
mk_label(
function_signature.to_owned(),
@@ -311,7 +311,7 @@ impl LspAdapter for RustLspAdapter {
)
} else if let Some((prefix, suffix)) = fn_prefixed {
let text = format!("{label}{suffix}");
- let source = Rope::from_iter([prefix, " ", &text, " {}"]);
+ let source = Rope::from_iter_small([prefix, " ", &text, " {}"]);
let run_start = prefix.len() + 1;
let runs = language.highlight_text(&source, run_start..run_start + text.len());
mk_label(text, &|| 0..label.len(), runs)
@@ -322,7 +322,7 @@ impl LspAdapter for RustLspAdapter {
{
let text = completion.label.clone();
let len = text.len();
- let source = Rope::from(text.as_str());
+ let source = Rope::from_str_small(text.as_str());
let runs = language.highlight_text(&source, 0..len);
mk_label(text, &|| 0..completion.label.len(), runs)
} else if detail_left.is_none() {
@@ -399,7 +399,10 @@ impl LspAdapter for RustLspAdapter {
Some(CodeLabel::new(
format!("{prefix}{name}"),
filter_range,
- language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range),
+ language.highlight_text(
+ &Rope::from_iter_small([prefix, name, suffix]),
+ display_range,
+ ),
))
}
@@ -1558,7 +1558,9 @@ impl MarkdownElementBuilder {
if let Some(Some(language)) = self.code_block_stack.last() {
let mut offset = 0;
- for (range, highlight_id) in language.highlight_text(&Rope::from(text), 0..text.len()) {
+ for (range, highlight_id) in
+ language.highlight_text(&Rope::from_str_small(text), 0..text.len())
+ {
if range.start > offset {
self.pending_line
.runs
@@ -779,7 +779,7 @@ impl<'a> MarkdownParser<'a> {
let highlights = if let Some(language) = &language {
if let Some(registry) = &self.language_registry {
- let rope: language::Rope = code.as_str().into();
+ let rope = language::Rope::from_str_small(code.as_str());
registry
.language_for_name_or_extension(language)
.await
@@ -1,6 +1,6 @@
use super::*;
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
-use gpui::{App, TestAppContext};
+use gpui::{App, BackgroundExecutor, TestAppContext};
use indoc::indoc;
use language::{Buffer, Rope};
use parking_lot::RwLock;
@@ -79,9 +79,14 @@ fn test_remote(cx: &mut App) {
let ops = cx
.background_executor()
.block(host_buffer.read(cx).serialize_ops(None, cx));
- let mut buffer =
- Buffer::from_proto(ReplicaId::REMOTE_SERVER, Capability::ReadWrite, state, None)
- .unwrap();
+ let mut buffer = Buffer::from_proto(
+ ReplicaId::REMOTE_SERVER,
+ Capability::ReadWrite,
+ state,
+ None,
+ cx.background_executor(),
+ )
+ .unwrap();
buffer.apply_ops(
ops.into_iter()
.map(|op| language::proto::deserialize_operation(op).unwrap()),
@@ -1224,7 +1229,7 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
assert_chunks_in_ranges(&snapshot);
assert_consistent_line_numbers(&snapshot);
assert_position_translation(&snapshot);
- assert_line_indents(&snapshot);
+ assert_line_indents(&snapshot, cx.background_executor());
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
@@ -1248,7 +1253,7 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
assert_chunks_in_ranges(&snapshot);
assert_consistent_line_numbers(&snapshot);
assert_position_translation(&snapshot);
- assert_line_indents(&snapshot);
+ assert_line_indents(&snapshot, cx.background_executor());
// Expand the first diff hunk
multibuffer.update(cx, |multibuffer, cx| {
@@ -1300,7 +1305,7 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
assert_chunks_in_ranges(&snapshot);
assert_consistent_line_numbers(&snapshot);
assert_position_translation(&snapshot);
- assert_line_indents(&snapshot);
+ assert_line_indents(&snapshot, cx.background_executor());
// Edit the buffer before the first hunk
buffer.update(cx, |buffer, cx| {
@@ -1342,7 +1347,7 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
assert_chunks_in_ranges(&snapshot);
assert_consistent_line_numbers(&snapshot);
assert_position_translation(&snapshot);
- assert_line_indents(&snapshot);
+ assert_line_indents(&snapshot, cx.background_executor());
// Recalculate the diff, changing the first diff hunk.
diff.update(cx, |diff, cx| {
@@ -2067,7 +2072,7 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
}
assert_position_translation(&snapshot);
- assert_line_indents(&snapshot);
+ assert_line_indents(&snapshot, cx.background_executor());
assert_eq!(
snapshot
@@ -2118,7 +2123,7 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
),
);
- assert_line_indents(&snapshot);
+ assert_line_indents(&snapshot, cx.background_executor());
}
/// A naive implementation of a multi-buffer that does not maintain
@@ -2888,7 +2893,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
);
}
- let text_rope = Rope::from(expected_text.as_str());
+ let text_rope = Rope::from_str(expected_text.as_str(), cx.background_executor());
for _ in 0..10 {
let end_ix = text_rope.clip_offset(rng.random_range(0..=text_rope.len()), Bias::Right);
let start_ix = text_rope.clip_offset(rng.random_range(0..=end_ix), Bias::Left);
@@ -3512,7 +3517,7 @@ fn assert_consistent_line_numbers(snapshot: &MultiBufferSnapshot) {
#[track_caller]
fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
- let text = Rope::from(snapshot.text());
+ let text = Rope::from_str_small(&snapshot.text());
let mut left_anchors = Vec::new();
let mut right_anchors = Vec::new();
@@ -3636,10 +3641,10 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) {
}
}
-fn assert_line_indents(snapshot: &MultiBufferSnapshot) {
+fn assert_line_indents(snapshot: &MultiBufferSnapshot, executor: &BackgroundExecutor) {
let max_row = snapshot.max_point().row;
let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id();
- let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text());
+ let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text(), executor);
let mut line_indents = text
.line_indents_in_row_range(0..max_row + 1)
.collect::<Vec<_>>();
@@ -180,7 +180,13 @@ impl RemoteBufferStore {
buffer_file = Some(Arc::new(File::from_proto(file, worktree, cx)?)
as Arc<dyn language::File>);
}
- Buffer::from_proto(replica_id, capability, state, buffer_file)
+ Buffer::from_proto(
+ replica_id,
+ capability,
+ state,
+ buffer_file,
+ cx.background_executor(),
+ )
});
match buffer_result {
@@ -628,9 +634,10 @@ impl LocalBufferStore {
Ok(loaded) => {
let reservation = cx.reserve_entity::<Buffer>()?;
let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64());
+ let executor = cx.background_executor().clone();
let text_buffer = cx
.background_spawn(async move {
- text::Buffer::new(ReplicaId::LOCAL, buffer_id, loaded.text)
+ text::Buffer::new(ReplicaId::LOCAL, buffer_id, loaded.text, &executor)
})
.await;
cx.insert_entity(reservation, |_| {
@@ -639,7 +646,12 @@ impl LocalBufferStore {
}
Err(error) if is_not_found_error(&error) => cx.new(|cx| {
let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64());
- let text_buffer = text::Buffer::new(ReplicaId::LOCAL, buffer_id, "");
+ let text_buffer = text::Buffer::new(
+ ReplicaId::LOCAL,
+ buffer_id,
+ "",
+ cx.background_executor(),
+ );
Buffer::build(
text_buffer,
Some(Arc::new(File {
@@ -276,8 +276,8 @@ mod tests {
use util::{path, rel_path::rel_path};
use worktree::WorktreeSettings;
- #[test]
- fn test_parse_conflicts_in_buffer() {
+ #[gpui::test]
+ fn test_parse_conflicts_in_buffer(cx: &mut TestAppContext) {
// Create a buffer with conflict markers
let test_content = r#"
This is some text before the conflict.
@@ -299,7 +299,12 @@ mod tests {
.unindent();
let buffer_id = BufferId::new(1).unwrap();
- let buffer = Buffer::new(ReplicaId::LOCAL, buffer_id, test_content);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ buffer_id,
+ test_content,
+ cx.background_executor(),
+ );
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
@@ -355,8 +360,8 @@ mod tests {
assert_eq!(conflicts_in_range.len(), 0);
}
- #[test]
- fn test_nested_conflict_markers() {
+ #[gpui::test]
+ fn test_nested_conflict_markers(cx: &mut TestAppContext) {
// Create a buffer with nested conflict markers
let test_content = r#"
This is some text before the conflict.
@@ -374,7 +379,12 @@ mod tests {
.unindent();
let buffer_id = BufferId::new(1).unwrap();
- let buffer = Buffer::new(ReplicaId::LOCAL, buffer_id, test_content);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ buffer_id,
+ test_content,
+ cx.background_executor(),
+ );
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
@@ -396,8 +406,8 @@ mod tests {
assert_eq!(their_text, "This is their version in a nested conflict\n");
}
- #[test]
- fn test_conflict_markers_at_eof() {
+ #[gpui::test]
+ fn test_conflict_markers_at_eof(cx: &mut TestAppContext) {
let test_content = r#"
<<<<<<< ours
=======
@@ -405,15 +415,20 @@ mod tests {
>>>>>>> "#
.unindent();
let buffer_id = BufferId::new(1).unwrap();
- let buffer = Buffer::new(ReplicaId::LOCAL, buffer_id, test_content);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ buffer_id,
+ test_content,
+ cx.background_executor(),
+ );
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
assert_eq!(conflict_snapshot.conflicts.len(), 1);
}
- #[test]
- fn test_conflicts_in_range() {
+ #[gpui::test]
+ fn test_conflicts_in_range(cx: &mut TestAppContext) {
// Create a buffer with conflict markers
let test_content = r#"
one
@@ -447,7 +462,12 @@ mod tests {
.unindent();
let buffer_id = BufferId::new(1).unwrap();
- let buffer = Buffer::new(ReplicaId::LOCAL, buffer_id, test_content.clone());
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ buffer_id,
+ test_content.clone(),
+ cx.background_executor(),
+ );
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
@@ -13,7 +13,9 @@ use futures::{
future::{self, Shared},
stream::FuturesUnordered,
};
-use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
+use gpui::{
+ AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity,
+};
use language::{
Buffer, LanguageRegistry, LocalFile,
language_settings::{Formatter, LanguageSettings},
@@ -558,99 +560,137 @@ impl PrettierStore {
let plugins_to_install = new_plugins.clone();
let fs = Arc::clone(&self.fs);
let new_installation_task = cx
- .spawn(async move |prettier_store, cx| {
- cx.background_executor().timer(Duration::from_millis(30)).await;
+ .spawn(async move |prettier_store, cx| {
+ cx.background_executor()
+ .timer(Duration::from_millis(30))
+ .await;
let location_data = prettier_store.update(cx, |prettier_store, cx| {
- worktree.and_then(|worktree_id| {
- prettier_store.worktree_store
- .read(cx)
- .worktree_for_id(worktree_id, cx)
- .map(|worktree| worktree.read(cx).abs_path())
- }).map(|locate_from| {
- let installed_prettiers = prettier_store.prettier_instances.keys().cloned().collect();
- (locate_from, installed_prettiers)
- })
+ worktree
+ .and_then(|worktree_id| {
+ prettier_store
+ .worktree_store
+ .read(cx)
+ .worktree_for_id(worktree_id, cx)
+ .map(|worktree| worktree.read(cx).abs_path())
+ })
+ .map(|locate_from| {
+ let installed_prettiers =
+ prettier_store.prettier_instances.keys().cloned().collect();
+ (locate_from, installed_prettiers)
+ })
})?;
let locate_prettier_installation = match location_data {
- Some((locate_from, installed_prettiers)) => Prettier::locate_prettier_installation(
- fs.as_ref(),
- &installed_prettiers,
- locate_from.as_ref(),
- )
- .await
- .context("locate prettier installation").map_err(Arc::new)?,
+ Some((locate_from, installed_prettiers)) => {
+ Prettier::locate_prettier_installation(
+ fs.as_ref(),
+ &installed_prettiers,
+ locate_from.as_ref(),
+ )
+ .await
+ .context("locate prettier installation")
+ .map_err(Arc::new)?
+ }
None => ControlFlow::Continue(None),
};
- match locate_prettier_installation
- {
+ match locate_prettier_installation {
ControlFlow::Break(()) => return Ok(()),
ControlFlow::Continue(prettier_path) => {
if prettier_path.is_some() {
new_plugins.clear();
}
- let mut needs_install = should_write_prettier_server_file(fs.as_ref()).await;
+ let mut needs_install =
+ should_write_prettier_server_file(fs.as_ref()).await;
if let Some(previous_installation_task) = previous_installation_task
- && let Err(e) = previous_installation_task.await {
- log::error!("Failed to install default prettier: {e:#}");
- prettier_store.update(cx, |prettier_store, _| {
- if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut prettier_store.default_prettier.prettier {
- *attempts += 1;
- new_plugins.extend(not_installed_plugins.iter().cloned());
- installation_attempt = *attempts;
- needs_install = true;
- };
- })?;
- };
+ && let Err(e) = previous_installation_task.await
+ {
+ log::error!("Failed to install default prettier: {e:#}");
+ prettier_store.update(cx, |prettier_store, _| {
+ if let PrettierInstallation::NotInstalled {
+ attempts,
+ not_installed_plugins,
+ ..
+ } = &mut prettier_store.default_prettier.prettier
+ {
+ *attempts += 1;
+ new_plugins.extend(not_installed_plugins.iter().cloned());
+ installation_attempt = *attempts;
+ needs_install = true;
+ };
+ })?;
+ };
if installation_attempt > prettier::FAIL_THRESHOLD {
prettier_store.update(cx, |prettier_store, _| {
- if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut prettier_store.default_prettier.prettier {
+ if let PrettierInstallation::NotInstalled {
+ installation_task,
+ ..
+ } = &mut prettier_store.default_prettier.prettier
+ {
*installation_task = None;
};
})?;
log::warn!(
- "Default prettier installation had failed {installation_attempt} times, not attempting again",
+ "Default prettier installation had failed {installation_attempt} \
+ times, not attempting again",
);
return Ok(());
}
prettier_store.update(cx, |prettier_store, _| {
new_plugins.retain(|plugin| {
- !prettier_store.default_prettier.installed_plugins.contains(plugin)
+ !prettier_store
+ .default_prettier
+ .installed_plugins
+ .contains(plugin)
});
- if let PrettierInstallation::NotInstalled { not_installed_plugins, .. } = &mut prettier_store.default_prettier.prettier {
+ if let PrettierInstallation::NotInstalled {
+ not_installed_plugins,
+ ..
+ } = &mut prettier_store.default_prettier.prettier
+ {
not_installed_plugins.retain(|plugin| {
- !prettier_store.default_prettier.installed_plugins.contains(plugin)
+ !prettier_store
+ .default_prettier
+ .installed_plugins
+ .contains(plugin)
});
not_installed_plugins.extend(new_plugins.iter().cloned());
}
needs_install |= !new_plugins.is_empty();
})?;
if needs_install {
- log::info!("Initializing default prettier with plugins {new_plugins:?}");
+ log::info!(
+ "Initializing default prettier with plugins {new_plugins:?}"
+ );
let installed_plugins = new_plugins.clone();
+ let executor = cx.background_executor().clone();
cx.background_spawn(async move {
install_prettier_packages(fs.as_ref(), new_plugins, node).await?;
// Save the server file last, so the reinstall need could be determined by the absence of the file.
- save_prettier_server_file(fs.as_ref()).await?;
+ save_prettier_server_file(fs.as_ref(), &executor).await?;
anyhow::Ok(())
})
- .await
- .context("prettier & plugins install")
- .map_err(Arc::new)?;
- log::info!("Initialized default prettier with plugins: {installed_plugins:?}");
+ .await
+ .context("prettier & plugins install")
+ .map_err(Arc::new)?;
+ log::info!(
+ "Initialized default prettier with plugins: {installed_plugins:?}"
+ );
prettier_store.update(cx, |prettier_store, _| {
prettier_store.default_prettier.prettier =
PrettierInstallation::Installed(PrettierInstance {
attempt: 0,
prettier: None,
});
- prettier_store.default_prettier
+ prettier_store
+ .default_prettier
.installed_plugins
.extend(installed_plugins);
})?;
} else {
prettier_store.update(cx, |prettier_store, _| {
- if let PrettierInstallation::NotInstalled { .. } = &mut prettier_store.default_prettier.prettier {
+ if let PrettierInstallation::NotInstalled { .. } =
+ &mut prettier_store.default_prettier.prettier
+ {
prettier_store.default_prettier.prettier =
PrettierInstallation::Installed(PrettierInstance {
attempt: 0,
@@ -936,11 +976,14 @@ async fn install_prettier_packages(
anyhow::Ok(())
}
-async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
+async fn save_prettier_server_file(
+ fs: &dyn Fs,
+ executor: &BackgroundExecutor,
+) -> anyhow::Result<()> {
let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
fs.save(
&prettier_wrapper_path,
- &text::Rope::from(prettier::PRETTIER_SERVER_JS),
+ &text::Rope::from_str(prettier::PRETTIER_SERVER_JS, executor),
text::LineEnding::Unix,
)
.await
@@ -712,8 +712,10 @@ pub enum ResolveState {
impl InlayHint {
pub fn text(&self) -> Rope {
match &self.label {
- InlayHintLabel::String(s) => Rope::from(s),
- InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &*part.value).collect(),
+ InlayHintLabel::String(s) => Rope::from_str_small(s),
+ InlayHintLabel::LabelParts(parts) => {
+ Rope::from_iter_small(parts.iter().map(|part| &*part.value))
+ }
}
}
}
@@ -5402,7 +5404,12 @@ impl Project {
worktree
.update(cx, |worktree, cx| {
let line_ending = text::LineEnding::detect(&new_text);
- worktree.write_file(rel_path.clone(), new_text.into(), line_ending, cx)
+ worktree.write_file(
+ rel_path.clone(),
+ Rope::from_str(&new_text, cx.background_executor()),
+ line_ending,
+ cx,
+ )
})?
.await
.context("Failed to write settings file")?;
@@ -1461,21 +1461,21 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
.unwrap();
fs.save(
path!("/the-root/Cargo.lock").as_ref(),
- &"".into(),
+ &Rope::default(),
Default::default(),
)
.await
.unwrap();
fs.save(
path!("/the-stdlib/LICENSE").as_ref(),
- &"".into(),
+ &Rope::default(),
Default::default(),
)
.await
.unwrap();
fs.save(
path!("/the/stdlib/src/string.rs").as_ref(),
- &"".into(),
+ &Rope::default(),
Default::default(),
)
.await
@@ -4072,7 +4072,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
// to be detected by the worktree, so that the buffer starts reloading.
fs.save(
path!("/dir/file1").as_ref(),
- &"the first contents".into(),
+ &Rope::from_str("the first contents", cx.background_executor()),
Default::default(),
)
.await
@@ -4083,7 +4083,7 @@ async fn test_file_changes_multiple_times_on_disk(cx: &mut gpui::TestAppContext)
// previous file change may still be in progress.
fs.save(
path!("/dir/file1").as_ref(),
- &"the second contents".into(),
+ &Rope::from_str("the second contents", cx.background_executor()),
Default::default(),
)
.await
@@ -4127,7 +4127,7 @@ async fn test_edit_buffer_while_it_reloads(cx: &mut gpui::TestAppContext) {
// to be detected by the worktree, so that the buffer starts reloading.
fs.save(
path!("/dir/file1").as_ref(),
- &"the first contents".into(),
+ &Rope::from_str("the first contents", cx.background_executor()),
Default::default(),
)
.await
@@ -4805,7 +4805,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
marked_text_offsets("oneΛ\nthree ΛFOURΛ five\nsixtyΛ seven\n");
fs.save(
path!("/dir/the-file").as_ref(),
- &new_contents.as_str().into(),
+ &Rope::from_str(new_contents.as_str(), cx.background_executor()),
LineEnding::Unix,
)
.await
@@ -4837,7 +4837,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
// Change the file on disk again, adding blank lines to the beginning.
fs.save(
path!("/dir/the-file").as_ref(),
- &"\n\n\nAAAA\naaa\nBB\nbbbbb\n".into(),
+ &Rope::from_str("\n\n\nAAAA\naaa\nBB\nbbbbb\n", cx.background_executor()),
LineEnding::Unix,
)
.await
@@ -4889,7 +4889,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
// state updates correctly.
fs.save(
path!("/dir/file1").as_ref(),
- &"aaa\nb\nc\n".into(),
+ &Rope::from_str("aaa\nb\nc\n", cx.background_executor()),
LineEnding::Windows,
)
.await
@@ -13,7 +13,7 @@ use fs::{FakeFs, Fs};
use gpui::{AppContext as _, Entity, SemanticVersion, SharedString, TestAppContext};
use http_client::{BlockedHttpClient, FakeHttpClient};
use language::{
- Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding,
+ Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, Rope,
language_settings::{AllLanguageSettings, language_settings},
};
use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};
@@ -120,7 +120,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
// sees the new file.
fs.save(
path!("/code/project1/src/main.rs").as_ref(),
- &"fn main() {}".into(),
+ &Rope::from_str_small("fn main() {}"),
Default::default(),
)
.await
@@ -766,7 +766,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
fs.save(
&PathBuf::from(path!("/code/project1/src/lib.rs")),
- &("bangles".to_string().into()),
+ &Rope::from_str_small("bangles"),
LineEnding::Unix,
)
.await
@@ -781,7 +781,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont
fs.save(
&PathBuf::from(path!("/code/project1/src/lib.rs")),
- &("bloop".to_string().into()),
+ &Rope::from_str_small("bloop"),
LineEnding::Unix,
)
.await
@@ -1,9 +1,10 @@
use futures::FutureExt;
use gpui::{
- AnyElement, AnyView, App, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText,
- IntoElement, SharedString, StrikethroughStyle, StyledText, UnderlineStyle, Window,
+ AnyElement, AnyView, App, BackgroundExecutor, ElementId, FontStyle, FontWeight, HighlightStyle,
+ InteractiveText, IntoElement, SharedString, StrikethroughStyle, StyledText, UnderlineStyle,
+ Window,
};
-use language::{HighlightId, Language, LanguageRegistry};
+use language::{HighlightId, Language, LanguageRegistry, Rope};
use std::{ops::Range, sync::Arc};
use theme::ActiveTheme;
use ui::LinkPreview;
@@ -56,6 +57,7 @@ impl RichText {
block: String,
mentions: &[Mention],
language_registry: &Arc<LanguageRegistry>,
+ executor: &BackgroundExecutor,
) -> Self {
let mut text = String::new();
let mut highlights = Vec::new();
@@ -70,6 +72,7 @@ impl RichText {
&mut highlights,
&mut link_ranges,
&mut link_urls,
+ executor,
);
text.truncate(text.trim_end().len());
@@ -184,6 +187,7 @@ pub fn render_markdown_mut(
highlights: &mut Vec<(Range<usize>, Highlight)>,
link_ranges: &mut Vec<Range<usize>>,
link_urls: &mut Vec<String>,
+ executor: &BackgroundExecutor,
) {
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
@@ -202,7 +206,7 @@ pub fn render_markdown_mut(
match event {
Event::Text(t) => {
if let Some(language) = ¤t_language {
- render_code(text, highlights, t.as_ref(), language);
+ render_code(text, highlights, t.as_ref(), language, executor);
} else {
while let Some(mention) = mentions.first() {
if !source_range.contains_inclusive(&mention.range) {
@@ -373,11 +377,14 @@ pub fn render_code(
highlights: &mut Vec<(Range<usize>, Highlight)>,
content: &str,
language: &Arc<Language>,
+ executor: &BackgroundExecutor,
) {
let prev_len = text.len();
text.push_str(content);
let mut offset = 0;
- for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
+ for (range, highlight_id) in
+ language.highlight_text(&Rope::from_str(content, executor), 0..content.len())
+ {
if range.start > offset {
highlights.push((prev_len + offset..prev_len + range.start, Highlight::Code));
}
@@ -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(
|| {
@@ -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));
}
@@ -554,7 +554,7 @@ impl RulesLibrary {
let prompt_id = PromptId::new();
let save = self.store.update(cx, |store, cx| {
- store.save(prompt_id, None, false, "".into(), cx)
+ store.save(prompt_id, None, false, Default::default(), cx)
});
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
@@ -888,7 +888,13 @@ impl RulesLibrary {
let new_id = PromptId::new();
let body = rule.body_editor.read(cx).text(cx);
let save = self.store.update(cx, |store, cx| {
- store.save(new_id, Some(title.into()), false, body.into(), cx)
+ store.save(
+ new_id,
+ Some(title.into()),
+ false,
+ Rope::from_str(&body, cx.background_executor()),
+ cx,
+ )
});
self.picker
.update(cx, |picker, cx| picker.refresh(window, cx));
@@ -14,6 +14,7 @@ path = "src/streaming_diff.rs"
[dependencies]
ordered-float.workspace = true
rope.workspace = true
+gpui.workspace = true
[dev-dependencies]
rand.workspace = true
@@ -503,11 +503,12 @@ fn is_line_end(point: Point, text: &Rope) -> bool {
#[cfg(test)]
mod tests {
use super::*;
+ use gpui::BackgroundExecutor;
use rand::prelude::*;
use std::env;
- #[test]
- fn test_delete_first_of_two_lines() {
+ #[gpui::test]
+ fn test_delete_first_of_two_lines(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb";
let char_ops = vec![
CharOperation::Delete { bytes: 5 },
@@ -523,18 +524,18 @@ mod tests {
apply_line_operations(old_text, &new_text, &expected_line_ops)
);
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(line_ops, expected_line_ops);
}
- #[test]
- fn test_delete_second_of_two_lines() {
+ #[gpui::test]
+ fn test_delete_second_of_two_lines(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb";
let char_ops = vec![
CharOperation::Keep { bytes: 5 },
CharOperation::Delete { bytes: 4 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -550,8 +551,8 @@ mod tests {
);
}
- #[test]
- fn test_add_new_line() {
+ #[gpui::test]
+ fn test_add_new_line(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb";
let char_ops = vec![
CharOperation::Keep { bytes: 9 },
@@ -559,7 +560,7 @@ mod tests {
text: "\ncccc".into(),
},
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -574,15 +575,15 @@ mod tests {
);
}
- #[test]
- fn test_delete_line_in_middle() {
+ #[gpui::test]
+ fn test_delete_line_in_middle(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb\ncccc";
let char_ops = vec![
CharOperation::Keep { bytes: 5 },
CharOperation::Delete { bytes: 5 },
CharOperation::Keep { bytes: 4 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -598,8 +599,8 @@ mod tests {
);
}
- #[test]
- fn test_replace_line() {
+ #[gpui::test]
+ fn test_replace_line(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb\ncccc";
let char_ops = vec![
CharOperation::Keep { bytes: 5 },
@@ -609,7 +610,7 @@ mod tests {
},
CharOperation::Keep { bytes: 5 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -626,8 +627,8 @@ mod tests {
);
}
- #[test]
- fn test_multiple_edits_on_different_lines() {
+ #[gpui::test]
+ fn test_multiple_edits_on_different_lines(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb\ncccc\ndddd";
let char_ops = vec![
CharOperation::Insert { text: "A".into() },
@@ -638,7 +639,7 @@ mod tests {
text: "\nEEEE".into(),
},
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -656,15 +657,15 @@ mod tests {
);
}
- #[test]
- fn test_edit_at_end_of_line() {
+ #[gpui::test]
+ fn test_edit_at_end_of_line(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb\ncccc";
let char_ops = vec![
CharOperation::Keep { bytes: 4 },
CharOperation::Insert { text: "A".into() },
CharOperation::Keep { bytes: 10 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -680,8 +681,8 @@ mod tests {
);
}
- #[test]
- fn test_insert_newline_character() {
+ #[gpui::test]
+ fn test_insert_newline_character(cx: &mut gpui::TestAppContext) {
let old_text = "aaaabbbb";
let char_ops = vec![
CharOperation::Keep { bytes: 4 },
@@ -689,7 +690,7 @@ mod tests {
CharOperation::Keep { bytes: 4 },
];
let new_text = apply_char_operations(old_text, &char_ops);
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -703,14 +704,14 @@ mod tests {
);
}
- #[test]
- fn test_insert_newline_at_beginning() {
+ #[gpui::test]
+ fn test_insert_newline_at_beginning(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb";
let char_ops = vec![
CharOperation::Insert { text: "\n".into() },
CharOperation::Keep { bytes: 9 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -725,15 +726,15 @@ mod tests {
);
}
- #[test]
- fn test_delete_newline() {
+ #[gpui::test]
+ fn test_delete_newline(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb";
let char_ops = vec![
CharOperation::Keep { bytes: 4 },
CharOperation::Delete { bytes: 1 },
CharOperation::Keep { bytes: 4 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -749,8 +750,8 @@ mod tests {
);
}
- #[test]
- fn test_insert_multiple_newlines() {
+ #[gpui::test]
+ fn test_insert_multiple_newlines(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\nbbbb";
let char_ops = vec![
CharOperation::Keep { bytes: 5 },
@@ -759,7 +760,7 @@ mod tests {
},
CharOperation::Keep { bytes: 4 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -775,15 +776,15 @@ mod tests {
);
}
- #[test]
- fn test_delete_multiple_newlines() {
+ #[gpui::test]
+ fn test_delete_multiple_newlines(cx: &mut gpui::TestAppContext) {
let old_text = "aaaa\n\n\nbbbb";
let char_ops = vec![
CharOperation::Keep { bytes: 5 },
CharOperation::Delete { bytes: 2 },
CharOperation::Keep { bytes: 4 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -799,8 +800,8 @@ mod tests {
);
}
- #[test]
- fn test_complex_scenario() {
+ #[gpui::test]
+ fn test_complex_scenario(cx: &mut gpui::TestAppContext) {
let old_text = "line1\nline2\nline3\nline4";
let char_ops = vec![
CharOperation::Keep { bytes: 6 },
@@ -814,7 +815,7 @@ mod tests {
},
CharOperation::Keep { bytes: 6 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -834,8 +835,8 @@ mod tests {
);
}
- #[test]
- fn test_cleaning_up_common_suffix() {
+ #[gpui::test]
+ fn test_cleaning_up_common_suffix(cx: &mut gpui::TestAppContext) {
let old_text = concat!(
" for y in 0..size.y() {\n",
" let a = 10;\n",
@@ -883,7 +884,7 @@ mod tests {
},
CharOperation::Keep { bytes: 1 },
];
- let line_ops = char_ops_to_line_ops(old_text, &char_ops);
+ let line_ops = char_ops_to_line_ops(old_text, &char_ops, cx.background_executor());
assert_eq!(
line_ops,
vec![
@@ -901,8 +902,8 @@ mod tests {
);
}
- #[test]
- fn test_random_diffs() {
+ #[gpui::test]
+ fn test_random_diffs(cx: &mut gpui::TestAppContext) {
random_test(|mut rng| {
let old_text_len = env::var("OLD_TEXT_LEN")
.map(|i| i.parse().expect("invalid `OLD_TEXT_LEN` variable"))
@@ -922,15 +923,19 @@ mod tests {
assert_eq!(patched, new);
// Test char_ops_to_line_ops
- let line_ops = char_ops_to_line_ops(&old, &char_operations);
+ let line_ops = char_ops_to_line_ops(&old, &char_operations, cx.background_executor());
println!("line operations: {:?}", line_ops);
let patched = apply_line_operations(&old, &new, &line_ops);
assert_eq!(patched, new);
});
}
- fn char_ops_to_line_ops(old_text: &str, char_ops: &[CharOperation]) -> Vec<LineOperation> {
- let old_rope = Rope::from(old_text);
+ fn char_ops_to_line_ops(
+ old_text: &str,
+ char_ops: &[CharOperation],
+ executor: &BackgroundExecutor,
+ ) -> Vec<LineOperation> {
+ let old_rope = Rope::from_str(old_text, executor);
let mut diff = LineDiff::default();
for op in char_ops {
diff.push_char_operation(op, &old_rope);
@@ -15,10 +15,12 @@ doctest = false
[dependencies]
arrayvec = "0.7.1"
-rayon.workspace = true
log.workspace = true
+futures.workspace = true
+itertools.workspace = true
[dev-dependencies]
ctor.workspace = true
rand.workspace = true
zlog.workspace = true
+pollster = "0.4.0"
@@ -3,7 +3,8 @@ mod tree_map;
use arrayvec::ArrayVec;
pub use cursor::{Cursor, FilterCursor, Iter};
-use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator as _};
+use futures::{StreamExt, stream};
+use itertools::Itertools as _;
use std::marker::PhantomData;
use std::mem;
use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc};
@@ -14,6 +15,18 @@ pub const TREE_BASE: usize = 2;
#[cfg(not(test))]
pub const TREE_BASE: usize = 6;
+pub trait BackgroundSpawn {
+ type Task<R>: Future<Output = R> + Send + Sync
+ where
+ R: Send + Sync;
+ fn background_spawn<R>(
+ &self,
+ future: impl Future<Output = R> + Send + Sync + 'static,
+ ) -> Self::Task<R>
+ where
+ R: Send + Sync + 'static;
+}
+
/// An item that can be stored in a [`SumTree`]
///
/// Must be summarized by a type that implements [`Summary`]
@@ -298,62 +311,71 @@ impl<T: Item> SumTree<T> {
}
}
- pub fn from_par_iter<I, Iter>(iter: I, cx: <T::Summary as Summary>::Context<'_>) -> Self
+ pub async fn from_iter_async<I, S>(iter: I, spawn: S) -> Self
where
- I: IntoParallelIterator<Iter = Iter>,
- Iter: IndexedParallelIterator<Item = T>,
- T: Send + Sync,
- T::Summary: Send + Sync,
- for<'a> <T::Summary as Summary>::Context<'a>: Sync,
+ T: 'static + Send + Sync,
+ for<'a> T::Summary: Summary<Context<'a> = ()> + Send + Sync,
+ S: BackgroundSpawn,
+ I: IntoIterator<Item = T>,
{
- let mut nodes = iter
- .into_par_iter()
- .chunks(2 * TREE_BASE)
- .map(|items| {
- let items: ArrayVec<T, { 2 * TREE_BASE }> = items.into_iter().collect();
+ let mut futures = vec![];
+ let chunks = iter.into_iter().chunks(2 * TREE_BASE);
+ for chunk in chunks.into_iter() {
+ let items: ArrayVec<T, { 2 * TREE_BASE }> = chunk.into_iter().collect();
+ futures.push(async move {
let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> =
- items.iter().map(|item| item.summary(cx)).collect();
+ items.iter().map(|item| item.summary(())).collect();
let mut summary = item_summaries[0].clone();
for item_summary in &item_summaries[1..] {
- <T::Summary as Summary>::add_summary(&mut summary, item_summary, cx);
+ <T::Summary as Summary>::add_summary(&mut summary, item_summary, ());
}
SumTree(Arc::new(Node::Leaf {
summary,
items,
item_summaries,
}))
- })
- .collect::<Vec<_>>();
+ });
+ }
+
+ let mut nodes = futures::stream::iter(futures)
+ .map(|future| spawn.background_spawn(future))
+ .buffered(4)
+ .collect::<Vec<_>>()
+ .await;
let mut height = 0;
while nodes.len() > 1 {
height += 1;
- nodes = nodes
- .into_par_iter()
+ let current_nodes = mem::take(&mut nodes);
+ nodes = stream::iter(current_nodes)
.chunks(2 * TREE_BASE)
- .map(|child_nodes| {
- let child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }> =
- child_nodes.into_iter().collect();
- let child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> = child_trees
- .iter()
- .map(|child_tree| child_tree.summary().clone())
- .collect();
- let mut summary = child_summaries[0].clone();
- for child_summary in &child_summaries[1..] {
- <T::Summary as Summary>::add_summary(&mut summary, child_summary, cx);
- }
- SumTree(Arc::new(Node::Internal {
- height,
- summary,
- child_summaries,
- child_trees,
- }))
+ .map(|chunk| {
+ spawn.background_spawn(async move {
+ let child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }> =
+ chunk.into_iter().collect();
+ let child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> = child_trees
+ .iter()
+ .map(|child_tree| child_tree.summary().clone())
+ .collect();
+ let mut summary = child_summaries[0].clone();
+ for child_summary in &child_summaries[1..] {
+ <T::Summary as Summary>::add_summary(&mut summary, child_summary, ());
+ }
+ SumTree(Arc::new(Node::Internal {
+ height,
+ summary,
+ child_summaries,
+ child_trees,
+ }))
+ })
})
- .collect::<Vec<_>>();
+ .buffered(4)
+ .collect::<Vec<_>>()
+ .await;
}
if nodes.is_empty() {
- Self::new(cx)
+ Self::new(())
} else {
debug_assert_eq!(nodes.len(), 1);
nodes.pop().unwrap()
@@ -597,15 +619,15 @@ impl<T: Item> SumTree<T> {
self.append(Self::from_iter(iter, cx), cx);
}
- pub fn par_extend<I, Iter>(&mut self, iter: I, cx: <T::Summary as Summary>::Context<'_>)
+ pub async fn async_extend<S, I>(&mut self, iter: I, spawn: S)
where
- I: IntoParallelIterator<Iter = Iter>,
- Iter: IndexedParallelIterator<Item = T>,
- T: Send + Sync,
- T::Summary: Send + Sync,
- for<'a> <T::Summary as Summary>::Context<'a>: Sync,
+ S: BackgroundSpawn,
+ I: IntoIterator<Item = T> + 'static,
+ T: 'static + Send + Sync,
+ for<'b> T::Summary: Summary<Context<'b> = ()> + Send + Sync,
{
- self.append(Self::from_par_iter(iter, cx), cx);
+ let other = Self::from_iter_async(iter, spawn);
+ self.append(other.await, ());
}
pub fn push(&mut self, item: T, cx: <T::Summary as Summary>::Context<'_>) {
@@ -1070,6 +1092,23 @@ mod tests {
#[test]
fn test_random() {
+ struct NoSpawn;
+ impl BackgroundSpawn for NoSpawn {
+ type Task<R>
+ = std::pin::Pin<Box<dyn Future<Output = R> + Sync + Send>>
+ where
+ R: Send + Sync;
+ fn background_spawn<R>(
+ &self,
+ future: impl Future<Output = R> + Send + Sync + 'static,
+ ) -> Self::Task<R>
+ where
+ R: Send + Sync + 'static,
+ {
+ Box::pin(future)
+ }
+ }
+
let mut starting_seed = 0;
if let Ok(value) = std::env::var("SEED") {
starting_seed = value.parse().expect("invalid SEED variable");
@@ -1095,7 +1134,7 @@ mod tests {
.sample_iter(StandardUniform)
.take(count)
.collect::<Vec<_>>();
- tree.par_extend(items, ());
+ pollster::block_on(tree.async_extend(items, NoSpawn));
}
for _ in 0..num_operations {
@@ -1117,7 +1156,7 @@ mod tests {
if rng.random() {
new_tree.extend(new_items, ());
} else {
- new_tree.par_extend(new_items, ());
+ pollster::block_on(new_tree.async_extend(new_items, NoSpawn));
}
cursor.seek(&Count(splice_end), Bias::Right);
new_tree.append(cursor.slice(&tree_end, Bias::Right), ());
@@ -28,6 +28,7 @@ rope.workspace = true
smallvec.workspace = true
sum_tree.workspace = true
util.workspace = true
+gpui.workspace = true
[dev-dependencies]
collections = { workspace = true, features = ["test-support"] }
@@ -14,24 +14,29 @@ fn init_logger() {
zlog::init_test();
}
-#[test]
-fn test_edit() {
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "abc");
+#[gpui::test]
+fn test_edit(cx: &mut gpui::TestAppContext) {
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "abc",
+ cx.background_executor(),
+ );
assert_eq!(buffer.text(), "abc");
- buffer.edit([(3..3, "def")]);
+ buffer.edit([(3..3, "def")], cx.background_executor());
assert_eq!(buffer.text(), "abcdef");
- buffer.edit([(0..0, "ghi")]);
+ buffer.edit([(0..0, "ghi")], cx.background_executor());
assert_eq!(buffer.text(), "ghiabcdef");
- buffer.edit([(5..5, "jkl")]);
+ buffer.edit([(5..5, "jkl")], cx.background_executor());
assert_eq!(buffer.text(), "ghiabjklcdef");
- buffer.edit([(6..7, "")]);
+ buffer.edit([(6..7, "")], cx.background_executor());
assert_eq!(buffer.text(), "ghiabjlcdef");
- buffer.edit([(4..9, "mno")]);
+ buffer.edit([(4..9, "mno")], cx.background_executor());
assert_eq!(buffer.text(), "ghiamnoef");
}
#[gpui::test(iterations = 100)]
-fn test_random_edits(mut rng: StdRng) {
+fn test_random_edits(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
@@ -44,6 +49,7 @@ fn test_random_edits(mut rng: StdRng) {
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
reference_string.clone(),
+ cx.background_executor(),
);
LineEnding::normalize(&mut reference_string);
@@ -56,7 +62,7 @@ fn test_random_edits(mut rng: StdRng) {
);
for _i in 0..operations {
- let (edits, _) = buffer.randomly_edit(&mut rng, 5);
+ let (edits, _) = buffer.randomly_edit(&mut rng, 5, cx.background_executor());
for (old_range, new_text) in edits.iter().rev() {
reference_string.replace_range(old_range.clone(), new_text);
}
@@ -106,7 +112,11 @@ fn test_random_edits(mut rng: StdRng) {
let mut text = old_buffer.visible_text.clone();
for edit in edits {
let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
- text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text);
+ text.replace(
+ edit.new.start..edit.new.start + edit.old.len(),
+ &new_text,
+ cx.background_executor(),
+ );
}
assert_eq!(text.to_string(), buffer.text());
@@ -161,14 +171,18 @@ fn test_random_edits(mut rng: StdRng) {
let mut text = old_buffer.visible_text.clone();
for edit in subscription_edits.into_inner() {
let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
- text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text);
+ text.replace(
+ edit.new.start..edit.new.start + edit.old.len(),
+ &new_text,
+ cx.background_executor(),
+ );
}
assert_eq!(text.to_string(), buffer.text());
}
}
-#[test]
-fn test_line_endings() {
+#[gpui::test]
+fn test_line_endings(cx: &mut gpui::TestAppContext) {
assert_eq!(LineEnding::detect(&"πβ
\n".repeat(1000)), LineEnding::Unix);
assert_eq!(LineEnding::detect(&"abcd\n".repeat(1000)), LineEnding::Unix);
assert_eq!(
@@ -184,25 +198,34 @@ fn test_line_endings() {
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"one\r\ntwo\rthree",
+ cx.background_executor(),
);
assert_eq!(buffer.text(), "one\ntwo\nthree");
assert_eq!(buffer.line_ending(), LineEnding::Windows);
buffer.check_invariants();
- buffer.edit([(buffer.len()..buffer.len(), "\r\nfour")]);
- buffer.edit([(0..0, "zero\r\n")]);
+ buffer.edit(
+ [(buffer.len()..buffer.len(), "\r\nfour")],
+ cx.background_executor(),
+ );
+ buffer.edit([(0..0, "zero\r\n")], cx.background_executor());
assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
assert_eq!(buffer.line_ending(), LineEnding::Windows);
buffer.check_invariants();
}
-#[test]
-fn test_line_len() {
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
- buffer.edit([(0..0, "abcd\nefg\nhij")]);
- buffer.edit([(12..12, "kl\nmno")]);
- buffer.edit([(18..18, "\npqrs\n")]);
- buffer.edit([(18..21, "\nPQ")]);
+#[gpui::test]
+fn test_line_len(cx: &mut gpui::TestAppContext) {
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "",
+ cx.background_executor(),
+ );
+ buffer.edit([(0..0, "abcd\nefg\nhij")], cx.background_executor());
+ buffer.edit([(12..12, "kl\nmno")], cx.background_executor());
+ buffer.edit([(18..18, "\npqrs\n")], cx.background_executor());
+ buffer.edit([(18..21, "\nPQ")], cx.background_executor());
assert_eq!(buffer.line_len(0), 4);
assert_eq!(buffer.line_len(1), 3);
@@ -212,10 +235,15 @@ fn test_line_len() {
assert_eq!(buffer.line_len(5), 0);
}
-#[test]
-fn test_common_prefix_at_position() {
+#[gpui::test]
+fn test_common_prefix_at_position(cx: &mut gpui::TestAppContext) {
let text = "a = str; b = δα";
- let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ text,
+ cx.background_executor(),
+ );
let offset1 = offset_after(text, "str");
let offset2 = offset_after(text, "δα");
@@ -261,12 +289,13 @@ fn test_common_prefix_at_position() {
}
}
-#[test]
-fn test_text_summary_for_range() {
+#[gpui::test]
+fn test_text_summary_for_range(cx: &mut gpui::TestAppContext) {
let buffer = Buffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
"ab\nefg\nhklm\nnopqrs\ntuvwxyz",
+ cx.background_executor(),
);
assert_eq!(
buffer.text_summary_for_range::<TextSummary, _>(0..2),
@@ -354,13 +383,18 @@ fn test_text_summary_for_range() {
);
}
-#[test]
-fn test_chars_at() {
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
- buffer.edit([(0..0, "abcd\nefgh\nij")]);
- buffer.edit([(12..12, "kl\nmno")]);
- buffer.edit([(18..18, "\npqrs")]);
- buffer.edit([(18..21, "\nPQ")]);
+#[gpui::test]
+fn test_chars_at(cx: &mut gpui::TestAppContext) {
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "",
+ cx.background_executor(),
+ );
+ buffer.edit([(0..0, "abcd\nefgh\nij")], cx.background_executor());
+ buffer.edit([(12..12, "kl\nmno")], cx.background_executor());
+ buffer.edit([(18..18, "\npqrs")], cx.background_executor());
+ buffer.edit([(18..21, "\nPQ")], cx.background_executor());
let chars = buffer.chars_at(Point::new(0, 0));
assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
@@ -378,43 +412,53 @@ fn test_chars_at() {
assert_eq!(chars.collect::<String>(), "PQrs");
// Regression test:
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
- buffer.edit([(0..0, "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n")]);
- buffer.edit([(60..60, "\n")]);
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "",
+ cx.background_executor(),
+ );
+ buffer.edit([(0..0, "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n")], cx.background_executor());
+ buffer.edit([(60..60, "\n")], cx.background_executor());
let chars = buffer.chars_at(Point::new(6, 0));
assert_eq!(chars.collect::<String>(), " \"xray_wasm\",\n]\n");
}
-#[test]
-fn test_anchors() {
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
- buffer.edit([(0..0, "abc")]);
+#[gpui::test]
+fn test_anchors(cx: &mut gpui::TestAppContext) {
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "",
+ cx.background_executor(),
+ );
+ buffer.edit([(0..0, "abc")], cx.background_executor());
let left_anchor = buffer.anchor_before(2);
let right_anchor = buffer.anchor_after(2);
- buffer.edit([(1..1, "def\n")]);
+ buffer.edit([(1..1, "def\n")], cx.background_executor());
assert_eq!(buffer.text(), "adef\nbc");
assert_eq!(left_anchor.to_offset(&buffer), 6);
assert_eq!(right_anchor.to_offset(&buffer), 6);
assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
- buffer.edit([(2..3, "")]);
+ buffer.edit([(2..3, "")], cx.background_executor());
assert_eq!(buffer.text(), "adf\nbc");
assert_eq!(left_anchor.to_offset(&buffer), 5);
assert_eq!(right_anchor.to_offset(&buffer), 5);
assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
- buffer.edit([(5..5, "ghi\n")]);
+ buffer.edit([(5..5, "ghi\n")], cx.background_executor());
assert_eq!(buffer.text(), "adf\nbghi\nc");
assert_eq!(left_anchor.to_offset(&buffer), 5);
assert_eq!(right_anchor.to_offset(&buffer), 9);
assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
- buffer.edit([(7..9, "")]);
+ buffer.edit([(7..9, "")], cx.background_executor());
assert_eq!(buffer.text(), "adf\nbghc");
assert_eq!(left_anchor.to_offset(&buffer), 5);
assert_eq!(right_anchor.to_offset(&buffer), 7);
@@ -504,13 +548,18 @@ fn test_anchors() {
);
}
-#[test]
-fn test_anchors_at_start_and_end() {
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
+#[gpui::test]
+fn test_anchors_at_start_and_end(cx: &mut gpui::TestAppContext) {
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "",
+ cx.background_executor(),
+ );
let before_start_anchor = buffer.anchor_before(0);
let after_end_anchor = buffer.anchor_after(0);
- buffer.edit([(0..0, "abc")]);
+ buffer.edit([(0..0, "abc")], cx.background_executor());
assert_eq!(buffer.text(), "abc");
assert_eq!(before_start_anchor.to_offset(&buffer), 0);
assert_eq!(after_end_anchor.to_offset(&buffer), 3);
@@ -518,8 +567,8 @@ fn test_anchors_at_start_and_end() {
let after_start_anchor = buffer.anchor_after(0);
let before_end_anchor = buffer.anchor_before(3);
- buffer.edit([(3..3, "def")]);
- buffer.edit([(0..0, "ghi")]);
+ buffer.edit([(3..3, "def")], cx.background_executor());
+ buffer.edit([(0..0, "ghi")], cx.background_executor());
assert_eq!(buffer.text(), "ghiabcdef");
assert_eq!(before_start_anchor.to_offset(&buffer), 0);
assert_eq!(after_start_anchor.to_offset(&buffer), 3);
@@ -527,15 +576,20 @@ fn test_anchors_at_start_and_end() {
assert_eq!(after_end_anchor.to_offset(&buffer), 9);
}
-#[test]
-fn test_undo_redo() {
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "1234");
+#[gpui::test]
+fn test_undo_redo(cx: &mut gpui::TestAppContext) {
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "1234",
+ cx.background_executor(),
+ );
// Set group interval to zero so as to not group edits in the undo stack.
buffer.set_group_interval(Duration::from_secs(0));
- buffer.edit([(1..1, "abx")]);
- buffer.edit([(3..4, "yzef")]);
- buffer.edit([(3..5, "cd")]);
+ buffer.edit([(1..1, "abx")], cx.background_executor());
+ buffer.edit([(3..4, "yzef")], cx.background_executor());
+ buffer.edit([(3..5, "cd")], cx.background_executor());
assert_eq!(buffer.text(), "1abcdef234");
let entries = buffer.history.undo_stack.clone();
@@ -563,26 +617,31 @@ fn test_undo_redo() {
assert_eq!(buffer.text(), "1234");
}
-#[test]
-fn test_history() {
+#[gpui::test]
+fn test_history(cx: &mut gpui::TestAppContext) {
let mut now = Instant::now();
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "123456");
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "123456",
+ cx.background_executor(),
+ );
buffer.set_group_interval(Duration::from_millis(300));
let transaction_1 = buffer.start_transaction_at(now).unwrap();
- buffer.edit([(2..4, "cd")]);
+ buffer.edit([(2..4, "cd")], cx.background_executor());
buffer.end_transaction_at(now);
assert_eq!(buffer.text(), "12cd56");
buffer.start_transaction_at(now);
- buffer.edit([(4..5, "e")]);
+ buffer.edit([(4..5, "e")], cx.background_executor());
buffer.end_transaction_at(now).unwrap();
assert_eq!(buffer.text(), "12cde6");
now += buffer.transaction_group_interval() + Duration::from_millis(1);
buffer.start_transaction_at(now);
- buffer.edit([(0..1, "a")]);
- buffer.edit([(1..1, "b")]);
+ buffer.edit([(0..1, "a")], cx.background_executor());
+ buffer.edit([(1..1, "b")], cx.background_executor());
buffer.end_transaction_at(now).unwrap();
assert_eq!(buffer.text(), "ab2cde6");
@@ -609,7 +668,7 @@ fn test_history() {
// Redo stack gets cleared after performing an edit.
buffer.start_transaction_at(now);
- buffer.edit([(0..0, "X")]);
+ buffer.edit([(0..0, "X")], cx.background_executor());
buffer.end_transaction_at(now);
assert_eq!(buffer.text(), "X12cde6");
buffer.redo();
@@ -630,26 +689,31 @@ fn test_history() {
assert_eq!(buffer.text(), "X12cde6");
}
-#[test]
-fn test_finalize_last_transaction() {
+#[gpui::test]
+fn test_finalize_last_transaction(cx: &mut gpui::TestAppContext) {
let now = Instant::now();
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "123456");
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "123456",
+ cx.background_executor(),
+ );
buffer.history.group_interval = Duration::from_millis(1);
buffer.start_transaction_at(now);
- buffer.edit([(2..4, "cd")]);
+ buffer.edit([(2..4, "cd")], cx.background_executor());
buffer.end_transaction_at(now);
assert_eq!(buffer.text(), "12cd56");
buffer.finalize_last_transaction();
buffer.start_transaction_at(now);
- buffer.edit([(4..5, "e")]);
+ buffer.edit([(4..5, "e")], cx.background_executor());
buffer.end_transaction_at(now).unwrap();
assert_eq!(buffer.text(), "12cde6");
buffer.start_transaction_at(now);
- buffer.edit([(0..1, "a")]);
- buffer.edit([(1..1, "b")]);
+ buffer.edit([(0..1, "a")], cx.background_executor());
+ buffer.edit([(1..1, "b")], cx.background_executor());
buffer.end_transaction_at(now).unwrap();
assert_eq!(buffer.text(), "ab2cde6");
@@ -666,14 +730,19 @@ fn test_finalize_last_transaction() {
assert_eq!(buffer.text(), "ab2cde6");
}
-#[test]
-fn test_edited_ranges_for_transaction() {
+#[gpui::test]
+fn test_edited_ranges_for_transaction(cx: &mut gpui::TestAppContext) {
let now = Instant::now();
- let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "1234567");
+ let mut buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ "1234567",
+ cx.background_executor(),
+ );
buffer.start_transaction_at(now);
- buffer.edit([(2..4, "cd")]);
- buffer.edit([(6..6, "efg")]);
+ buffer.edit([(2..4, "cd")], cx.background_executor());
+ buffer.edit([(6..6, "efg")], cx.background_executor());
buffer.end_transaction_at(now);
assert_eq!(buffer.text(), "12cd56efg7");
@@ -685,7 +754,7 @@ fn test_edited_ranges_for_transaction() {
[2..4, 6..9]
);
- buffer.edit([(5..5, "hijk")]);
+ buffer.edit([(5..5, "hijk")], cx.background_executor());
assert_eq!(buffer.text(), "12cd5hijk6efg7");
assert_eq!(
buffer
@@ -694,7 +763,7 @@ fn test_edited_ranges_for_transaction() {
[2..4, 10..13]
);
- buffer.edit([(4..4, "l")]);
+ buffer.edit([(4..4, "l")], cx.background_executor());
assert_eq!(buffer.text(), "12cdl5hijk6efg7");
assert_eq!(
buffer
@@ -704,27 +773,42 @@ fn test_edited_ranges_for_transaction() {
);
}
-#[test]
-fn test_concurrent_edits() {
+#[gpui::test]
+fn test_concurrent_edits(cx: &mut gpui::TestAppContext) {
let text = "abcdef";
- let mut buffer1 = Buffer::new(ReplicaId::new(1), BufferId::new(1).unwrap(), text);
- let mut buffer2 = Buffer::new(ReplicaId::new(2), BufferId::new(1).unwrap(), text);
- let mut buffer3 = Buffer::new(ReplicaId::new(3), BufferId::new(1).unwrap(), text);
+ let mut buffer1 = Buffer::new(
+ ReplicaId::new(1),
+ BufferId::new(1).unwrap(),
+ text,
+ cx.background_executor(),
+ );
+ let mut buffer2 = Buffer::new(
+ ReplicaId::new(2),
+ BufferId::new(1).unwrap(),
+ text,
+ cx.background_executor(),
+ );
+ let mut buffer3 = Buffer::new(
+ ReplicaId::new(3),
+ BufferId::new(1).unwrap(),
+ text,
+ cx.background_executor(),
+ );
- let buf1_op = buffer1.edit([(1..2, "12")]);
+ let buf1_op = buffer1.edit([(1..2, "12")], cx.background_executor());
assert_eq!(buffer1.text(), "a12cdef");
- let buf2_op = buffer2.edit([(3..4, "34")]);
+ let buf2_op = buffer2.edit([(3..4, "34")], cx.background_executor());
assert_eq!(buffer2.text(), "abc34ef");
- let buf3_op = buffer3.edit([(5..6, "56")]);
+ let buf3_op = buffer3.edit([(5..6, "56")], cx.background_executor());
assert_eq!(buffer3.text(), "abcde56");
- buffer1.apply_op(buf2_op.clone());
- buffer1.apply_op(buf3_op.clone());
- buffer2.apply_op(buf1_op.clone());
- buffer2.apply_op(buf3_op);
- buffer3.apply_op(buf1_op);
- buffer3.apply_op(buf2_op);
+ buffer1.apply_op(buf2_op.clone(), Some(cx.background_executor()));
+ buffer1.apply_op(buf3_op.clone(), Some(cx.background_executor()));
+ buffer2.apply_op(buf1_op.clone(), Some(cx.background_executor()));
+ buffer2.apply_op(buf3_op, Some(cx.background_executor()));
+ buffer3.apply_op(buf1_op, Some(cx.background_executor()));
+ buffer3.apply_op(buf2_op, Some(cx.background_executor()));
assert_eq!(buffer1.text(), "a12c34e56");
assert_eq!(buffer2.text(), "a12c34e56");
@@ -732,7 +816,7 @@ fn test_concurrent_edits() {
}
#[gpui::test(iterations = 100)]
-fn test_random_concurrent_edits(mut rng: StdRng) {
+fn test_random_concurrent_edits(mut rng: StdRng, cx: &mut gpui::TestAppContext) {
let peers = env::var("PEERS")
.map(|i| i.parse().expect("invalid `PEERS` variable"))
.unwrap_or(5);
@@ -753,6 +837,7 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
ReplicaId::new(i as u16),
BufferId::new(1).unwrap(),
base_text.clone(),
+ cx.background_executor(),
);
buffer.history.group_interval = Duration::from_millis(rng.random_range(0..=200));
buffers.push(buffer);
@@ -769,7 +854,9 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
let buffer = &mut buffers[replica_index];
match rng.random_range(0..=100) {
0..=50 if mutation_count != 0 => {
- let op = buffer.randomly_edit(&mut rng, 5).1;
+ let op = buffer
+ .randomly_edit(&mut rng, 5, cx.background_executor())
+ .1;
network.broadcast(buffer.replica_id, vec![op]);
log::info!("buffer {:?} text: {:?}", buffer.replica_id, buffer.text());
mutation_count -= 1;
@@ -787,7 +874,7 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
replica_id,
ops.len()
);
- buffer.apply_ops(ops);
+ buffer.apply_ops(ops, Some(cx.background_executor()));
}
}
_ => {}
@@ -15,6 +15,7 @@ use anyhow::{Context as _, Result};
use clock::Lamport;
pub use clock::ReplicaId;
use collections::{HashMap, HashSet};
+use gpui::BackgroundExecutor;
use locator::Locator;
use operation_queue::OperationQueue;
pub use patch::Patch;
@@ -709,11 +710,41 @@ impl FromIterator<char> for LineIndent {
}
impl Buffer {
- pub fn new(replica_id: ReplicaId, remote_id: BufferId, base_text: impl Into<String>) -> Buffer {
+ /// Create a new buffer from a string.
+ pub fn new(
+ replica_id: ReplicaId,
+ remote_id: BufferId,
+ base_text: impl Into<String>,
+ executor: &BackgroundExecutor,
+ ) -> Buffer {
let mut base_text = base_text.into();
let line_ending = LineEnding::detect(&base_text);
LineEnding::normalize(&mut base_text);
- Self::new_normalized(replica_id, remote_id, line_ending, Rope::from(&*base_text))
+ Self::new_normalized(
+ replica_id,
+ remote_id,
+ line_ending,
+ Rope::from_str(&base_text, executor),
+ )
+ }
+
+ /// Create a new buffer from a string.
+ ///
+ /// Unlike [`Buffer::new`], this does not construct the backing rope in parallel if it is large enough.
+ pub fn new_slow(
+ replica_id: ReplicaId,
+ remote_id: BufferId,
+ base_text: impl Into<String>,
+ ) -> Buffer {
+ let mut base_text = base_text.into();
+ let line_ending = LineEnding::detect(&base_text);
+ LineEnding::normalize(&mut base_text);
+ Self::new_normalized(
+ replica_id,
+ remote_id,
+ line_ending,
+ Rope::from_str_small(&base_text),
+ )
}
pub fn new_normalized(
@@ -808,7 +839,7 @@ impl Buffer {
self.history.group_interval
}
- pub fn edit<R, I, S, T>(&mut self, edits: R) -> Operation
+ pub fn edit<R, I, S, T>(&mut self, edits: R, cx: &BackgroundExecutor) -> Operation
where
R: IntoIterator<IntoIter = I>,
I: ExactSizeIterator<Item = (Range<S>, T)>,
@@ -821,7 +852,7 @@ impl Buffer {
self.start_transaction();
let timestamp = self.lamport_clock.tick();
- let operation = Operation::Edit(self.apply_local_edit(edits, timestamp));
+ let operation = Operation::Edit(self.apply_local_edit(edits, timestamp, cx));
self.history.push(operation.clone());
self.history.push_undo(operation.timestamp());
@@ -834,6 +865,7 @@ impl Buffer {
&mut self,
edits: impl ExactSizeIterator<Item = (Range<S>, T)>,
timestamp: clock::Lamport,
+ executor: &BackgroundExecutor,
) -> EditOperation {
let mut edits_patch = Patch::default();
let mut edit_op = EditOperation {
@@ -922,7 +954,7 @@ impl Buffer {
});
insertion_slices.push(InsertionSlice::from_fragment(timestamp, &fragment));
new_insertions.push(InsertionFragment::insert_new(&fragment));
- new_ropes.push_str(new_text.as_ref());
+ new_ropes.push_str(new_text.as_ref(), executor);
new_fragments.push(fragment, &None);
insertion_offset += new_text.len();
}
@@ -1001,22 +1033,26 @@ impl Buffer {
self.snapshot.line_ending = line_ending;
}
- pub fn apply_ops<I: IntoIterator<Item = Operation>>(&mut self, ops: I) {
+ pub fn apply_ops<I: IntoIterator<Item = Operation>>(
+ &mut self,
+ ops: I,
+ executor: Option<&BackgroundExecutor>,
+ ) {
let mut deferred_ops = Vec::new();
for op in ops {
self.history.push(op.clone());
if self.can_apply_op(&op) {
- self.apply_op(op);
+ self.apply_op(op, executor);
} else {
self.deferred_replicas.insert(op.replica_id());
deferred_ops.push(op);
}
}
self.deferred_ops.insert(deferred_ops);
- self.flush_deferred_ops();
+ self.flush_deferred_ops(executor);
}
- fn apply_op(&mut self, op: Operation) {
+ fn apply_op(&mut self, op: Operation, executor: Option<&BackgroundExecutor>) {
match op {
Operation::Edit(edit) => {
if !self.version.observed(edit.timestamp) {
@@ -1025,6 +1061,7 @@ impl Buffer {
&edit.ranges,
&edit.new_text,
edit.timestamp,
+ executor,
);
self.snapshot.version.observe(edit.timestamp);
self.lamport_clock.observe(edit.timestamp);
@@ -1055,6 +1092,7 @@ impl Buffer {
ranges: &[Range<FullOffset>],
new_text: &[Arc<str>],
timestamp: clock::Lamport,
+ executor: Option<&BackgroundExecutor>,
) {
if ranges.is_empty() {
return;
@@ -1170,7 +1208,10 @@ impl Buffer {
});
insertion_slices.push(InsertionSlice::from_fragment(timestamp, &fragment));
new_insertions.push(InsertionFragment::insert_new(&fragment));
- new_ropes.push_str(new_text);
+ match executor {
+ Some(executor) => new_ropes.push_str(new_text, executor),
+ None => new_ropes.push_str_small(new_text),
+ }
new_fragments.push(fragment, &None);
insertion_offset += new_text.len();
}
@@ -1348,12 +1389,12 @@ impl Buffer {
self.subscriptions.publish_mut(&edits);
}
- fn flush_deferred_ops(&mut self) {
+ fn flush_deferred_ops(&mut self, executor: Option<&BackgroundExecutor>) {
self.deferred_replicas.clear();
let mut deferred_ops = Vec::new();
for op in self.deferred_ops.drain().iter().cloned() {
if self.can_apply_op(&op) {
- self.apply_op(op);
+ self.apply_op(op, executor);
} else {
self.deferred_replicas.insert(op.replica_id());
deferred_ops.push(op);
@@ -1711,9 +1752,9 @@ impl Buffer {
#[cfg(any(test, feature = "test-support"))]
impl Buffer {
#[track_caller]
- pub fn edit_via_marked_text(&mut self, marked_string: &str) {
+ pub fn edit_via_marked_text(&mut self, marked_string: &str, cx: &BackgroundExecutor) {
let edits = self.edits_for_marked_text(marked_string);
- self.edit(edits);
+ self.edit(edits, cx);
}
#[track_caller]
@@ -1850,6 +1891,7 @@ impl Buffer {
&mut self,
rng: &mut T,
edit_count: usize,
+ executor: &BackgroundExecutor,
) -> (Vec<(Range<usize>, Arc<str>)>, Operation)
where
T: rand::Rng,
@@ -1857,7 +1899,7 @@ impl Buffer {
let mut edits = self.get_random_edits(rng, edit_count);
log::info!("mutating buffer {:?} with {:?}", self.replica_id, edits);
- let op = self.edit(edits.iter().cloned());
+ let op = self.edit(edits.iter().cloned(), executor);
if let Operation::Edit(edit) = &op {
assert_eq!(edits.len(), edit.new_text.len());
for (edit, new_text) in edits.iter_mut().zip(&edit.new_text) {
@@ -2692,8 +2734,12 @@ impl<'a> RopeBuilder<'a> {
}
}
- fn push_str(&mut self, text: &str) {
- self.new_visible.push(text);
+ fn push_str(&mut self, text: &str, cx: &BackgroundExecutor) {
+ self.new_visible.push(text, cx);
+ }
+
+ fn push_str_small(&mut self, text: &str) {
+ self.new_visible.push_small(text);
}
fn finish(mut self) -> (Rope, Rope) {
@@ -3096,6 +3096,7 @@ mod test {
use indoc::indoc;
use language::Point;
use multi_buffer::MultiBufferRow;
+ use text::Rope;
#[gpui::test]
async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) {
@@ -3822,7 +3823,7 @@ mod test {
cx.update_editor(|editor, _window, cx| {
let range = editor.selections.newest_anchor().range();
let inlay_text = " field: int,\n field2: string\n field3: float";
- let inlay = Inlay::edit_prediction(1, range.start, inlay_text);
+ let inlay = Inlay::edit_prediction(1, range.start, Rope::from_str_small(inlay_text));
editor.splice_inlays(&[], vec![inlay], cx);
});
@@ -3854,7 +3855,7 @@ mod test {
let end_of_line =
snapshot.anchor_after(Point::new(0, snapshot.line_len(MultiBufferRow(0))));
let inlay_text = " hint";
- let inlay = Inlay::edit_prediction(1, end_of_line, inlay_text);
+ let inlay = Inlay::edit_prediction(1, end_of_line, Rope::from_str_small(inlay_text));
editor.splice_inlays(&[], vec![inlay], cx);
});
cx.simulate_keystrokes("$");
@@ -3893,7 +3894,7 @@ mod test {
// The empty line is at line 3 (0-indexed)
let line_start = snapshot.anchor_after(Point::new(3, 0));
let inlay_text = ": Vec<u32>";
- let inlay = Inlay::edit_prediction(1, line_start, inlay_text);
+ let inlay = Inlay::edit_prediction(1, line_start, Rope::from_str_small(inlay_text));
editor.splice_inlays(&[], vec![inlay], cx);
});
@@ -3937,7 +3938,8 @@ mod test {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let empty_line_start = snapshot.anchor_after(Point::new(2, 0));
let inlay_text = ": i32";
- let inlay = Inlay::edit_prediction(2, empty_line_start, inlay_text);
+ let inlay =
+ Inlay::edit_prediction(2, empty_line_start, Rope::from_str_small(inlay_text));
editor.splice_inlays(&[], vec![inlay], cx);
});
@@ -7580,13 +7580,13 @@ pub fn create_and_open_local_file(
path: &'static Path,
window: &mut Window,
cx: &mut Context<Workspace>,
- default_content: impl 'static + Send + FnOnce() -> Rope,
+ default_content: impl 'static + Send + FnOnce(&mut AsyncApp) -> Rope,
) -> Task<Result<Box<dyn ItemHandle>>> {
cx.spawn_in(window, async move |workspace, cx| {
let fs = workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
if !fs.is_file(path).await {
fs.create_file(path, Default::default()).await?;
- fs.save(path, &default_content(), Default::default())
+ fs.save(path, &default_content(cx), Default::default())
.await?;
}
@@ -20,6 +20,7 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
+use text::Rope;
use util::{
ResultExt, path,
rel_path::{RelPath, rel_path},
@@ -646,9 +647,13 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
// Update the gitignore so that node_modules is no longer ignored,
// but a subdirectory is ignored
- fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
- .await
- .unwrap();
+ fs.save(
+ "/root/.gitignore".as_ref(),
+ &Rope::from_str("e", cx.background_executor()),
+ Default::default(),
+ )
+ .await
+ .unwrap();
cx.executor().run_until_parked();
// All of the directories that are no longer ignored are now loaded.
@@ -716,7 +721,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
.update(cx, |tree, cx| {
tree.write_file(
rel_path("tracked-dir/file.txt").into(),
- "hello".into(),
+ Rope::from_str("hello", cx.background_executor()),
Default::default(),
cx,
)
@@ -727,7 +732,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
.update(cx, |tree, cx| {
tree.write_file(
rel_path("ignored-dir/file.txt").into(),
- "world".into(),
+ Rope::from_str("world", cx.background_executor()),
Default::default(),
cx,
)
@@ -1465,7 +1470,7 @@ async fn test_random_worktree_operations_during_initial_scan(
let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
fs.as_fake().insert_tree(root_dir, json!({})).await;
for _ in 0..initial_entries {
- randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
+ randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng, cx.background_executor()).await;
}
log::info!("generated initial tree");
@@ -1555,7 +1560,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
let fs = FakeFs::new(cx.background_executor.clone()) as Arc<dyn Fs>;
fs.as_fake().insert_tree(root_dir, json!({})).await;
for _ in 0..initial_entries {
- randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
+ randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng, cx.background_executor()).await;
}
log::info!("generated initial tree");
@@ -1598,7 +1603,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
.await
.log_err();
} else {
- randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng).await;
+ randomly_mutate_fs(&fs, root_dir, 1.0, &mut rng, cx.background_executor()).await;
}
let buffered_event_count = fs.as_fake().buffered_event_count();
@@ -1607,7 +1612,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
log::info!("flushing {} events", len);
fs.as_fake().flush_events(len);
} else {
- randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng).await;
+ randomly_mutate_fs(&fs, root_dir, 0.6, &mut rng, cx.background_executor()).await;
mutations_len -= 1;
}
@@ -1759,8 +1764,12 @@ fn randomly_mutate_worktree(
})
} else {
log::info!("overwriting file {:?} ({})", &entry.path, entry.id.0);
- let task =
- worktree.write_file(entry.path.clone(), "".into(), Default::default(), cx);
+ let task = worktree.write_file(
+ entry.path.clone(),
+ Rope::default(),
+ Default::default(),
+ cx,
+ );
cx.background_spawn(async move {
task.await?;
Ok(())
@@ -1775,6 +1784,7 @@ async fn randomly_mutate_fs(
root_path: &Path,
insertion_probability: f64,
rng: &mut impl Rng,
+ executor: &BackgroundExecutor,
) {
log::info!("mutating fs");
let mut files = Vec::new();
@@ -1849,7 +1859,7 @@ async fn randomly_mutate_fs(
);
fs.save(
&ignore_path,
- &ignore_contents.as_str().into(),
+ &Rope::from_str(ignore_contents.as_str(), executor),
Default::default(),
)
.await
@@ -28,10 +28,10 @@ use git_ui::commit_view::CommitViewToolbar;
use git_ui::git_panel::GitPanel;
use git_ui::project_diff::ProjectDiffToolbar;
use gpui::{
- Action, App, AppContext as _, Context, DismissEvent, Element, Entity, Focusable, KeyBinding,
- ParentElement, PathPromptOptions, PromptLevel, ReadGlobal, SharedString, Styled, Task,
- TitlebarOptions, UpdateGlobal, Window, WindowKind, WindowOptions, actions, image_cache, point,
- px, retain_all,
+ Action, App, AppContext as _, AsyncApp, Context, DismissEvent, Element, Entity, Focusable,
+ KeyBinding, ParentElement, PathPromptOptions, PromptLevel, ReadGlobal, SharedString, Styled,
+ Task, TitlebarOptions, UpdateGlobal, Window, WindowKind, WindowOptions, actions, image_cache,
+ point, px, retain_all,
};
use image_viewer::ImageInfo;
use language::Capability;
@@ -201,7 +201,12 @@ pub fn init(cx: &mut App) {
with_active_or_new_workspace(cx, |_, window, cx| {
open_settings_file(
paths::keymap_file(),
- || settings::initial_keymap_content().as_ref().into(),
+ |cx| {
+ Rope::from_str(
+ settings::initial_keymap_content().as_ref(),
+ cx.background_executor(),
+ )
+ },
window,
cx,
);
@@ -211,7 +216,12 @@ pub fn init(cx: &mut App) {
with_active_or_new_workspace(cx, |_, window, cx| {
open_settings_file(
paths::settings_file(),
- || settings::initial_user_settings_content().as_ref().into(),
+ |cx| {
+ Rope::from_str(
+ settings::initial_user_settings_content().as_ref(),
+ cx.background_executor(),
+ )
+ },
window,
cx,
);
@@ -226,7 +236,12 @@ pub fn init(cx: &mut App) {
with_active_or_new_workspace(cx, |_, window, cx| {
open_settings_file(
paths::tasks_file(),
- || settings::initial_tasks_content().as_ref().into(),
+ |cx| {
+ Rope::from_str(
+ settings::initial_tasks_content().as_ref(),
+ cx.background_executor(),
+ )
+ },
window,
cx,
);
@@ -236,7 +251,12 @@ pub fn init(cx: &mut App) {
with_active_or_new_workspace(cx, |_, window, cx| {
open_settings_file(
paths::debug_scenarios_file(),
- || settings::initial_debug_tasks_content().as_ref().into(),
+ |cx| {
+ Rope::from_str(
+ settings::initial_debug_tasks_content().as_ref(),
+ cx.background_executor(),
+ )
+ },
window,
cx,
);
@@ -1939,7 +1959,7 @@ fn open_bundled_file(
fn open_settings_file(
abs_path: &'static Path,
- default_content: impl FnOnce() -> Rope + Send + 'static,
+ default_content: impl FnOnce(&mut AsyncApp) -> Rope + Send + 'static,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
@@ -4355,7 +4375,7 @@ mod tests {
.fs
.save(
"/settings.json".as_ref(),
- &r#"{"base_keymap": "Atom"}"#.into(),
+ &Rope::from_str_small(r#"{"base_keymap": "Atom"}"#),
Default::default(),
)
.await
@@ -4365,7 +4385,7 @@ mod tests {
.fs
.save(
"/keymap.json".as_ref(),
- &r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#.into(),
+ &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#),
Default::default(),
)
.await
@@ -4413,7 +4433,7 @@ mod tests {
.fs
.save(
"/keymap.json".as_ref(),
- &r#"[{"bindings": {"backspace": "test_only::ActionB"}}]"#.into(),
+ &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionB"}}]"#),
Default::default(),
)
.await
@@ -4433,7 +4453,7 @@ mod tests {
.fs
.save(
"/settings.json".as_ref(),
- &r#"{"base_keymap": "JetBrains"}"#.into(),
+ &Rope::from_str_small(r#"{"base_keymap": "JetBrains"}"#),
Default::default(),
)
.await
@@ -4473,7 +4493,7 @@ mod tests {
.fs
.save(
"/settings.json".as_ref(),
- &r#"{"base_keymap": "Atom"}"#.into(),
+ &Rope::from_str_small(r#"{"base_keymap": "Atom"}"#),
Default::default(),
)
.await
@@ -4482,7 +4502,7 @@ mod tests {
.fs
.save(
"/keymap.json".as_ref(),
- &r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#.into(),
+ &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#),
Default::default(),
)
.await
@@ -4525,7 +4545,7 @@ mod tests {
.fs
.save(
"/keymap.json".as_ref(),
- &r#"[{"bindings": {"backspace": null}}]"#.into(),
+ &Rope::from_str_small(r#"[{"bindings": {"backspace": null}}]"#),
Default::default(),
)
.await
@@ -4545,7 +4565,7 @@ mod tests {
.fs
.save(
"/settings.json".as_ref(),
- &r#"{"base_keymap": "JetBrains"}"#.into(),
+ &Rope::from_str_small(r#"{"base_keymap": "JetBrains"}"#),
Default::default(),
)
.await
@@ -861,7 +861,7 @@ mod tests {
.fs
.save(
Path::new(file1_path),
- &Rope::from("content1"),
+ &Rope::from_str("content1", cx.background_executor()),
LineEnding::Unix,
)
.await
@@ -875,7 +875,7 @@ mod tests {
.fs
.save(
Path::new(file2_path),
- &Rope::from("content2"),
+ &Rope::from_str("content2", cx.background_executor()),
LineEnding::Unix,
)
.await
@@ -1836,12 +1836,13 @@ mod tests {
let fs = project::FakeFs::new(cx.executor());
let project = Project::test(fs.clone(), [], cx).await;
- let buffer = cx.new(|_cx| {
+ let buffer = cx.new(|cx| {
Buffer::remote(
language::BufferId::new(1).unwrap(),
ReplicaId::new(1),
language::Capability::ReadWrite,
"fn main() {\n println!(\"Hello\");\n}",
+ cx.background_executor(),
)
});