From b3ad8c1c3e88ac1716b60dc58e3953b56986c70b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 26 Oct 2023 15:56:09 +0200 Subject: [PATCH] Get most of the language2 tests passing Co-authored-by: Conrad --- Cargo.lock | 1 - crates/language2/Cargo.toml | 4 +- crates/language2/src/buffer_tests.rs | 4556 +++++++++++++------------- 3 files changed, 2279 insertions(+), 2282 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f9ecafc34..021f3a1a72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4304,7 +4304,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "settings", "settings2", "similar", "smallvec", diff --git a/crates/language2/Cargo.toml b/crates/language2/Cargo.toml index a3e1bf3e2a..1e49d0890f 100644 --- a/crates/language2/Cargo.toml +++ b/crates/language2/Cargo.toml @@ -17,7 +17,7 @@ test-support = [ "text/test-support", "tree-sitter-rust", "tree-sitter-typescript", - "settings/test-support", + "settings2/test-support", "util/test-support", ] @@ -65,7 +65,7 @@ collections = { path = "../collections", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] } lsp2 = { path = "../lsp2", features = ["test-support"] } text = { path = "../text", features = ["test-support"] } -settings = { path = "../settings", features = ["test-support"] } +settings2 = { path = "../settings2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } ctor.workspace = true env_logger.workspace = true diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index b40253da21..c5790de881 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/crates/language2/src/buffer_tests.rs @@ -1,229 +1,220 @@ +use super::*; +use crate::language_settings::{ + AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, +}; use crate::Buffer; +use clock::ReplicaId; +use collections::BTreeMap; +use gpui2::{AppContext, Handle}; use gpui2::{Context, TestAppContext}; +use indoc::indoc; +use proto::deserialize_operation; +use rand::prelude::*; +use regex::RegexBuilder; +use settings2::SettingsStore; +use std::{ + env, + ops::Range, + time::{Duration, Instant}, +}; +use text::network::Network; +use text::LineEnding; use text::{Point, ToPoint}; +use unindent::Unindent as _; +use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter}; -// use crate::language_settings::{ -// AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, -// }; +lazy_static! { + static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$") + .multi_line(true) + .build() + .unwrap(); +} -// use super::*; -// use clock::ReplicaId; -// use collections::BTreeMap; -// use gpui2::{AppContext, Handle}; -// use indoc::indoc; -// use proto::deserialize_operation; -// use rand::prelude::*; -// use regex::RegexBuilder; -// use settings::SettingsStore; -// use std::{ -// cell::RefCell, -// env, -// ops::Range, -// rc::Rc, -// time::{Duration, Instant}, -// }; -// use text::network::Network; -// use text::LineEnding; -// use unindent::Unindent as _; -// use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter}; +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } +} -// lazy_static! { -// static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$") -// .multi_line(true) -// .build() -// .unwrap(); -// } +#[gpui2::test] +fn test_line_endings(cx: &mut gpui2::AppContext) { + init_settings(cx, |_| {}); -// #[cfg(test)] -// #[ctor::ctor] -// fn init_logger() { -// if std::env::var("RUST_LOG").is_ok() { -// env_logger::init(); -// } -// } + cx.entity(|cx| { + let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree") + .with_language(Arc::new(rust_lang()), cx); + assert_eq!(buffer.text(), "one\ntwo\nthree"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); -// #[gpui::test] -// fn test_line_endings(cx: &mut gpui::AppContext) { -// init_settings(cx, |_| {}); + buffer.check_invariants(); + buffer.edit( + [(buffer.len()..buffer.len(), "\r\nfour")], + Some(AutoindentMode::EachLine), + cx, + ); + buffer.edit([(0..0, "zero\r\n")], None, cx); + assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); -// cx.add_model(|cx| { -// let mut buffer = Buffer::new(0, cx.model_id() as u64, "one\r\ntwo\rthree") -// .with_language(Arc::new(rust_lang()), cx); -// assert_eq!(buffer.text(), "one\ntwo\nthree"); -// assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer + }); +} -// buffer.check_invariants(); -// buffer.edit( -// [(buffer.len()..buffer.len(), "\r\nfour")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// buffer.edit([(0..0, "zero\r\n")], None, cx); -// assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour"); -// assert_eq!(buffer.line_ending(), LineEnding::Windows); -// buffer.check_invariants(); +#[gpui2::test] +fn test_select_language() { + let registry = Arc::new(LanguageRegistry::test()); + registry.add(Arc::new(Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); + registry.add(Arc::new(Language::new( + LanguageConfig { + name: "Make".into(), + path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ))); -// buffer -// }); -// } + // matching file extension + assert_eq!( + registry + .language_for_file("zed/lib.rs", None) + .now_or_never() + .and_then(|l| Some(l.ok()?.name())), + Some("Rust".into()) + ); + assert_eq!( + registry + .language_for_file("zed/lib.mk", None) + .now_or_never() + .and_then(|l| Some(l.ok()?.name())), + Some("Make".into()) + ); -// #[gpui::test] -// fn test_select_language() { -// let registry = Arc::new(LanguageRegistry::test()); -// registry.add(Arc::new(Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ))); -// registry.add(Arc::new(Language::new( -// LanguageConfig { -// name: "Make".into(), -// path_suffixes: vec!["Makefile".to_string(), "mk".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ))); + // matching filename + assert_eq!( + registry + .language_for_file("zed/Makefile", None) + .now_or_never() + .and_then(|l| Some(l.ok()?.name())), + Some("Make".into()) + ); -// // matching file extension -// assert_eq!( -// registry -// .language_for_file("zed/lib.rs", None) -// .now_or_never() -// .and_then(|l| Some(l.ok()?.name())), -// Some("Rust".into()) -// ); -// assert_eq!( -// registry -// .language_for_file("zed/lib.mk", None) -// .now_or_never() -// .and_then(|l| Some(l.ok()?.name())), -// Some("Make".into()) -// ); + // matching suffix that is not the full file extension or filename + assert_eq!( + registry + .language_for_file("zed/cars", None) + .now_or_never() + .and_then(|l| Some(l.ok()?.name())), + None + ); + assert_eq!( + registry + .language_for_file("zed/a.cars", None) + .now_or_never() + .and_then(|l| Some(l.ok()?.name())), + None + ); + assert_eq!( + registry + .language_for_file("zed/sumk", None) + .now_or_never() + .and_then(|l| Some(l.ok()?.name())), + None + ); +} -// // matching filename -// assert_eq!( -// registry -// .language_for_file("zed/Makefile", None) -// .now_or_never() -// .and_then(|l| Some(l.ok()?.name())), -// Some("Make".into()) -// ); +#[gpui2::test] +fn test_edit_events(cx: &mut gpui2::AppContext) { + let mut now = Instant::now(); + let buffer_1_events = Arc::new(Mutex::new(Vec::new())); + let buffer_2_events = Arc::new(Mutex::new(Vec::new())); -// // matching suffix that is not the full file extension or filename -// assert_eq!( -// registry -// .language_for_file("zed/cars", None) -// .now_or_never() -// .and_then(|l| Some(l.ok()?.name())), -// None -// ); -// assert_eq!( -// registry -// .language_for_file("zed/a.cars", None) -// .now_or_never() -// .and_then(|l| Some(l.ok()?.name())), -// None -// ); -// assert_eq!( -// registry -// .language_for_file("zed/sumk", None) -// .now_or_never() -// .and_then(|l| Some(l.ok()?.name())), -// None -// ); -// } + let buffer1 = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef")); + let buffer2 = cx.entity(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef")); + let buffer1_ops = Arc::new(Mutex::new(Vec::new())); + buffer1.update(cx, { + let buffer1_ops = buffer1_ops.clone(); + |buffer, cx| { + let buffer_1_events = buffer_1_events.clone(); + cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() { + Event::Operation(op) => buffer1_ops.lock().push(op), + event => buffer_1_events.lock().push(event), + }) + .detach(); + let buffer_2_events = buffer_2_events.clone(); + cx.subscribe(&buffer2, move |_, _, event, _| { + buffer_2_events.lock().push(event.clone()) + }) + .detach(); -// #[gpui::test] -// fn test_edit_events(cx: &mut gpui::AppContext) { -// let mut now = Instant::now(); -// let buffer_1_events = Rc::new(RefCell::new(Vec::new())); -// let buffer_2_events = Rc::new(RefCell::new(Vec::new())); + // An edit emits an edited event, followed by a dirty changed event, + // since the buffer was previously in a clean state. + buffer.edit([(2..4, "XYZ")], None, cx); -// let buffer1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcdef")); -// let buffer2 = cx.add_model(|cx| Buffer::new(1, cx.model_id() as u64, "abcdef")); -// let buffer1_ops = Rc::new(RefCell::new(Vec::new())); -// buffer1.update(cx, { -// let buffer1_ops = buffer1_ops.clone(); -// |buffer, cx| { -// let buffer_1_events = buffer_1_events.clone(); -// cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() { -// Event::Operation(op) => buffer1_ops.borrow_mut().push(op), -// event => buffer_1_events.borrow_mut().push(event), -// }) -// .detach(); -// let buffer_2_events = buffer_2_events.clone(); -// cx.subscribe(&buffer2, move |_, _, event, _| { -// buffer_2_events.borrow_mut().push(event.clone()) -// }) -// .detach(); + // An empty transaction does not emit any events. + buffer.start_transaction(); + buffer.end_transaction(cx); -// // An edit emits an edited event, followed by a dirty changed event, -// // since the buffer was previously in a clean state. -// buffer.edit([(2..4, "XYZ")], None, cx); + // A transaction containing two edits emits one edited event. + now += Duration::from_secs(1); + buffer.start_transaction_at(now); + buffer.edit([(5..5, "u")], None, cx); + buffer.edit([(6..6, "w")], None, cx); + buffer.end_transaction_at(now, cx); -// // An empty transaction does not emit any events. -// buffer.start_transaction(); -// buffer.end_transaction(cx); + // Undoing a transaction emits one edited event. + buffer.undo(cx); + } + }); -// // A transaction containing two edits emits one edited event. -// now += Duration::from_secs(1); -// buffer.start_transaction_at(now); -// buffer.edit([(5..5, "u")], None, cx); -// buffer.edit([(6..6, "w")], None, cx); -// buffer.end_transaction_at(now, cx); + // Incorporating a set of remote ops emits a single edited event, + // followed by a dirty changed event. + buffer2.update(cx, |buffer, cx| { + buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap(); + }); + assert_eq!( + mem::take(&mut *buffer_1_events.lock()), + vec![ + Event::Edited, + Event::DirtyChanged, + Event::Edited, + Event::Edited, + ] + ); + assert_eq!( + mem::take(&mut *buffer_2_events.lock()), + vec![Event::Edited, Event::DirtyChanged] + ); -// // Undoing a transaction emits one edited event. -// buffer.undo(cx); -// } -// }); + buffer1.update(cx, |buffer, cx| { + // Undoing the first transaction emits edited event, followed by a + // dirty changed event, since the buffer is again in a clean state. + buffer.undo(cx); + }); + // Incorporating the remote ops again emits a single edited event, + // followed by a dirty changed event. + buffer2.update(cx, |buffer, cx| { + buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap(); + }); + assert_eq!( + mem::take(&mut *buffer_1_events.lock()), + vec![Event::Edited, Event::DirtyChanged,] + ); + assert_eq!( + mem::take(&mut *buffer_2_events.lock()), + vec![Event::Edited, Event::DirtyChanged] + ); +} -// // Incorporating a set of remote ops emits a single edited event, -// // followed by a dirty changed event. -// buffer2.update(cx, |buffer, cx| { -// buffer -// .apply_ops(buffer1_ops.borrow_mut().drain(..), cx) -// .unwrap(); -// }); -// assert_eq!( -// mem::take(&mut *buffer_1_events.borrow_mut()), -// vec![ -// Event::Edited, -// Event::DirtyChanged, -// Event::Edited, -// Event::Edited, -// ] -// ); -// assert_eq!( -// mem::take(&mut *buffer_2_events.borrow_mut()), -// vec![Event::Edited, Event::DirtyChanged] -// ); - -// buffer1.update(cx, |buffer, cx| { -// // Undoing the first transaction emits edited event, followed by a -// // dirty changed event, since the buffer is again in a clean state. -// buffer.undo(cx); -// }); -// // Incorporating the remote ops again emits a single edited event, -// // followed by a dirty changed event. -// buffer2.update(cx, |buffer, cx| { -// buffer -// .apply_ops(buffer1_ops.borrow_mut().drain(..), cx) -// .unwrap(); -// }); -// assert_eq!( -// mem::take(&mut *buffer_1_events.borrow_mut()), -// vec![Event::Edited, Event::DirtyChanged,] -// ); -// assert_eq!( -// mem::take(&mut *buffer_2_events.borrow_mut()), -// vec![Event::Edited, Event::DirtyChanged] -// ); -// } - -// #[gpui::test] todo!() #[gpui2::test] async fn test_apply_diff(cx: &mut TestAppContext) { let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n"; @@ -247,88 +238,89 @@ async fn test_apply_diff(cx: &mut TestAppContext) { }); } -// #[gpui::test(iterations = 10)] -// async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) { -// let text = [ -// "zero", // -// "one ", // 2 trailing spaces -// "two", // -// "three ", // 3 trailing spaces -// "four", // -// "five ", // 4 trailing spaces -// ] -// .join("\n"); +#[gpui2::test(iterations = 10)] +async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) { + let text = [ + "zero", // + "one ", // 2 trailing spaces + "two", // + "three ", // 3 trailing spaces + "four", // + "five ", // 4 trailing spaces + ] + .join("\n"); -// let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text)); + let buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); -// // Spawn a task to format the buffer's whitespace. -// // Pause so that the foratting task starts running. -// let format = buffer.read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx)); -// smol::future::yield_now().await; + // Spawn a task to format the buffer's whitespace. + // Pause so that the foratting task starts running. + let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx)); + smol::future::yield_now().await; -// // Edit the buffer while the normalization task is running. -// let version_before_edit = buffer.read_with(cx, |buffer, _| buffer.version()); -// buffer.update(cx, |buffer, cx| { -// buffer.edit( -// [ -// (Point::new(0, 1)..Point::new(0, 1), "EE"), -// (Point::new(3, 5)..Point::new(3, 5), "EEE"), -// ], -// None, -// cx, -// ); -// }); + // Edit the buffer while the normalization task is running. + let version_before_edit = buffer.update(cx, |buffer, _| buffer.version()); + buffer.update(cx, |buffer, cx| { + buffer.edit( + [ + (Point::new(0, 1)..Point::new(0, 1), "EE"), + (Point::new(3, 5)..Point::new(3, 5), "EEE"), + ], + None, + cx, + ); + }); -// let format_diff = format.await; -// buffer.update(cx, |buffer, cx| { -// let version_before_format = format_diff.base_version.clone(); -// buffer.apply_diff(format_diff, cx); + let format_diff = format.await; + buffer.update(cx, |buffer, cx| { + let version_before_format = format_diff.base_version.clone(); + buffer.apply_diff(format_diff, cx); -// // The outcome depends on the order of concurrent taks. -// // -// // If the edit occurred while searching for trailing whitespace ranges, -// // then the trailing whitespace region touched by the edit is left intact. -// if version_before_format == version_before_edit { -// assert_eq!( -// buffer.text(), -// [ -// "zEEero", // -// "one", // -// "two", // -// "threeEEE ", // -// "four", // -// "five", // -// ] -// .join("\n") -// ); -// } -// // Otherwise, all trailing whitespace is removed. -// else { -// assert_eq!( -// buffer.text(), -// [ -// "zEEero", // -// "one", // -// "two", // -// "threeEEE", // -// "four", // -// "five", // -// ] -// .join("\n") -// ); -// } -// }); -// } + // The outcome depends on the order of concurrent taks. + // + // If the edit occurred while searching for trailing whitespace ranges, + // then the trailing whitespace region touched by the edit is left intact. + if version_before_format == version_before_edit { + assert_eq!( + buffer.text(), + [ + "zEEero", // + "one", // + "two", // + "threeEEE ", // + "four", // + "five", // + ] + .join("\n") + ); + } + // Otherwise, all trailing whitespace is removed. + else { + assert_eq!( + buffer.text(), + [ + "zEEero", // + "one", // + "two", // + "threeEEE", // + "four", // + "five", // + ] + .join("\n") + ); + } + }); +} -// #[gpui::test] -// async fn test_reparse(cx: &mut gpui::TestAppContext) { +// #[gpui2::test] +// async fn test_reparse(cx: &mut gpui2::TestAppContext) { // let text = "fn a() {}"; -// let buffer = cx.add_model(|cx| { -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx) +// let buffer = cx.entity(|cx| { +// Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx) // }); // // Wait for the initial text to parse -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; +// cx.executor().run_until_parked(); +// assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing())); // assert_eq!( // get_tree_sexp(&buffer, cx), // concat!( @@ -359,7 +351,8 @@ async fn test_apply_diff(cx: &mut TestAppContext) { // assert_eq!(buf.text(), "fn a(b: C) { d; }"); // assert!(buf.is_parsing()); // }); -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; +// cx.executor().run_until_parked(); +// assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing())); // assert_eq!( // get_tree_sexp(&buffer, cx), // concat!( @@ -391,7 +384,7 @@ async fn test_apply_diff(cx: &mut TestAppContext) { // assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); // assert!(buf.is_parsing()); // }); -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; +// cx.executor().run_until_parked(); // assert_eq!( // get_tree_sexp(&buffer, cx), // concat!( @@ -413,7 +406,8 @@ async fn test_apply_diff(cx: &mut TestAppContext) { // assert_eq!(buf.text(), "fn a() {}"); // assert!(buf.is_parsing()); // }); -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; + +// cx.executor().run_until_parked(); // assert_eq!( // get_tree_sexp(&buffer, cx), // concat!( @@ -431,7 +425,7 @@ async fn test_apply_diff(cx: &mut TestAppContext) { // assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); // assert!(buf.is_parsing()); // }); -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; +// cx.executor().run_until_parked(); // assert_eq!( // get_tree_sexp(&buffer, cx), // concat!( @@ -446,2002 +440,2006 @@ async fn test_apply_diff(cx: &mut TestAppContext) { // ); // } -// #[gpui::test] -// async fn test_resetting_language(cx: &mut gpui::TestAppContext) { -// let buffer = cx.add_model(|cx| { -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, "{}").with_language(Arc::new(rust_lang()), cx); -// buffer.set_sync_parse_timeout(Duration::ZERO); -// buffer -// }); - -// // Wait for the initial text to parse -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; -// assert_eq!( -// get_tree_sexp(&buffer, cx), -// "(source_file (expression_statement (block)))" -// ); - -// buffer.update(cx, |buffer, cx| { -// buffer.set_language(Some(Arc::new(json_lang())), cx) -// }); -// buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await; -// assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))"); -// } - -// #[gpui::test] -// async fn test_outline(cx: &mut gpui::TestAppContext) { -// let text = r#" -// struct Person { -// name: String, -// age: usize, -// } - -// mod module { -// enum LoginState { -// LoggedOut, -// LoggingOn, -// LoggedIn { -// person: Person, -// time: Instant, -// } -// } -// } - -// impl Eq for Person {} - -// impl Drop for Person { -// fn drop(&mut self) { -// println!("bye"); -// } -// } -// "# -// .unindent(); - -// let buffer = cx.add_model(|cx| { -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx) -// }); -// let outline = buffer -// .read_with(cx, |buffer, _| buffer.snapshot().outline(None)) -// .unwrap(); - -// assert_eq!( -// outline -// .items -// .iter() -// .map(|item| (item.text.as_str(), item.depth)) -// .collect::>(), -// &[ -// ("struct Person", 0), -// ("name", 1), -// ("age", 1), -// ("mod module", 0), -// ("enum LoginState", 1), -// ("LoggedOut", 2), -// ("LoggingOn", 2), -// ("LoggedIn", 2), -// ("person", 3), -// ("time", 3), -// ("impl Eq for Person", 0), -// ("impl Drop for Person", 0), -// ("fn drop", 1), -// ] -// ); - -// // Without space, we only match on names -// assert_eq!( -// search(&outline, "oon", cx).await, -// &[ -// ("mod module", vec![]), // included as the parent of a match -// ("enum LoginState", vec![]), // included as the parent of a match -// ("LoggingOn", vec![1, 7, 8]), // matches -// ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names -// ] -// ); - -// assert_eq!( -// search(&outline, "dp p", cx).await, -// &[ -// ("impl Drop for Person", vec![5, 8, 9, 14]), -// ("fn drop", vec![]), -// ] -// ); -// assert_eq!( -// search(&outline, "dpn", cx).await, -// &[("impl Drop for Person", vec![5, 14, 19])] -// ); -// assert_eq!( -// search(&outline, "impl ", cx).await, -// &[ -// ("impl Eq for Person", vec![0, 1, 2, 3, 4]), -// ("impl Drop for Person", vec![0, 1, 2, 3, 4]), -// ("fn drop", vec![]), -// ] -// ); - -// async fn search<'a>( -// outline: &'a Outline, -// query: &'a str, -// cx: &'a gpui::TestAppContext, -// ) -> Vec<(&'a str, Vec)> { -// let matches = cx -// .read(|cx| outline.search(query, cx.background().clone())) -// .await; -// matches -// .into_iter() -// .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions)) -// .collect::>() -// } -// } - -// #[gpui::test] -// async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) { -// let text = r#" -// impl A for B< -// C -// > { -// }; -// "# -// .unindent(); - -// let buffer = cx.add_model(|cx| { -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx) -// }); -// let outline = buffer -// .read_with(cx, |buffer, _| buffer.snapshot().outline(None)) -// .unwrap(); - -// assert_eq!( -// outline -// .items -// .iter() -// .map(|item| (item.text.as_str(), item.depth)) -// .collect::>(), -// &[("impl A for B<", 0)] -// ); -// } - -// #[gpui::test] -// async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) { -// let language = javascript_lang() -// .with_outline_query( -// r#" -// (function_declaration -// "function" @context -// name: (_) @name -// parameters: (formal_parameters -// "(" @context.extra -// ")" @context.extra)) @item -// "#, -// ) -// .unwrap(); - -// let text = r#" -// function a() {} -// function b(c) {} -// "# -// .unindent(); - -// let buffer = cx.add_model(|cx| { -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(language), cx) -// }); -// let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - -// // extra context nodes are included in the outline. -// let outline = snapshot.outline(None).unwrap(); -// assert_eq!( -// outline -// .items -// .iter() -// .map(|item| (item.text.as_str(), item.depth)) -// .collect::>(), -// &[("function a()", 0), ("function b( )", 0),] -// ); - -// // extra context nodes do not appear in breadcrumbs. -// let symbols = snapshot.symbols_containing(3, None).unwrap(); -// assert_eq!( -// symbols -// .iter() -// .map(|item| (item.text.as_str(), item.depth)) -// .collect::>(), -// &[("function a", 0)] -// ); -// } - -// #[gpui::test] -// async fn test_symbols_containing(cx: &mut gpui::TestAppContext) { -// let text = r#" -// impl Person { -// fn one() { -// 1 -// } - -// fn two() { -// 2 -// }fn three() { -// 3 -// } -// } -// "# -// .unindent(); - -// let buffer = cx.add_model(|cx| { -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx) -// }); -// let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - -// // point is at the start of an item -// assert_eq!( -// symbols_containing(Point::new(1, 4), &snapshot), -// vec![ -// ( -// "impl Person".to_string(), -// Point::new(0, 0)..Point::new(10, 1) -// ), -// ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) -// ] -// ); - -// // point is in the middle of an item -// assert_eq!( -// symbols_containing(Point::new(2, 8), &snapshot), -// vec![ -// ( -// "impl Person".to_string(), -// Point::new(0, 0)..Point::new(10, 1) -// ), -// ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) -// ] -// ); - -// // point is at the end of an item -// assert_eq!( -// symbols_containing(Point::new(3, 5), &snapshot), -// vec![ -// ( -// "impl Person".to_string(), -// Point::new(0, 0)..Point::new(10, 1) -// ), -// ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) -// ] -// ); - -// // point is in between two adjacent items -// assert_eq!( -// symbols_containing(Point::new(7, 5), &snapshot), -// vec![ -// ( -// "impl Person".to_string(), -// Point::new(0, 0)..Point::new(10, 1) -// ), -// ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5)) -// ] -// ); - -// fn symbols_containing( -// position: Point, -// snapshot: &BufferSnapshot, -// ) -> Vec<(String, Range)> { -// snapshot -// .symbols_containing(position, None) -// .unwrap() -// .into_iter() -// .map(|item| { -// ( -// item.text, -// item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot), -// ) -// }) -// .collect() -// } -// } - -// #[gpui::test] -// fn test_enclosing_bracket_ranges(cx: &mut AppContext) { -// let mut assert = |selection_text, range_markers| { -// assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx) -// }; - -// assert( -// indoc! {" -// mod x { -// moˇd y { - -// } -// } -// let foo = 1;"}, -// vec![indoc! {" -// mod x «{» -// mod y { - -// } -// «}» -// let foo = 1;"}], -// ); - -// assert( -// indoc! {" -// mod x { -// mod y ˇ{ - -// } -// } -// let foo = 1;"}, -// vec![ -// indoc! {" -// mod x «{» -// mod y { - -// } -// «}» -// let foo = 1;"}, -// indoc! {" -// mod x { -// mod y «{» - -// «}» -// } -// let foo = 1;"}, -// ], -// ); - -// assert( -// indoc! {" -// mod x { -// mod y { - -// }ˇ -// } -// let foo = 1;"}, -// vec![ -// indoc! {" -// mod x «{» -// mod y { - -// } -// «}» -// let foo = 1;"}, -// indoc! {" -// mod x { -// mod y «{» - -// «}» -// } -// let foo = 1;"}, -// ], -// ); - -// assert( -// indoc! {" -// mod x { -// mod y { - -// } -// ˇ} -// let foo = 1;"}, -// vec![indoc! {" -// mod x «{» -// mod y { - -// } -// «}» -// let foo = 1;"}], -// ); - -// assert( -// indoc! {" -// mod x { -// mod y { - -// } -// } -// let fˇoo = 1;"}, -// vec![], -// ); - -// // Regression test: avoid crash when querying at the end of the buffer. -// assert( -// indoc! {" -// mod x { -// mod y { - -// } -// } -// let foo = 1;ˇ"}, -// vec![], -// ); -// } - -// #[gpui::test] -// fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) { -// let mut assert = |selection_text, bracket_pair_texts| { -// assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx) -// }; - -// assert( -// indoc! {" -// for (const a in b)ˇ { -// // a comment that's longer than the for-loop header -// }"}, -// vec![indoc! {" -// for «(»const a in b«)» { -// // a comment that's longer than the for-loop header -// }"}], -// ); - -// // Regression test: even though the parent node of the parentheses (the for loop) does -// // intersect the given range, the parentheses themselves do not contain the range, so -// // they should not be returned. Only the curly braces contain the range. -// assert( -// indoc! {" -// for (const a in b) {ˇ -// // a comment that's longer than the for-loop header -// }"}, -// vec![indoc! {" -// for (const a in b) «{» -// // a comment that's longer than the for-loop header -// «}»"}], -// ); -// } - -// #[gpui::test] -// fn test_range_for_syntax_ancestor(cx: &mut AppContext) { -// cx.add_model(|cx| { -// let text = "fn a() { b(|c| {}) }"; -// let buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); -// let snapshot = buffer.snapshot(); - -// assert_eq!( -// snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")), -// Some(range_of(text, "|")) -// ); -// assert_eq!( -// snapshot.range_for_syntax_ancestor(range_of(text, "|")), -// Some(range_of(text, "|c|")) -// ); -// assert_eq!( -// snapshot.range_for_syntax_ancestor(range_of(text, "|c|")), -// Some(range_of(text, "|c| {}")) -// ); -// assert_eq!( -// snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")), -// Some(range_of(text, "(|c| {})")) -// ); - -// buffer -// }); - -// fn empty_range_at(text: &str, part: &str) -> Range { -// let start = text.find(part).unwrap(); -// start..start -// } - -// fn range_of(text: &str, part: &str) -> Range { -// let start = text.find(part).unwrap(); -// start..start + part.len() -// } -// } - -// #[gpui::test] -// fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = "fn a() {}"; -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); - -// buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx); -// assert_eq!(buffer.text(), "fn a() {\n \n}"); - -// buffer.edit( -// [(Point::new(1, 4)..Point::new(1, 4), "b()\n")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "fn a() {\n b()\n \n}"); - -// // Create a field expression on a new line, causing that line -// // to be indented. -// buffer.edit( -// [(Point::new(2, 4)..Point::new(2, 4), ".c")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}"); - -// // Remove the dot so that the line is no longer a field expression, -// // causing the line to be outdented. -// buffer.edit( -// [(Point::new(2, 8)..Point::new(2, 9), "")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}"); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { -// init_settings(cx, |settings| { -// settings.defaults.hard_tabs = Some(true); -// }); - -// cx.add_model(|cx| { -// let text = "fn a() {}"; -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); - -// buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx); -// assert_eq!(buffer.text(), "fn a() {\n\t\n}"); - -// buffer.edit( -// [(Point::new(1, 1)..Point::new(1, 1), "b()\n")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}"); - -// // Create a field expression on a new line, causing that line -// // to be indented. -// buffer.edit( -// [(Point::new(2, 1)..Point::new(2, 1), ".c")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}"); - -// // Remove the dot so that the line is no longer a field expression, -// // causing the line to be outdented. -// buffer.edit( -// [(Point::new(2, 2)..Point::new(2, 3), "")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}"); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let mut buffer = Buffer::new( -// 0, -// cx.model_id() as u64, -// " -// fn a() { -// c; -// d; -// } -// " -// .unindent(), -// ) -// .with_language(Arc::new(rust_lang()), cx); - -// // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, -// // their indentation is not adjusted. -// buffer.edit_via_marked_text( -// &" -// fn a() { -// c«()»; -// d«()»; -// } -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a() { -// c(); -// d(); -// } -// " -// .unindent() -// ); - -// // When appending new content after these lines, the indentation is based on the -// // preceding lines' actual indentation. -// buffer.edit_via_marked_text( -// &" -// fn a() { -// c« -// .f -// .g()»; -// d« -// .f -// .g()»; -// } -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); - -// assert_eq!( -// buffer.text(), -// " -// fn a() { -// c -// .f -// .g(); -// d -// .f -// .g(); -// } -// " -// .unindent() -// ); -// buffer -// }); - -// cx.add_model(|cx| { -// let mut buffer = Buffer::new( -// 0, -// cx.model_id() as u64, -// " -// fn a() { -// b(); -// | -// " -// .replace("|", "") // marker to preserve trailing whitespace -// .unindent(), -// ) -// .with_language(Arc::new(rust_lang()), cx); - -// // Insert a closing brace. It is outdented. -// buffer.edit_via_marked_text( -// &" -// fn a() { -// b(); -// «}» -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a() { -// b(); -// } -// " -// .unindent() -// ); - -// // Manually edit the leading whitespace. The edit is preserved. -// buffer.edit_via_marked_text( -// &" -// fn a() { -// b(); -// « »} -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a() { -// b(); -// } -// " -// .unindent() -// ); -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let mut buffer = Buffer::new( -// 0, -// cx.model_id() as u64, -// " -// fn a() { -// i -// } -// " -// .unindent(), -// ) -// .with_language(Arc::new(rust_lang()), cx); - -// // Regression test: line does not get outdented due to syntax error -// buffer.edit_via_marked_text( -// &" -// fn a() { -// i«f let Some(x) = y» -// } -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a() { -// if let Some(x) = y -// } -// " -// .unindent() -// ); - -// buffer.edit_via_marked_text( -// &" -// fn a() { -// if let Some(x) = y« {» -// } -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a() { -// if let Some(x) = y { -// } -// " -// .unindent() -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let mut buffer = Buffer::new( -// 0, -// cx.model_id() as u64, -// " -// fn a() {} -// " -// .unindent(), -// ) -// .with_language(Arc::new(rust_lang()), cx); - -// buffer.edit_via_marked_text( -// &" -// fn a(« -// b») {} -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a( -// b) {} -// " -// .unindent() -// ); - -// // The indentation suggestion changed because `@end` node (a close paren) -// // is now at the beginning of the line. -// buffer.edit_via_marked_text( -// &" -// fn a( -// ˇ) {} -// " -// .unindent(), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// fn a( -// ) {} -// " -// .unindent() -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = "a\nb"; -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); -// buffer.edit( -// [(0..1, "\n"), (2..3, "\n")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!(buffer.text(), "\n\n\n"); -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = " -// const a: usize = 1; -// fn b() { -// if c { -// let d = 2; -// } -// } -// " -// .unindent(); - -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); -// buffer.edit( -// [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// const a: usize = 1; -// fn b() { -// if c { -// e( -// f() -// ); -// let d = 2; -// } -// } -// " -// .unindent() -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_block_mode(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = r#" -// fn a() { -// b(); -// } -// "# -// .unindent(); -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); - -// // When this text was copied, both of the quotation marks were at the same -// // indent level, but the indentation of the first line was not included in -// // the copied text. This information is retained in the -// // 'original_indent_columns' vector. -// let original_indent_columns = vec![4]; -// let inserted_text = r#" -// " -// c -// d -// e -// " -// "# -// .unindent(); - -// // Insert the block at column zero. The entire block is indented -// // so that the first line matches the previous line's indentation. -// buffer.edit( -// [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], -// Some(AutoindentMode::Block { -// original_indent_columns: original_indent_columns.clone(), -// }), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// r#" -// fn a() { -// b(); -// " -// c -// d -// e -// " -// } -// "# -// .unindent() -// ); - -// // Grouping is disabled in tests, so we need 2 undos -// buffer.undo(cx); // Undo the auto-indent -// buffer.undo(cx); // Undo the original edit - -// // Insert the block at a deeper indent level. The entire block is outdented. -// buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx); -// buffer.edit( -// [(Point::new(2, 8)..Point::new(2, 8), inserted_text)], -// Some(AutoindentMode::Block { -// original_indent_columns: original_indent_columns.clone(), -// }), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// r#" -// fn a() { -// b(); -// " -// c -// d -// e -// " -// } -// "# -// .unindent() -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = r#" -// fn a() { -// if b() { - -// } -// } -// "# -// .unindent(); -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx); - -// // The original indent columns are not known, so this text is -// // auto-indented in a block as if the first line was copied in -// // its entirety. -// let original_indent_columns = Vec::new(); -// let inserted_text = " c\n .d()\n .e();"; - -// // Insert the block at column zero. The entire block is indented -// // so that the first line matches the previous line's indentation. -// buffer.edit( -// [(Point::new(2, 0)..Point::new(2, 0), inserted_text)], -// Some(AutoindentMode::Block { -// original_indent_columns: original_indent_columns.clone(), -// }), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// r#" -// fn a() { -// if b() { -// c -// .d() -// .e(); -// } -// } -// "# -// .unindent() -// ); - -// // Grouping is disabled in tests, so we need 2 undos -// buffer.undo(cx); // Undo the auto-indent -// buffer.undo(cx); // Undo the original edit - -// // Insert the block at a deeper indent level. The entire block is outdented. -// buffer.edit( -// [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))], -// None, -// cx, -// ); -// buffer.edit( -// [(Point::new(2, 12)..Point::new(2, 12), inserted_text)], -// Some(AutoindentMode::Block { -// original_indent_columns: Vec::new(), -// }), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// r#" -// fn a() { -// if b() { -// c -// .d() -// .e(); -// } -// } -// "# -// .unindent() -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = " -// * one -// - a -// - b -// * two -// " -// .unindent(); - -// let mut buffer = Buffer::new(0, cx.model_id() as u64, text).with_language( -// Arc::new(Language::new( -// LanguageConfig { -// name: "Markdown".into(), -// auto_indent_using_last_non_empty_line: false, -// ..Default::default() -// }, -// Some(tree_sitter_json::language()), -// )), -// cx, -// ); -// buffer.edit( -// [(Point::new(3, 0)..Point::new(3, 0), "\n")], -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -// * one -// - a -// - b - -// * two -// " -// .unindent() -// ); -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_with_injected_languages(cx: &mut AppContext) { -// init_settings(cx, |settings| { -// settings.languages.extend([ -// ( -// "HTML".into(), -// LanguageSettingsContent { -// tab_size: Some(2.try_into().unwrap()), -// ..Default::default() -// }, -// ), -// ( -// "JavaScript".into(), -// LanguageSettingsContent { -// tab_size: Some(8.try_into().unwrap()), -// ..Default::default() -// }, -// ), -// ]) -// }); - -// let html_language = Arc::new(html_lang()); - -// let javascript_language = Arc::new(javascript_lang()); - -// let language_registry = Arc::new(LanguageRegistry::test()); -// language_registry.add(html_language.clone()); -// language_registry.add(javascript_language.clone()); - -// cx.add_model(|cx| { -// let (text, ranges) = marked_text_ranges( -// &" -//
ˇ -//
-// -// ˇ -// -// " -// .unindent(), -// false, -// ); - -// let mut buffer = Buffer::new(0, cx.model_id() as u64, text); -// buffer.set_language_registry(language_registry); -// buffer.set_language(Some(html_language), cx); -// buffer.edit( -// ranges.into_iter().map(|range| (range, "\na")), -// Some(AutoindentMode::EachLine), -// cx, -// ); -// assert_eq!( -// buffer.text(), -// " -//
-// a -//
-// -// -// a -// -// " -// .unindent() -// ); -// buffer -// }); -// } - -// #[gpui::test] -// fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { -// init_settings(cx, |settings| { -// settings.defaults.tab_size = Some(2.try_into().unwrap()); -// }); - -// cx.add_model(|cx| { -// let mut buffer = -// Buffer::new(0, cx.model_id() as u64, "").with_language(Arc::new(ruby_lang()), cx); - -// let text = r#" -// class C -// def a(b, c) -// puts b -// puts c -// rescue -// puts "errored" -// exit 1 -// end -// end -// "# -// .unindent(); - -// buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx); - -// assert_eq!( -// buffer.text(), -// r#" -// class C -// def a(b, c) -// puts b -// puts c -// rescue -// puts "errored" -// exit 1 -// end -// end -// "# -// .unindent() -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_language_scope_at_with_javascript(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let language = Language::new( -// LanguageConfig { -// name: "JavaScript".into(), -// line_comment: Some("// ".into()), -// brackets: BracketPairConfig { -// pairs: vec![ -// BracketPair { -// start: "{".into(), -// end: "}".into(), -// close: true, -// newline: false, -// }, -// BracketPair { -// start: "'".into(), -// end: "'".into(), -// close: true, -// newline: false, -// }, -// ], -// disabled_scopes_by_bracket_ix: vec![ -// Vec::new(), // -// vec!["string".into()], -// ], -// }, -// overrides: [( -// "element".into(), -// LanguageConfigOverride { -// line_comment: Override::Remove { remove: true }, -// block_comment: Override::Set(("{/*".into(), "*/}".into())), -// ..Default::default() -// }, -// )] -// .into_iter() -// .collect(), -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_tsx()), -// ) -// .with_override_query( -// r#" -// (jsx_element) @element -// (string) @string -// "#, -// ) -// .unwrap(); - -// let text = r#"a["b"] = ;"#; - -// let buffer = -// Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(language), cx); -// let snapshot = buffer.snapshot(); - -// let config = snapshot.language_scope_at(0).unwrap(); -// assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); -// // Both bracket pairs are enabled -// assert_eq!( -// config.brackets().map(|e| e.1).collect::>(), -// &[true, true] -// ); - -// let string_config = snapshot.language_scope_at(3).unwrap(); -// assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// "); -// // Second bracket pair is disabled -// assert_eq!( -// string_config.brackets().map(|e| e.1).collect::>(), -// &[true, false] -// ); - -// let element_config = snapshot.language_scope_at(10).unwrap(); -// assert_eq!(element_config.line_comment_prefix(), None); -// assert_eq!( -// element_config.block_comment_delimiters(), -// Some((&"{/*".into(), &"*/}".into())) -// ); -// // Both bracket pairs are enabled -// assert_eq!( -// element_config.brackets().map(|e| e.1).collect::>(), -// &[true, true] -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_language_scope_at_with_rust(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// brackets: BracketPairConfig { -// pairs: vec![ -// BracketPair { -// start: "{".into(), -// end: "}".into(), -// close: true, -// newline: false, -// }, -// BracketPair { -// start: "'".into(), -// end: "'".into(), -// close: true, -// newline: false, -// }, -// ], -// disabled_scopes_by_bracket_ix: vec![ -// Vec::new(), // -// vec!["string".into()], -// ], -// }, -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ) -// .with_override_query( -// r#" -// (string_literal) @string -// "#, -// ) -// .unwrap(); - -// let text = r#" -// const S: &'static str = "hello"; -// "# -// .unindent(); - -// let buffer = Buffer::new(0, cx.model_id() as u64, text.clone()) -// .with_language(Arc::new(language), cx); -// let snapshot = buffer.snapshot(); - -// // By default, all brackets are enabled -// let config = snapshot.language_scope_at(0).unwrap(); -// assert_eq!( -// config.brackets().map(|e| e.1).collect::>(), -// &[true, true] -// ); - -// // Within a string, the quotation brackets are disabled. -// let string_config = snapshot -// .language_scope_at(text.find("ello").unwrap()) -// .unwrap(); -// assert_eq!( -// string_config.brackets().map(|e| e.1).collect::>(), -// &[true, false] -// ); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { -// init_settings(cx, |_| {}); - -// cx.add_model(|cx| { -// let text = r#" -//
    -// <% people.each do |person| %> -//
  1. -// <%= person.name %> -//
  2. -// <% end %> -//
