sum_tree: Replace rayon with futures (#41586)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored by: Kate <kate@zed.dev>

Change summary

Cargo.lock                                                    |  14 
crates/acp_thread/src/diff.rs                                 |   4 
crates/action_log/src/action_log.rs                           |  18 
crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs        |  69 
crates/agent/src/tools/edit_file_tool.rs                      |   5 
crates/agent_ui/src/agent_configuration.rs                    |   8 
crates/agent_ui/src/buffer_codegen.rs                         |   7 
crates/assistant_text_thread/src/text_thread.rs               |   3 
crates/buffer_diff/src/buffer_diff.rs                         |  58 
crates/channel/src/channel_buffer.rs                          |   1 
crates/collab/src/db/queries/buffers.rs                       |   4 
crates/collab/src/db/tests/buffer_tests.rs                    |  56 
crates/collab/src/tests/integration_tests.rs                  |   4 
crates/collab/src/tests/random_project_collaboration_tests.rs |   7 
crates/diagnostics/src/diagnostics_tests.rs                   |   4 
crates/edit_prediction_button/src/edit_prediction_button.rs   |   9 
crates/editor/src/display_map.rs                              |   3 
crates/editor/src/display_map/inlay_map.rs                    |  36 
crates/editor/src/display_map/tab_map.rs                      |   2 
crates/editor/src/display_map/wrap_map.rs                     |   9 
crates/editor/src/editor.rs                                   |   2 
crates/editor/src/git/blame.rs                                |   5 
crates/editor/src/inlays.rs                                   |  18 
crates/editor/src/movement.rs                                 |   9 
crates/editor/src/signature_help.rs                           |   2 
crates/extension_host/src/extension_host.rs                   |  15 
crates/git_ui/src/commit_view.rs                              |   9 
crates/git_ui/src/file_diff_view.rs                           |  11 
crates/gpui/src/app/async_context.rs                          |  13 
crates/gpui/src/app/test_context.rs                           |   5 
crates/gpui/src/executor.rs                                   |   2 
crates/keymap_editor/src/keymap_editor.rs                     |   4 
crates/language/src/buffer.rs                                 |  21 
crates/language/src/buffer_tests.rs                           |  39 
crates/language/src/syntax_map/syntax_map_tests.rs            |  45 
crates/language_extension/src/extension_lsp_adapter.rs        |   7 
crates/languages/src/c.rs                                     |  10 
crates/languages/src/go.rs                                    |  24 
crates/languages/src/python.rs                                |   7 
crates/languages/src/rust.rs                                  |  15 
crates/markdown/src/markdown.rs                               |   4 
crates/markdown_preview/src/markdown_parser.rs                |   2 
crates/multi_buffer/src/multi_buffer_tests.rs                 |  33 
crates/project/src/buffer_store.rs                            |  18 
crates/project/src/git_store/conflict_set.rs                  |  44 
crates/project/src/prettier_store.rs                          | 139 +
crates/project/src/project.rs                                 |  13 
crates/project/src/project_tests.rs                           |  18 
crates/remote_server/src/remote_editing_tests.rs              |   8 
crates/rich_text/src/rich_text.rs                             |  17 
crates/rope/Cargo.toml                                        |   2 
crates/rope/benches/rope_benchmark.rs                         |  37 
crates/rope/src/rope.rs                                       | 276 +++-
crates/rules_library/src/rules_library.rs                     |  10 
crates/streaming_diff/Cargo.toml                              |   1 
crates/streaming_diff/src/streaming_diff.rs                   |  99 
crates/sum_tree/Cargo.toml                                    |   4 
crates/sum_tree/src/sum_tree.rs                               | 131 +
crates/text/Cargo.toml                                        |   1 
crates/text/src/tests.rs                                      | 281 +++-
crates/text/src/text.rs                                       |  80 +
crates/vim/src/motion.rs                                      |  10 
crates/workspace/src/workspace.rs                             |   4 
crates/worktree/src/worktree_tests.rs                         |  34 
crates/zed/src/zed.rs                                         |  54 
crates/zed/src/zed/open_listener.rs                           |   4 
crates/zeta/src/zeta.rs                                       |   3 
67 files changed, 1,271 insertions(+), 640 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -12709,6 +12709,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"
@@ -12757,7 +12763,7 @@ dependencies = [
  "log",
  "parking_lot",
  "pin-project",
- "pollster",
+ "pollster 0.2.5",
  "static_assertions",
  "thiserror 1.0.69",
 ]
@@ -14310,7 +14316,6 @@ dependencies = [
  "gpui",
  "log",
  "rand 0.9.2",
- "rayon",
  "sum_tree",
  "unicode-segmentation",
  "util",
@@ -16236,6 +16241,7 @@ checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
 name = "streaming_diff"
 version = "0.1.0"
 dependencies = [
+ "gpui",
  "ordered-float 2.10.1",
  "rand 0.9.2",
  "rope",
@@ -16354,9 +16360,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",
 ]
 

crates/acp_thread/src/diff.rs πŸ”—

@@ -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

crates/action_log/src/action_log.rs πŸ”—

@@ -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());

crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs πŸ”—

@@ -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);

