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