-// "# -// .unindent(); - -// let language_registry = Arc::new(LanguageRegistry::test()); -// language_registry.add(Arc::new(ruby_lang())); -// language_registry.add(Arc::new(html_lang())); -// language_registry.add(Arc::new(erb_lang())); - -// let mut buffer = Buffer::new(0, cx.model_id() as u64, text); -// buffer.set_language_registry(language_registry.clone()); -// buffer.set_language( -// language_registry -// .language_for_name("ERB") -// .now_or_never() -// .unwrap() -// .ok(), -// cx, -// ); - -// let snapshot = buffer.snapshot(); -// let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap(); -// assert_eq!(html_config.line_comment_prefix(), None); -// assert_eq!( -// html_config.block_comment_delimiters(), -// Some((&"".into())) -// ); - -// let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap(); -// assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# "); -// assert_eq!(ruby_config.block_comment_delimiters(), None); - -// buffer -// }); -// } - -// #[gpui::test] -// fn test_serialization(cx: &mut gpui::AppContext) { -// let mut now = Instant::now(); - -// let buffer1 = cx.add_model(|cx| { -// let mut buffer = Buffer::new(0, cx.model_id() as u64, "abc"); -// buffer.edit([(3..3, "D")], None, cx); - -// now += Duration::from_secs(1); -// buffer.start_transaction_at(now); -// buffer.edit([(4..4, "E")], None, cx); -// buffer.end_transaction_at(now, cx); -// assert_eq!(buffer.text(), "abcDE"); - -// buffer.undo(cx); -// assert_eq!(buffer.text(), "abcD"); - -// buffer.edit([(4..4, "F")], None, cx); -// assert_eq!(buffer.text(), "abcDF"); -// buffer -// }); -// assert_eq!(buffer1.read(cx).text(), "abcDF"); - -// let state = buffer1.read(cx).to_proto(); -// let ops = cx -// .background() -// .block(buffer1.read(cx).serialize_ops(None, cx)); -// let buffer2 = cx.add_model(|cx| { -// let mut buffer = Buffer::from_proto(1, state, None).unwrap(); -// buffer -// .apply_ops( -// ops.into_iter() -// .map(|op| proto::deserialize_operation(op).unwrap()), -// cx, -// ) -// .unwrap(); -// buffer -// }); -// assert_eq!(buffer2.read(cx).text(), "abcDF"); -// } - -// #[gpui::test(iterations = 100)] -// fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { -// let min_peers = env::var("MIN_PEERS") -// .map(|i| i.parse().expect("invalid `MIN_PEERS` variable")) -// .unwrap_or(1); -// let max_peers = env::var("MAX_PEERS") -// .map(|i| i.parse().expect("invalid `MAX_PEERS` variable")) -// .unwrap_or(5); -// let operations = env::var("OPERATIONS") -// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) -// .unwrap_or(10); - -// let base_text_len = rng.gen_range(0..10); -// let base_text = RandomCharIter::new(&mut rng) -// .take(base_text_len) -// .collect::(); -// let mut replica_ids = Vec::new(); -// let mut buffers = Vec::new(); -// let network = Rc::new(RefCell::new(Network::new(rng.clone()))); -// let base_buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, base_text.as_str())); - -// for i in 0..rng.gen_range(min_peers..=max_peers) { -// let buffer = cx.add_model(|cx| { -// let state = base_buffer.read(cx).to_proto(); -// let ops = cx -// .background() -// .block(base_buffer.read(cx).serialize_ops(None, cx)); -// let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap(); -// buffer -// .apply_ops( -// ops.into_iter() -// .map(|op| proto::deserialize_operation(op).unwrap()), -// cx, -// ) -// .unwrap(); -// buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); -// let network = network.clone(); -// cx.subscribe(&cx.handle(), move |buffer, _, event, _| { -// if let Event::Operation(op) = event { -// network -// .borrow_mut() -// .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]); -// } -// }) -// .detach(); -// buffer -// }); -// buffers.push(buffer); -// replica_ids.push(i as ReplicaId); -// network.borrow_mut().add_peer(i as ReplicaId); -// log::info!("Adding initial peer with replica id {}", i); -// } - -// log::info!("initial text: {:?}", base_text); - -// let mut now = Instant::now(); -// let mut mutation_count = operations; -// let mut next_diagnostic_id = 0; -// let mut active_selections = BTreeMap::default(); -// loop { -// let replica_index = rng.gen_range(0..replica_ids.len()); -// let replica_id = replica_ids[replica_index]; -// let buffer = &mut buffers[replica_index]; -// let mut new_buffer = None; -// match rng.gen_range(0..100) { -// 0..=29 if mutation_count != 0 => { -// buffer.update(cx, |buffer, cx| { -// buffer.start_transaction_at(now); -// buffer.randomly_edit(&mut rng, 5, cx); -// buffer.end_transaction_at(now, cx); -// log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text()); -// }); -// mutation_count -= 1; -// } -// 30..=39 if mutation_count != 0 => { -// buffer.update(cx, |buffer, cx| { -// if rng.gen_bool(0.2) { -// log::info!("peer {} clearing active selections", replica_id); -// active_selections.remove(&replica_id); -// buffer.remove_active_selections(cx); -// } else { -// let mut selections = Vec::new(); -// for id in 0..rng.gen_range(1..=5) { -// let range = buffer.random_byte_range(0, &mut rng); -// selections.push(Selection { -// id, -// start: buffer.anchor_before(range.start), -// end: buffer.anchor_before(range.end), -// reversed: false, -// goal: SelectionGoal::None, -// }); -// } -// let selections: Arc<[Selection]> = selections.into(); -// log::info!( -// "peer {} setting active selections: {:?}", -// replica_id, -// selections -// ); -// active_selections.insert(replica_id, selections.clone()); -// buffer.set_active_selections(selections, false, Default::default(), cx); -// } -// }); -// mutation_count -= 1; -// } -// 40..=49 if mutation_count != 0 && replica_id == 0 => { -// let entry_count = rng.gen_range(1..=5); -// buffer.update(cx, |buffer, cx| { -// let diagnostics = DiagnosticSet::new( -// (0..entry_count).map(|_| { -// let range = buffer.random_byte_range(0, &mut rng); -// let range = range.to_point_utf16(buffer); -// let range = range.start..range.end; -// DiagnosticEntry { -// range, -// diagnostic: Diagnostic { -// message: post_inc(&mut next_diagnostic_id).to_string(), -// ..Default::default() -// }, -// } -// }), -// buffer, -// ); -// log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics); -// buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx); -// }); -// mutation_count -= 1; -// } -// 50..=59 if replica_ids.len() < max_peers => { -// let old_buffer_state = buffer.read(cx).to_proto(); -// let old_buffer_ops = cx -// .background() -// .block(buffer.read(cx).serialize_ops(None, cx)); -// let new_replica_id = (0..=replica_ids.len() as ReplicaId) -// .filter(|replica_id| *replica_id != buffer.read(cx).replica_id()) -// .choose(&mut rng) -// .unwrap(); -// log::info!( -// "Adding new replica {} (replicating from {})", -// new_replica_id, -// replica_id -// ); -// new_buffer = Some(cx.add_model(|cx| { -// let mut new_buffer = -// Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap(); -// new_buffer -// .apply_ops( -// old_buffer_ops -// .into_iter() -// .map(|op| deserialize_operation(op).unwrap()), -// cx, -// ) -// .unwrap(); -// log::info!( -// "New replica {} text: {:?}", -// new_buffer.replica_id(), -// new_buffer.text() -// ); -// new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); -// let network = network.clone(); -// cx.subscribe(&cx.handle(), move |buffer, _, event, _| { -// if let Event::Operation(op) = event { -// network.borrow_mut().broadcast( -// buffer.replica_id(), -// vec![proto::serialize_operation(op)], -// ); -// } -// }) -// .detach(); -// new_buffer -// })); -// network.borrow_mut().replicate(replica_id, new_replica_id); - -// if new_replica_id as usize == replica_ids.len() { -// replica_ids.push(new_replica_id); -// } else { -// let new_buffer = new_buffer.take().unwrap(); -// while network.borrow().has_unreceived(new_replica_id) { -// let ops = network -// .borrow_mut() -// .receive(new_replica_id) -// .into_iter() -// .map(|op| proto::deserialize_operation(op).unwrap()); -// if ops.len() > 0 { -// log::info!( -// "peer {} (version: {:?}) applying {} ops from the network. {:?}", -// new_replica_id, -// buffer.read(cx).version(), -// ops.len(), -// ops -// ); -// new_buffer.update(cx, |new_buffer, cx| { -// new_buffer.apply_ops(ops, cx).unwrap(); -// }); -// } -// } -// buffers[new_replica_id as usize] = new_buffer; -// } -// } -// 60..=69 if mutation_count != 0 => { -// buffer.update(cx, |buffer, cx| { -// buffer.randomly_undo_redo(&mut rng, cx); -// log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text()); -// }); -// mutation_count -= 1; -// } -// _ if network.borrow().has_unreceived(replica_id) => { -// let ops = network -// .borrow_mut() -// .receive(replica_id) -// .into_iter() -// .map(|op| proto::deserialize_operation(op).unwrap()); -// if ops.len() > 0 { -// log::info!( -// "peer {} (version: {:?}) applying {} ops from the network. {:?}", -// replica_id, -// buffer.read(cx).version(), -// ops.len(), -// ops -// ); -// buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap()); -// } -// } -// _ => {} -// } - -// now += Duration::from_millis(rng.gen_range(0..=200)); -// buffers.extend(new_buffer); - -// for buffer in &buffers { -// buffer.read(cx).check_invariants(); -// } - -// if mutation_count == 0 && network.borrow().is_idle() { -// break; -// } -// } - -// let first_buffer = buffers[0].read(cx).snapshot(); -// for buffer in &buffers[1..] { -// let buffer = buffer.read(cx).snapshot(); -// assert_eq!( -// buffer.version(), -// first_buffer.version(), -// "Replica {} version != Replica 0 version", -// buffer.replica_id() -// ); -// assert_eq!( -// buffer.text(), -// first_buffer.text(), -// "Replica {} text != Replica 0 text", -// buffer.replica_id() -// ); -// assert_eq!( -// buffer -// .diagnostics_in_range::<_, usize>(0..buffer.len(), false) -// .collect::>(), -// first_buffer -// .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false) -// .collect::>(), -// "Replica {} diagnostics != Replica 0 diagnostics", -// buffer.replica_id() -// ); -// } - -// for buffer in &buffers { -// let buffer = buffer.read(cx).snapshot(); -// let actual_remote_selections = buffer -// .remote_selections_in_range(Anchor::MIN..Anchor::MAX) -// .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::>())) -// .collect::>(); -// let expected_remote_selections = active_selections -// .iter() -// .filter(|(replica_id, _)| **replica_id != buffer.replica_id()) -// .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::>())) -// .collect::>(); -// assert_eq!( -// actual_remote_selections, -// expected_remote_selections, -// "Replica {} remote selections != expected selections", -// buffer.replica_id() -// ); -// } -// } - -// #[test] -// fn test_contiguous_ranges() { -// assert_eq!( -// contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::>(), -// &[1..4, 5..7, 9..13] -// ); - -// // Respects the `max_len` parameter -// assert_eq!( -// contiguous_ranges( -// [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(), -// 3 -// ) -// .collect::>(), -// &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32], -// ); -// } - -// #[gpui::test(iterations = 500)] -// fn test_trailing_whitespace_ranges(mut rng: StdRng) { -// // Generate a random multi-line string containing -// // some lines with trailing whitespace. -// let mut text = String::new(); -// for _ in 0..rng.gen_range(0..16) { -// for _ in 0..rng.gen_range(0..36) { -// text.push(match rng.gen_range(0..10) { -// 0..=1 => ' ', -// 3 => '\t', -// _ => rng.gen_range('a'..'z'), -// }); -// } -// text.push('\n'); -// } - -// match rng.gen_range(0..10) { -// // sometimes remove the last newline -// 0..=1 => drop(text.pop()), // - -// // sometimes add extra newlines -// 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))), -// _ => {} -// } - -// let rope = Rope::from(text.as_str()); -// let actual_ranges = trailing_whitespace_ranges(&rope); -// let expected_ranges = TRAILING_WHITESPACE_REGEX -// .find_iter(&text) -// .map(|m| m.range()) -// .collect::>(); -// assert_eq!( -// actual_ranges, -// expected_ranges, -// "wrong ranges for text lines:\n{:?}", -// text.split("\n").collect::>() -// ); -// } - -// fn ruby_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "Ruby".into(), -// path_suffixes: vec!["rb".to_string()], -// line_comment: Some("# ".into()), -// ..Default::default() -// }, -// Some(tree_sitter_ruby::language()), -// ) -// .with_indents_query( -// r#" -// (class "end" @end) @indent -// (method "end" @end) @indent -// (rescue) @outdent -// (then) @indent -// "#, -// ) -// .unwrap() -// } - -// fn html_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "HTML".into(), -// block_comment: Some(("".into())), -// ..Default::default() -// }, -// Some(tree_sitter_html::language()), -// ) -// .with_indents_query( -// " -// (element -// (start_tag) @start -// (end_tag)? @end) @indent -// ", -// ) -// .unwrap() -// .with_injection_query( -// r#" -// (script_element -// (raw_text) @content -// (#set! "language" "javascript")) -// "#, -// ) -// .unwrap() -// } - -// fn erb_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "ERB".into(), -// path_suffixes: vec!["erb".to_string()], -// block_comment: Some(("<%#".into(), "%>".into())), -// ..Default::default() -// }, -// Some(tree_sitter_embedded_template::language()), -// ) -// .with_injection_query( -// r#" -// ( -// (code) @content -// (#set! "language" "ruby") -// (#set! "combined") -// ) - -// ( -// (content) @content -// (#set! "language" "html") -// (#set! "combined") -// ) -// "#, -// ) -// .unwrap() -// } - -// fn rust_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ) -// .with_indents_query( -// r#" -// (call_expression) @indent -// (field_expression) @indent -// (_ "(" ")" @end) @indent -// (_ "{" "}" @end) @indent -// "#, -// ) -// .unwrap() -// .with_brackets_query( -// r#" -// ("{" @open "}" @close) -// "#, -// ) -// .unwrap() -// .with_outline_query( -// r#" -// (struct_item -// "struct" @context -// name: (_) @name) @item -// (enum_item -// "enum" @context -// name: (_) @name) @item -// (enum_variant -// name: (_) @name) @item -// (field_declaration -// name: (_) @name) @item -// (impl_item -// "impl" @context -// trait: (_)? @name -// "for"? @context -// type: (_) @name) @item -// (function_item -// "fn" @context -// name: (_) @name) @item -// (mod_item -// "mod" @context -// name: (_) @name) @item -// "#, -// ) -// .unwrap() -// } - -// fn json_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "Json".into(), -// path_suffixes: vec!["js".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_json::language()), -// ) -// } - -// fn javascript_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "JavaScript".into(), -// ..Default::default() -// }, -// Some(tree_sitter_typescript::language_tsx()), -// ) -// .with_brackets_query( -// r#" -// ("{" @open "}" @close) -// ("(" @open ")" @close) -// "#, -// ) -// .unwrap() -// .with_indents_query( -// r#" -// (object "}" @end) @indent -// "#, -// ) -// .unwrap() -// } - -// fn get_tree_sexp(buffer: &ModelHandle, cx: &gpui::TestAppContext) -> String { -// buffer.read_with(cx, |buffer, _| { -// let snapshot = buffer.snapshot(); -// let layers = snapshot.syntax.layers(buffer.as_text_snapshot()); -// layers[0].node().to_sexp() -// }) -// } - -// // Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers` -// fn assert_bracket_pairs( -// selection_text: &'static str, -// bracket_pair_texts: Vec<&'static str>, -// language: Language, -// cx: &mut AppContext, -// ) { -// let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false); -// let buffer = cx.add_model(|cx| { -// Buffer::new(0, cx.model_id() as u64, expected_text.clone()) -// .with_language(Arc::new(language), cx) -// }); -// let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot()); - -// let selection_range = selection_ranges[0].clone(); - -// let bracket_pairs = bracket_pair_texts -// .into_iter() -// .map(|pair_text| { -// let (bracket_text, ranges) = marked_text_ranges(pair_text, false); -// assert_eq!(bracket_text, expected_text); -// (ranges[0].clone(), ranges[1].clone()) -// }) -// .collect::>(); - -// assert_set_eq!( -// buffer.bracket_ranges(selection_range).collect::>(), -// bracket_pairs -// ); -// } - -// fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) { -// cx.set_global(SettingsStore::test(cx)); -// crate::init(cx); -// cx.update_global::(|settings, cx| { -// settings.update_user_settings::(cx, f); -// }); -// } +#[gpui2::test] +async fn test_resetting_language(cx: &mut gpui2::TestAppContext) { + let buffer = cx.entity(|cx| { + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx); + buffer.set_sync_parse_timeout(Duration::ZERO); + buffer + }); + + // Wait for the initial text to parse + cx.executor().run_until_parked(); + assert_eq!( + get_tree_sexp(&buffer, cx), + "(source_file (expression_statement (block)))" + ); + + buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(Arc::new(json_lang())), cx) + }); + cx.executor().run_until_parked(); + assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))"); +} + +#[gpui2::test] +async fn test_outline(cx: &mut gpui2::TestAppContext) { + let text = r#" + struct Person { + name: String, + age: usize, + } + + mod module { + enum LoginState { + LoggedOut, + LoggingOn, + LoggedIn { + person: Person, + time: Instant, + } + } + } + + impl Eq for Person {} + + impl Drop for Person { + fn drop(&mut self) { + println!("bye"); + } + } + "# + .unindent(); + + let buffer = cx.entity(|cx| { + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx) + }); + let outline = buffer + .update(cx, |buffer, _| buffer.snapshot().outline(None)) + .unwrap(); + + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[ + ("struct Person", 0), + ("name", 1), + ("age", 1), + ("mod module", 0), + ("enum LoginState", 1), + ("LoggedOut", 2), + ("LoggingOn", 2), + ("LoggedIn", 2), + ("person", 3), + ("time", 3), + ("impl Eq for Person", 0), + ("impl Drop for Person", 0), + ("fn drop", 1), + ] + ); + + // Without space, we only match on names + assert_eq!( + search(&outline, "oon", cx).await, + &[ + ("mod module", vec![]), // included as the parent of a match + ("enum LoginState", vec![]), // included as the parent of a match + ("LoggingOn", vec![1, 7, 8]), // matches + ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names + ] + ); + + assert_eq!( + search(&outline, "dp p", cx).await, + &[ + ("impl Drop for Person", vec![5, 8, 9, 14]), + ("fn drop", vec![]), + ] + ); + assert_eq!( + search(&outline, "dpn", cx).await, + &[("impl Drop for Person", vec![5, 14, 19])] + ); + assert_eq!( + search(&outline, "impl ", cx).await, + &[ + ("impl Eq for Person", vec![0, 1, 2, 3, 4]), + ("impl Drop for Person", vec![0, 1, 2, 3, 4]), + ("fn drop", vec![]), + ] + ); + + async fn search<'a>( + outline: &'a Outline, + query: &'a str, + cx: &'a gpui2::TestAppContext, + ) -> Vec<(&'a str, Vec)> { + let matches = cx + .update(|cx| outline.search(query, cx.executor().clone())) + .await; + matches + .into_iter() + .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions)) + .collect::>() + } +} + +#[gpui2::test] +async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) { + let text = r#" + impl A for B< + C + > { + }; + "# + .unindent(); + + let buffer = cx.entity(|cx| { + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx) + }); + let outline = buffer + .update(cx, |buffer, _| buffer.snapshot().outline(None)) + .unwrap(); + + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[("impl A for B<", 0)] + ); +} + +#[gpui2::test] +async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) { + let language = javascript_lang() + .with_outline_query( + r#" + (function_declaration + "function" @context + name: (_) @name + parameters: (formal_parameters + "(" @context.extra + ")" @context.extra)) @item + "#, + ) + .unwrap(); + + let text = r#" + function a() {} + function b(c) {} + "# + .unindent(); + + let buffer = cx.entity(|cx| { + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx) + }); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + + // extra context nodes are included in the outline. + let outline = snapshot.outline(None).unwrap(); + assert_eq!( + outline + .items + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[("function a()", 0), ("function b( )", 0),] + ); + + // extra context nodes do not appear in breadcrumbs. + let symbols = snapshot.symbols_containing(3, None).unwrap(); + assert_eq!( + symbols + .iter() + .map(|item| (item.text.as_str(), item.depth)) + .collect::>(), + &[("function a", 0)] + ); +} + +#[gpui2::test] +async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) { + let text = r#" + impl Person { + fn one() { + 1 + } + + fn two() { + 2 + }fn three() { + 3 + } + } + "# + .unindent(); + + let buffer = cx.entity(|cx| { + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx) + }); + let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot()); + + // point is at the start of an item + assert_eq!( + symbols_containing(Point::new(1, 4), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) + ] + ); + + // point is in the middle of an item + assert_eq!( + symbols_containing(Point::new(2, 8), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) + ] + ); + + // point is at the end of an item + assert_eq!( + symbols_containing(Point::new(3, 5), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5)) + ] + ); + + // point is in between two adjacent items + assert_eq!( + symbols_containing(Point::new(7, 5), &snapshot), + vec![ + ( + "impl Person".to_string(), + Point::new(0, 0)..Point::new(10, 1) + ), + ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5)) + ] + ); + + fn symbols_containing( + position: Point, + snapshot: &BufferSnapshot, + ) -> Vec<(String, Range)> { + snapshot + .symbols_containing(position, None) + .unwrap() + .into_iter() + .map(|item| { + ( + item.text, + item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot), + ) + }) + .collect() + } +} + +#[gpui2::test] +fn test_enclosing_bracket_ranges(cx: &mut AppContext) { + let mut assert = |selection_text, range_markers| { + assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx) + }; + + assert( + indoc! {" + mod x { + moˇd y { + + } + } + let foo = 1;"}, + vec![indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}], + ); + + assert( + indoc! {" + mod x { + mod y ˇ{ + + } + } + let foo = 1;"}, + vec![ + indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}, + indoc! {" + mod x { + mod y «{» + + «}» + } + let foo = 1;"}, + ], + ); + + assert( + indoc! {" + mod x { + mod y { + + }ˇ + } + let foo = 1;"}, + vec![ + indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}, + indoc! {" + mod x { + mod y «{» + + «}» + } + let foo = 1;"}, + ], + ); + + assert( + indoc! {" + mod x { + mod y { + + } + ˇ} + let foo = 1;"}, + vec![indoc! {" + mod x «{» + mod y { + + } + «}» + let foo = 1;"}], + ); + + assert( + indoc! {" + mod x { + mod y { + + } + } + let fˇoo = 1;"}, + vec![], + ); + + // Regression test: avoid crash when querying at the end of the buffer. + assert( + indoc! {" + mod x { + mod y { + + } + } + let foo = 1;ˇ"}, + vec![], + ); +} + +#[gpui2::test] +fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) { + let mut assert = |selection_text, bracket_pair_texts| { + assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx) + }; + + assert( + indoc! {" + for (const a in b)ˇ { + // a comment that's longer than the for-loop header + }"}, + vec![indoc! {" + for «(»const a in b«)» { + // a comment that's longer than the for-loop header + }"}], + ); + + // Regression test: even though the parent node of the parentheses (the for loop) does + // intersect the given range, the parentheses themselves do not contain the range, so + // they should not be returned. Only the curly braces contain the range. + assert( + indoc! {" + for (const a in b) {ˇ + // a comment that's longer than the for-loop header + }"}, + vec![indoc! {" + for (const a in b) «{» + // a comment that's longer than the for-loop header + «}»"}], + ); +} + +#[gpui2::test] +fn test_range_for_syntax_ancestor(cx: &mut AppContext) { + cx.entity(|cx| { + let text = "fn a() { b(|c| {}) }"; + let buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + let snapshot = buffer.snapshot(); + + assert_eq!( + snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")), + Some(range_of(text, "|")) + ); + assert_eq!( + snapshot.range_for_syntax_ancestor(range_of(text, "|")), + Some(range_of(text, "|c|")) + ); + assert_eq!( + snapshot.range_for_syntax_ancestor(range_of(text, "|c|")), + Some(range_of(text, "|c| {}")) + ); + assert_eq!( + snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")), + Some(range_of(text, "(|c| {})")) + ); + + buffer + }); + + fn empty_range_at(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + } + + fn range_of(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + part.len() + } +} + +#[gpui2::test] +fn test_autoindent_with_soft_tabs(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = "fn a() {}"; + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + + buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx); + assert_eq!(buffer.text(), "fn a() {\n \n}"); + + buffer.edit( + [(Point::new(1, 4)..Point::new(1, 4), "b()\n")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "fn a() {\n b()\n \n}"); + + // Create a field expression on a new line, causing that line + // to be indented. + buffer.edit( + [(Point::new(2, 4)..Point::new(2, 4), ".c")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}"); + + // Remove the dot so that the line is no longer a field expression, + // causing the line to be outdented. + buffer.edit( + [(Point::new(2, 8)..Point::new(2, 9), "")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}"); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_with_hard_tabs(cx: &mut AppContext) { + init_settings(cx, |settings| { + settings.defaults.hard_tabs = Some(true); + }); + + cx.entity(|cx| { + let text = "fn a() {}"; + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + + buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx); + assert_eq!(buffer.text(), "fn a() {\n\t\n}"); + + buffer.edit( + [(Point::new(1, 1)..Point::new(1, 1), "b()\n")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}"); + + // Create a field expression on a new line, causing that line + // to be indented. + buffer.edit( + [(Point::new(2, 1)..Point::new(2, 1), ".c")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}"); + + // Remove the dot so that the line is no longer a field expression, + // causing the line to be outdented. + buffer.edit( + [(Point::new(2, 2)..Point::new(2, 3), "")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}"); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let entity_id = cx.entity_id(); + let mut buffer = Buffer::new( + 0, + entity_id.as_u64(), + " + fn a() { + c; + d; + } + " + .unindent(), + ) + .with_language(Arc::new(rust_lang()), cx); + + // Lines 2 and 3 don't match the indentation suggestion. When editing these lines, + // their indentation is not adjusted. + buffer.edit_via_marked_text( + &" + fn a() { + c«()»; + d«()»; + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + c(); + d(); + } + " + .unindent() + ); + + // When appending new content after these lines, the indentation is based on the + // preceding lines' actual indentation. + buffer.edit_via_marked_text( + &" + fn a() { + c« + .f + .g()»; + d« + .f + .g()»; + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + + assert_eq!( + buffer.text(), + " + fn a() { + c + .f + .g(); + d + .f + .g(); + } + " + .unindent() + ); + buffer + }); + + cx.entity(|cx| { + eprintln!("second buffer: {:?}", cx.entity_id()); + + let mut buffer = Buffer::new( + 0, + cx.entity_id().as_u64(), + " + fn a() { + b(); + | + " + .replace("|", "") // marker to preserve trailing whitespace + .unindent(), + ) + .with_language(Arc::new(rust_lang()), cx); + + // Insert a closing brace. It is outdented. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + «}» + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + b(); + } + " + .unindent() + ); + + // Manually edit the leading whitespace. The edit is preserved. + buffer.edit_via_marked_text( + &" + fn a() { + b(); + « »} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + b(); + } + " + .unindent() + ); + buffer + }); + + eprintln!("DONE"); +} + +#[gpui2::test] +fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let mut buffer = Buffer::new( + 0, + cx.entity_id().as_u64(), + " + fn a() { + i + } + " + .unindent(), + ) + .with_language(Arc::new(rust_lang()), cx); + + // Regression test: line does not get outdented due to syntax error + buffer.edit_via_marked_text( + &" + fn a() { + i«f let Some(x) = y» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + if let Some(x) = y + } + " + .unindent() + ); + + buffer.edit_via_marked_text( + &" + fn a() { + if let Some(x) = y« {» + } + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a() { + if let Some(x) = y { + } + " + .unindent() + ); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let mut buffer = Buffer::new( + 0, + cx.entity_id().as_u64(), + " + fn a() {} + " + .unindent(), + ) + .with_language(Arc::new(rust_lang()), cx); + + buffer.edit_via_marked_text( + &" + fn a(« + b») {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a( + b) {} + " + .unindent() + ); + + // The indentation suggestion changed because `@end` node (a close paren) + // is now at the beginning of the line. + buffer.edit_via_marked_text( + &" + fn a( + ˇ) {} + " + .unindent(), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + fn a( + ) {} + " + .unindent() + ); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = "a\nb"; + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + buffer.edit( + [(0..1, "\n"), (2..3, "\n")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!(buffer.text(), "\n\n\n"); + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_multi_line_insertion(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = " + const a: usize = 1; + fn b() { + if c { + let d = 2; + } + } + " + .unindent(); + + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + buffer.edit( + [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + const a: usize = 1; + fn b() { + if c { + e( + f() + ); + let d = 2; + } + } + " + .unindent() + ); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_block_mode(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = r#" + fn a() { + b(); + } + "# + .unindent(); + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + + // When this text was copied, both of the quotation marks were at the same + // indent level, but the indentation of the first line was not included in + // the copied text. This information is retained in the + // 'original_indent_columns' vector. + let original_indent_columns = vec![4]; + let inserted_text = r#" + " + c + d + e + " + "# + .unindent(); + + // Insert the block at column zero. The entire block is indented + // so that the first line matches the previous line's indentation. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())], + Some(AutoindentMode::Block { + original_indent_columns: original_indent_columns.clone(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + b(); + " + c + d + e + " + } + "# + .unindent() + ); + + // Grouping is disabled in tests, so we need 2 undos + buffer.undo(cx); // Undo the auto-indent + buffer.undo(cx); // Undo the original edit + + // Insert the block at a deeper indent level. The entire block is outdented. + buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx); + buffer.edit( + [(Point::new(2, 8)..Point::new(2, 8), inserted_text)], + Some(AutoindentMode::Block { + original_indent_columns: original_indent_columns.clone(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + b(); + " + c + d + e + " + } + "# + .unindent() + ); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = r#" + fn a() { + if b() { + + } + } + "# + .unindent(); + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx); + + // The original indent columns are not known, so this text is + // auto-indented in a block as if the first line was copied in + // its entirety. + let original_indent_columns = Vec::new(); + let inserted_text = " c\n .d()\n .e();"; + + // Insert the block at column zero. The entire block is indented + // so that the first line matches the previous line's indentation. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), inserted_text)], + Some(AutoindentMode::Block { + original_indent_columns: original_indent_columns.clone(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + // Grouping is disabled in tests, so we need 2 undos + buffer.undo(cx); // Undo the auto-indent + buffer.undo(cx); // Undo the original edit + + // Insert the block at a deeper indent level. The entire block is outdented. + buffer.edit( + [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))], + None, + cx, + ); + buffer.edit( + [(Point::new(2, 12)..Point::new(2, 12), inserted_text)], + Some(AutoindentMode::Block { + original_indent_columns: Vec::new(), + }), + cx, + ); + assert_eq!( + buffer.text(), + r#" + fn a() { + if b() { + c + .d() + .e(); + } + } + "# + .unindent() + ); + + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_language_without_indents_query(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = " + * one + - a + - b + * two + " + .unindent(); + + let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text).with_language( + Arc::new(Language::new( + LanguageConfig { + name: "Markdown".into(), + auto_indent_using_last_non_empty_line: false, + ..Default::default() + }, + Some(tree_sitter_json::language()), + )), + cx, + ); + buffer.edit( + [(Point::new(3, 0)..Point::new(3, 0), "\n")], + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " + * one + - a + - b + + * two + " + .unindent() + ); + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_with_injected_languages(cx: &mut AppContext) { + init_settings(cx, |settings| { + settings.languages.extend([ + ( + "HTML".into(), + LanguageSettingsContent { + tab_size: Some(2.try_into().unwrap()), + ..Default::default() + }, + ), + ( + "JavaScript".into(), + LanguageSettingsContent { + tab_size: Some(8.try_into().unwrap()), + ..Default::default() + }, + ), + ]) + }); + + let html_language = Arc::new(html_lang()); + + let javascript_language = Arc::new(javascript_lang()); + + let language_registry = Arc::new(LanguageRegistry::test()); + language_registry.add(html_language.clone()); + language_registry.add(javascript_language.clone()); + + cx.entity(|cx| { + let (text, ranges) = marked_text_ranges( + &" +
ˇ +
+ + ˇ + + " + .unindent(), + false, + ); + + let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text); + buffer.set_language_registry(language_registry); + buffer.set_language(Some(html_language), cx); + buffer.edit( + ranges.into_iter().map(|range| (range, "\na")), + Some(AutoindentMode::EachLine), + cx, + ); + assert_eq!( + buffer.text(), + " +
+ a +
+ + + a + + " + .unindent() + ); + buffer + }); +} + +#[gpui2::test] +fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) { + init_settings(cx, |settings| { + settings.defaults.tab_size = Some(2.try_into().unwrap()); + }); + + cx.entity(|cx| { + let mut buffer = + Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx); + + let text = r#" + class C + def a(b, c) + puts b + puts c + rescue + puts "errored" + exit 1 + end + end + "# + .unindent(); + + buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx); + + assert_eq!( + buffer.text(), + r#" + class C + def a(b, c) + puts b + puts c + rescue + puts "errored" + exit 1 + end + end + "# + .unindent() + ); + + buffer + }); +} + +#[gpui2::test] +fn test_language_scope_at_with_javascript(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let language = Language::new( + LanguageConfig { + name: "JavaScript".into(), + line_comment: Some("// ".into()), + brackets: BracketPairConfig { + pairs: vec![ + BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }, + BracketPair { + start: "'".into(), + end: "'".into(), + close: true, + newline: false, + }, + ], + disabled_scopes_by_bracket_ix: vec![ + Vec::new(), // + vec!["string".into()], + ], + }, + overrides: [( + "element".into(), + LanguageConfigOverride { + line_comment: Override::Remove { remove: true }, + block_comment: Override::Set(("{/*".into(), "*/}".into())), + ..Default::default() + }, + )] + .into_iter() + .collect(), + ..Default::default() + }, + Some(tree_sitter_typescript::language_tsx()), + ) + .with_override_query( + r#" + (jsx_element) @element + (string) @string + "#, + ) + .unwrap(); + + let text = r#"a["b"] = ;"#; + + let buffer = + Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx); + let snapshot = buffer.snapshot(); + + let config = snapshot.language_scope_at(0).unwrap(); + assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// "); + // Both bracket pairs are enabled + assert_eq!( + config.brackets().map(|e| e.1).collect::>(), + &[true, true] + ); + + let string_config = snapshot.language_scope_at(3).unwrap(); + assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// "); + // Second bracket pair is disabled + assert_eq!( + string_config.brackets().map(|e| e.1).collect::>(), + &[true, false] + ); + + let element_config = snapshot.language_scope_at(10).unwrap(); + assert_eq!(element_config.line_comment_prefix(), None); + assert_eq!( + element_config.block_comment_delimiters(), + Some((&"{/*".into(), &"*/}".into())) + ); + // Both bracket pairs are enabled + assert_eq!( + element_config.brackets().map(|e| e.1).collect::>(), + &[true, true] + ); + + buffer + }); +} + +#[gpui2::test] +fn test_language_scope_at_with_rust(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let language = Language::new( + LanguageConfig { + name: "Rust".into(), + brackets: BracketPairConfig { + pairs: vec![ + BracketPair { + start: "{".into(), + end: "}".into(), + close: true, + newline: false, + }, + BracketPair { + start: "'".into(), + end: "'".into(), + close: true, + newline: false, + }, + ], + disabled_scopes_by_bracket_ix: vec![ + Vec::new(), // + vec!["string".into()], + ], + }, + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ) + .with_override_query( + r#" + (string_literal) @string + "#, + ) + .unwrap(); + + let text = r#" + const S: &'static str = "hello"; + "# + .unindent(); + + let buffer = Buffer::new(0, cx.entity_id().as_u64(), text.clone()) + .with_language(Arc::new(language), cx); + let snapshot = buffer.snapshot(); + + // By default, all brackets are enabled + let config = snapshot.language_scope_at(0).unwrap(); + assert_eq!( + config.brackets().map(|e| e.1).collect::>(), + &[true, true] + ); + + // Within a string, the quotation brackets are disabled. + let string_config = snapshot + .language_scope_at(text.find("ello").unwrap()) + .unwrap(); + assert_eq!( + string_config.brackets().map(|e| e.1).collect::>(), + &[true, false] + ); + + buffer + }); +} + +#[gpui2::test] +fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) { + init_settings(cx, |_| {}); + + cx.entity(|cx| { + let text = r#" +
    + <% people.each do |person| %> +
  1. + <%= person.name %> +
  2. + <% end %> +
