feat: dim whitespace markers and configure scope and newlines
This commit is contained in:
+75
-8
@@ -24,7 +24,7 @@ pub enum SyntaxKind {
|
||||
Markup,
|
||||
}
|
||||
|
||||
/// A syntax foreground range in the original line's bytes, excluding its newline.
|
||||
/// A syntax range in original line bytes; string coverage may include its newline.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct SyntaxSpan {
|
||||
pub start_byte: usize,
|
||||
@@ -37,6 +37,7 @@ pub struct SyntaxSpan {
|
||||
pub struct SyntaxCache {
|
||||
revision: Option<u64>,
|
||||
path: Option<PathBuf>,
|
||||
final_newline: bool,
|
||||
syntax: Option<&'static LanguageSpec>,
|
||||
source: String,
|
||||
next_source: String,
|
||||
@@ -89,6 +90,7 @@ impl SyntaxCache {
|
||||
path,
|
||||
revision,
|
||||
std::slice::from_ref(&(0..lines.len())),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,7 +103,26 @@ impl SyntaxCache {
|
||||
revision: u64,
|
||||
visible: &[std::ops::Range<usize>],
|
||||
) {
|
||||
self.update_rows(lines, path, revision, visible);
|
||||
self.update_rows(lines, path, revision, visible, false);
|
||||
}
|
||||
|
||||
/// Diff sources store a final line break separately instead of a trailing empty row.
|
||||
pub(crate) fn update_diff(
|
||||
&mut self,
|
||||
lines: &[String],
|
||||
path: Option<&Path>,
|
||||
revision: u64,
|
||||
final_newline: bool,
|
||||
visible: Option<&[std::ops::Range<usize>]>,
|
||||
) {
|
||||
let all_rows = 0..lines.len();
|
||||
self.update_rows(
|
||||
lines,
|
||||
path,
|
||||
revision,
|
||||
visible.unwrap_or(std::slice::from_ref(&all_rows)),
|
||||
final_newline && !lines.is_empty(),
|
||||
);
|
||||
}
|
||||
|
||||
fn update_rows(
|
||||
@@ -110,9 +131,11 @@ impl SyntaxCache {
|
||||
path: Option<&Path>,
|
||||
revision: u64,
|
||||
visible: &[std::ops::Range<usize>],
|
||||
final_newline: bool,
|
||||
) {
|
||||
let path_changed = self.path.as_deref() != path;
|
||||
let revision_changed = self.revision != Some(revision);
|
||||
let revision_changed =
|
||||
self.revision != Some(revision) || self.final_newline != final_newline;
|
||||
let syntax = if path_changed || revision_changed {
|
||||
languages::detect(path, lines.first().map(String::as_str))
|
||||
} else {
|
||||
@@ -120,6 +143,7 @@ impl SyntaxCache {
|
||||
};
|
||||
let language_changed = !same_language(self.syntax, syntax);
|
||||
self.revision = Some(revision);
|
||||
self.final_newline = final_newline;
|
||||
if path_changed {
|
||||
self.path = path.map(Path::to_path_buf);
|
||||
}
|
||||
@@ -128,8 +152,9 @@ impl SyntaxCache {
|
||||
let mut edit = None;
|
||||
if revision_changed {
|
||||
self.next_source.clear();
|
||||
let length =
|
||||
lines.iter().map(String::len).sum::<usize>() + lines.len().saturating_sub(1);
|
||||
let length = lines.iter().map(String::len).sum::<usize>()
|
||||
+ lines.len().saturating_sub(1)
|
||||
+ usize::from(final_newline);
|
||||
self.next_source.reserve(length);
|
||||
for (row, line) in lines.iter().enumerate() {
|
||||
if row > 0 {
|
||||
@@ -137,6 +162,9 @@ impl SyntaxCache {
|
||||
}
|
||||
self.next_source.push_str(line);
|
||||
}
|
||||
if final_newline {
|
||||
self.next_source.push('\n');
|
||||
}
|
||||
if self.source != self.next_source {
|
||||
edit = Some(minimal_edit(&self.source, &self.next_source));
|
||||
std::mem::swap(&mut self.source, &mut self.next_source);
|
||||
@@ -147,6 +175,9 @@ impl SyntaxCache {
|
||||
self.line_starts.push(start);
|
||||
start += line.len() + 1;
|
||||
}
|
||||
if final_newline {
|
||||
self.line_starts.push(self.source.len());
|
||||
}
|
||||
self.lines.resize_with(lines.len(), Vec::new);
|
||||
self.string_spans.resize_with(lines.len(), Vec::new);
|
||||
self.highlighted.resize(lines.len(), false);
|
||||
@@ -185,6 +216,8 @@ impl SyntaxCache {
|
||||
start_point: Point::new(start, 0),
|
||||
end_point: if row < lines.len() {
|
||||
Point::new(row, 0)
|
||||
} else if final_newline {
|
||||
Point::new(lines.len(), 0)
|
||||
} else {
|
||||
Point::new(
|
||||
lines.len().saturating_sub(1),
|
||||
@@ -228,6 +261,7 @@ impl SyntaxCache {
|
||||
}
|
||||
|
||||
/// String coverage before foreground overrides, including embedded-language highlighting.
|
||||
/// An included line break has end_byte == line.len() + 1; foreground spans exclude it.
|
||||
pub(crate) fn string_spans(&self, row: usize) -> &[SyntaxSpan] {
|
||||
self.string_spans.get(row).map_or(&[], Vec::as_slice)
|
||||
}
|
||||
@@ -1135,10 +1169,10 @@ fn render_spans(
|
||||
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);
|
||||
append_global(source, starts, lines, low, high, kind, false);
|
||||
}
|
||||
if active_strings > 0 {
|
||||
append_global(source, starts, strings, low, high, SyntaxKind::String);
|
||||
append_global(source, starts, strings, low, high, SyntaxKind::String, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1166,6 +1200,7 @@ fn append_global(
|
||||
mut start: usize,
|
||||
end: usize,
|
||||
kind: SyntaxKind,
|
||||
include_newline: bool,
|
||||
) {
|
||||
if starts.is_empty() {
|
||||
return;
|
||||
@@ -1175,7 +1210,7 @@ fn append_global(
|
||||
.saturating_sub(1);
|
||||
while start < end && row < lines.len() {
|
||||
let line_end = starts.get(row + 1).map_or(source.len(), |next| next - 1);
|
||||
let high = end.min(line_end);
|
||||
let high = end.min(line_end + usize::from(include_newline && row + 1 < starts.len()));
|
||||
if start < high {
|
||||
let spans = &mut lines[row];
|
||||
let low = start - starts[row];
|
||||
@@ -1215,6 +1250,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_newlines_remain_literal_across_visible_row_caching_and_edits() {
|
||||
let source = "text = \"\"\"a\n\nb\"\"\"\nplain\n";
|
||||
let input = lines(source);
|
||||
let path = Path::new("strings.py");
|
||||
let mut cache = SyntaxCache::new();
|
||||
let includes_newline = |cache: &SyntaxCache, row: usize, length: usize| {
|
||||
cache
|
||||
.string_spans(row)
|
||||
.iter()
|
||||
.any(|span| span.start_byte <= length && span.end_byte > length)
|
||||
};
|
||||
cache.update_visible(&input, Some(path), 0, std::slice::from_ref(&(0..1)));
|
||||
assert!(includes_newline(&cache, 0, input[0].len()));
|
||||
cache.update_visible(&input, Some(path), 0, std::slice::from_ref(&(1..4)));
|
||||
assert!(includes_newline(&cache, 0, input[0].len()));
|
||||
assert!(includes_newline(&cache, 1, input[1].len()));
|
||||
assert!(!includes_newline(&cache, 2, input[2].len()));
|
||||
assert!(!includes_newline(&cache, 3, input[3].len()));
|
||||
for (row, line) in input.iter().enumerate() {
|
||||
assert!(
|
||||
cache
|
||||
.line(row)
|
||||
.iter()
|
||||
.all(|span| span.end_byte <= line.len())
|
||||
);
|
||||
}
|
||||
cache.update(&lines("# a\n\nb\nplain\n"), Some(path), 1);
|
||||
assert!(!includes_newline(&cache, 0, 3));
|
||||
assert!(!includes_newline(&cache, 1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visible_windows_match_full_colors_after_edits_and_scrolling_without_reparsing() {
|
||||
let cases = [
|
||||
|
||||
Reference in New Issue
Block a user