crates/agent/src/tools/edit_file_tool.rs πŸ”—

@@ -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

crates/agent_ui/src/agent_configuration.rs πŸ”—

@@ -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?

crates/agent_ui/src/buffer_codegen.rs πŸ”—

@@ -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);
 

crates/assistant_text_thread/src/text_thread.rs πŸ”—

@@ -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

crates/buffer_diff/src/buffer_diff.rs πŸ”—

@@ -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()
         };

crates/channel/src/channel_buffer.rs πŸ”—

@@ -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))?;

crates/collab/src/db/queries/buffers.rs πŸ”—

@@ -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;

crates/collab/src/db/tests/buffer_tests.rs πŸ”—

@@ -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;
 

crates/collab/src/tests/integration_tests.rs πŸ”—

@@ -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

crates/collab/src/tests/random_project_collaboration_tests.rs πŸ”—

@@ -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();
                 }

crates/diagnostics/src/diagnostics_tests.rs πŸ”—

@@ -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));

crates/edit_prediction_button/src/edit_prediction_button.rs πŸ”—

@@ -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;
@@ -960,8 +960,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?

crates/editor/src/display_map.rs πŸ”—

@@ -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,
             );

crates/editor/src/display_map/inlay_map.rs πŸ”—

@@ -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]);

crates/editor/src/display_map/tab_map.rs πŸ”—

@@ -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,

crates/editor/src/display_map/wrap_map.rs πŸ”—

@@ -866,7 +866,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;
@@ -1416,9 +1416,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(
@@ -1434,7 +1435,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());
         }

crates/editor/src/editor.rs πŸ”—

@@ -7849,7 +7849,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);

crates/editor/src/git/blame.rs πŸ”—

@@ -1109,18 +1109,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);
 

crates/editor/src/inlays.rs πŸ”—

@@ -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("β—Ό")),
         }
     }
 

crates/editor/src/movement.rs πŸ”—

@@ -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"),
                     ),
                 ]
             })

crates/editor/src/signature_help.rs πŸ”—

@@ -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()

crates/extension_host/src/extension_host.rs πŸ”—

@@ -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?;

crates/git_ui/src/commit_view.rs πŸ”—

@@ -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,

crates/git_ui/src/file_diff_view.rs πŸ”—

@@ -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

crates/gpui/src/app/async_context.rs πŸ”—

@@ -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)]

crates/gpui/src/app/test_context.rs πŸ”—

@@ -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()

crates/gpui/src/executor.rs πŸ”—

@@ -341,7 +341,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);

crates/keymap_editor/src/keymap_editor.rs πŸ”—

@@ -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;
 

crates/language/src/buffer.rs πŸ”—

@@ -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);
 
@@ -2358,7 +2362,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 {
@@ -2589,7 +2595,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);

crates/language/src/buffer_tests.rs πŸ”—

@@ -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)

crates/language/src/syntax_map/syntax_map_tests.rs πŸ”—

@@ -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);

crates/language_extension/src/extension_lsp_adapter.rs πŸ”—

@@ -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)
         })

crates/languages/src/c.rs πŸ”—