+ "# + .unindent(); + + let language_registry = Arc::new(LanguageRegistry::test()); + language_registry.add(Arc::new(ruby_lang())); + language_registry.add(Arc::new(html_lang())); + language_registry.add(Arc::new(erb_lang())); + + let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text); + buffer.set_language_registry(language_registry.clone()); + buffer.set_language( + language_registry + .language_for_name("ERB") + .now_or_never() + .unwrap() + .ok(), + cx, + ); + + let snapshot = buffer.snapshot(); + let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap(); + assert_eq!(html_config.line_comment_prefix(), None); + assert_eq!( + html_config.block_comment_delimiters(), + Some((&"".into())) + ); + + let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap(); + assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# "); + assert_eq!(ruby_config.block_comment_delimiters(), None); + + buffer + }); +} + +#[gpui2::test] +fn test_serialization(cx: &mut gpui2::AppContext) { + let mut now = Instant::now(); + + let buffer1 = cx.entity(|cx| { + let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "abc"); + buffer.edit([(3..3, "D")], None, cx); + + now += Duration::from_secs(1); + buffer.start_transaction_at(now); + buffer.edit([(4..4, "E")], None, cx); + buffer.end_transaction_at(now, cx); + assert_eq!(buffer.text(), "abcDE"); + + buffer.undo(cx); + assert_eq!(buffer.text(), "abcD"); + + buffer.edit([(4..4, "F")], None, cx); + assert_eq!(buffer.text(), "abcDF"); + buffer + }); + assert_eq!(buffer1.read(cx).text(), "abcDF"); + + let state = buffer1.read(cx).to_proto(); + let ops = cx + .executor() + .block(buffer1.read(cx).serialize_ops(None, cx)); + let buffer2 = cx.entity(|cx| { + let mut buffer = Buffer::from_proto(1, state, None).unwrap(); + buffer + .apply_ops( + ops.into_iter() + .map(|op| proto::deserialize_operation(op).unwrap()), + cx, + ) + .unwrap(); + buffer + }); + assert_eq!(buffer2.read(cx).text(), "abcDF"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) { + let min_peers = env::var("MIN_PEERS") + .map(|i| i.parse().expect("invalid `MIN_PEERS` variable")) + .unwrap_or(1); + let max_peers = env::var("MAX_PEERS") + .map(|i| i.parse().expect("invalid `MAX_PEERS` variable")) + .unwrap_or(5); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let base_text_len = rng.gen_range(0..10); + let base_text = RandomCharIter::new(&mut rng) + .take(base_text_len) + .collect::(); + let mut replica_ids = Vec::new(); + let mut buffers = Vec::new(); + let network = Arc::new(Mutex::new(Network::new(rng.clone()))); + let base_buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str())); + + for i in 0..rng.gen_range(min_peers..=max_peers) { + let buffer = cx.entity(|cx| { + let state = base_buffer.read(cx).to_proto(); + let ops = cx + .executor() + .block(base_buffer.read(cx).serialize_ops(None, cx)); + let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap(); + buffer + .apply_ops( + ops.into_iter() + .map(|op| proto::deserialize_operation(op).unwrap()), + cx, + ) + .unwrap(); + buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); + let network = network.clone(); + cx.subscribe(&cx.handle(), move |buffer, _, event, _| { + if let Event::Operation(op) = event { + network + .lock() + .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]); + } + }) + .detach(); + buffer + }); + buffers.push(buffer); + replica_ids.push(i as ReplicaId); + network.lock().add_peer(i as ReplicaId); + log::info!("Adding initial peer with replica id {}", i); + } + + log::info!("initial text: {:?}", base_text); + + let mut now = Instant::now(); + let mut mutation_count = operations; + let mut next_diagnostic_id = 0; + let mut active_selections = BTreeMap::default(); + loop { + let replica_index = rng.gen_range(0..replica_ids.len()); + let replica_id = replica_ids[replica_index]; + let buffer = &mut buffers[replica_index]; + let mut new_buffer = None; + match rng.gen_range(0..100) { + 0..=29 if mutation_count != 0 => { + buffer.update(cx, |buffer, cx| { + buffer.start_transaction_at(now); + buffer.randomly_edit(&mut rng, 5, cx); + buffer.end_transaction_at(now, cx); + log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text()); + }); + mutation_count -= 1; + } + 30..=39 if mutation_count != 0 => { + buffer.update(cx, |buffer, cx| { + if rng.gen_bool(0.2) { + log::info!("peer {} clearing active selections", replica_id); + active_selections.remove(&replica_id); + buffer.remove_active_selections(cx); + } else { + let mut selections = Vec::new(); + for id in 0..rng.gen_range(1..=5) { + let range = buffer.random_byte_range(0, &mut rng); + selections.push(Selection { + id, + start: buffer.anchor_before(range.start), + end: buffer.anchor_before(range.end), + reversed: false, + goal: SelectionGoal::None, + }); + } + let selections: Arc<[Selection]> = selections.into(); + log::info!( + "peer {} setting active selections: {:?}", + replica_id, + selections + ); + active_selections.insert(replica_id, selections.clone()); + buffer.set_active_selections(selections, false, Default::default(), cx); + } + }); + mutation_count -= 1; + } + 40..=49 if mutation_count != 0 && replica_id == 0 => { + let entry_count = rng.gen_range(1..=5); + buffer.update(cx, |buffer, cx| { + let diagnostics = DiagnosticSet::new( + (0..entry_count).map(|_| { + let range = buffer.random_byte_range(0, &mut rng); + let range = range.to_point_utf16(buffer); + let range = range.start..range.end; + DiagnosticEntry { + range, + diagnostic: Diagnostic { + message: post_inc(&mut next_diagnostic_id).to_string(), + ..Default::default() + }, + } + }), + buffer, + ); + log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics); + buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx); + }); + mutation_count -= 1; + } + 50..=59 if replica_ids.len() < max_peers => { + let old_buffer_state = buffer.read(cx).to_proto(); + let old_buffer_ops = cx.executor().block(buffer.read(cx).serialize_ops(None, cx)); + let new_replica_id = (0..=replica_ids.len() as ReplicaId) + .filter(|replica_id| *replica_id != buffer.read(cx).replica_id()) + .choose(&mut rng) + .unwrap(); + log::info!( + "Adding new replica {} (replicating from {})", + new_replica_id, + replica_id + ); + new_buffer = Some(cx.entity(|cx| { + let mut new_buffer = + Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap(); + new_buffer + .apply_ops( + old_buffer_ops + .into_iter() + .map(|op| deserialize_operation(op).unwrap()), + cx, + ) + .unwrap(); + log::info!( + "New replica {} text: {:?}", + new_buffer.replica_id(), + new_buffer.text() + ); + new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); + let network = network.clone(); + cx.subscribe(&cx.handle(), move |buffer, _, event, _| { + if let Event::Operation(op) = event { + network.lock().broadcast( + buffer.replica_id(), + vec![proto::serialize_operation(op)], + ); + } + }) + .detach(); + new_buffer + })); + network.lock().replicate(replica_id, new_replica_id); + + if new_replica_id as usize == replica_ids.len() { + replica_ids.push(new_replica_id); + } else { + let new_buffer = new_buffer.take().unwrap(); + while network.lock().has_unreceived(new_replica_id) { + let ops = network + .lock() + .receive(new_replica_id) + .into_iter() + .map(|op| proto::deserialize_operation(op).unwrap()); + if ops.len() > 0 { + log::info!( + "peer {} (version: {:?}) applying {} ops from the network. {:?}", + new_replica_id, + buffer.read(cx).version(), + ops.len(), + ops + ); + new_buffer.update(cx, |new_buffer, cx| { + new_buffer.apply_ops(ops, cx).unwrap(); + }); + } + } + buffers[new_replica_id as usize] = new_buffer; + } + } + 60..=69 if mutation_count != 0 => { + buffer.update(cx, |buffer, cx| { + buffer.randomly_undo_redo(&mut rng, cx); + log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text()); + }); + mutation_count -= 1; + } + _ if network.lock().has_unreceived(replica_id) => { + let ops = network + .lock() + .receive(replica_id) + .into_iter() + .map(|op| proto::deserialize_operation(op).unwrap()); + if ops.len() > 0 { + log::info!( + "peer {} (version: {:?}) applying {} ops from the network. {:?}", + replica_id, + buffer.read(cx).version(), + ops.len(), + ops + ); + buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap()); + } + } + _ => {} + } + + now += Duration::from_millis(rng.gen_range(0..=200)); + buffers.extend(new_buffer); + + for buffer in &buffers { + buffer.read(cx).check_invariants(); + } + + if mutation_count == 0 && network.lock().is_idle() { + break; + } + } + + let first_buffer = buffers[0].read(cx).snapshot(); + for buffer in &buffers[1..] { + let buffer = buffer.read(cx).snapshot(); + assert_eq!( + buffer.version(), + first_buffer.version(), + "Replica {} version != Replica 0 version", + buffer.replica_id() + ); + assert_eq!( + buffer.text(), + first_buffer.text(), + "Replica {} text != Replica 0 text", + buffer.replica_id() + ); + assert_eq!( + buffer + .diagnostics_in_range::<_, usize>(0..buffer.len(), false) + .collect::>(), + first_buffer + .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false) + .collect::>(), + "Replica {} diagnostics != Replica 0 diagnostics", + buffer.replica_id() + ); + } + + for buffer in &buffers { + let buffer = buffer.read(cx).snapshot(); + let actual_remote_selections = buffer + .remote_selections_in_range(Anchor::MIN..Anchor::MAX) + .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::>())) + .collect::>(); + let expected_remote_selections = active_selections + .iter() + .filter(|(replica_id, _)| **replica_id != buffer.replica_id()) + .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::>())) + .collect::>(); + assert_eq!( + actual_remote_selections, + expected_remote_selections, + "Replica {} remote selections != expected selections", + buffer.replica_id() + ); + } +} + +#[test] +fn test_contiguous_ranges() { + assert_eq!( + contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::>(), + &[1..4, 5..7, 9..13] + ); + + // Respects the `max_len` parameter + assert_eq!( + contiguous_ranges( + [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(), + 3 + ) + .collect::>(), + &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32], + ); +} + +#[gpui2::test(iterations = 500)] +fn test_trailing_whitespace_ranges(mut rng: StdRng) { + // Generate a random multi-line string containing + // some lines with trailing whitespace. + let mut text = String::new(); + for _ in 0..rng.gen_range(0..16) { + for _ in 0..rng.gen_range(0..36) { + text.push(match rng.gen_range(0..10) { + 0..=1 => ' ', + 3 => '\t', + _ => rng.gen_range('a'..'z'), + }); + } + text.push('\n'); + } + + match rng.gen_range(0..10) { + // sometimes remove the last newline + 0..=1 => drop(text.pop()), // + + // sometimes add extra newlines + 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))), + _ => {} + } + + let rope = Rope::from(text.as_str()); + let actual_ranges = trailing_whitespace_ranges(&rope); + let expected_ranges = TRAILING_WHITESPACE_REGEX + .find_iter(&text) + .map(|m| m.range()) + .collect::>(); + assert_eq!( + actual_ranges, + expected_ranges, + "wrong ranges for text lines:\n{:?}", + text.split("\n").collect::>() + ); +} + +fn ruby_lang() -> Language { + Language::new( + LanguageConfig { + name: "Ruby".into(), + path_suffixes: vec!["rb".to_string()], + line_comment: Some("# ".into()), + ..Default::default() + }, + Some(tree_sitter_ruby::language()), + ) + .with_indents_query( + r#" + (class "end" @end) @indent + (method "end" @end) @indent + (rescue) @outdent + (then) @indent + "#, + ) + .unwrap() +} + +fn html_lang() -> Language { + Language::new( + LanguageConfig { + name: "HTML".into(), + block_comment: Some(("".into())), + ..Default::default() + }, + Some(tree_sitter_html::language()), + ) + .with_indents_query( + " + (element + (start_tag) @start + (end_tag)? @end) @indent + ", + ) + .unwrap() + .with_injection_query( + r#" + (script_element + (raw_text) @content + (#set! "language" "javascript")) + "#, + ) + .unwrap() +} + +fn erb_lang() -> Language { + Language::new( + LanguageConfig { + name: "ERB".into(), + path_suffixes: vec!["erb".to_string()], + block_comment: Some(("<%#".into(), "%>".into())), + ..Default::default() + }, + Some(tree_sitter_embedded_template::language()), + ) + .with_injection_query( + r#" + ( + (code) @content + (#set! "language" "ruby") + (#set! "combined") + ) + + ( + (content) @content + (#set! "language" "html") + (#set! "combined") + ) + "#, + ) + .unwrap() +} + +fn rust_lang() -> Language { + Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ) + .with_indents_query( + r#" + (call_expression) @indent + (field_expression) @indent + (_ "(" ")" @end) @indent + (_ "{" "}" @end) @indent + "#, + ) + .unwrap() + .with_brackets_query( + r#" + ("{" @open "}" @close) + "#, + ) + .unwrap() + .with_outline_query( + r#" + (struct_item + "struct" @context + name: (_) @name) @item + (enum_item + "enum" @context + name: (_) @name) @item + (enum_variant + name: (_) @name) @item + (field_declaration + name: (_) @name) @item + (impl_item + "impl" @context + trait: (_)? @name + "for"? @context + type: (_) @name) @item + (function_item + "fn" @context + name: (_) @name) @item + (mod_item + "mod" @context + name: (_) @name) @item + "#, + ) + .unwrap() +} + +fn json_lang() -> Language { + Language::new( + LanguageConfig { + name: "Json".into(), + path_suffixes: vec!["js".to_string()], + ..Default::default() + }, + Some(tree_sitter_json::language()), + ) +} + +fn javascript_lang() -> Language { + Language::new( + LanguageConfig { + name: "JavaScript".into(), + ..Default::default() + }, + Some(tree_sitter_typescript::language_tsx()), + ) + .with_brackets_query( + r#" + ("{" @open "}" @close) + ("(" @open ")" @close) + "#, + ) + .unwrap() + .with_indents_query( + r#" + (object "}" @end) @indent + "#, + ) + .unwrap() +} + +fn get_tree_sexp(buffer: &Handle, cx: &mut gpui2::TestAppContext) -> String { + buffer.update(cx, |buffer, _| { + let snapshot = buffer.snapshot(); + let layers = snapshot.syntax.layers(buffer.as_text_snapshot()); + layers[0].node().to_sexp() + }) +} + +// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers` +fn assert_bracket_pairs( + selection_text: &'static str, + bracket_pair_texts: Vec<&'static str>, + language: Language, + cx: &mut AppContext, +) { + let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false); + let buffer = cx.entity(|cx| { + Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone()) + .with_language(Arc::new(language), cx) + }); + let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot()); + + let selection_range = selection_ranges[0].clone(); + + let bracket_pairs = bracket_pair_texts + .into_iter() + .map(|pair_text| { + let (bracket_text, ranges) = marked_text_ranges(pair_text, false); + assert_eq!(bracket_text, expected_text); + (ranges[0].clone(), ranges[1].clone()) + }) + .collect::>(); + + assert_set_eq!( + buffer.bracket_ranges(selection_range).collect::>(), + bracket_pairs + ); +} + +fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + crate::init(cx); + cx.update_global::(|settings, cx| { + settings.update_user_settings::(cx, f); + }); +}