catch up with main
This commit is contained in:
commit
b9fdfd60f0
93 changed files with 3016 additions and 760 deletions
|
@ -46,7 +46,6 @@ lazy_static.workspace = true
|
|||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
@ -56,10 +55,12 @@ similar = "1.3"
|
|||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-rust = { version = "*", optional = true }
|
||||
tree-sitter-typescript = { version = "*", optional = true }
|
||||
unicase = "2.6"
|
||||
|
||||
rand = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
|
@ -74,12 +75,13 @@ indoc.workspace = true
|
|||
rand.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
tree-sitter-embedded-template = "*"
|
||||
tree-sitter-html = "*"
|
||||
tree-sitter-javascript = "*"
|
||||
tree-sitter-json = "*"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-rust = "*"
|
||||
tree-sitter-python = "*"
|
||||
tree-sitter-typescript = "*"
|
||||
tree-sitter-ruby = "*"
|
||||
tree-sitter-embedded-template.workspace = true
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter-markdown.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-ruby.workspace = true
|
||||
tree-sitter-elixir.workspace = true
|
||||
tree-sitter-heex.workspace = true
|
||||
|
|
|
@ -2145,23 +2145,27 @@ impl BufferSnapshot {
|
|||
|
||||
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
|
||||
let offset = position.to_offset(self);
|
||||
let mut range = 0..self.len();
|
||||
let mut scope = self.language.clone().map(|language| LanguageScope {
|
||||
language,
|
||||
override_id: None,
|
||||
});
|
||||
|
||||
if let Some(layer_info) = self
|
||||
.syntax
|
||||
.layers_for_range(offset..offset, &self.text)
|
||||
.filter(|l| l.node().end_byte() > offset)
|
||||
.last()
|
||||
{
|
||||
Some(LanguageScope {
|
||||
language: layer_info.language.clone(),
|
||||
override_id: layer_info.override_id(offset, &self.text),
|
||||
})
|
||||
} else {
|
||||
self.language.clone().map(|language| LanguageScope {
|
||||
language,
|
||||
override_id: None,
|
||||
})
|
||||
// Use the layer that has the smallest node intersecting the given point.
|
||||
for layer in self.syntax.layers_for_range(offset..offset, &self.text) {
|
||||
let mut cursor = layer.node().walk();
|
||||
while cursor.goto_first_child_for_byte(offset).is_some() {}
|
||||
let node_range = cursor.node().byte_range();
|
||||
if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() {
|
||||
range = node_range;
|
||||
scope = Some(LanguageScope {
|
||||
language: layer.language.clone(),
|
||||
override_id: layer.override_id(offset, &self.text),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
|
||||
|
|
|
@ -1533,47 +1533,9 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
|
|||
])
|
||||
});
|
||||
|
||||
let html_language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "HTML".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_html::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
"
|
||||
(element
|
||||
(start_tag) @start
|
||||
(end_tag)? @end) @indent
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let html_language = Arc::new(html_lang());
|
||||
|
||||
let javascript_language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "JavaScript".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
r#"
|
||||
(object "}" @end) @indent
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let javascript_language = Arc::new(javascript_lang());
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test());
|
||||
language_registry.add(html_language.clone());
|
||||
|
@ -1669,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_language_config_at(cx: &mut AppContext) {
|
||||
fn test_language_scope_at(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
cx.add_model(|cx| {
|
||||
|
@ -1709,7 +1671,7 @@ fn test_language_config_at(cx: &mut AppContext) {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
)
|
||||
.with_override_query(
|
||||
r#"
|
||||
|
@ -1756,6 +1718,54 @@ fn test_language_config_at(cx: &mut AppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
cx.add_model(|cx| {
|
||||
let text = r#"
|
||||
<ol>
|
||||
<% people.each do |person| %>
|
||||
<li>
|
||||
<%= person.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test());
|
||||
language_registry.add(Arc::new(ruby_lang()));
|
||||
language_registry.add(Arc::new(html_lang()));
|
||||
language_registry.add(Arc::new(erb_lang()));
|
||||
|
||||
let mut buffer = Buffer::new(0, text, cx);
|
||||
buffer.set_language_registry(language_registry.clone());
|
||||
buffer.set_language(
|
||||
language_registry
|
||||
.language_for_name("ERB")
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.ok(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let snapshot = buffer.snapshot();
|
||||
let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
|
||||
assert_eq!(html_config.line_comment_prefix(), None);
|
||||
assert_eq!(
|
||||
html_config.block_comment_delimiters(),
|
||||
Some((&"<!--".into(), &"-->".into()))
|
||||
);
|
||||
|
||||
let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
|
||||
assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# ");
|
||||
assert_eq!(ruby_config.block_comment_delimiters(), None);
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_serialization(cx: &mut gpui::AppContext) {
|
||||
let mut now = Instant::now();
|
||||
|
@ -2143,6 +2153,7 @@ fn ruby_lang() -> Language {
|
|||
LanguageConfig {
|
||||
name: "Ruby".into(),
|
||||
path_suffixes: vec!["rb".to_string()],
|
||||
line_comment: Some("# ".into()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_ruby::language()),
|
||||
|
@ -2158,6 +2169,61 @@ fn ruby_lang() -> Language {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn html_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "HTML".into(),
|
||||
block_comment: Some(("<!--".into(), "-->".into())),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_html::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
"
|
||||
(element
|
||||
(start_tag) @start
|
||||
(end_tag)? @end) @indent
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn erb_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "ERB".into(),
|
||||
path_suffixes: vec!["erb".to_string()],
|
||||
block_comment: Some(("<%#".into(), "%>".into())),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_embedded_template::language()),
|
||||
)
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(
|
||||
(code) @content
|
||||
(#set! "language" "ruby")
|
||||
(#set! "combined")
|
||||
)
|
||||
|
||||
(
|
||||
(content) @content
|
||||
(#set! "language" "html")
|
||||
(#set! "combined")
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn rust_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
|
@ -2227,7 +2293,7 @@ fn javascript_lang() -> Language {
|
|||
name: "JavaScript".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
)
|
||||
.with_brackets_query(
|
||||
r#"
|
||||
|
@ -2236,6 +2302,12 @@ fn javascript_lang() -> Language {
|
|||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_indents_query(
|
||||
r#"
|
||||
(object "}" @end) @indent
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
|
||||
|
|
|
@ -830,6 +830,7 @@ impl LanguageRegistry {
|
|||
Ok(language) => {
|
||||
let language = Arc::new(language);
|
||||
let mut state = this.state.write();
|
||||
|
||||
state.add(language.clone());
|
||||
state.mark_language_loaded(id);
|
||||
if let Some(mut txs) = state.loading_languages.remove(&id) {
|
||||
|
@ -1787,7 +1788,7 @@ mod tests {
|
|||
first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
|
||||
..Default::default()
|
||||
},
|
||||
tree_sitter_javascript::language(),
|
||||
tree_sitter_typescript::language_tsx(),
|
||||
vec![],
|
||||
|_| Default::default(),
|
||||
);
|
||||
|
|
|
@ -569,11 +569,19 @@ impl SyntaxSnapshot {
|
|||
range.end = range.end.saturating_sub(step_start_byte);
|
||||
}
|
||||
|
||||
included_ranges = splice_included_ranges(
|
||||
let changed_indices;
|
||||
(included_ranges, changed_indices) = splice_included_ranges(
|
||||
old_tree.included_ranges(),
|
||||
&parent_layer_changed_ranges,
|
||||
&included_ranges,
|
||||
);
|
||||
insert_newlines_between_ranges(
|
||||
changed_indices,
|
||||
&mut included_ranges,
|
||||
&text,
|
||||
step_start_byte,
|
||||
step_start_point,
|
||||
);
|
||||
}
|
||||
|
||||
if included_ranges.is_empty() {
|
||||
|
@ -586,7 +594,7 @@ impl SyntaxSnapshot {
|
|||
}
|
||||
|
||||
log::trace!(
|
||||
"update layer. language:{}, start:{:?}, ranges:{:?}",
|
||||
"update layer. language:{}, start:{:?}, included_ranges:{:?}",
|
||||
language.name(),
|
||||
LogAnchorRange(&step.range, text),
|
||||
LogIncludedRanges(&included_ranges),
|
||||
|
@ -608,6 +616,16 @@ impl SyntaxSnapshot {
|
|||
}),
|
||||
);
|
||||
} else {
|
||||
if matches!(step.mode, ParseMode::Combined { .. }) {
|
||||
insert_newlines_between_ranges(
|
||||
0..included_ranges.len(),
|
||||
&mut included_ranges,
|
||||
text,
|
||||
step_start_byte,
|
||||
step_start_point,
|
||||
);
|
||||
}
|
||||
|
||||
if included_ranges.is_empty() {
|
||||
included_ranges.push(tree_sitter::Range {
|
||||
start_byte: 0,
|
||||
|
@ -771,8 +789,10 @@ impl SyntaxSnapshot {
|
|||
range: Range<T>,
|
||||
buffer: &'a BufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = SyntaxLayerInfo> {
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
let start_offset = range.start.to_offset(buffer);
|
||||
let end_offset = range.end.to_offset(buffer);
|
||||
let start = buffer.anchor_before(start_offset);
|
||||
let end = buffer.anchor_after(end_offset);
|
||||
|
||||
let mut cursor = self.layers.filter::<_, ()>(move |summary| {
|
||||
if summary.max_depth > summary.min_depth {
|
||||
|
@ -787,20 +807,21 @@ impl SyntaxSnapshot {
|
|||
cursor.next(buffer);
|
||||
iter::from_fn(move || {
|
||||
while let Some(layer) = cursor.item() {
|
||||
let mut info = None;
|
||||
if let SyntaxLayerContent::Parsed { tree, language } = &layer.content {
|
||||
let info = SyntaxLayerInfo {
|
||||
let layer_start_offset = layer.range.start.to_offset(buffer);
|
||||
let layer_start_point = layer.range.start.to_point(buffer).to_ts_point();
|
||||
|
||||
info = Some(SyntaxLayerInfo {
|
||||
tree,
|
||||
language,
|
||||
depth: layer.depth,
|
||||
offset: (
|
||||
layer.range.start.to_offset(buffer),
|
||||
layer.range.start.to_point(buffer).to_ts_point(),
|
||||
),
|
||||
};
|
||||
cursor.next(buffer);
|
||||
return Some(info);
|
||||
} else {
|
||||
cursor.next(buffer);
|
||||
offset: (layer_start_offset, layer_start_point),
|
||||
});
|
||||
}
|
||||
cursor.next(buffer);
|
||||
if info.is_some() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -1272,14 +1293,20 @@ fn get_injections(
|
|||
}
|
||||
}
|
||||
|
||||
/// Update the given list of included `ranges`, removing any ranges that intersect
|
||||
/// `removed_ranges`, and inserting the given `new_ranges`.
|
||||
///
|
||||
/// Returns a new vector of ranges, and the range of the vector that was changed,
|
||||
/// from the previous `ranges` vector.
|
||||
pub(crate) fn splice_included_ranges(
|
||||
mut ranges: Vec<tree_sitter::Range>,
|
||||
removed_ranges: &[Range<usize>],
|
||||
new_ranges: &[tree_sitter::Range],
|
||||
) -> Vec<tree_sitter::Range> {
|
||||
) -> (Vec<tree_sitter::Range>, Range<usize>) {
|
||||
let mut removed_ranges = removed_ranges.iter().cloned().peekable();
|
||||
let mut new_ranges = new_ranges.into_iter().cloned().peekable();
|
||||
let mut ranges_ix = 0;
|
||||
let mut changed_portion = usize::MAX..0;
|
||||
loop {
|
||||
let next_new_range = new_ranges.peek();
|
||||
let next_removed_range = removed_ranges.peek();
|
||||
|
@ -1341,11 +1368,69 @@ pub(crate) fn splice_included_ranges(
|
|||
}
|
||||
}
|
||||
|
||||
changed_portion.start = changed_portion.start.min(start_ix);
|
||||
changed_portion.end = changed_portion.end.max(if insert.is_some() {
|
||||
start_ix + 1
|
||||
} else {
|
||||
start_ix
|
||||
});
|
||||
|
||||
ranges.splice(start_ix..end_ix, insert);
|
||||
ranges_ix = start_ix;
|
||||
}
|
||||
|
||||
ranges
|
||||
if changed_portion.end < changed_portion.start {
|
||||
changed_portion = 0..0;
|
||||
}
|
||||
|
||||
(ranges, changed_portion)
|
||||
}
|
||||
|
||||
/// Ensure there are newline ranges in between content range that appear on
|
||||
/// different lines. For performance, only iterate through the given range of
|
||||
/// indices. All of the ranges in the array are relative to a given start byte
|
||||
/// and point.
|
||||
fn insert_newlines_between_ranges(
|
||||
indices: Range<usize>,
|
||||
ranges: &mut Vec<tree_sitter::Range>,
|
||||
text: &text::BufferSnapshot,
|
||||
start_byte: usize,
|
||||
start_point: Point,
|
||||
) {
|
||||
let mut ix = indices.end + 1;
|
||||
while ix > indices.start {
|
||||
ix -= 1;
|
||||
if 0 == ix || ix == ranges.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let range_b = ranges[ix].clone();
|
||||
let range_a = &mut ranges[ix - 1];
|
||||
if range_a.end_point.column == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if range_a.end_point.row < range_b.start_point.row {
|
||||
let end_point = start_point + Point::from_ts_point(range_a.end_point);
|
||||
let line_end = Point::new(end_point.row, text.line_len(end_point.row));
|
||||
if end_point.column as u32 >= line_end.column {
|
||||
range_a.end_byte += 1;
|
||||
range_a.end_point.row += 1;
|
||||
range_a.end_point.column = 0;
|
||||
} else {
|
||||
let newline_offset = text.point_to_offset(line_end);
|
||||
ranges.insert(
|
||||
ix,
|
||||
tree_sitter::Range {
|
||||
start_byte: newline_offset - start_byte,
|
||||
end_byte: newline_offset - start_byte + 1,
|
||||
start_point: (line_end - start_point).to_ts_point(),
|
||||
end_point: ((line_end - start_point) + Point::new(1, 0)).to_ts_point(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedSyntaxLayerInfo {
|
||||
|
|
|
@ -11,7 +11,7 @@ use util::test::marked_text_ranges;
|
|||
fn test_splice_included_ranges() {
|
||||
let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)];
|
||||
|
||||
let new_ranges = splice_included_ranges(
|
||||
let (new_ranges, change) = splice_included_ranges(
|
||||
ranges.clone(),
|
||||
&[54..56, 58..68],
|
||||
&[ts_range(50..54), ts_range(59..67)],
|
||||
|
@ -25,14 +25,16 @@ fn test_splice_included_ranges() {
|
|||
ts_range(80..90),
|
||||
]
|
||||
);
|
||||
assert_eq!(change, 1..3);
|
||||
|
||||
let new_ranges = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]);
|
||||
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 =
|
||||
let (new_ranges, change) =
|
||||
splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]);
|
||||
assert_eq!(
|
||||
new_ranges,
|
||||
|
@ -44,16 +46,21 @@ fn test_splice_included_ranges() {
|
|||
ts_range(80..90)
|
||||
]
|
||||
);
|
||||
assert_eq!(change, 0..4);
|
||||
|
||||
let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]);
|
||||
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 = splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]);
|
||||
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 {
|
||||
|
@ -511,7 +518,7 @@ fn test_removing_injection_by_replacing_across_boundary() {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_combined_injections() {
|
||||
fn test_combined_injections_simple() {
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"ERB",
|
||||
&[
|
||||
|
@ -653,33 +660,78 @@ fn test_combined_injections_editing_after_last_injection() {
|
|||
|
||||
#[gpui::test]
|
||||
fn test_combined_injections_inside_injections() {
|
||||
let (_buffer, _syntax_map) = test_edit_sequence(
|
||||
let (buffer, syntax_map) = test_edit_sequence(
|
||||
"Markdown",
|
||||
&[
|
||||
r#"
|
||||
here is some ERB code:
|
||||
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:
|
||||
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]
|
||||
|
@ -711,11 +763,7 @@ fn test_empty_combined_injections_inside_injections() {
|
|||
}
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
fn test_random_syntax_map_edits(mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
fn test_random_syntax_map_edits_rust_macros(rng: StdRng) {
|
||||
let text = r#"
|
||||
fn test_something() {
|
||||
let vec = vec![5, 1, 3, 8];
|
||||
|
@ -736,68 +784,12 @@ fn test_random_syntax_map_edits(mut rng: StdRng) {
|
|||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let language = Arc::new(rust_lang());
|
||||
registry.add(language.clone());
|
||||
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());
|
||||
}
|
||||
test_random_edits(text, registry, language, rng);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
fn test_random_syntax_map_edits_with_combined_injections(mut rng: StdRng) {
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
fn test_random_syntax_map_edits_with_erb(rng: StdRng) {
|
||||
let text = r#"
|
||||
<div id="main">
|
||||
<% if one?(:two) %>
|
||||
|
@ -814,13 +806,60 @@ fn test_random_syntax_map_edits_with_combined_injections(mut rng: StdRng) {
|
|||
</div>
|
||||
"#
|
||||
.unindent()
|
||||
.repeat(8);
|
||||
.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, 0, text);
|
||||
|
||||
let mut syntax_map = SyntaxMap::new();
|
||||
|
@ -984,11 +1023,14 @@ fn check_interpolation(
|
|||
|
||||
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()
|
||||
|
@ -1074,6 +1116,7 @@ fn ruby_lang() -> Language {
|
|||
r#"
|
||||
["if" "do" "else" "end"] @keyword
|
||||
(instance_variable) @ivar
|
||||
(call method: (identifier) @method)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
|
@ -1158,6 +1201,52 @@ fn markdown_lang() -> Language {
|
|||
.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<usize> {
|
||||
let start = buffer.as_rope().to_string().find(text).unwrap();
|
||||
start..start + text.len()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue