fix: keep large-file editing responsive
Bound synchronous full-source syntax parsing, cache dirty comparisons, edit ordinary inline changes in place, retain changed-row undo history, and buffer atomic saves. Skip repository snapshots beyond existing worker limits before copying their text.
This commit is contained in:
@@ -729,6 +729,152 @@ fn unchanged_revision_and_same_source_do_not_allocate_or_parse() {
|
||||
assert_eq!(cache.incremental_parses, 0);
|
||||
}
|
||||
|
||||
fn assert_plain_text_storage(cache: &SyntaxCache) {
|
||||
assert!(cache.tree.is_none());
|
||||
assert!(cache.context.is_none());
|
||||
assert!(cache.scopes.get().is_none());
|
||||
assert_eq!(cache.source.capacity(), 0);
|
||||
assert_eq!(cache.next_source.capacity(), 0);
|
||||
assert_eq!(cache.line_starts.capacity(), 0);
|
||||
assert_eq!(cache.lines.capacity(), 0);
|
||||
assert_eq!(cache.string_spans.capacity(), 0);
|
||||
assert_eq!(cache.highlighted.capacity(), 0);
|
||||
assert_eq!(cache.injections.capacity(), 0);
|
||||
assert_eq!(cache.captures.capacity(), 0);
|
||||
assert_eq!(cache.highlights.capacity(), 0);
|
||||
assert_eq!(cache.events.capacity(), 0);
|
||||
assert!(cache.line(0).is_empty());
|
||||
assert!(cache.string_spans(0).is_empty());
|
||||
assert!(cache.scope_at(0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_budget_crossing_releases_state_and_reenables_after_shrinking() {
|
||||
let small = "fn main() { let value = \"ok\"; }";
|
||||
let mut input = lines(small);
|
||||
let mut remaining = MAX_SYNTAX_BYTES - small.len();
|
||||
while remaining > 0 {
|
||||
let length = (remaining - 1).min(16 * 1024);
|
||||
input.push(" ".repeat(length));
|
||||
remaining -= length + 1;
|
||||
}
|
||||
let path = Some(Path::new("budget.rs"));
|
||||
let mut cache = SyntaxCache::new();
|
||||
cache.update_visible(&input, path, 0, std::slice::from_ref(&(0..1)));
|
||||
assert_eq!(cache.source.len(), MAX_SYNTAX_BYTES);
|
||||
assert_eq!(cache.error(), None);
|
||||
assert_eq!(cache.parses, 1);
|
||||
assert_token(&cache, small, 0, "fn", SyntaxKind::Keyword);
|
||||
assert!(cache.scope_at(0).is_some());
|
||||
input.last_mut().unwrap().push(' ');
|
||||
cache.update_visible(&input, path, 1, std::slice::from_ref(&(0..1)));
|
||||
assert_eq!(cache.language(), Some("Rust"));
|
||||
assert!(cache.error().unwrap().contains("plain text"));
|
||||
assert_eq!(cache.parses, 1);
|
||||
assert_plain_text_storage(&cache);
|
||||
let notice = cache.error.as_ref().unwrap().as_ptr();
|
||||
cache.update(&input, path, 1);
|
||||
assert_eq!(cache.error.as_ref().unwrap().as_ptr(), notice);
|
||||
assert_eq!(cache.parses, 1);
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update(&input, Some(Path::new("budget.py")), 1);
|
||||
assert_eq!(cache.language(), Some("Python"));
|
||||
assert!(cache.error().unwrap().starts_with("Python"));
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update(&input, Some(Path::new("budget.txt")), 1);
|
||||
assert_eq!(cache.language(), None);
|
||||
assert_eq!(cache.error(), None);
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update(&lines(small), path, 2);
|
||||
assert_eq!(cache.parses, 2);
|
||||
assert_eq!(cache.error(), None);
|
||||
assert_token(&cache, small, 0, "fn", SyntaxKind::Keyword);
|
||||
assert!(cache.scope_at(0).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_syntax_budget_counts_the_separate_final_newline() {
|
||||
let mut input = vec!["//".to_owned() + &" ".repeat(16 * 1024 - 2); 63];
|
||||
input.push("//".to_owned() + &" ".repeat(16 * 1024 - 65));
|
||||
let path = Some(Path::new("budget.rs"));
|
||||
let mut cache = SyntaxCache::new();
|
||||
cache.update_diff(&input, path, 0, false, None);
|
||||
assert_eq!(cache.source.len(), MAX_SYNTAX_BYTES);
|
||||
assert_eq!(cache.parses, 1);
|
||||
assert_eq!(kind_at(&cache, 0, 0), Some(SyntaxKind::Comment));
|
||||
cache.update_diff(&input, path, 0, true, None);
|
||||
assert_eq!(cache.parses, 1);
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update_diff(&input, path, 0, false, Some(std::slice::from_ref(&(0..1))));
|
||||
assert_eq!(cache.parses, 2);
|
||||
assert_eq!(cache.error(), None);
|
||||
assert_eq!(kind_at(&cache, 0, 0), Some(SyntaxKind::Comment));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_lines_and_large_text_never_copy_or_parse_and_clear_injections() {
|
||||
let markdown = "# Title\n```rust\nfn main() { \"ok\"; }\n```";
|
||||
let mut cache = self::cache(markdown, "hover.md");
|
||||
assert!(!cache.injections.is_empty());
|
||||
assert!(cache.scope_at(2).is_some());
|
||||
let parses = cache.parse_counts();
|
||||
let long_line = "//".to_owned() + &" ".repeat(MAX_SYNTAX_LINE_BYTES - 1);
|
||||
cache.update_text(&long_line, Path::new("large.rs"));
|
||||
assert_eq!(cache.language(), Some("Rust"));
|
||||
assert_eq!(cache.parse_counts(), parses);
|
||||
assert!(cache.error().unwrap().contains("line"));
|
||||
assert_plain_text_storage(&cache);
|
||||
let large = " ".repeat(52 * 1024 * 1024);
|
||||
cache.update_text(&large, Path::new("parser.c"));
|
||||
assert_eq!(cache.language(), Some("C"));
|
||||
assert_eq!(cache.parse_counts(), parses);
|
||||
assert_plain_text_storage(&cache);
|
||||
let notice = cache.error.as_ref().unwrap().as_ptr();
|
||||
cache.update_text(&large, Path::new("parser.c"));
|
||||
assert_eq!(cache.error.as_ref().unwrap().as_ptr(), notice);
|
||||
assert_plain_text_storage(&cache);
|
||||
let large_lines = vec![large];
|
||||
cache.update(&large_lines, Some(Path::new("parser.c")), 10);
|
||||
assert_eq!(cache.language(), Some("C"));
|
||||
assert_eq!(cache.parse_counts(), parses);
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update_diff(&large_lines, Some(Path::new("parser.c")), 10, true, None);
|
||||
assert_eq!(cache.parse_counts(), parses);
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update_text(markdown, Path::new("hover.md"));
|
||||
assert!(cache.parse_counts().0 > parses.0);
|
||||
assert_eq!(cache.error(), None);
|
||||
assert!(!cache.injections.is_empty());
|
||||
assert!(cache.scope_at(2).is_some());
|
||||
let boundary = "//".to_owned() + &" ".repeat(MAX_SYNTAX_LINE_BYTES - 2);
|
||||
cache.update(&lines(&boundary), Some(Path::new("line.rs")), 50);
|
||||
assert_eq!(cache.error(), None);
|
||||
assert_eq!(kind_at(&cache, 0, 0), Some(SyntaxKind::Comment));
|
||||
cache.update(&lines(&long_line), Some(Path::new("line.rs")), 51);
|
||||
assert_plain_text_storage(&cache);
|
||||
cache.update_text("", Path::new("line.rs"));
|
||||
assert_eq!(cache.language(), Some("Rust"));
|
||||
assert_eq!(cache.error(), None);
|
||||
assert!(cache.budget_exceeded.is_none());
|
||||
assert!(cache.line(0).is_empty());
|
||||
assert!(cache.scope_at(0).is_none());
|
||||
|
||||
let mut script = vec!["#!/usr/bin/env python3".to_owned(), long_line.clone()];
|
||||
cache.update(&script, None, 60);
|
||||
assert_eq!(cache.language(), Some("Python"));
|
||||
assert_plain_text_storage(&cache);
|
||||
script[0] = "#!/usr/bin/env ruby".to_owned();
|
||||
cache.update(&script, None, 61);
|
||||
assert_eq!(cache.language(), Some("Ruby"));
|
||||
assert!(cache.error().unwrap().starts_with("Ruby"));
|
||||
assert_plain_text_storage(&cache);
|
||||
script.pop();
|
||||
cache.update(&script, None, 62);
|
||||
assert_eq!(cache.language(), Some("Ruby"));
|
||||
assert_eq!(cache.error(), None);
|
||||
assert!(cache.tree.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_minimal_edits_use_byte_points_and_never_split_codepoints() {
|
||||
let old = "x\n\t\"é 👩💻\"\n";
|
||||
|
||||
Reference in New Issue
Block a user