Include newlines in between combined injection ranges on different lines
This commit is contained in:
parent
2f2ef7c165
commit
21e7e35e73
4 changed files with 201 additions and 17 deletions
|
@ -569,11 +569,19 @@ impl SyntaxSnapshot {
|
||||||
range.end = range.end.saturating_sub(step_start_byte);
|
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(),
|
old_tree.included_ranges(),
|
||||||
&parent_layer_changed_ranges,
|
&parent_layer_changed_ranges,
|
||||||
&included_ranges,
|
&included_ranges,
|
||||||
);
|
);
|
||||||
|
insert_newlines_between_ranges(
|
||||||
|
changed_indices,
|
||||||
|
&mut included_ranges,
|
||||||
|
&text,
|
||||||
|
step_start_byte,
|
||||||
|
step_start_point,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if included_ranges.is_empty() {
|
if included_ranges.is_empty() {
|
||||||
|
@ -586,7 +594,7 @@ impl SyntaxSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"update layer. language:{}, start:{:?}, ranges:{:?}",
|
"update layer. language:{}, start:{:?}, included_ranges:{:?}",
|
||||||
language.name(),
|
language.name(),
|
||||||
LogAnchorRange(&step.range, text),
|
LogAnchorRange(&step.range, text),
|
||||||
LogIncludedRanges(&included_ranges),
|
LogIncludedRanges(&included_ranges),
|
||||||
|
@ -608,6 +616,16 @@ impl SyntaxSnapshot {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} 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() {
|
if included_ranges.is_empty() {
|
||||||
included_ranges.push(tree_sitter::Range {
|
included_ranges.push(tree_sitter::Range {
|
||||||
start_byte: 0,
|
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(
|
pub(crate) fn splice_included_ranges(
|
||||||
mut ranges: Vec<tree_sitter::Range>,
|
mut ranges: Vec<tree_sitter::Range>,
|
||||||
removed_ranges: &[Range<usize>],
|
removed_ranges: &[Range<usize>],
|
||||||
new_ranges: &[tree_sitter::Range],
|
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 removed_ranges = removed_ranges.iter().cloned().peekable();
|
||||||
let mut new_ranges = new_ranges.into_iter().cloned().peekable();
|
let mut new_ranges = new_ranges.into_iter().cloned().peekable();
|
||||||
let mut ranges_ix = 0;
|
let mut ranges_ix = 0;
|
||||||
|
let mut changed_portion = usize::MAX..0;
|
||||||
loop {
|
loop {
|
||||||
let next_new_range = new_ranges.peek();
|
let next_new_range = new_ranges.peek();
|
||||||
let next_removed_range = removed_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.splice(start_ix..end_ix, insert);
|
||||||
ranges_ix = start_ix;
|
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 {
|
impl OwnedSyntaxLayerInfo {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use util::test::marked_text_ranges;
|
||||||
fn test_splice_included_ranges() {
|
fn test_splice_included_ranges() {
|
||||||
let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)];
|
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(),
|
ranges.clone(),
|
||||||
&[54..56, 58..68],
|
&[54..56, 58..68],
|
||||||
&[ts_range(50..54), ts_range(59..67)],
|
&[ts_range(50..54), ts_range(59..67)],
|
||||||
|
@ -25,14 +25,16 @@ fn test_splice_included_ranges() {
|
||||||
ts_range(80..90),
|
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!(
|
assert_eq!(
|
||||||
new_ranges,
|
new_ranges,
|
||||||
&[ts_range(20..30), ts_range(50..60), ts_range(80..90)]
|
&[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)]);
|
splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
new_ranges,
|
new_ranges,
|
||||||
|
@ -44,16 +46,21 @@ fn test_splice_included_ranges() {
|
||||||
ts_range(80..90)
|
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!(new_ranges, &[ts_range(25..55), ts_range(80..90)]);
|
||||||
|
assert_eq!(change, 0..1);
|
||||||
|
|
||||||
// does not create overlapping ranges
|
// 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!(
|
assert_eq!(
|
||||||
new_ranges,
|
new_ranges,
|
||||||
&[ts_range(20..32), ts_range(50..60), ts_range(80..90)]
|
&[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 {
|
fn ts_range(range: Range<usize>) -> tree_sitter::Range {
|
||||||
tree_sitter::Range {
|
tree_sitter::Range {
|
||||||
|
@ -511,7 +518,7 @@ fn test_removing_injection_by_replacing_across_boundary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_combined_injections() {
|
fn test_combined_injections_simple() {
|
||||||
let (buffer, syntax_map) = test_edit_sequence(
|
let (buffer, syntax_map) = test_edit_sequence(
|
||||||
"ERB",
|
"ERB",
|
||||||
&[
|
&[
|
||||||
|
@ -653,33 +660,78 @@ fn test_combined_injections_editing_after_last_injection() {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_combined_injections_inside_injections() {
|
fn test_combined_injections_inside_injections() {
|
||||||
let (_buffer, _syntax_map) = test_edit_sequence(
|
let (buffer, syntax_map) = test_edit_sequence(
|
||||||
"Markdown",
|
"Markdown",
|
||||||
&[
|
&[
|
||||||
r#"
|
r#"
|
||||||
here is some ERB code:
|
here is
|
||||||
|
some
|
||||||
|
ERB code:
|
||||||
|
|
||||||
```erb
|
```erb
|
||||||
<ul>
|
<ul>
|
||||||
<% people.each do |person| %>
|
<% people.each do |person| %>
|
||||||
<li><%= person.name %></li>
|
<li><%= person.name %></li>
|
||||||
|
<li><%= person.age %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
```
|
```
|
||||||
"#,
|
"#,
|
||||||
r#"
|
r#"
|
||||||
here is some ERB code:
|
here is
|
||||||
|
some
|
||||||
|
ERB code:
|
||||||
|
|
||||||
```erb
|
```erb
|
||||||
<ul>
|
<ul>
|
||||||
<% people«2».each do |person| %>
|
<% people«2».each do |person| %>
|
||||||
<li><%= person.name %></li>
|
<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 %>
|
<% end %>
|
||||||
</ul>
|
</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]
|
#[gpui::test]
|
||||||
|
@ -984,11 +1036,14 @@ fn check_interpolation(
|
||||||
|
|
||||||
fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) {
|
fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) {
|
||||||
let registry = Arc::new(LanguageRegistry::test());
|
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(rust_lang()));
|
||||||
registry.add(Arc::new(ruby_lang()));
|
registry.add(Arc::new(ruby_lang()));
|
||||||
registry.add(Arc::new(html_lang()));
|
registry.add(Arc::new(html_lang()));
|
||||||
registry.add(Arc::new(erb_lang()));
|
registry.add(Arc::new(erb_lang()));
|
||||||
registry.add(Arc::new(markdown_lang()));
|
registry.add(Arc::new(markdown_lang()));
|
||||||
|
|
||||||
let language = registry
|
let language = registry
|
||||||
.language_for_name(language_name)
|
.language_for_name(language_name)
|
||||||
.now_or_never()
|
.now_or_never()
|
||||||
|
@ -1074,6 +1129,7 @@ fn ruby_lang() -> Language {
|
||||||
r#"
|
r#"
|
||||||
["if" "do" "else" "end"] @keyword
|
["if" "do" "else" "end"] @keyword
|
||||||
(instance_variable) @ivar
|
(instance_variable) @ivar
|
||||||
|
(call method: (identifier) @method)
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -1158,6 +1214,52 @@ fn markdown_lang() -> Language {
|
||||||
.unwrap()
|
.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> {
|
fn range_for_text(buffer: &Buffer, text: &str) -> Range<usize> {
|
||||||
let start = buffer.as_rope().to_string().find(text).unwrap();
|
let start = buffer.as_rope().to_string().find(text).unwrap();
|
||||||
start..start + text.len()
|
start..start + text.len()
|
||||||
|
|
|
@ -4,4 +4,4 @@ autoclose_before = ">})"
|
||||||
brackets = [
|
brackets = [
|
||||||
{ start = "<", end = ">", close = true, newline = true },
|
{ start = "<", end = ">", close = true, newline = true },
|
||||||
]
|
]
|
||||||
block_comment = ["<%!--", "--%>"]
|
block_comment = ["<%!-- ", " --%>"]
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
; HEEx delimiters
|
; HEEx delimiters
|
||||||
[
|
[
|
||||||
"--%>"
|
|
||||||
"-->"
|
|
||||||
"/>"
|
"/>"
|
||||||
"<!"
|
"<!"
|
||||||
"<!--"
|
|
||||||
"<"
|
"<"
|
||||||
"</"
|
"</"
|
||||||
"</:"
|
"</:"
|
||||||
|
@ -21,6 +18,9 @@
|
||||||
"<%%="
|
"<%%="
|
||||||
"<%="
|
"<%="
|
||||||
"%>"
|
"%>"
|
||||||
|
"--%>"
|
||||||
|
"-->"
|
||||||
|
"<!--"
|
||||||
] @keyword
|
] @keyword
|
||||||
|
|
||||||
; HEEx operators are highlighted as such
|
; HEEx operators are highlighted as such
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue