diff --git a/crates/gpui2/src/executor.rs b/crates/gpui2/src/executor.rs index 28d4b6d117..6bfd9ce15a 100644 --- a/crates/gpui2/src/executor.rs +++ b/crates/gpui2/src/executor.rs @@ -163,6 +163,10 @@ impl Executor { future: impl Future, ) -> Result> { let mut future = Box::pin(future); + if duration.is_zero() { + return Err(future); + } + let timeout = { let future = &mut future; async { diff --git a/crates/language2/src/buffer_tests.rs b/crates/language2/src/buffer_tests.rs index c5790de881..fc60f31018 100644 --- a/crates/language2/src/buffer_tests.rs +++ b/crates/language2/src/buffer_tests.rs @@ -311,134 +311,134 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) { }); } -// #[gpui2::test] -// async fn test_reparse(cx: &mut gpui2::TestAppContext) { -// let text = "fn a() {}"; -// let buffer = cx.entity(|cx| { -// Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx) -// }); +#[gpui2::test] +async fn test_reparse(cx: &mut gpui2::TestAppContext) { + let text = "fn a() {}"; + 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 -// cx.executor().run_until_parked(); -// assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing())); -// assert_eq!( -// get_tree_sexp(&buffer, cx), -// concat!( -// "(source_file (function_item name: (identifier) ", -// "parameters: (parameters) ", -// "body: (block)))" -// ) -// ); + // Wait for the initial text to parse + cx.executor().run_until_parked(); + assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing())); + assert_eq!( + get_tree_sexp(&buffer, cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters) ", + "body: (block)))" + ) + ); -// buffer.update(cx, |buffer, _| { -// buffer.set_sync_parse_timeout(Duration::ZERO) -// }); + buffer.update(cx, |buffer, _| { + buffer.set_sync_parse_timeout(Duration::ZERO) + }); -// // Perform some edits (add parameter and variable reference) -// // Parsing doesn't begin until the transaction is complete -// buffer.update(cx, |buf, cx| { -// buf.start_transaction(); + // Perform some edits (add parameter and variable reference) + // Parsing doesn't begin until the transaction is complete + buffer.update(cx, |buf, cx| { + buf.start_transaction(); -// let offset = buf.text().find(')').unwrap(); -// buf.edit([(offset..offset, "b: C")], None, cx); -// assert!(!buf.is_parsing()); + let offset = buf.text().find(')').unwrap(); + buf.edit([(offset..offset, "b: C")], None, cx); + assert!(!buf.is_parsing()); -// let offset = buf.text().find('}').unwrap(); -// buf.edit([(offset..offset, " d; ")], None, cx); -// assert!(!buf.is_parsing()); + let offset = buf.text().find('}').unwrap(); + buf.edit([(offset..offset, " d; ")], None, cx); + assert!(!buf.is_parsing()); -// buf.end_transaction(cx); -// assert_eq!(buf.text(), "fn a(b: C) { d; }"); -// assert!(buf.is_parsing()); -// }); -// cx.executor().run_until_parked(); -// assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing())); -// assert_eq!( -// get_tree_sexp(&buffer, cx), -// concat!( -// "(source_file (function_item name: (identifier) ", -// "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", -// "body: (block (expression_statement (identifier)))))" -// ) -// ); + buf.end_transaction(cx); + assert_eq!(buf.text(), "fn a(b: C) { d; }"); + assert!(buf.is_parsing()); + }); + cx.executor().run_until_parked(); + assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing())); + assert_eq!( + get_tree_sexp(&buffer, cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (expression_statement (identifier)))))" + ) + ); -// // Perform a series of edits without waiting for the current parse to complete: -// // * turn identifier into a field expression -// // * turn field expression into a method call -// // * add a turbofish to the method call -// buffer.update(cx, |buf, cx| { -// let offset = buf.text().find(';').unwrap(); -// buf.edit([(offset..offset, ".e")], None, cx); -// assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); -// assert!(buf.is_parsing()); -// }); -// buffer.update(cx, |buf, cx| { -// let offset = buf.text().find(';').unwrap(); -// buf.edit([(offset..offset, "(f)")], None, cx); -// assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); -// assert!(buf.is_parsing()); -// }); -// buffer.update(cx, |buf, cx| { -// let offset = buf.text().find("(f)").unwrap(); -// buf.edit([(offset..offset, "::")], None, cx); -// assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); -// assert!(buf.is_parsing()); -// }); -// cx.executor().run_until_parked(); -// assert_eq!( -// get_tree_sexp(&buffer, cx), -// concat!( -// "(source_file (function_item name: (identifier) ", -// "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", -// "body: (block (expression_statement (call_expression ", -// "function: (generic_function ", -// "function: (field_expression value: (identifier) field: (field_identifier)) ", -// "type_arguments: (type_arguments (type_identifier))) ", -// "arguments: (arguments (identifier)))))))", -// ) -// ); + // Perform a series of edits without waiting for the current parse to complete: + // * turn identifier into a field expression + // * turn field expression into a method call + // * add a turbofish to the method call + buffer.update(cx, |buf, cx| { + let offset = buf.text().find(';').unwrap(); + buf.edit([(offset..offset, ".e")], None, cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e; }"); + assert!(buf.is_parsing()); + }); + buffer.update(cx, |buf, cx| { + let offset = buf.text().find(';').unwrap(); + buf.edit([(offset..offset, "(f)")], None, cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }"); + assert!(buf.is_parsing()); + }); + buffer.update(cx, |buf, cx| { + let offset = buf.text().find("(f)").unwrap(); + buf.edit([(offset..offset, "::")], None, cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); + assert!(buf.is_parsing()); + }); + cx.executor().run_until_parked(); + assert_eq!( + get_tree_sexp(&buffer, cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (expression_statement (call_expression ", + "function: (generic_function ", + "function: (field_expression value: (identifier) field: (field_identifier)) ", + "type_arguments: (type_arguments (type_identifier))) ", + "arguments: (arguments (identifier)))))))", + ) + ); -// buffer.update(cx, |buf, cx| { -// buf.undo(cx); -// buf.undo(cx); -// buf.undo(cx); -// buf.undo(cx); -// assert_eq!(buf.text(), "fn a() {}"); -// assert!(buf.is_parsing()); -// }); + buffer.update(cx, |buf, cx| { + buf.undo(cx); + buf.undo(cx); + buf.undo(cx); + buf.undo(cx); + assert_eq!(buf.text(), "fn a() {}"); + assert!(buf.is_parsing()); + }); -// cx.executor().run_until_parked(); -// assert_eq!( -// get_tree_sexp(&buffer, cx), -// concat!( -// "(source_file (function_item name: (identifier) ", -// "parameters: (parameters) ", -// "body: (block)))" -// ) -// ); + cx.executor().run_until_parked(); + assert_eq!( + get_tree_sexp(&buffer, cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters) ", + "body: (block)))" + ) + ); -// buffer.update(cx, |buf, cx| { -// buf.redo(cx); -// buf.redo(cx); -// buf.redo(cx); -// buf.redo(cx); -// assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); -// assert!(buf.is_parsing()); -// }); -// cx.executor().run_until_parked(); -// assert_eq!( -// get_tree_sexp(&buffer, cx), -// concat!( -// "(source_file (function_item name: (identifier) ", -// "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", -// "body: (block (expression_statement (call_expression ", -// "function: (generic_function ", -// "function: (field_expression value: (identifier) field: (field_identifier)) ", -// "type_arguments: (type_arguments (type_identifier))) ", -// "arguments: (arguments (identifier)))))))", -// ) -// ); -// } + buffer.update(cx, |buf, cx| { + buf.redo(cx); + buf.redo(cx); + buf.redo(cx); + buf.redo(cx); + assert_eq!(buf.text(), "fn a(b: C) { d.e::(f); }"); + assert!(buf.is_parsing()); + }); + cx.executor().run_until_parked(); + assert_eq!( + get_tree_sexp(&buffer, cx), + concat!( + "(source_file (function_item name: (identifier) ", + "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ", + "body: (block (expression_statement (call_expression ", + "function: (generic_function ", + "function: (field_expression value: (identifier) field: (field_identifier)) ", + "type_arguments: (type_arguments (type_identifier))) ", + "arguments: (arguments (identifier)))))))", + ) + ); +} #[gpui2::test] async fn test_resetting_language(cx: &mut gpui2::TestAppContext) { diff --git a/crates/language2/src/highlight_map.rs b/crates/language2/src/highlight_map.rs index 8953fa571b..b394d0446e 100644 --- a/crates/language2/src/highlight_map.rs +++ b/crates/language2/src/highlight_map.rs @@ -76,36 +76,36 @@ impl Default for HighlightId { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use gpui2::color::Color; +#[cfg(test)] +mod tests { + use super::*; + use gpui2::rgba; -// #[test] -// fn test_highlight_map() { -// let theme = SyntaxTheme::new( -// [ -// ("function", Color::from_u32(0x100000ff)), -// ("function.method", Color::from_u32(0x200000ff)), -// ("function.async", Color::from_u32(0x300000ff)), -// ("variable.builtin.self.rust", Color::from_u32(0x400000ff)), -// ("variable.builtin", Color::from_u32(0x500000ff)), -// ("variable", Color::from_u32(0x600000ff)), -// ] -// .iter() -// .map(|(name, color)| (name.to_string(), (*color).into())) -// .collect(), -// ); + #[test] + fn test_highlight_map() { + let theme = SyntaxTheme { + highlights: [ + ("function", rgba(0x100000ff)), + ("function.method", rgba(0x200000ff)), + ("function.async", rgba(0x300000ff)), + ("variable.builtin.self.rust", rgba(0x400000ff)), + ("variable.builtin", rgba(0x500000ff)), + ("variable", rgba(0x600000ff)), + ] + .iter() + .map(|(name, color)| (name.to_string(), (*color).into())) + .collect(), + }; -// let capture_names = &[ -// "function.special".to_string(), -// "function.async.rust".to_string(), -// "variable.builtin.self".to_string(), -// ]; + let capture_names = &[ + "function.special".to_string(), + "function.async.rust".to_string(), + "variable.builtin.self".to_string(), + ]; -// let map = HighlightMap::new(capture_names, &theme); -// assert_eq!(map.get(0).name(&theme), Some("function")); -// assert_eq!(map.get(1).name(&theme), Some("function.async")); -// assert_eq!(map.get(2).name(&theme), Some("variable.builtin")); -// } -// } + let map = HighlightMap::new(capture_names, &theme); + assert_eq!(map.get(0).name(&theme), Some("function")); + assert_eq!(map.get(1).name(&theme), Some("function.async")); + assert_eq!(map.get(2).name(&theme), Some("variable.builtin")); + } +} diff --git a/crates/language2/src/syntax_map/syntax_map_tests.rs b/crates/language2/src/syntax_map/syntax_map_tests.rs index 55b0b8b778..732ed7e936 100644 --- a/crates/language2/src/syntax_map/syntax_map_tests.rs +++ b/crates/language2/src/syntax_map/syntax_map_tests.rs @@ -1,1323 +1,1323 @@ -// use super::*; -// use crate::LanguageConfig; -// use rand::rngs::StdRng; -// use std::{env, ops::Range, sync::Arc}; -// use text::Buffer; -// use tree_sitter::Node; -// use unindent::Unindent as _; -// use util::test::marked_text_ranges; - -// #[test] -// fn test_splice_included_ranges() { -// let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)]; - -// let (new_ranges, change) = splice_included_ranges( -// ranges.clone(), -// &[54..56, 58..68], -// &[ts_range(50..54), ts_range(59..67)], -// ); -// assert_eq!( -// new_ranges, -// &[ -// ts_range(20..30), -// ts_range(50..54), -// ts_range(59..67), -// ts_range(80..90), -// ] -// ); -// assert_eq!(change, 1..3); - -// let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); -// assert_eq!( -// new_ranges, -// &[ts_range(20..30), ts_range(50..60), ts_range(80..90)] -// ); -// assert_eq!(change, 2..3); - -// let (new_ranges, change) = -// splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]); -// assert_eq!( -// new_ranges, -// &[ -// ts_range(0..2), -// ts_range(20..30), -// ts_range(50..60), -// ts_range(70..75), -// ts_range(80..90) -// ] -// ); -// assert_eq!(change, 0..4); - -// let (new_ranges, change) = -// splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); -// assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]); -// assert_eq!(change, 0..1); - -// // does not create overlapping ranges -// let (new_ranges, change) = -// splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]); -// assert_eq!( -// new_ranges, -// &[ts_range(20..32), ts_range(50..60), ts_range(80..90)] -// ); -// assert_eq!(change, 0..1); - -// fn ts_range(range: Range) -> tree_sitter::Range { -// tree_sitter::Range { -// start_byte: range.start, -// start_point: tree_sitter::Point { -// row: 0, -// column: range.start, -// }, -// end_byte: range.end, -// end_point: tree_sitter::Point { -// row: 0, -// column: range.end, -// }, -// } -// } -// } - -// #[gpui::test] -// fn test_syntax_map_layers_for_range() { -// let registry = Arc::new(LanguageRegistry::test()); -// let language = Arc::new(rust_lang()); -// registry.add(language.clone()); - -// let mut buffer = Buffer::new( -// 0, -// 0, -// r#" -// fn a() { -// assert_eq!( -// b(vec![C {}]), -// vec![d.e], -// ); -// println!("{}", f(|_| true)); -// } -// "# -// .unindent(), -// ); - -// let mut syntax_map = SyntaxMap::new(); -// syntax_map.set_language_registry(registry.clone()); -// syntax_map.reparse(language.clone(), &buffer); - -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(2, 0)..Point::new(2, 0), -// &[ -// "...(function_item ... (block (expression_statement (macro_invocation...", -// "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", -// ], -// ); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(2, 14)..Point::new(2, 16), -// &[ -// "...(function_item ...", -// "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", -// "...(array_expression (struct_expression ...", -// ], -// ); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(3, 14)..Point::new(3, 16), -// &[ -// "...(function_item ...", -// "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", -// "...(array_expression (field_expression ...", -// ], -// ); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(5, 12)..Point::new(5, 16), -// &[ -// "...(function_item ...", -// "...(call_expression ... (arguments (closure_expression ...", -// ], -// ); - -// // Replace a vec! macro invocation with a plain slice, removing a syntactic layer. -// let macro_name_range = range_for_text(&buffer, "vec!"); -// buffer.edit([(macro_name_range, "&")]); -// syntax_map.interpolate(&buffer); -// syntax_map.reparse(language.clone(), &buffer); - -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(2, 14)..Point::new(2, 16), -// &[ -// "...(function_item ...", -// "...(tuple_expression (call_expression ... arguments: (arguments (reference_expression value: (array_expression...", -// ], -// ); - -// // Put the vec! macro back, adding back the syntactic layer. -// buffer.undo(); -// syntax_map.interpolate(&buffer); -// syntax_map.reparse(language.clone(), &buffer); - -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(2, 14)..Point::new(2, 16), -// &[ -// "...(function_item ...", -// "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", -// "...(array_expression (struct_expression ...", -// ], -// ); -// } - -// #[gpui::test] -// fn test_dynamic_language_injection() { -// let registry = Arc::new(LanguageRegistry::test()); -// let markdown = Arc::new(markdown_lang()); -// registry.add(markdown.clone()); -// registry.add(Arc::new(rust_lang())); -// registry.add(Arc::new(ruby_lang())); - -// let mut buffer = Buffer::new( -// 0, -// 0, -// r#" -// This is a code block: - -// ```rs -// fn foo() {} -// ``` -// "# -// .unindent(), -// ); - -// let mut syntax_map = SyntaxMap::new(); -// syntax_map.set_language_registry(registry.clone()); -// syntax_map.reparse(markdown.clone(), &buffer); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(3, 0)..Point::new(3, 0), -// &[ -// "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", -// "...(function_item name: (identifier) parameters: (parameters) body: (block)...", -// ], -// ); - -// // Replace Rust with Ruby in code block. -// let macro_name_range = range_for_text(&buffer, "rs"); -// buffer.edit([(macro_name_range, "ruby")]); -// syntax_map.interpolate(&buffer); -// syntax_map.reparse(markdown.clone(), &buffer); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(3, 0)..Point::new(3, 0), -// &[ -// "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", -// "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...", -// ], -// ); - -// // Replace Ruby with a language that hasn't been loaded yet. -// let macro_name_range = range_for_text(&buffer, "ruby"); -// buffer.edit([(macro_name_range, "html")]); -// syntax_map.interpolate(&buffer); -// syntax_map.reparse(markdown.clone(), &buffer); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(3, 0)..Point::new(3, 0), -// &[ -// "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter..." -// ], -// ); -// assert!(syntax_map.contains_unknown_injections()); - -// registry.add(Arc::new(html_lang())); -// syntax_map.reparse(markdown.clone(), &buffer); -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(3, 0)..Point::new(3, 0), -// &[ -// "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", -// "(fragment (text))", -// ], -// ); -// assert!(!syntax_map.contains_unknown_injections()); -// } - -// #[gpui::test] -// fn test_typing_multiple_new_injections() { -// let (buffer, syntax_map) = test_edit_sequence( -// "Rust", -// &[ -// "fn a() { dbg }", -// "fn a() { dbg«!» }", -// "fn a() { dbg!«()» }", -// "fn a() { dbg!(«b») }", -// "fn a() { dbg!(b«.») }", -// "fn a() { dbg!(b.«c») }", -// "fn a() { dbg!(b.c«()») }", -// "fn a() { dbg!(b.c(«vec»)) }", -// "fn a() { dbg!(b.c(vec«!»)) }", -// "fn a() { dbg!(b.c(vec!«[]»)) }", -// "fn a() { dbg!(b.c(vec![«d»])) }", -// "fn a() { dbg!(b.c(vec![d«.»])) }", -// "fn a() { dbg!(b.c(vec![d.«e»])) }", -// ], -// ); - -// assert_capture_ranges( -// &syntax_map, -// &buffer, -// &["field"], -// "fn a() { dbg!(b.«c»(vec![d.«e»])) }", -// ); -// } - -// #[gpui::test] -// fn test_pasting_new_injection_line_between_others() { -// let (buffer, syntax_map) = test_edit_sequence( -// "Rust", -// &[ -// " -// fn a() { -// b!(B {}); -// c!(C {}); -// d!(D {}); -// e!(E {}); -// f!(F {}); -// g!(G {}); -// } -// ", -// " -// fn a() { -// b!(B {}); -// c!(C {}); -// d!(D {}); -// « h!(H {}); -// » e!(E {}); -// f!(F {}); -// g!(G {}); -// } -// ", -// ], -// ); - -// assert_capture_ranges( -// &syntax_map, -// &buffer, -// &["struct"], -// " -// fn a() { -// b!(«B {}»); -// c!(«C {}»); -// d!(«D {}»); -// h!(«H {}»); -// e!(«E {}»); -// f!(«F {}»); -// g!(«G {}»); -// } -// ", -// ); -// } - -// #[gpui::test] -// fn test_joining_injections_with_child_injections() { -// let (buffer, syntax_map) = test_edit_sequence( -// "Rust", -// &[ -// " -// fn a() { -// b!( -// c![one.two.three], -// d![four.five.six], -// ); -// e!( -// f![seven.eight], -// ); -// } -// ", -// " -// fn a() { -// b!( -// c![one.two.three], -// d![four.five.six], -// ˇ f![seven.eight], -// ); -// } -// ", -// ], -// ); - -// assert_capture_ranges( -// &syntax_map, -// &buffer, -// &["field"], -// " -// fn a() { -// b!( -// c![one.«two».«three»], -// d![four.«five».«six»], -// f![seven.«eight»], -// ); -// } -// ", -// ); -// } - -// #[gpui::test] -// fn test_editing_edges_of_injection() { -// test_edit_sequence( -// "Rust", -// &[ -// " -// fn a() { -// b!(c!()) -// } -// ", -// " -// fn a() { -// «d»!(c!()) -// } -// ", -// " -// fn a() { -// «e»d!(c!()) -// } -// ", -// " -// fn a() { -// ed!«[»c!()«]» -// } -// ", -// ], -// ); -// } - -// #[gpui::test] -// fn test_edits_preceding_and_intersecting_injection() { -// test_edit_sequence( -// "Rust", -// &[ -// // -// "const aaaaaaaaaaaa: B = c!(d(e.f));", -// "const aˇa: B = c!(d(eˇ));", -// ], -// ); -// } - -// #[gpui::test] -// fn test_non_local_changes_create_injections() { -// test_edit_sequence( -// "Rust", -// &[ -// " -// // a! { -// static B: C = d; -// // } -// ", -// " -// ˇa! { -// static B: C = d; -// ˇ} -// ", -// ], -// ); -// } - -// #[gpui::test] -// fn test_creating_many_injections_in_one_edit() { -// test_edit_sequence( -// "Rust", -// &[ -// " -// fn a() { -// one(Two::three(3)); -// four(Five::six(6)); -// seven(Eight::nine(9)); -// } -// ", -// " -// fn a() { -// one«!»(Two::three(3)); -// four«!»(Five::six(6)); -// seven«!»(Eight::nine(9)); -// } -// ", -// " -// fn a() { -// one!(Two::three«!»(3)); -// four!(Five::six«!»(6)); -// seven!(Eight::nine«!»(9)); -// } -// ", -// ], -// ); -// } - -// #[gpui::test] -// fn test_editing_across_injection_boundary() { -// test_edit_sequence( -// "Rust", -// &[ -// " -// fn one() { -// two(); -// three!( -// three.four, -// five.six, -// ); -// } -// ", -// " -// fn one() { -// two(); -// th«irty_five![» -// three.four, -// five.six, -// « seven.eight, -// ];» -// } -// ", -// ], -// ); -// } - -// #[gpui::test] -// fn test_removing_injection_by_replacing_across_boundary() { -// test_edit_sequence( -// "Rust", -// &[ -// " -// fn one() { -// two!( -// three.four, -// ); -// } -// ", -// " -// fn one() { -// t«en -// .eleven( -// twelve, -// » -// three.four, -// ); -// } -// ", -// ], -// ); -// } - -// #[gpui::test] -// fn test_combined_injections_simple() { -// let (buffer, syntax_map) = test_edit_sequence( -// "ERB", -// &[ -// " -// -// <% if @one %> -//
-// <% else %> -//
-// <% end %> -//
-// -// ", -// " -// -// <% if @one %> -//
-// ˇ else ˇ -//
-// <% end %> -//
-// -// ", -// " -// -// <% if @one «;» end %> -//
-// -// ", -// ], -// ); - -// assert_capture_ranges( -// &syntax_map, -// &buffer, -// &["tag", "ivar"], -// " -// <«body»> -// <% if «@one» ; end %> -// -// -// ", -// ); -// } - -// #[gpui::test] -// fn test_combined_injections_empty_ranges() { -// test_edit_sequence( -// "ERB", -// &[ -// " -// <% if @one %> -// <% else %> -// <% end %> -// ", -// " -// <% if @one %> -// ˇ<% end %> -// ", -// ], -// ); -// } - -// #[gpui::test] -// fn test_combined_injections_edit_edges_of_ranges() { -// let (buffer, syntax_map) = test_edit_sequence( -// "ERB", -// &[ -// " -// <%= one @two %> -// <%= three @four %> -// ", -// " -// <%= one @two %ˇ -// <%= three @four %> -// ", -// " -// <%= one @two %«>» -// <%= three @four %> -// ", -// ], -// ); - -// assert_capture_ranges( -// &syntax_map, -// &buffer, -// &["tag", "ivar"], -// " -// <%= one «@two» %> -// <%= three «@four» %> -// ", -// ); -// } - -// #[gpui::test] -// fn test_combined_injections_splitting_some_injections() { -// let (_buffer, _syntax_map) = test_edit_sequence( -// "ERB", -// &[ -// r#" -// <%A if b(:c) %> -// d -// <% end %> -// eee -// <% f %> -// "#, -// r#" -// <%« AAAAAAA %> -// hhhhhhh -// <%=» if b(:c) %> -// d -// <% end %> -// eee -// <% f %> -// "#, -// ], -// ); -// } - -// #[gpui::test] -// fn test_combined_injections_editing_after_last_injection() { -// test_edit_sequence( -// "ERB", -// &[ -// r#" -// <% foo %> -//
-// <% bar %> -// "#, -// r#" -// <% foo %> -//
-// <% bar %>« -// more text» -// "#, -// ], -// ); -// } - -// #[gpui::test] -// fn test_combined_injections_inside_injections() { -// let (buffer, syntax_map) = test_edit_sequence( -// "Markdown", -// &[ -// r#" -// here is -// some -// ERB code: - -// ```erb -//
    -// <% people.each do |person| %> -//
  • <%= person.name %>
  • -//
  • <%= person.age %>
  • -// <% end %> -//
