@@ -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,
@@ -1275,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();
@@ -1344,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]
@@ -984,11 +1036,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 +1129,7 @@ fn ruby_lang() -> Language {
r#"
["if" "do" "else" "end"] @keyword
(instance_variable) @ivar
+ (call method: (identifier) @method)
"#,
)
.unwrap()
@@ -1158,6 +1214,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()