@@ -178,7 +178,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
@@ -195,7 +195,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()
@@ -211,7 +212,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()
@@ -315,7 +317,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),
         ))
     }
 

crates/languages/src/go.rs πŸ”—

@@ -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),
         ))
     }
 

crates/languages/src/python.rs πŸ”—

@@ -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),
         ))
     }
 

crates/languages/src/rust.rs πŸ”—

@@ -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,
+            ),
         ))
     }
 

crates/markdown/src/markdown.rs πŸ”—

@@ -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

crates/markdown_preview/src/markdown_parser.rs πŸ”—

@@ -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

crates/multi_buffer/src/multi_buffer_tests.rs πŸ”—

@@ -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<_>>();

crates/project/src/buffer_store.rs πŸ”—

@@ -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 {
                 let loaded = load_file.await.with_context(|| {
                     format!("Could not open path: {}", path.display(PathStyle::local()))
                 })?;
+                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, |_| {
@@ -644,7 +651,12 @@ impl LocalBufferStore {
                 Ok(buffer) => Ok(buffer),
                 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 {

crates/project/src/git_store/conflict_set.rs πŸ”—

@@ -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);

crates/project/src/prettier_store.rs πŸ”—

@@ -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

crates/project/src/project.rs πŸ”—

@@ -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")?;

crates/project/src/project_tests.rs πŸ”—

@@ -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

crates/remote_server/src/remote_editing_tests.rs πŸ”—

@@ -13,7 +13,7 @@ use fs::{FakeFs, Fs};
 use gpui::{AppContext as _, Entity, SemanticVersion, 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
@@ -762,7 +762,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
@@ -777,7 +777,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

crates/rich_text/src/rich_text.rs πŸ”—

@@ -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) = &current_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));
         }

crates/rope/Cargo.toml πŸ”—

@@ -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

crates/rope/benches/rope_benchmark.rs πŸ”—

@@ -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(
                 || {

crates/rope/src/rope.rs πŸ”—

@@ -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));
         }

crates/rules_library/src/rules_library.rs πŸ”—

@@ -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));

crates/streaming_diff/Cargo.toml πŸ”—

@@ -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

crates/streaming_diff/src/streaming_diff.rs πŸ”—

@@ -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);

crates/sum_tree/Cargo.toml πŸ”—

@@ -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"

crates/sum_tree/src/sum_tree.rs πŸ”—

@@ -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), ());

crates/text/Cargo.toml πŸ”—

@@ -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"] }

crates/text/src/tests.rs πŸ”—

@@ -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()));
                 }
             }
             _ => {}

crates/text/src/text.rs πŸ”—

@@ -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) {

crates/vim/src/motion.rs πŸ”—

@@ -3087,6 +3087,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) {
@@ -3813,7 +3814,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);
         });
 
@@ -3845,7 +3846,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("$");
@@ -3884,7 +3885,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);
         });
 
@@ -3928,7 +3929,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);
         });
 

crates/workspace/src/workspace.rs πŸ”—

@@ -7577,13 +7577,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?;
         }
 

crates/worktree/src/worktree_tests.rs πŸ”—

@@ -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

crates/zed/src/zed.rs πŸ”—

@@ -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,
             );
@@ -1938,7 +1958,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>,
 ) {
@@ -4350,7 +4370,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
@@ -4360,7 +4380,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
@@ -4408,7 +4428,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
@@ -4428,7 +4448,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
@@ -4468,7 +4488,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
@@ -4477,7 +4497,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
@@ -4520,7 +4540,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
@@ -4540,7 +4560,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

crates/zed/src/zed/open_listener.rs πŸ”—

@@ -850,7 +850,7 @@ mod tests {
             .fs
             .save(
                 Path::new(file1_path),
-                &Rope::from("content1"),
+                &Rope::from_str("content1", cx.background_executor()),
                 LineEnding::Unix,
             )
             .await
@@ -864,7 +864,7 @@ mod tests {
             .fs
             .save(
                 Path::new(file2_path),
-                &Rope::from("content2"),
+                &Rope::from_str("content2", cx.background_executor()),
                 LineEnding::Unix,
             )
             .await

crates/zeta/src/zeta.rs πŸ”—

@@ -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(),
             )
         });