From f2ce06c7b00a79a4e2932ffbbbc880c1a3fb0a15 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 31 Oct 2025 11:39:01 +0100 Subject: [PATCH] sum_tree: Replace rayon with futures (#41586) Release Notes: - N/A *or* Added/Fixed/Improved ... Co-authored by: Kate --- Cargo.lock | 14 +- crates/acp_thread/src/diff.rs | 4 +- crates/action_log/src/action_log.rs | 18 +- .../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 +- .../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 +- .../random_project_collaboration_tests.rs | 7 +- crates/diagnostics/src/diagnostics_tests.rs | 4 +- .../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 ++- .../src/syntax_map/syntax_map_tests.rs | 45 ++- .../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 +- .../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 +- .../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, 1271 insertions(+), 640 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78c972865a4e01ba66357142ff8737b634639b27..25a22e64c6db0632ca1357cebe02f0bbe04fa0a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12711,6 +12711,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -12759,7 +12765,7 @@ dependencies = [ "log", "parking_lot", "pin-project", - "pollster", + "pollster 0.2.5", "static_assertions", "thiserror 1.0.69", ] @@ -14311,7 +14317,6 @@ dependencies = [ "gpui", "log", "rand 0.9.2", - "rayon", "sum_tree", "unicode-segmentation", "util", @@ -16237,6 +16242,7 @@ checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" name = "streaming_diff" version = "0.1.0" dependencies = [ + "gpui", "ordered-float 2.10.1", "rand 0.9.2", "rope", @@ -16355,9 +16361,11 @@ version = "0.1.0" dependencies = [ "arrayvec", "ctor", + "futures 0.3.31", + "itertools 0.14.0", "log", + "pollster 0.4.0", "rand 0.9.2", - "rayon", "zlog", ] diff --git a/crates/acp_thread/src/diff.rs b/crates/acp_thread/src/diff.rs index 055b2f7fb86ffe9d7f12459b6b16405ce77815a0..39cd8ad38e5bf223987dc8efe771614b3ed2172b 100644 --- a/crates/acp_thread/src/diff.rs +++ b/crates/acp_thread/src/diff.rs @@ -361,10 +361,12 @@ async fn build_buffer_diff( ) -> Result> { 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 diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index b7722f211afda3a77bc96292a50acf869e7424d6..1730163a4ce7b53aa051a6af87da8ab10ad4320f 100644 --- a/crates/action_log/src/action_log.rs +++ b/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::(), + 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>, 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::(), + 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()); diff --git a/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs b/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs index 904ec05a8c7565d5052cd546fc0bf6d723ffa375..021892e738eed229568c909f72f327d93199cdc0 100644 --- a/crates/agent/src/edit_agent/streaming_fuzzy_matcher.rs +++ b/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); diff --git a/crates/agent/src/tools/edit_file_tool.rs b/crates/agent/src/tools/edit_file_tool.rs index 0adff2dee3571f09b40ee69896c05e50c56b51b9..078273dbb8a4399e1770ca08daeb1f7f44491e2a 100644 --- a/crates/agent/src/tools/edit_file_tool.rs +++ b/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 diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index ef0d4735d2d7690111ee2549cdee8ab31e32196e..61f8ee60a794cbd6622759a89efb6f40c8f1503d 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/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? diff --git a/crates/agent_ui/src/buffer_codegen.rs b/crates/agent_ui/src/buffer_codegen.rs index 215e2a74d7be9cbcb18442dcefa1581d08eec7b2..f9269e0bb62160633dc991b147d1d779a517e2e8 100644 --- a/crates/agent_ui/src/buffer_codegen.rs +++ b/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::(); + 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); diff --git a/crates/assistant_text_thread/src/text_thread.rs b/crates/assistant_text_thread/src/text_thread.rs index 9ad383cdfd43eed236268349e2ff97c34a0178c0..ddc8912aef5c08ecb9406cc27fbcdf5418ec48e2 100644 --- a/crates/assistant_text_thread/src/text_thread.rs +++ b/crates/assistant_text_thread/src/text_thread.rs @@ -744,12 +744,13 @@ impl TextThread { telemetry: Option>, cx: &mut Context, ) -> 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 diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index d6ae5545200bb47976554814e346be3039fa276e..b8ce85b6db25fdcad21245b41e4979ef61220485 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/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 { 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() }; diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index efa0850753887c2116ee7916727a870a3528b627..0e59ccedf5e8e0767eb9be56608eb433d63d1bf4 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/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))?; diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index 6c4cd58d132bdeaaa791f4da8406e0e6d9052981..fb457abcd46cf32b4a34d87637011b307bbacf9d 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/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; diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index 4eae7a54cba4a906351f05e5945cff5691fd1126..82310331ffc864d4bba942f3924dcc644427891b 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -74,11 +74,21 @@ async fn test_channel_buffers(db: &Arc) { 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) { 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; diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 4fa32b6c9ba55e6962547510f52251f16fc9be81..37e6622b0343bca9ae6b9179c830071999bf51df 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/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 diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 7e9b84c0571ed6dff19702ce3532c45d56f6413f..399f1a663fe72798a4269804955dcfd3678c5cca 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/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(); } diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index d97a5ab65aab4bb238182040821ecf9fdf828bc3..824d4db6a58c06db5df4c04ac79ee1e509d55d4d 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/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)); diff --git a/crates/edit_prediction_button/src/edit_prediction_button.rs b/crates/edit_prediction_button/src/edit_prediction_button.rs index 70c861ab1112630c2e3293cb54a4e96c6754b3bd..594c290730d5c734430e747ac6d09d6cbbbd4d0e 100644 --- a/crates/edit_prediction_button/src/edit_prediction_button.rs +++ b/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; @@ -1056,8 +1056,11 @@ async fn open_disabled_globs_setting_in_editor( ) -> Result<()> { let settings_editor = workspace .update_in(cx, |_, window, cx| { - create_and_open_local_file(paths::settings_file(), window, cx, || { - settings::initial_user_settings_content().as_ref().into() + create_and_open_local_file(paths::settings_file(), window, cx, |cx| { + Rope::from_str( + settings::initial_user_settings_content().as_ref(), + cx.background_executor(), + ) }) })? .await? diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7a225d6019edf8f09b1758d62e8181917649cc2b..a269d22d71a95eef1ca1485437863091e3505439 100644 --- a/crates/editor/src/display_map.rs +++ b/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, ); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 486676f1120bc2e9d85effd4c328a2b7a547e06b..3c7cedb6574d02bcf6b06075b8db79cc3a6080db 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -700,16 +700,20 @@ impl InlayMap { .collect::(); 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::>(); - 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]); diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 7a63723f53a49483eaa728373a5ae8530aa6f4d6..084ced82b3aa311f90f905077e2d18dd831e0bd6 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/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, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 7371eb678538dbc12abe43bde4073ffd9d2bdb21..1f50ff28daff51e9e16da683053104ea4800977b 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -863,7 +863,7 @@ impl WrapSnapshot { } } - let text = language::Rope::from(self.text().as_str()); + let text = language::Rope::from_str_small(self.text().as_str()); let mut input_buffer_rows = self.tab_snapshot.rows(0); let mut expected_buffer_rows = Vec::new(); let mut prev_tab_row = 0; @@ -1413,9 +1413,10 @@ mod tests { } } - let mut initial_text = Rope::from(initial_snapshot.text().as_str()); + let mut initial_text = + Rope::from_str(initial_snapshot.text().as_str(), cx.background_executor()); for (snapshot, patch) in edits { - let snapshot_text = Rope::from(snapshot.text().as_str()); + let snapshot_text = Rope::from_str(snapshot.text().as_str(), cx.background_executor()); for edit in &patch { let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); let old_end = initial_text.point_to_offset(cmp::min( @@ -1431,7 +1432,7 @@ mod tests { .chunks_in_range(new_start..new_end) .collect::(); - 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()); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9bdebcd24a20697fb041ae14fa7dc2f034d00d92..ed6b8ec2eca4dcb558bc832ac56b92af8791712c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7852,7 +7852,7 @@ impl Editor { let inlay = Inlay::edit_prediction( post_inc(&mut self.next_inlay_id), range.start, - new_text.as_str(), + Rope::from_str_small(new_text.as_str()), ); inlay_ids.push(inlay.id); inlays.push(inlay); diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index b36a57a7e47bf148fff4201ec87ac7c868658a04..94bc67e684cd512942d42527d0adb802500ed49f 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -1115,18 +1115,19 @@ mod tests { let fs = FakeFs::new(cx.executor()); let buffer_initial_text_len = rng.random_range(5..15); - let mut buffer_initial_text = Rope::from( + let mut buffer_initial_text = Rope::from_str( RandomCharIter::new(&mut rng) .take(buffer_initial_text_len) .collect::() .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); diff --git a/crates/editor/src/inlays.rs b/crates/editor/src/inlays.rs index f07bf0b315161f0ce9cdf3ef7e2f6db6d60abfb5..1d411fef5617c00ef4d34b521f2321ac9baac934 100644 --- a/crates/editor/src/inlays.rs +++ b/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) -> 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>(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>(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 = 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("◼")), } } diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 418fa4fcb442b1de133972457497c0e592e77d15..1c15990b13ea99db269d21bcdcd591e50ebf4d69 100644 --- a/crates/editor/src/movement.rs +++ b/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"), ), ] }) diff --git a/crates/editor/src/signature_help.rs b/crates/editor/src/signature_help.rs index 8d74638e4c2aaf356ffabdeef717b9b105487ee3..3ef8ca09ab0af2714c353b1ad3c31556b0783c3d 100644 --- a/crates/editor/src/signature_help.rs +++ b/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() diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 04b03352d83fd3323770a00a13c4377dc111535a..50b5169f7ad1196a3628c59d4fda6162126b2190 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/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?; diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 0a0c4c18e1f528a9ebaad9a8d9862982632dd04f..b302d551ddccd17a757b81452f0ed597dde88c57 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/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> { 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, diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs index 387bda808708cf38beded2fe17edd92466885672..a99b7f8e2428ca0bcf726f2ac7661df171bef34a 100644 --- a/crates/git_ui/src/file_diff_view.rs +++ b/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 diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index 381541d4b11377b988dd30e03155855c7ba25aed..260a07cc3ba6805b91207e000b02d23e57f2be4e 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/crates/gpui/src/app/async_context.rs @@ -260,6 +260,19 @@ impl AsyncApp { } } +impl sum_tree::BackgroundSpawn for BackgroundExecutor { + type Task + = Task + where + R: Send + Sync; + fn background_spawn(&self, future: impl Future + Send + 'static) -> Self::Task + 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)] diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index d974823396d9f0d546a6b035f47b569145eb021b..40397f9d9d359d2ac914b6006b0ae883fa151fc2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/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() diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index b6d3a407f5dbbab07e0273e668e9b5710824edda..c1e5c066b43604f5e7d47588ef3c2ebc33cd524e 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -342,7 +342,7 @@ impl BackgroundExecutor { /// for all of them to complete before returning. pub async fn scoped<'scope, F>(&self, scheduler: F) where - F: FnOnce(&mut Scope<'scope>), + F: for<'a> FnOnce(&'a mut Scope<'scope>), { let mut scope = Scope::new(self.clone()); (scheduler)(&mut scope); diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index e3fb30d46eb57059afc53682c57be392ec8254ed..70e58de3d14403440a0cd291754e0a4593290d01 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/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; diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index c72350f38561e7aea62b7d3402eaa24bbdb08044..d67434741032ae7f42dc5e95ec34a57b7c84ebb4 100644 --- a/crates/language/src/buffer.rs +++ b/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, + 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>, + cx: &BackgroundExecutor, ) -> Result { let buffer_id = BufferId::new(message.id).context("Could not deserialize buffer_id")?; - let buffer = TextBuffer::new(replica_id, buffer_id, message.base_text); + let buffer = TextBuffer::new(replica_id, buffer_id, message.base_text, cx); let mut this = Self::build(buffer, file, capability); this.text.set_line_ending(proto::deserialize_line_ending( rpc::proto::LineEnding::from_i32(message.line_ending).context("missing line_ending")?, @@ -1138,13 +1141,14 @@ impl Buffer { let old_snapshot = self.text.snapshot(); let mut branch_buffer = self.text.branch(); let mut syntax_snapshot = self.syntax_map.lock().snapshot(); + let executor = cx.background_executor().clone(); cx.background_spawn(async move { if !edits.is_empty() { if let Some(language) = language.clone() { syntax_snapshot.reparse(&old_snapshot, registry.clone(), language); } - branch_buffer.edit(edits.iter().cloned()); + branch_buffer.edit(edits.iter().cloned(), &executor); let snapshot = branch_buffer.snapshot(); syntax_snapshot.interpolate(&snapshot); @@ -2361,7 +2365,9 @@ impl Buffer { let autoindent_request = autoindent_mode .and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode))); - let edit_operation = self.text.edit(edits.iter().cloned()); + let edit_operation = self + .text + .edit(edits.iter().cloned(), cx.background_executor()); let edit_id = edit_operation.timestamp(); if let Some((before_edit, mode)) = autoindent_request { @@ -2592,7 +2598,8 @@ impl Buffer { for operation in buffer_ops.iter() { self.send_operation(Operation::Buffer(operation.clone()), false, cx); } - self.text.apply_ops(buffer_ops); + self.text + .apply_ops(buffer_ops, Some(cx.background_executor())); self.deferred_ops.insert(deferred_ops); self.flush_deferred_ops(cx); self.did_edit(&old_version, was_dirty, cx); diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index f824639ad762191f4168586551af51fb4e37c8dc..6b6d85c3790123acd6e95dd1d196f6c5845f5ede 100644 --- a/crates/language/src/buffer_tests.rs +++ b/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) diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index 9c4eecad363de386cddc6e943e20e5762634d713..99fd365b50f5c93b965b7193365b49b2bc636a2e 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/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, language: Arc, 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); diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index 01b726748649e29b4fe69ce26df5564819894985..cc9fb639f228ea7af42238296ae88c95ae439881 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/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) }) diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index 8e90cf821368c0c88781b2d10e82ad9eaa05989c..bbf4cc3240f0f33ee73fed10d96edc36467e51f4 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -189,7 +189,7 @@ impl super::LspAdapter for CLspAdapter { Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => { let detail = completion.detail.as_ref().unwrap(); let text = format!("{} {}", detail, label); - let source = Rope::from(format!("struct S {{ {} }}", text).as_str()); + let source = Rope::from_str_small(format!("struct S {{ {} }}", text).as_str()); let runs = language.highlight_text(&source, 11..11 + text.len()); let filter_range = completion .filter_text @@ -206,7 +206,8 @@ impl super::LspAdapter for CLspAdapter { { let detail = completion.detail.as_ref().unwrap(); let text = format!("{} {}", detail, label); - let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len()); + let runs = + language.highlight_text(&Rope::from_str_small(text.as_str()), 0..text.len()); let filter_range = completion .filter_text .as_deref() @@ -222,7 +223,8 @@ impl super::LspAdapter for CLspAdapter { { let detail = completion.detail.as_ref().unwrap(); let text = format!("{} {}", detail, label); - let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len()); + let runs = + language.highlight_text(&Rope::from_str_small(text.as_str()), 0..text.len()); let filter_range = completion .filter_text .as_deref() @@ -326,7 +328,7 @@ impl super::LspAdapter for CLspAdapter { Some(CodeLabel::new( text[display_range.clone()].to_string(), filter_range, - language.highlight_text(&text.as_str().into(), display_range), + language.highlight_text(&Rope::from_str_small(text.as_str()), display_range), )) } diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 6c75abf123af62b3f4ab43a6e94d3b040e2f010a..55acc64f3e1b5592a55c551aa6c0b255cae3834a 100644 --- a/crates/languages/src/go.rs +++ b/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), )) } diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index f676f5a7a6f028c095d52273fb8c616472a35ee5..a87f17795f5b6a1d69368d826688a6ed48309d23 100644 --- a/crates/languages/src/python.rs +++ b/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), )) } diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 4b56a617735ab1a5932a56a4f6e51397721d8a86..b6f7b10da69f7f3f8d8551a88fa8409f05c2fed8 100644 --- a/crates/languages/src/rust.rs +++ b/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, + ), )) } diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index c34ed69288e39c26d105877d76ee76c01c864c72..eb239fd46fe8c0a6cfcfb6ea4a7610ddb6dabf47 100644 --- a/crates/markdown/src/markdown.rs +++ b/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 diff --git a/crates/markdown_preview/src/markdown_parser.rs b/crates/markdown_preview/src/markdown_parser.rs index 8f2203c25b9a7193759668a35016c2d3203310b6..d46224a736dfd7e2a57c88d9512774562e10dab8 100644 --- a/crates/markdown_preview/src/markdown_parser.rs +++ b/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 diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index a9121b9104400d88d5f22801db1bfebaeeb060d6..947d6be1199ca73be910c5cc606147ef75bd9376 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/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::>(); diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 39e302a2d9b1ae92cce9691c957cb9fcfbf26d7d..3fb702518690585d3237324c04802c9deec0892e 100644 --- a/crates/project/src/buffer_store.rs +++ b/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); } - Buffer::from_proto(replica_id, capability, state, buffer_file) + Buffer::from_proto( + replica_id, + capability, + state, + buffer_file, + cx.background_executor(), + ) }); match buffer_result { @@ -628,9 +634,10 @@ impl LocalBufferStore { Ok(loaded) => { let reservation = cx.reserve_entity::()?; let buffer_id = BufferId::from(reservation.entity_id().as_non_zero_u64()); + let executor = cx.background_executor().clone(); let text_buffer = cx .background_spawn(async move { - text::Buffer::new(ReplicaId::LOCAL, buffer_id, loaded.text) + text::Buffer::new(ReplicaId::LOCAL, buffer_id, loaded.text, &executor) }) .await; cx.insert_entity(reservation, |_| { @@ -639,7 +646,12 @@ impl LocalBufferStore { } Err(error) if is_not_found_error(&error) => cx.new(|cx| { let buffer_id = BufferId::from(cx.entity_id().as_non_zero_u64()); - let text_buffer = text::Buffer::new(ReplicaId::LOCAL, buffer_id, ""); + let text_buffer = text::Buffer::new( + ReplicaId::LOCAL, + buffer_id, + "", + cx.background_executor(), + ); Buffer::build( text_buffer, Some(Arc::new(File { diff --git a/crates/project/src/git_store/conflict_set.rs b/crates/project/src/git_store/conflict_set.rs index 160a384a4a0ff4481c97b6eda75faded28f01624..46c2e1f92415044ce1d9e8bdf9053a3d3768f372 100644 --- a/crates/project/src/git_store/conflict_set.rs +++ b/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); diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index 40deac76404ddb4378fe08cae931d0f0e3583487..3743f9769eaaff7f3acd1cc5bad16e31f6e80987 100644 --- a/crates/project/src/prettier_store.rs +++ b/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 diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e188ebd5e32947777f987ff43df52f09d006d58f..7c7fe9a43091611a53dbde0ecbaf6691b7d768d0 100644 --- a/crates/project/src/project.rs +++ b/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")?; diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 891ad2420c6f8a79659a1f05afd0821b995b5b1a..3dc918d5a757af56038471e1a601d6f2cf7dbbe1 100644 --- a/crates/project/src/project_tests.rs +++ b/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 diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 969363fb2bd02e7bc514cd68d488ca57aef9f0b9..c7e09e3f681d770959709893561cf7a1ba377b37 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -13,7 +13,7 @@ use fs::{FakeFs, Fs}; use gpui::{AppContext as _, Entity, SemanticVersion, SharedString, TestAppContext}; use http_client::{BlockedHttpClient, FakeHttpClient}; use language::{ - Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, + Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, Rope, language_settings::{AllLanguageSettings, language_settings}, }; use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName}; @@ -120,7 +120,7 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test // sees the new file. fs.save( path!("/code/project1/src/main.rs").as_ref(), - &"fn main() {}".into(), + &Rope::from_str_small("fn main() {}"), Default::default(), ) .await @@ -766,7 +766,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont fs.save( &PathBuf::from(path!("/code/project1/src/lib.rs")), - &("bangles".to_string().into()), + &Rope::from_str_small("bangles"), LineEnding::Unix, ) .await @@ -781,7 +781,7 @@ async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppCont fs.save( &PathBuf::from(path!("/code/project1/src/lib.rs")), - &("bloop".to_string().into()), + &Rope::from_str_small("bloop"), LineEnding::Unix, ) .await diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 2af9988f032c5dc9651e1da6e8c3b52c6c668866..4e30c22e7f4da2f2656861b792ada5ef6fa9311b 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/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, + 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, Highlight)>, link_ranges: &mut Vec>, link_urls: &mut Vec, + executor: &BackgroundExecutor, ) { use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd}; @@ -202,7 +206,7 @@ pub fn render_markdown_mut( match event { Event::Text(t) => { if let Some(language) = ¤t_language { - render_code(text, highlights, t.as_ref(), language); + render_code(text, highlights, t.as_ref(), language, executor); } else { while let Some(mention) = mentions.first() { if !source_range.contains_inclusive(&mention.range) { @@ -373,11 +377,14 @@ pub fn render_code( highlights: &mut Vec<(Range, Highlight)>, content: &str, language: &Arc, + 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)); } diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index 4107c2e012debc13b0cc44003250f4da63e5039f..30f702292bf1e04524fe0c2489b1c4a8783e9ca4 100644 --- a/crates/rope/Cargo.toml +++ b/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 diff --git a/crates/rope/benches/rope_benchmark.rs b/crates/rope/benches/rope_benchmark.rs index 030bec01df4d223cd5288842ba0f9c1386dac31b..5075dff788dfadd49783e89937e19986d9234580 100644 --- a/crates/rope/benches/rope_benchmark.rs +++ b/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( || { diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 394e6ef0ca589d19ffcf7cf07a92bcd15c8e4a18..b515f46ea89ddd5f8f29ca7d462b48fe8fff1d38 100644 --- a/crates/rope/src/rope.rs +++ b/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>(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>( + 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, text: &str) { + pub fn replace(&mut self, range: Range, 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<&'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 for Rope { +// #[inline(always)] +// fn from(text: String) -> Self { +// Rope::from(text.as_str()) +// } +// } -impl<'a> FromIterator<&'a str> for Rope { - fn from_iter>(iter: T) -> Self { - let mut rope = Rope::new(); - for chunk in iter { - rope.push(chunk); - } - rope - } -} - -impl From 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)); } diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 207a9841e41bf35e1f63bb00b0c62073c1cf0224..3cc05fd2d26fa52282030ad1eb564e3cfd8cb609 100644 --- a/crates/rules_library/src/rules_library.rs +++ 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)); diff --git a/crates/streaming_diff/Cargo.toml b/crates/streaming_diff/Cargo.toml index b3645a182c3abf52c6ee2f2c23feaedeacf8574a..8825914baa8d08734e66485b4bea418840d72228 100644 --- a/crates/streaming_diff/Cargo.toml +++ b/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 diff --git a/crates/streaming_diff/src/streaming_diff.rs b/crates/streaming_diff/src/streaming_diff.rs index 5677981b0dc9878963e01d09e7281749d6603c8f..34a74afa84431079b4d9d0815c96e0114248ca98 100644 --- a/crates/streaming_diff/src/streaming_diff.rs +++ b/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 { - let old_rope = Rope::from(old_text); + fn char_ops_to_line_ops( + old_text: &str, + char_ops: &[CharOperation], + executor: &BackgroundExecutor, + ) -> Vec { + 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); diff --git a/crates/sum_tree/Cargo.toml b/crates/sum_tree/Cargo.toml index 81916c842225085ceec4721dbd8d212608f6bcb9..fd39bd4d83c65501b4731f31d3f357a3ff7f6fa3 100644 --- a/crates/sum_tree/Cargo.toml +++ b/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" diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index 95fbd5ed0d5f5700d0c894cda68ed15ce6590ced..8562766b1b49ac8eb1e3c816f210d1a60cae2aed 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/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: Future + Send + Sync + where + R: Send + Sync; + fn background_spawn( + &self, + future: impl Future + Send + Sync + 'static, + ) -> Self::Task + 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 SumTree { } } - pub fn from_par_iter(iter: I, cx: ::Context<'_>) -> Self + pub async fn from_iter_async(iter: I, spawn: S) -> Self where - I: IntoParallelIterator, - Iter: IndexedParallelIterator, - T: Send + Sync, - T::Summary: Send + Sync, - for<'a> ::Context<'a>: Sync, + T: 'static + Send + Sync, + for<'a> T::Summary: Summary = ()> + Send + Sync, + S: BackgroundSpawn, + I: IntoIterator, { - let mut nodes = iter - .into_par_iter() - .chunks(2 * TREE_BASE) - .map(|items| { - let items: ArrayVec = 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 = chunk.into_iter().collect(); + futures.push(async move { let item_summaries: ArrayVec = - 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..] { - ::add_summary(&mut summary, item_summary, cx); + ::add_summary(&mut summary, item_summary, ()); } SumTree(Arc::new(Node::Leaf { summary, items, item_summaries, })) - }) - .collect::>(); + }); + } + + let mut nodes = futures::stream::iter(futures) + .map(|future| spawn.background_spawn(future)) + .buffered(4) + .collect::>() + .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, { 2 * TREE_BASE }> = - child_nodes.into_iter().collect(); - let child_summaries: ArrayVec = 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..] { - ::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, { 2 * TREE_BASE }> = + chunk.into_iter().collect(); + let child_summaries: ArrayVec = 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..] { + ::add_summary(&mut summary, child_summary, ()); + } + SumTree(Arc::new(Node::Internal { + height, + summary, + child_summaries, + child_trees, + })) + }) }) - .collect::>(); + .buffered(4) + .collect::>() + .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 SumTree { self.append(Self::from_iter(iter, cx), cx); } - pub fn par_extend(&mut self, iter: I, cx: ::Context<'_>) + pub async fn async_extend(&mut self, iter: I, spawn: S) where - I: IntoParallelIterator, - Iter: IndexedParallelIterator, - T: Send + Sync, - T::Summary: Send + Sync, - for<'a> ::Context<'a>: Sync, + S: BackgroundSpawn, + I: IntoIterator + 'static, + T: 'static + Send + Sync, + for<'b> T::Summary: Summary = ()> + 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: ::Context<'_>) { @@ -1070,6 +1092,23 @@ mod tests { #[test] fn test_random() { + struct NoSpawn; + impl BackgroundSpawn for NoSpawn { + type Task + = std::pin::Pin + Sync + Send>> + where + R: Send + Sync; + fn background_spawn( + &self, + future: impl Future + Send + Sync + 'static, + ) -> Self::Task + 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::>(); - 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), ()); diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml index ed02381eb83db5daececd159171a90072244a340..e9f9279f0d0b41f651c2ac218adf58bd76af2021 100644 --- a/crates/text/Cargo.toml +++ b/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"] } diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index c9e04e407ffdb8ffde6b139e01d78822e54e1a4b..6281c2f0e2ef21cb3756cfe5da814d294b49b108 100644 --- a/crates/text/src/tests.rs +++ b/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::(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::(), "abcd\nefgh\nijkl\nmno\nPQrs"); @@ -378,43 +412,53 @@ fn test_chars_at() { assert_eq!(chars.collect::(), "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::(), " \"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())); } } _ => {} diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 6403c66106dca88cdac85e09888012d890158a23..9d3034c0e9603ee37dca802e545af9a593b930a1 100644 --- a/crates/text/src/text.rs +++ b/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 for LineIndent { } impl Buffer { - pub fn new(replica_id: ReplicaId, remote_id: BufferId, base_text: impl Into) -> Buffer { + /// Create a new buffer from a string. + pub fn new( + replica_id: ReplicaId, + remote_id: BufferId, + base_text: impl Into, + 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, + ) -> 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(&mut self, edits: R) -> Operation + pub fn edit(&mut self, edits: R, cx: &BackgroundExecutor) -> Operation where R: IntoIterator, I: ExactSizeIterator, 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, 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>(&mut self, ops: I) { + pub fn apply_ops>( + &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], new_text: &[Arc], 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, Arc)>, 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) { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 2da1083ee6623cc8a463ef31be7e90dca0063b34..a885538e13e11b9720c3d7ffed5f7e6461943598 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -3096,6 +3096,7 @@ mod test { use indoc::indoc; use language::Point; use multi_buffer::MultiBufferRow; + use text::Rope; #[gpui::test] async fn test_start_end_of_paragraph(cx: &mut gpui::TestAppContext) { @@ -3822,7 +3823,7 @@ mod test { cx.update_editor(|editor, _window, cx| { let range = editor.selections.newest_anchor().range(); let inlay_text = " field: int,\n field2: string\n field3: float"; - let inlay = Inlay::edit_prediction(1, range.start, inlay_text); + let inlay = Inlay::edit_prediction(1, range.start, Rope::from_str_small(inlay_text)); editor.splice_inlays(&[], vec![inlay], cx); }); @@ -3854,7 +3855,7 @@ mod test { let end_of_line = snapshot.anchor_after(Point::new(0, snapshot.line_len(MultiBufferRow(0)))); let inlay_text = " hint"; - let inlay = Inlay::edit_prediction(1, end_of_line, inlay_text); + let inlay = Inlay::edit_prediction(1, end_of_line, Rope::from_str_small(inlay_text)); editor.splice_inlays(&[], vec![inlay], cx); }); cx.simulate_keystrokes("$"); @@ -3893,7 +3894,7 @@ mod test { // The empty line is at line 3 (0-indexed) let line_start = snapshot.anchor_after(Point::new(3, 0)); let inlay_text = ": Vec"; - let inlay = Inlay::edit_prediction(1, line_start, inlay_text); + let inlay = Inlay::edit_prediction(1, line_start, Rope::from_str_small(inlay_text)); editor.splice_inlays(&[], vec![inlay], cx); }); @@ -3937,7 +3938,8 @@ mod test { let snapshot = editor.buffer().read(cx).snapshot(cx); let empty_line_start = snapshot.anchor_after(Point::new(2, 0)); let inlay_text = ": i32"; - let inlay = Inlay::edit_prediction(2, empty_line_start, inlay_text); + let inlay = + Inlay::edit_prediction(2, empty_line_start, Rope::from_str_small(inlay_text)); editor.splice_inlays(&[], vec![inlay], cx); }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b1de240eb62bcca6967333641bf8234825730300..62e29f215146c03060afe81ee67b78e3b3ea8a59 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7580,13 +7580,13 @@ pub fn create_and_open_local_file( path: &'static Path, window: &mut Window, cx: &mut Context, - default_content: impl 'static + Send + FnOnce() -> Rope, + default_content: impl 'static + Send + FnOnce(&mut AsyncApp) -> Rope, ) -> Task>> { 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?; } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index d89e1ef4e4df7dbef3cf51789c1f1fc8a5309eb1..1cce23712ae88f0e42faf240099ebecd9000fc4e 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/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; 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; 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 diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2d7d47e968e93eef3d455cec9c324a4d4e0cff42..bd0a600ce52a265f9785b1e26e7a123f270ce263 100644 --- a/crates/zed/src/zed.rs +++ b/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, ); @@ -1939,7 +1959,7 @@ fn open_bundled_file( fn open_settings_file( abs_path: &'static Path, - default_content: impl FnOnce() -> Rope + Send + 'static, + default_content: impl FnOnce(&mut AsyncApp) -> Rope + Send + 'static, window: &mut Window, cx: &mut Context, ) { @@ -4355,7 +4375,7 @@ mod tests { .fs .save( "/settings.json".as_ref(), - &r#"{"base_keymap": "Atom"}"#.into(), + &Rope::from_str_small(r#"{"base_keymap": "Atom"}"#), Default::default(), ) .await @@ -4365,7 +4385,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#.into(), + &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#), Default::default(), ) .await @@ -4413,7 +4433,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": "test_only::ActionB"}}]"#.into(), + &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionB"}}]"#), Default::default(), ) .await @@ -4433,7 +4453,7 @@ mod tests { .fs .save( "/settings.json".as_ref(), - &r#"{"base_keymap": "JetBrains"}"#.into(), + &Rope::from_str_small(r#"{"base_keymap": "JetBrains"}"#), Default::default(), ) .await @@ -4473,7 +4493,7 @@ mod tests { .fs .save( "/settings.json".as_ref(), - &r#"{"base_keymap": "Atom"}"#.into(), + &Rope::from_str_small(r#"{"base_keymap": "Atom"}"#), Default::default(), ) .await @@ -4482,7 +4502,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#.into(), + &Rope::from_str_small(r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#), Default::default(), ) .await @@ -4525,7 +4545,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": null}}]"#.into(), + &Rope::from_str_small(r#"[{"bindings": {"backspace": null}}]"#), Default::default(), ) .await @@ -4545,7 +4565,7 @@ mod tests { .fs .save( "/settings.json".as_ref(), - &r#"{"base_keymap": "JetBrains"}"#.into(), + &Rope::from_str_small(r#"{"base_keymap": "JetBrains"}"#), Default::default(), ) .await diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index 3abb76715d67e3d288cf812fc6a4bff58ac3ddfe..bc6c25105e69eb85e8db3714c48dc30791683109 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -861,7 +861,7 @@ mod tests { .fs .save( Path::new(file1_path), - &Rope::from("content1"), + &Rope::from_str("content1", cx.background_executor()), LineEnding::Unix, ) .await @@ -875,7 +875,7 @@ mod tests { .fs .save( Path::new(file2_path), - &Rope::from("content2"), + &Rope::from_str("content2", cx.background_executor()), LineEnding::Unix, ) .await diff --git a/crates/zeta/src/zeta.rs b/crates/zeta/src/zeta.rs index 454a1526a9e8c6a75d47bda875feb6843b454a0d..ca2edd0682e181c8db7b8f1973386d3190eab12d 100644 --- a/crates/zeta/src/zeta.rs +++ b/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(), ) });