Files
oak-gpui/crates/language/src/syntax_map/syntax_map_tests.rs
T
Max BrunsfeldandMarshall 6edeea7c8a Add logic for managing language and theme extensions (#7467)
This PR adds the initial support for loading extensions in Zed.

### Extensions Directory

Extensions are loaded from the extensions directory.

The extensions directory has the following structure:

```
extensions/
  installed/
    extension-a/
      grammars/
      languages/
    extension-b/
      themes/
  manifest.json
```

The `manifest.json` file is used internally by Zed to keep track of
which extensions are installed. This file should be maintained
automatically, and shouldn't require any direct interaction with it.

Extensions can provide Tree-sitter grammars, languages, and themes.

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
2024-02-07 15:14:50 -05:00

1345 lines
36 KiB
Rust

use super::*;
use crate::{LanguageConfig, LanguageMatcher};
use rand::rngs::StdRng;
use std::{env, ops::Range, sync::Arc};
use text::{Buffer, BufferId};
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<usize>) -> 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,
BufferId::new(1).unwrap(),
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,
BufferId::new(1).unwrap(),
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() { test_macro }",
"fn a() { test_macro«!» }",
"fn a() { test_macro!«()» }",
"fn a() { test_macro!(«b») }",
"fn a() { test_macro!(b«.») }",
"fn a() { test_macro!(b.«c») }",
"fn a() { test_macro!(b.c«()») }",
"fn a() { test_macro!(b.c(«vec»)) }",
"fn a() { test_macro!(b.c(vec«!»)) }",
"fn a() { test_macro!(b.c(vec!«[]»)) }",
"fn a() { test_macro!(b.c(vec![«d»])) }",
"fn a() { test_macro!(b.c(vec![d«.»])) }",
"fn a() { test_macro!(b.c(vec![d.«e»])) }",
],
);
assert_capture_ranges(
&syntax_map,
&buffer,
&["field"],
"fn a() { test_macro!(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",
&[
"
<body>
<% if @one %>
<div class=one>
<% else %>
<div class=two>
<% end %>
</div>
</body>
",
"
<body>
<% if @one %>
<div class=one>
ˇ else ˇ
<div class=two>
<% end %>
</div>
</body>
",
"
<body>
<% if @one «;» end %>
</div>
</body>
",
],
);
assert_capture_ranges(
&syntax_map,
&buffer,
&["tag", "ivar"],
"
<«body»>
<% if «@one» ; end %>
</«div»>
</«body»>
",
);
}
#[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 %>
<div></div>
<% bar %>
"#,
r#"
<% foo %>
<div></div>
<% 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
<ul>
<% people.each do |person| %>
<li><%= person.name %></li>
<li><%= person.age %></li>
<% end %>
</ul>
```
"#,
r#"
here is
some
ERB code:
```erb
<ul>
<% people«2».each do |person| %>
<li><%= person.name %></li>
<li><%= person.age %></li>
<% end %>
</ul>
```
"#,
// 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
<ul>
<% people2.each do |person| %>
<li><%= «# »person.name %></li>
<li><%= person.age %></li>
<% end %>
</ul>
```
"#,
],
);
// 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
<ul>
<% people2.«each» do |person| %>
<li><%= # person.name %></li>
<li><%= person.«age» %></li>
<% end %>
</ul>
```
",
);
}
#[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<usize>>(),
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#"
<div id="main">
<% if one?(:two) %>
<p class="three" four>
<%= yield :five %>
</p>
<% elsif Six.seven(8) %>
<p id="three" four>
<%= yield :five %>
</p>
<% else %>
<span>Ok</span>
<% end %>
</div>
"#
.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 %>
<div class="h-4"></div>
<% else %>
<div class="max-w-2xl w-full animate-pulse">
<div class="flex-1 space-y-4">
<div class={[@bg_class, "h-4 rounded-lg w-3/4"]}></div>
<div class={[@bg_class, "h-4 rounded-lg"]}></div>
<div class={[@bg_class, "h-4 rounded-lg w-5/6"]}></div>
</div>
</div>
<% 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<LanguageRegistry>,
language: Arc<Language>,
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, BufferId::new(1).unwrap(), 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::<usize>(&old_buffer.version())
.collect::<Vec<_>>();
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<Anchor>,
old_node: Node,
new_node: Node,
old_buffer: &BufferSnapshot,
new_buffer: &BufferSnapshot,
edits: &[text::Edit<usize>],
) {
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::<String>(),
new_buffer
.text_for_range(new_range.clone())
.collect::<String>(),
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, BufferId::new(1).unwrap(), 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(),
matcher: LanguageMatcher {
path_suffixes: vec!["html".to_string()],
..Default::default()
},
..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(),
matcher: LanguageMatcher {
path_suffixes: vec!["rb".to_string()],
..Default::default()
},
..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(),
matcher: LanguageMatcher {
path_suffixes: vec!["erb".to_string()],
..Default::default()
},
..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(),
matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
..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(),
matcher: LanguageMatcher {
path_suffixes: vec!["md".into()],
..Default::default()
},
..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(),
matcher: LanguageMatcher {
path_suffixes: vec!["ex".into()],
..Default::default()
},
..Default::default()
},
Some(tree_sitter_elixir::language()),
)
.with_highlights_query(
r#"
"#,
)
.unwrap()
}
fn heex_lang() -> Language {
Language::new(
LanguageConfig {
name: "HEEx".into(),
matcher: LanguageMatcher {
path_suffixes: vec!["heex".into()],
..Default::default()
},
..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<usize> {
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<Point>,
expected_layers: &[&str],
) {
let layers = syntax_map
.layers_for_range(range, &buffer)
.collect::<Vec<_>>();
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::<Vec<_>>()
),
"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::<Range<usize>>::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::<Vec<_>>();
for capture in captures {
let name = &queries[capture.grammar_index].capture_names()[capture.index as usize];
if highlight_query_capture_names.contains(&name) {
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
}