fix: preserve literal whitespace inside strings

This commit is contained in:
2026-10-13 23:05:39 +02:00
parent 2357b5be46
commit adcc4f2656
3 changed files with 255 additions and 47 deletions
+41 -19
View File
@@ -43,6 +43,7 @@ pub struct SyntaxCache {
line_starts: Vec<usize>,
lines: Vec<Vec<SyntaxSpan>>,
highlighted: Vec<bool>,
string_spans: Vec<Vec<SyntaxSpan>>,
context: Option<LayerContext>,
parser: Parser,
tree: Option<Tree>,
@@ -147,10 +148,11 @@ impl SyntaxCache {
start += line.len() + 1;
}
self.lines.resize_with(lines.len(), Vec::new);
self.string_spans.resize_with(lines.len(), Vec::new);
self.highlighted.resize(lines.len(), false);
}
if edit.is_some() || language_changed {
for spans in &mut self.lines {
for spans in self.lines.iter_mut().chain(&mut self.string_spans) {
spans.clear();
}
self.highlighted.fill(false);
@@ -209,7 +211,7 @@ impl SyntaxCache {
self.injections.clear();
self.context = None;
self.highlighted.fill(false);
for spans in &mut self.lines {
for spans in self.lines.iter_mut().chain(&mut self.string_spans) {
spans.clear();
}
} else {
@@ -225,6 +227,11 @@ impl SyntaxCache {
self.lines.get(row).map_or(&[], Vec::as_slice)
}
/// String coverage before foreground overrides, including embedded-language highlighting.
pub(crate) fn string_spans(&self, row: usize) -> &[SyntaxSpan] {
self.string_spans.get(row).map_or(&[], Vec::as_slice)
}
/// Bundled grammar name, or `None` for unknown languages and plain text.
pub fn language(&self) -> Option<&str> {
self.syntax.map(|syntax| syntax.name)
@@ -415,6 +422,7 @@ impl SyntaxCache {
&self.source,
&self.line_starts,
&mut self.lines,
&mut self.string_spans,
&self.highlights,
&mut self.events,
visible,
@@ -1089,6 +1097,7 @@ fn render_spans(
source: &str,
starts: &[usize],
lines: &mut [Vec<SyntaxSpan>],
strings: &mut [Vec<SyntaxSpan>],
highlights: &[Highlight],
events: &mut Vec<(usize, bool, usize)>,
visible: &[Range],
@@ -1111,24 +1120,34 @@ fn render_spans(
};
let mut active: BTreeSet<(_, usize)> = BTreeSet::new();
let mut previous = 0;
let mut active_strings = 0;
for &(byte, entering, index) in events.iter() {
if previous < byte
&& let Some((_, current)) = active.last()
&& let Some(kind) = highlights[*current].kind
{
let start = visible.partition_point(|range| range.end_byte <= previous);
for range in visible[start..]
.iter()
.take_while(|range| range.start_byte < byte)
{
append_global(
source,
starts,
lines,
previous.max(range.start_byte),
byte.min(range.end_byte),
kind,
);
if previous < byte {
let kind = active
.last()
.and_then(|(_, current)| highlights[*current].kind);
if kind.is_some() || active_strings > 0 {
let start = visible.partition_point(|range| range.end_byte <= previous);
for range in visible[start..]
.iter()
.take_while(|range| range.start_byte < byte)
{
let low = previous.max(range.start_byte);
let high = byte.min(range.end_byte);
if let Some(kind) = kind {
append_global(source, starts, lines, low, high, kind);
}
if active_strings > 0 {
append_global(source, starts, strings, low, high, SyntaxKind::String);
}
}
}
}
if highlights[index].kind == Some(SyntaxKind::String) {
if entering {
active_strings += 1;
} else {
active_strings -= 1;
}
}
if entering {
@@ -1893,6 +1912,7 @@ mod tests {
source,
&[0],
&mut spans,
&mut vec![Vec::new(); 1],
&highlights,
&mut events,
&[source_range(source)],
@@ -1963,6 +1983,7 @@ mod tests {
source,
&starts,
&mut spans,
&mut vec![Vec::new(); 3],
&highlights,
&mut Vec::new(),
&[source_range(source)],
@@ -2004,6 +2025,7 @@ mod tests {
source,
&starts,
&mut visible_spans,
&mut vec![Vec::new(); 3],
&visible_highlights,
&mut Vec::new(),
&window,