-// ``` -// "#, -// r#" -// here is -// some -// ERB code: - -// ```erb -//
    -// <% people«2».each do |person| %> -//
  • <%= person.name %>
  • -//
  • <%= person.age %>
  • -// <% end %> -//
-// ``` -// "#, -// // Inserting a comment character inside one code directive -// // does not cause the other code directive to become a comment, -// // because newlines are included in between each injection range. -// r#" -// here is -// some -// ERB code: - -// ```erb -//
    -// <% people2.each do |person| %> -//
  • <%= «# »person.name %>
  • -//
  • <%= person.age %>
  • -// <% end %> -//
-// ``` -// "#, -// ], -// ); - -// // Check that the code directive below the ruby comment is -// // not parsed as a comment. -// assert_capture_ranges( -// &syntax_map, -// &buffer, -// &["method"], -// " -// here is -// some -// ERB code: - -// ```erb -//
    -// <% people2.«each» do |person| %> -//
  • <%= # person.name %>
  • -//
  • <%= person.«age» %>
  • -// <% end %> -//
-// ``` -// ", -// ); -// } - -// #[gpui::test] -// fn test_empty_combined_injections_inside_injections() { -// let (buffer, syntax_map) = test_edit_sequence( -// "Markdown", -// &[r#" -// ```erb -// hello -// ``` - -// goodbye -// "#], -// ); - -// assert_layers_for_range( -// &syntax_map, -// &buffer, -// Point::new(0, 0)..Point::new(5, 0), -// &[ -// "...(paragraph)...", -// "(template...", -// "(fragment...", -// // The ruby syntax tree should be empty, since there are -// // no interpolations in the ERB template. -// "(program)", -// ], -// ); -// } - -// #[gpui::test(iterations = 50)] -// fn test_random_syntax_map_edits_rust_macros(rng: StdRng) { -// let text = r#" -// fn test_something() { -// let vec = vec![5, 1, 3, 8]; -// assert_eq!( -// vec -// .into_iter() -// .map(|i| i * 2) -// .collect::>(), -// vec![ -// 5 * 2, 1 * 2, 3 * 2, 8 * 2 -// ], -// ); -// } -// "# -// .unindent() -// .repeat(2); - -// let registry = Arc::new(LanguageRegistry::test()); -// let language = Arc::new(rust_lang()); -// registry.add(language.clone()); - -// test_random_edits(text, registry, language, rng); -// } - -// #[gpui::test(iterations = 50)] -// fn test_random_syntax_map_edits_with_erb(rng: StdRng) { -// let text = r#" -//
-// <% if one?(:two) %> -//

-// <%= yield :five %> -//

-// <% elsif Six.seven(8) %> -//

-// <%= yield :five %> -//

-// <% else %> -// Ok -// <% end %> -//
-// "# -// .unindent() -// .repeat(5); - -// let registry = Arc::new(LanguageRegistry::test()); -// let language = Arc::new(erb_lang()); -// registry.add(language.clone()); -// registry.add(Arc::new(ruby_lang())); -// registry.add(Arc::new(html_lang())); - -// test_random_edits(text, registry, language, rng); -// } - -// #[gpui::test(iterations = 50)] -// fn test_random_syntax_map_edits_with_heex(rng: StdRng) { -// let text = r#" -// defmodule TheModule do -// def the_method(assigns) do -// ~H""" -// <%= if @empty do %> -//
-// <% else %> -//
-//
-//
-//
-//
-//
-//
-// <% end %> -// """ -// end -// end -// "# -// .unindent() -// .repeat(3); - -// let registry = Arc::new(LanguageRegistry::test()); -// let language = Arc::new(elixir_lang()); -// registry.add(language.clone()); -// registry.add(Arc::new(heex_lang())); -// registry.add(Arc::new(html_lang())); - -// test_random_edits(text, registry, language, rng); -// } - -// fn test_random_edits( -// text: String, -// registry: Arc, -// language: Arc, -// mut rng: StdRng, -// ) { -// let operations = env::var("OPERATIONS") -// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) -// .unwrap_or(10); - -// let mut buffer = Buffer::new(0, 0, text); - -// let mut syntax_map = SyntaxMap::new(); -// syntax_map.set_language_registry(registry.clone()); -// syntax_map.reparse(language.clone(), &buffer); - -// let mut reference_syntax_map = SyntaxMap::new(); -// reference_syntax_map.set_language_registry(registry.clone()); - -// log::info!("initial text:\n{}", buffer.text()); - -// for _ in 0..operations { -// let prev_buffer = buffer.snapshot(); -// let prev_syntax_map = syntax_map.snapshot(); - -// buffer.randomly_edit(&mut rng, 3); -// log::info!("text:\n{}", buffer.text()); - -// syntax_map.interpolate(&buffer); -// check_interpolation(&prev_syntax_map, &syntax_map, &prev_buffer, &buffer); - -// syntax_map.reparse(language.clone(), &buffer); - -// reference_syntax_map.clear(); -// reference_syntax_map.reparse(language.clone(), &buffer); -// } - -// for i in 0..operations { -// let i = operations - i - 1; -// buffer.undo(); -// log::info!("undoing operation {}", i); -// log::info!("text:\n{}", buffer.text()); - -// syntax_map.interpolate(&buffer); -// syntax_map.reparse(language.clone(), &buffer); - -// reference_syntax_map.clear(); -// reference_syntax_map.reparse(language.clone(), &buffer); -// assert_eq!( -// syntax_map.layers(&buffer).len(), -// reference_syntax_map.layers(&buffer).len(), -// "wrong number of layers after undoing edit {i}" -// ); -// } - -// let layers = syntax_map.layers(&buffer); -// let reference_layers = reference_syntax_map.layers(&buffer); -// for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) { -// assert_eq!( -// edited_layer.node().to_sexp(), -// reference_layer.node().to_sexp() -// ); -// assert_eq!(edited_layer.node().range(), reference_layer.node().range()); -// } -// } - -// fn check_interpolation( -// old_syntax_map: &SyntaxSnapshot, -// new_syntax_map: &SyntaxSnapshot, -// old_buffer: &BufferSnapshot, -// new_buffer: &BufferSnapshot, -// ) { -// let edits = new_buffer -// .edits_since::(&old_buffer.version()) -// .collect::>(); - -// for (old_layer, new_layer) in old_syntax_map -// .layers -// .iter() -// .zip(new_syntax_map.layers.iter()) -// { -// assert_eq!(old_layer.range, new_layer.range); -// let Some(old_tree) = old_layer.content.tree() else { -// continue; -// }; -// let Some(new_tree) = new_layer.content.tree() else { -// continue; -// }; -// let old_start_byte = old_layer.range.start.to_offset(old_buffer); -// let new_start_byte = new_layer.range.start.to_offset(new_buffer); -// let old_start_point = old_layer.range.start.to_point(old_buffer).to_ts_point(); -// let new_start_point = new_layer.range.start.to_point(new_buffer).to_ts_point(); -// let old_node = old_tree.root_node_with_offset(old_start_byte, old_start_point); -// let new_node = new_tree.root_node_with_offset(new_start_byte, new_start_point); -// check_node_edits( -// old_layer.depth, -// &old_layer.range, -// old_node, -// new_node, -// old_buffer, -// new_buffer, -// &edits, -// ); -// } - -// fn check_node_edits( -// depth: usize, -// range: &Range, -// old_node: Node, -// new_node: Node, -// old_buffer: &BufferSnapshot, -// new_buffer: &BufferSnapshot, -// edits: &[text::Edit], -// ) { -// assert_eq!(old_node.kind(), new_node.kind()); - -// let old_range = old_node.byte_range(); -// let new_range = new_node.byte_range(); - -// let is_edited = edits -// .iter() -// .any(|edit| edit.new.start < new_range.end && edit.new.end > new_range.start); -// if is_edited { -// assert!( -// new_node.has_changes(), -// concat!( -// "failed to mark node as edited.\n", -// "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n", -// "node kind: {}, old node range: {:?}, new node range: {:?}", -// ), -// depth, -// range.to_offset(old_buffer), -// range.to_offset(new_buffer), -// new_node.kind(), -// old_range, -// new_range, -// ); -// } - -// if !new_node.has_changes() { -// assert_eq!( -// old_buffer -// .text_for_range(old_range.clone()) -// .collect::(), -// new_buffer -// .text_for_range(new_range.clone()) -// .collect::(), -// concat!( -// "mismatched text for node\n", -// "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n", -// "node kind: {}, old node range:{:?}, new node range:{:?}", -// ), -// depth, -// range.to_offset(old_buffer), -// range.to_offset(new_buffer), -// new_node.kind(), -// old_range, -// new_range, -// ); -// } - -// for i in 0..new_node.child_count() { -// check_node_edits( -// depth, -// range, -// old_node.child(i).unwrap(), -// new_node.child(i).unwrap(), -// old_buffer, -// new_buffer, -// edits, -// ) -// } -// } -// } - -// fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) { -// let registry = Arc::new(LanguageRegistry::test()); -// registry.add(Arc::new(elixir_lang())); -// registry.add(Arc::new(heex_lang())); -// registry.add(Arc::new(rust_lang())); -// registry.add(Arc::new(ruby_lang())); -// registry.add(Arc::new(html_lang())); -// registry.add(Arc::new(erb_lang())); -// registry.add(Arc::new(markdown_lang())); - -// let language = registry -// .language_for_name(language_name) -// .now_or_never() -// .unwrap() -// .unwrap(); -// let mut buffer = Buffer::new(0, 0, Default::default()); - -// let mut mutated_syntax_map = SyntaxMap::new(); -// mutated_syntax_map.set_language_registry(registry.clone()); -// mutated_syntax_map.reparse(language.clone(), &buffer); - -// for (i, marked_string) in steps.into_iter().enumerate() { -// let marked_string = marked_string.unindent(); -// log::info!("incremental parse {i}: {marked_string:?}"); -// buffer.edit_via_marked_text(&marked_string); - -// // Reparse the syntax map -// mutated_syntax_map.interpolate(&buffer); -// mutated_syntax_map.reparse(language.clone(), &buffer); - -// // Create a second syntax map from scratch -// log::info!("fresh parse {i}: {marked_string:?}"); -// let mut reference_syntax_map = SyntaxMap::new(); -// reference_syntax_map.set_language_registry(registry.clone()); -// reference_syntax_map.reparse(language.clone(), &buffer); - -// // Compare the mutated syntax map to the new syntax map -// let mutated_layers = mutated_syntax_map.layers(&buffer); -// let reference_layers = reference_syntax_map.layers(&buffer); -// assert_eq!( -// mutated_layers.len(), -// reference_layers.len(), -// "wrong number of layers at step {i}" -// ); -// for (edited_layer, reference_layer) in -// mutated_layers.into_iter().zip(reference_layers.into_iter()) -// { -// assert_eq!( -// edited_layer.node().to_sexp(), -// reference_layer.node().to_sexp(), -// "different layer at step {i}" -// ); -// assert_eq!( -// edited_layer.node().range(), -// reference_layer.node().range(), -// "different layer at step {i}" -// ); -// } -// } - -// (buffer, mutated_syntax_map) -// } - -// fn html_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "HTML".into(), -// path_suffixes: vec!["html".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_html::language()), -// ) -// .with_highlights_query( -// r#" -// (tag_name) @tag -// (erroneous_end_tag_name) @tag -// (attribute_name) @property -// "#, -// ) -// .unwrap() -// } - -// fn ruby_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "Ruby".into(), -// path_suffixes: vec!["rb".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_ruby::language()), -// ) -// .with_highlights_query( -// r#" -// ["if" "do" "else" "end"] @keyword -// (instance_variable) @ivar -// (call method: (identifier) @method) -// "#, -// ) -// .unwrap() -// } - -// fn erb_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "ERB".into(), -// path_suffixes: vec!["erb".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_embedded_template::language()), -// ) -// .with_highlights_query( -// r#" -// ["<%" "%>"] @keyword -// "#, -// ) -// .unwrap() -// .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_highlights_query( -// r#" -// (field_identifier) @field -// (struct_expression) @struct -// "#, -// ) -// .unwrap() -// .with_injection_query( -// r#" -// (macro_invocation -// (token_tree) @content -// (#set! "language" "rust")) -// "#, -// ) -// .unwrap() -// } - -// fn markdown_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "Markdown".into(), -// path_suffixes: vec!["md".into()], -// ..Default::default() -// }, -// Some(tree_sitter_markdown::language()), -// ) -// .with_injection_query( -// r#" -// (fenced_code_block -// (info_string -// (language) @language) -// (code_fence_content) @content) -// "#, -// ) -// .unwrap() -// } - -// fn elixir_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "Elixir".into(), -// path_suffixes: vec!["ex".into()], -// ..Default::default() -// }, -// Some(tree_sitter_elixir::language()), -// ) -// .with_highlights_query( -// r#" - -// "#, -// ) -// .unwrap() -// } - -// fn heex_lang() -> Language { -// Language::new( -// LanguageConfig { -// name: "HEEx".into(), -// path_suffixes: vec!["heex".into()], -// ..Default::default() -// }, -// Some(tree_sitter_heex::language()), -// ) -// .with_injection_query( -// r#" -// ( -// (directive -// [ -// (partial_expression_value) -// (expression_value) -// (ending_expression_value) -// ] @content) -// (#set! language "elixir") -// (#set! combined) -// ) - -// ((expression (expression_value) @content) -// (#set! language "elixir")) -// "#, -// ) -// .unwrap() -// } - -// fn range_for_text(buffer: &Buffer, text: &str) -> Range { -// let start = buffer.as_rope().to_string().find(text).unwrap(); -// start..start + text.len() -// } - -// #[track_caller] -// fn assert_layers_for_range( -// syntax_map: &SyntaxMap, -// buffer: &BufferSnapshot, -// range: Range, -// expected_layers: &[&str], -// ) { -// let layers = syntax_map -// .layers_for_range(range, &buffer) -// .collect::>(); -// assert_eq!( -// layers.len(), -// expected_layers.len(), -// "wrong number of layers" -// ); -// for (i, (layer, expected_s_exp)) in layers.iter().zip(expected_layers.iter()).enumerate() { -// let actual_s_exp = layer.node().to_sexp(); -// assert!( -// string_contains_sequence( -// &actual_s_exp, -// &expected_s_exp.split("...").collect::>() -// ), -// "layer {i}:\n\nexpected: {expected_s_exp}\nactual: {actual_s_exp}", -// ); -// } -// } - -// fn assert_capture_ranges( -// syntax_map: &SyntaxMap, -// buffer: &BufferSnapshot, -// highlight_query_capture_names: &[&str], -// marked_string: &str, -// ) { -// let mut actual_ranges = Vec::>::new(); -// let captures = syntax_map.captures(0..buffer.len(), buffer, |grammar| { -// grammar.highlights_query.as_ref() -// }); -// let queries = captures -// .grammars() -// .iter() -// .map(|grammar| grammar.highlights_query.as_ref().unwrap()) -// .collect::>(); -// for capture in captures { -// let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; -// if highlight_query_capture_names.contains(&name.as_str()) { -// actual_ranges.push(capture.node.byte_range()); -// } -// } - -// let (text, expected_ranges) = marked_text_ranges(&marked_string.unindent(), false); -// assert_eq!(text, buffer.text()); -// assert_eq!(actual_ranges, expected_ranges); -// } - -// pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool { -// let mut last_part_end = 0; -// for part in parts { -// if let Some(start_ix) = text[last_part_end..].find(part) { -// last_part_end = start_ix + part.len(); -// } else { -// return false; -// } -// } -// true -// } +use super::*; +use crate::LanguageConfig; +use rand::rngs::StdRng; +use std::{env, ops::Range, sync::Arc}; +use text::Buffer; +use tree_sitter::Node; +use unindent::Unindent as _; +use util::test::marked_text_ranges; + +#[test] +fn test_splice_included_ranges() { + let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)]; + + let (new_ranges, change) = splice_included_ranges( + ranges.clone(), + &[54..56, 58..68], + &[ts_range(50..54), ts_range(59..67)], + ); + assert_eq!( + new_ranges, + &[ + ts_range(20..30), + ts_range(50..54), + ts_range(59..67), + ts_range(80..90), + ] + ); + assert_eq!(change, 1..3); + + let (new_ranges, change) = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); + assert_eq!( + new_ranges, + &[ts_range(20..30), ts_range(50..60), ts_range(80..90)] + ); + assert_eq!(change, 2..3); + + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]); + assert_eq!( + new_ranges, + &[ + ts_range(0..2), + ts_range(20..30), + ts_range(50..60), + ts_range(70..75), + ts_range(80..90) + ] + ); + assert_eq!(change, 0..4); + + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); + assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]); + assert_eq!(change, 0..1); + + // does not create overlapping ranges + let (new_ranges, change) = + splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]); + assert_eq!( + new_ranges, + &[ts_range(20..32), ts_range(50..60), ts_range(80..90)] + ); + assert_eq!(change, 0..1); + + fn ts_range(range: Range) -> tree_sitter::Range { + tree_sitter::Range { + start_byte: range.start, + start_point: tree_sitter::Point { + row: 0, + column: range.start, + }, + end_byte: range.end, + end_point: tree_sitter::Point { + row: 0, + column: range.end, + }, + } + } +} + +#[gpui2::test] +fn test_syntax_map_layers_for_range() { + let registry = Arc::new(LanguageRegistry::test()); + let language = Arc::new(rust_lang()); + registry.add(language.clone()); + + let mut buffer = Buffer::new( + 0, + 0, + r#" + fn a() { + assert_eq!( + b(vec![C {}]), + vec![d.e], + ); + println!("{}", f(|_| true)); + } + "# + .unindent(), + ); + + let mut syntax_map = SyntaxMap::new(); + syntax_map.set_language_registry(registry.clone()); + syntax_map.reparse(language.clone(), &buffer); + + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(2, 0)..Point::new(2, 0), + &[ + "...(function_item ... (block (expression_statement (macro_invocation...", + "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", + ], + ); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(2, 14)..Point::new(2, 16), + &[ + "...(function_item ...", + "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", + "...(array_expression (struct_expression ...", + ], + ); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 14)..Point::new(3, 16), + &[ + "...(function_item ...", + "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", + "...(array_expression (field_expression ...", + ], + ); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(5, 12)..Point::new(5, 16), + &[ + "...(function_item ...", + "...(call_expression ... (arguments (closure_expression ...", + ], + ); + + // Replace a vec! macro invocation with a plain slice, removing a syntactic layer. + let macro_name_range = range_for_text(&buffer, "vec!"); + buffer.edit([(macro_name_range, "&")]); + syntax_map.interpolate(&buffer); + syntax_map.reparse(language.clone(), &buffer); + + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(2, 14)..Point::new(2, 16), + &[ + "...(function_item ...", + "...(tuple_expression (call_expression ... arguments: (arguments (reference_expression value: (array_expression...", + ], + ); + + // Put the vec! macro back, adding back the syntactic layer. + buffer.undo(); + syntax_map.interpolate(&buffer); + syntax_map.reparse(language.clone(), &buffer); + + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(2, 14)..Point::new(2, 16), + &[ + "...(function_item ...", + "...(tuple_expression (call_expression ... arguments: (arguments (macro_invocation...", + "...(array_expression (struct_expression ...", + ], + ); +} + +#[gpui2::test] +fn test_dynamic_language_injection() { + let registry = Arc::new(LanguageRegistry::test()); + let markdown = Arc::new(markdown_lang()); + registry.add(markdown.clone()); + registry.add(Arc::new(rust_lang())); + registry.add(Arc::new(ruby_lang())); + + let mut buffer = Buffer::new( + 0, + 0, + r#" + This is a code block: + + ```rs + fn foo() {} + ``` + "# + .unindent(), + ); + + let mut syntax_map = SyntaxMap::new(); + syntax_map.set_language_registry(registry.clone()); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", + "...(function_item name: (identifier) parameters: (parameters) body: (block)...", + ], + ); + + // Replace Rust with Ruby in code block. + let macro_name_range = range_for_text(&buffer, "rs"); + buffer.edit([(macro_name_range, "ruby")]); + syntax_map.interpolate(&buffer); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", + "...(call method: (identifier) arguments: (argument_list (call method: (identifier) arguments: (argument_list) block: (block)...", + ], + ); + + // Replace Ruby with a language that hasn't been loaded yet. + let macro_name_range = range_for_text(&buffer, "ruby"); + buffer.edit([(macro_name_range, "html")]); + syntax_map.interpolate(&buffer); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter..." + ], + ); + assert!(syntax_map.contains_unknown_injections()); + + registry.add(Arc::new(html_lang())); + syntax_map.reparse(markdown.clone(), &buffer); + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(3, 0)..Point::new(3, 0), + &[ + "...(fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (code_fence_content) (fenced_code_block_delimiter...", + "(fragment (text))", + ], + ); + assert!(!syntax_map.contains_unknown_injections()); +} + +#[gpui2::test] +fn test_typing_multiple_new_injections() { + let (buffer, syntax_map) = test_edit_sequence( + "Rust", + &[ + "fn a() { dbg }", + "fn a() { dbg«!» }", + "fn a() { dbg!«()» }", + "fn a() { dbg!(«b») }", + "fn a() { dbg!(b«.») }", + "fn a() { dbg!(b.«c») }", + "fn a() { dbg!(b.c«()») }", + "fn a() { dbg!(b.c(«vec»)) }", + "fn a() { dbg!(b.c(vec«!»)) }", + "fn a() { dbg!(b.c(vec!«[]»)) }", + "fn a() { dbg!(b.c(vec![«d»])) }", + "fn a() { dbg!(b.c(vec![d«.»])) }", + "fn a() { dbg!(b.c(vec![d.«e»])) }", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["field"], + "fn a() { dbg!(b.«c»(vec![d.«e»])) }", + ); +} + +#[gpui2::test] +fn test_pasting_new_injection_line_between_others() { + let (buffer, syntax_map) = test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!(B {}); + c!(C {}); + d!(D {}); + e!(E {}); + f!(F {}); + g!(G {}); + } + ", + " + fn a() { + b!(B {}); + c!(C {}); + d!(D {}); + « h!(H {}); + » e!(E {}); + f!(F {}); + g!(G {}); + } + ", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["struct"], + " + fn a() { + b!(«B {}»); + c!(«C {}»); + d!(«D {}»); + h!(«H {}»); + e!(«E {}»); + f!(«F {}»); + g!(«G {}»); + } + ", + ); +} + +#[gpui2::test] +fn test_joining_injections_with_child_injections() { + let (buffer, syntax_map) = test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!( + c![one.two.three], + d![four.five.six], + ); + e!( + f![seven.eight], + ); + } + ", + " + fn a() { + b!( + c![one.two.three], + d![four.five.six], + ˇ f![seven.eight], + ); + } + ", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["field"], + " + fn a() { + b!( + c![one.«two».«three»], + d![four.«five».«six»], + f![seven.«eight»], + ); + } + ", + ); +} + +#[gpui2::test] +fn test_editing_edges_of_injection() { + test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!(c!()) + } + ", + " + fn a() { + «d»!(c!()) + } + ", + " + fn a() { + «e»d!(c!()) + } + ", + " + fn a() { + ed!«[»c!()«]» + } + ", + ], + ); +} + +#[gpui2::test] +fn test_edits_preceding_and_intersecting_injection() { + test_edit_sequence( + "Rust", + &[ + // + "const aaaaaaaaaaaa: B = c!(d(e.f));", + "const aˇa: B = c!(d(eˇ));", + ], + ); +} + +#[gpui2::test] +fn test_non_local_changes_create_injections() { + test_edit_sequence( + "Rust", + &[ + " + // a! { + static B: C = d; + // } + ", + " + ˇa! { + static B: C = d; + ˇ} + ", + ], + ); +} + +#[gpui2::test] +fn test_creating_many_injections_in_one_edit() { + test_edit_sequence( + "Rust", + &[ + " + fn a() { + one(Two::three(3)); + four(Five::six(6)); + seven(Eight::nine(9)); + } + ", + " + fn a() { + one«!»(Two::three(3)); + four«!»(Five::six(6)); + seven«!»(Eight::nine(9)); + } + ", + " + fn a() { + one!(Two::three«!»(3)); + four!(Five::six«!»(6)); + seven!(Eight::nine«!»(9)); + } + ", + ], + ); +} + +#[gpui2::test] +fn test_editing_across_injection_boundary() { + test_edit_sequence( + "Rust", + &[ + " + fn one() { + two(); + three!( + three.four, + five.six, + ); + } + ", + " + fn one() { + two(); + th«irty_five![» + three.four, + five.six, + « seven.eight, + ];» + } + ", + ], + ); +} + +#[gpui2::test] +fn test_removing_injection_by_replacing_across_boundary() { + test_edit_sequence( + "Rust", + &[ + " + fn one() { + two!( + three.four, + ); + } + ", + " + fn one() { + t«en + .eleven( + twelve, + » + three.four, + ); + } + ", + ], + ); +} + +#[gpui2::test] +fn test_combined_injections_simple() { + let (buffer, syntax_map) = test_edit_sequence( + "ERB", + &[ + " + + <% if @one %> +
+ <% else %> +
+ <% end %> +
+ + ", + " + + <% if @one %> +
+ ˇ else ˇ +
+ <% end %> +
+ + ", + " + + <% if @one «;» end %> +
+ + ", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["tag", "ivar"], + " + <«body»> + <% if «@one» ; end %> + + + ", + ); +} + +#[gpui2::test] +fn test_combined_injections_empty_ranges() { + test_edit_sequence( + "ERB", + &[ + " + <% if @one %> + <% else %> + <% end %> + ", + " + <% if @one %> + ˇ<% end %> + ", + ], + ); +} + +#[gpui2::test] +fn test_combined_injections_edit_edges_of_ranges() { + let (buffer, syntax_map) = test_edit_sequence( + "ERB", + &[ + " + <%= one @two %> + <%= three @four %> + ", + " + <%= one @two %ˇ + <%= three @four %> + ", + " + <%= one @two %«>» + <%= three @four %> + ", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["tag", "ivar"], + " + <%= one «@two» %> + <%= three «@four» %> + ", + ); +} + +#[gpui2::test] +fn test_combined_injections_splitting_some_injections() { + let (_buffer, _syntax_map) = test_edit_sequence( + "ERB", + &[ + r#" + <%A if b(:c) %> + d + <% end %> + eee + <% f %> + "#, + r#" + <%« AAAAAAA %> + hhhhhhh + <%=» if b(:c) %> + d + <% end %> + eee + <% f %> + "#, + ], + ); +} + +#[gpui2::test] +fn test_combined_injections_editing_after_last_injection() { + test_edit_sequence( + "ERB", + &[ + r#" + <% foo %> +
+ <% bar %> + "#, + r#" + <% foo %> +
+ <% bar %>« + more text» + "#, + ], + ); +} + +#[gpui2::test] +fn test_combined_injections_inside_injections() { + let (buffer, syntax_map) = test_edit_sequence( + "Markdown", + &[ + r#" + here is + some + ERB code: + + ```erb +
    + <% people.each do |person| %> +
  • <%= person.name %>
  • +
  • <%= person.age %>
  • + <% end %> +
+ ``` + "#, + r#" + here is + some + ERB code: + + ```erb +
    + <% people«2».each do |person| %> +
  • <%= person.name %>
  • +
  • <%= person.age %>
  • + <% end %> +
+ ``` + "#, + // Inserting a comment character inside one code directive + // does not cause the other code directive to become a comment, + // because newlines are included in between each injection range. + r#" + here is + some + ERB code: + + ```erb +
    + <% people2.each do |person| %> +
  • <%= «# »person.name %>
  • +
  • <%= person.age %>
  • + <% end %> +
+ ``` + "#, + ], + ); + + // Check that the code directive below the ruby comment is + // not parsed as a comment. + assert_capture_ranges( + &syntax_map, + &buffer, + &["method"], + " + here is + some + ERB code: + + ```erb +
    + <% people2.«each» do |person| %> +
  • <%= # person.name %>
  • +
  • <%= person.«age» %>
  • + <% end %> +
+ ``` + ", + ); +} + +#[gpui2::test] +fn test_empty_combined_injections_inside_injections() { + let (buffer, syntax_map) = test_edit_sequence( + "Markdown", + &[r#" + ```erb + hello + ``` + + goodbye + "#], + ); + + assert_layers_for_range( + &syntax_map, + &buffer, + Point::new(0, 0)..Point::new(5, 0), + &[ + "...(paragraph)...", + "(template...", + "(fragment...", + // The ruby syntax tree should be empty, since there are + // no interpolations in the ERB template. + "(program)", + ], + ); +} + +#[gpui2::test(iterations = 50)] +fn test_random_syntax_map_edits_rust_macros(rng: StdRng) { + let text = r#" + fn test_something() { + let vec = vec![5, 1, 3, 8]; + assert_eq!( + vec + .into_iter() + .map(|i| i * 2) + .collect::>(), + vec![ + 5 * 2, 1 * 2, 3 * 2, 8 * 2 + ], + ); + } + "# + .unindent() + .repeat(2); + + let registry = Arc::new(LanguageRegistry::test()); + let language = Arc::new(rust_lang()); + registry.add(language.clone()); + + test_random_edits(text, registry, language, rng); +} + +#[gpui2::test(iterations = 50)] +fn test_random_syntax_map_edits_with_erb(rng: StdRng) { + let text = r#" +
+ <% if one?(:two) %> +

+ <%= yield :five %> +

+ <% elsif Six.seven(8) %> +

+ <%= yield :five %> +

+ <% else %> + Ok + <% end %> +
+ "# + .unindent() + .repeat(5); + + let registry = Arc::new(LanguageRegistry::test()); + let language = Arc::new(erb_lang()); + registry.add(language.clone()); + registry.add(Arc::new(ruby_lang())); + registry.add(Arc::new(html_lang())); + + test_random_edits(text, registry, language, rng); +} + +#[gpui2::test(iterations = 50)] +fn test_random_syntax_map_edits_with_heex(rng: StdRng) { + let text = r#" + defmodule TheModule do + def the_method(assigns) do + ~H""" + <%= if @empty do %> +
+ <% else %> +
+
+
+
+
+
+
+ <% end %> + """ + end + end + "# + .unindent() + .repeat(3); + + let registry = Arc::new(LanguageRegistry::test()); + let language = Arc::new(elixir_lang()); + registry.add(language.clone()); + registry.add(Arc::new(heex_lang())); + registry.add(Arc::new(html_lang())); + + test_random_edits(text, registry, language, rng); +} + +fn test_random_edits( + text: String, + registry: Arc, + language: Arc, + mut rng: StdRng, +) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let mut buffer = Buffer::new(0, 0, text); + + let mut syntax_map = SyntaxMap::new(); + syntax_map.set_language_registry(registry.clone()); + syntax_map.reparse(language.clone(), &buffer); + + let mut reference_syntax_map = SyntaxMap::new(); + reference_syntax_map.set_language_registry(registry.clone()); + + log::info!("initial text:\n{}", buffer.text()); + + for _ in 0..operations { + let prev_buffer = buffer.snapshot(); + let prev_syntax_map = syntax_map.snapshot(); + + buffer.randomly_edit(&mut rng, 3); + log::info!("text:\n{}", buffer.text()); + + syntax_map.interpolate(&buffer); + check_interpolation(&prev_syntax_map, &syntax_map, &prev_buffer, &buffer); + + syntax_map.reparse(language.clone(), &buffer); + + reference_syntax_map.clear(); + reference_syntax_map.reparse(language.clone(), &buffer); + } + + for i in 0..operations { + let i = operations - i - 1; + buffer.undo(); + log::info!("undoing operation {}", i); + log::info!("text:\n{}", buffer.text()); + + syntax_map.interpolate(&buffer); + syntax_map.reparse(language.clone(), &buffer); + + reference_syntax_map.clear(); + reference_syntax_map.reparse(language.clone(), &buffer); + assert_eq!( + syntax_map.layers(&buffer).len(), + reference_syntax_map.layers(&buffer).len(), + "wrong number of layers after undoing edit {i}" + ); + } + + let layers = syntax_map.layers(&buffer); + let reference_layers = reference_syntax_map.layers(&buffer); + for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) { + assert_eq!( + edited_layer.node().to_sexp(), + reference_layer.node().to_sexp() + ); + assert_eq!(edited_layer.node().range(), reference_layer.node().range()); + } +} + +fn check_interpolation( + old_syntax_map: &SyntaxSnapshot, + new_syntax_map: &SyntaxSnapshot, + old_buffer: &BufferSnapshot, + new_buffer: &BufferSnapshot, +) { + let edits = new_buffer + .edits_since::(&old_buffer.version()) + .collect::>(); + + for (old_layer, new_layer) in old_syntax_map + .layers + .iter() + .zip(new_syntax_map.layers.iter()) + { + assert_eq!(old_layer.range, new_layer.range); + let Some(old_tree) = old_layer.content.tree() else { + continue; + }; + let Some(new_tree) = new_layer.content.tree() else { + continue; + }; + let old_start_byte = old_layer.range.start.to_offset(old_buffer); + let new_start_byte = new_layer.range.start.to_offset(new_buffer); + let old_start_point = old_layer.range.start.to_point(old_buffer).to_ts_point(); + let new_start_point = new_layer.range.start.to_point(new_buffer).to_ts_point(); + let old_node = old_tree.root_node_with_offset(old_start_byte, old_start_point); + let new_node = new_tree.root_node_with_offset(new_start_byte, new_start_point); + check_node_edits( + old_layer.depth, + &old_layer.range, + old_node, + new_node, + old_buffer, + new_buffer, + &edits, + ); + } + + fn check_node_edits( + depth: usize, + range: &Range, + old_node: Node, + new_node: Node, + old_buffer: &BufferSnapshot, + new_buffer: &BufferSnapshot, + edits: &[text::Edit], + ) { + assert_eq!(old_node.kind(), new_node.kind()); + + let old_range = old_node.byte_range(); + let new_range = new_node.byte_range(); + + let is_edited = edits + .iter() + .any(|edit| edit.new.start < new_range.end && edit.new.end > new_range.start); + if is_edited { + assert!( + new_node.has_changes(), + concat!( + "failed to mark node as edited.\n", + "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n", + "node kind: {}, old node range: {:?}, new node range: {:?}", + ), + depth, + range.to_offset(old_buffer), + range.to_offset(new_buffer), + new_node.kind(), + old_range, + new_range, + ); + } + + if !new_node.has_changes() { + assert_eq!( + old_buffer + .text_for_range(old_range.clone()) + .collect::(), + new_buffer + .text_for_range(new_range.clone()) + .collect::(), + concat!( + "mismatched text for node\n", + "layer depth: {}, old layer range: {:?}, new layer range: {:?},\n", + "node kind: {}, old node range:{:?}, new node range:{:?}", + ), + depth, + range.to_offset(old_buffer), + range.to_offset(new_buffer), + new_node.kind(), + old_range, + new_range, + ); + } + + for i in 0..new_node.child_count() { + check_node_edits( + depth, + range, + old_node.child(i).unwrap(), + new_node.child(i).unwrap(), + old_buffer, + new_buffer, + edits, + ) + } + } +} + +fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) { + let registry = Arc::new(LanguageRegistry::test()); + registry.add(Arc::new(elixir_lang())); + registry.add(Arc::new(heex_lang())); + registry.add(Arc::new(rust_lang())); + registry.add(Arc::new(ruby_lang())); + registry.add(Arc::new(html_lang())); + registry.add(Arc::new(erb_lang())); + registry.add(Arc::new(markdown_lang())); + + let language = registry + .language_for_name(language_name) + .now_or_never() + .unwrap() + .unwrap(); + let mut buffer = Buffer::new(0, 0, Default::default()); + + let mut mutated_syntax_map = SyntaxMap::new(); + mutated_syntax_map.set_language_registry(registry.clone()); + mutated_syntax_map.reparse(language.clone(), &buffer); + + for (i, marked_string) in steps.into_iter().enumerate() { + let marked_string = marked_string.unindent(); + log::info!("incremental parse {i}: {marked_string:?}"); + buffer.edit_via_marked_text(&marked_string); + + // Reparse the syntax map + mutated_syntax_map.interpolate(&buffer); + mutated_syntax_map.reparse(language.clone(), &buffer); + + // Create a second syntax map from scratch + log::info!("fresh parse {i}: {marked_string:?}"); + let mut reference_syntax_map = SyntaxMap::new(); + reference_syntax_map.set_language_registry(registry.clone()); + reference_syntax_map.reparse(language.clone(), &buffer); + + // Compare the mutated syntax map to the new syntax map + let mutated_layers = mutated_syntax_map.layers(&buffer); + let reference_layers = reference_syntax_map.layers(&buffer); + assert_eq!( + mutated_layers.len(), + reference_layers.len(), + "wrong number of layers at step {i}" + ); + for (edited_layer, reference_layer) in + mutated_layers.into_iter().zip(reference_layers.into_iter()) + { + assert_eq!( + edited_layer.node().to_sexp(), + reference_layer.node().to_sexp(), + "different layer at step {i}" + ); + assert_eq!( + edited_layer.node().range(), + reference_layer.node().range(), + "different layer at step {i}" + ); + } + } + + (buffer, mutated_syntax_map) +} + +fn html_lang() -> Language { + Language::new( + LanguageConfig { + name: "HTML".into(), + path_suffixes: vec!["html".to_string()], + ..Default::default() + }, + Some(tree_sitter_html::language()), + ) + .with_highlights_query( + r#" + (tag_name) @tag + (erroneous_end_tag_name) @tag + (attribute_name) @property + "#, + ) + .unwrap() +} + +fn ruby_lang() -> Language { + Language::new( + LanguageConfig { + name: "Ruby".into(), + path_suffixes: vec!["rb".to_string()], + ..Default::default() + }, + Some(tree_sitter_ruby::language()), + ) + .with_highlights_query( + r#" + ["if" "do" "else" "end"] @keyword + (instance_variable) @ivar + (call method: (identifier) @method) + "#, + ) + .unwrap() +} + +fn erb_lang() -> Language { + Language::new( + LanguageConfig { + name: "ERB".into(), + path_suffixes: vec!["erb".to_string()], + ..Default::default() + }, + Some(tree_sitter_embedded_template::language()), + ) + .with_highlights_query( + r#" + ["<%" "%>"] @keyword + "#, + ) + .unwrap() + .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_highlights_query( + r#" + (field_identifier) @field + (struct_expression) @struct + "#, + ) + .unwrap() + .with_injection_query( + r#" + (macro_invocation + (token_tree) @content + (#set! "language" "rust")) + "#, + ) + .unwrap() +} + +fn markdown_lang() -> Language { + Language::new( + LanguageConfig { + name: "Markdown".into(), + path_suffixes: vec!["md".into()], + ..Default::default() + }, + Some(tree_sitter_markdown::language()), + ) + .with_injection_query( + r#" + (fenced_code_block + (info_string + (language) @language) + (code_fence_content) @content) + "#, + ) + .unwrap() +} + +fn elixir_lang() -> Language { + Language::new( + LanguageConfig { + name: "Elixir".into(), + path_suffixes: vec!["ex".into()], + ..Default::default() + }, + Some(tree_sitter_elixir::language()), + ) + .with_highlights_query( + r#" + + "#, + ) + .unwrap() +} + +fn heex_lang() -> Language { + Language::new( + LanguageConfig { + name: "HEEx".into(), + path_suffixes: vec!["heex".into()], + ..Default::default() + }, + Some(tree_sitter_heex::language()), + ) + .with_injection_query( + r#" + ( + (directive + [ + (partial_expression_value) + (expression_value) + (ending_expression_value) + ] @content) + (#set! language "elixir") + (#set! combined) + ) + + ((expression (expression_value) @content) + (#set! language "elixir")) + "#, + ) + .unwrap() +} + +fn range_for_text(buffer: &Buffer, text: &str) -> Range { + let start = buffer.as_rope().to_string().find(text).unwrap(); + start..start + text.len() +} + +#[track_caller] +fn assert_layers_for_range( + syntax_map: &SyntaxMap, + buffer: &BufferSnapshot, + range: Range, + expected_layers: &[&str], +) { + let layers = syntax_map + .layers_for_range(range, &buffer) + .collect::>(); + assert_eq!( + layers.len(), + expected_layers.len(), + "wrong number of layers" + ); + for (i, (layer, expected_s_exp)) in layers.iter().zip(expected_layers.iter()).enumerate() { + let actual_s_exp = layer.node().to_sexp(); + assert!( + string_contains_sequence( + &actual_s_exp, + &expected_s_exp.split("...").collect::>() + ), + "layer {i}:\n\nexpected: {expected_s_exp}\nactual: {actual_s_exp}", + ); + } +} + +fn assert_capture_ranges( + syntax_map: &SyntaxMap, + buffer: &BufferSnapshot, + highlight_query_capture_names: &[&str], + marked_string: &str, +) { + let mut actual_ranges = Vec::>::new(); + let captures = syntax_map.captures(0..buffer.len(), buffer, |grammar| { + grammar.highlights_query.as_ref() + }); + let queries = captures + .grammars() + .iter() + .map(|grammar| grammar.highlights_query.as_ref().unwrap()) + .collect::>(); + for capture in captures { + let name = &queries[capture.grammar_index].capture_names()[capture.index as usize]; + if highlight_query_capture_names.contains(&name.as_str()) { + actual_ranges.push(capture.node.byte_range()); + } + } + + let (text, expected_ranges) = marked_text_ranges(&marked_string.unindent(), false); + assert_eq!(text, buffer.text()); + assert_eq!(actual_ranges, expected_ranges); +} + +pub fn string_contains_sequence(text: &str, parts: &[&str]) -> bool { + let mut last_part_end = 0; + for part in parts { + if let Some(start_ix) = text[last_part_end..].find(part) { + last_part_end = start_ix + part.len(); + } else { + return false; + } + } + true +}