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:
+4
-1
@@ -1844,7 +1844,10 @@ fn column_view_preserves_split_gaps_unified_sides_and_folded_coordinates() {
|
||||
let ColumnRow::Text(line) = before.row(1).unwrap() else {
|
||||
panic!("unified insertion must paint the after source");
|
||||
};
|
||||
assert_eq!((line.text, line.side, line.source_row), (Some("added"), Focus::After, 1));
|
||||
assert_eq!(
|
||||
(line.text, line.side, line.source_row),
|
||||
(Some("added"), Focus::After, 1)
|
||||
);
|
||||
assert_eq!(line.numbers, [None, Some(1)]);
|
||||
assert!(line.changed);
|
||||
assert_eq!(before.native_row(1), None);
|
||||
|
||||
+122
-13
@@ -323,6 +323,7 @@ fn resolver_explorer_retains_last_jj_conflict_state_until_confirmation() {
|
||||
ResolverStatus::Unconfirmed
|
||||
);
|
||||
editor.active.saved_lines.clone_from(&editor.active.lines);
|
||||
editor.active.dirty_cache.set(None);
|
||||
editor.refresh_resolver_status();
|
||||
assert_eq!(
|
||||
editor.resolver().unwrap().files[0].status,
|
||||
@@ -3245,7 +3246,10 @@ fn review_identity_changes_retire_drafts_before_reusing_comment_ids() {
|
||||
let restored = root.join("restored.json");
|
||||
editor.execute(&format!("diff.review.save {}", restored.display()));
|
||||
assert_eq!(
|
||||
crate::review::Review::load(&restored, &root).unwrap().comments[0].body,
|
||||
crate::review::Review::load(&restored, &root)
|
||||
.unwrap()
|
||||
.comments[0]
|
||||
.body,
|
||||
"Original feedback"
|
||||
);
|
||||
|
||||
@@ -7145,19 +7149,25 @@ mod system_clipboard {
|
||||
editor
|
||||
.apply(vec![Effect::Replace("aa\nbb\ncc".into())])
|
||||
.unwrap();
|
||||
editor.active.saved_lines.clone_from(&editor.active.lines);
|
||||
editor.active.dirty_cache.set(Some(false));
|
||||
assert!(!editor.dirty());
|
||||
type_text(&mut editor, "Vj");
|
||||
fixture.fail("fail-write", true);
|
||||
let before = state(&editor);
|
||||
editor.handle(Input::Char('.'));
|
||||
assert_eq!(state(&editor), before);
|
||||
assert!(!editor.dirty());
|
||||
assert_eq!(fixture.bytes("state"), b"a\n");
|
||||
assert!(editor.visual_linewise());
|
||||
fixture.fail("fail-write", false);
|
||||
editor.handle(Input::Char('.'));
|
||||
assert_eq!(editor.context().text, "cc");
|
||||
assert_eq!(fixture.bytes("state"), b"aa\nbb\n");
|
||||
assert!(editor.dirty());
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.context().text, "aa\nbb\ncc");
|
||||
assert!(!editor.dirty());
|
||||
assert_eq!(editor.mode(), Mode::Normal);
|
||||
}
|
||||
|
||||
@@ -7795,11 +7805,12 @@ fn filter_escape_resume_and_clear_do_not_edit_underlying_buffers_or_history() {
|
||||
editor.count = Some(7);
|
||||
let snapshot =
|
||||
|state: &Snapshot| (state.lines.clone(), state.targets.clone(), state.primary);
|
||||
let history = |entry: &HistoryEntry| (entry.start, entry.end, snapshot(&entry.before));
|
||||
let lines = editor.lines().to_owned();
|
||||
let targets = editor.active.view.targets.clone();
|
||||
let primary = editor.active.view.primary;
|
||||
let undo: Vec<_> = editor.active.undo.iter().map(&snapshot).collect();
|
||||
let redo: Vec<_> = editor.active.redo.iter().map(&snapshot).collect();
|
||||
let undo: Vec<_> = editor.active.undo.iter().map(&history).collect();
|
||||
let redo: Vec<_> = editor.active.redo.iter().map(&history).collect();
|
||||
let insert = editor.active.view.insert_start.as_ref().map(snapshot);
|
||||
let buffer = editor.buffer_id();
|
||||
let buffers = editor.buffer_count();
|
||||
@@ -7845,11 +7856,11 @@ fn filter_escape_resume_and_clear_do_not_edit_underlying_buffers_or_history() {
|
||||
assert_eq!(editor.buffer_id(), buffer);
|
||||
assert_eq!(editor.buffer_count(), buffers);
|
||||
assert_eq!(
|
||||
editor.active.undo.iter().map(&snapshot).collect::<Vec<_>>(),
|
||||
editor.active.undo.iter().map(&history).collect::<Vec<_>>(),
|
||||
undo
|
||||
);
|
||||
assert_eq!(
|
||||
editor.active.redo.iter().map(&snapshot).collect::<Vec<_>>(),
|
||||
editor.active.redo.iter().map(&history).collect::<Vec<_>>(),
|
||||
redo
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -8078,11 +8089,12 @@ fn selection_navigation_wraps_without_touching_any_underlying_mode() {
|
||||
assert!(len > 1);
|
||||
let snapshot =
|
||||
|state: &Snapshot| (state.lines.clone(), state.targets.clone(), state.primary);
|
||||
let history = |entry: &HistoryEntry| (entry.start, entry.end, snapshot(&entry.before));
|
||||
let text = editor.context().text;
|
||||
let targets = editor.active.view.targets.clone();
|
||||
let primary = editor.active.view.primary;
|
||||
let undo: Vec<_> = editor.active.undo.iter().map(&snapshot).collect();
|
||||
let redo: Vec<_> = editor.active.redo.iter().map(&snapshot).collect();
|
||||
let undo: Vec<_> = editor.active.undo.iter().map(&history).collect();
|
||||
let redo: Vec<_> = editor.active.redo.iter().map(&history).collect();
|
||||
let insert = editor.active.view.insert_start.as_ref().map(snapshot);
|
||||
editor.count = Some(7);
|
||||
for (input, selected) in [
|
||||
@@ -8104,11 +8116,11 @@ fn selection_navigation_wraps_without_touching_any_underlying_mode() {
|
||||
assert_eq!(editor.active.view.command_line, "original prompt");
|
||||
assert_eq!(editor.count(), Some(7));
|
||||
assert_eq!(
|
||||
editor.active.undo.iter().map(&snapshot).collect::<Vec<_>>(),
|
||||
editor.active.undo.iter().map(&history).collect::<Vec<_>>(),
|
||||
undo
|
||||
);
|
||||
assert_eq!(
|
||||
editor.active.redo.iter().map(&snapshot).collect::<Vec<_>>(),
|
||||
editor.active.redo.iter().map(&history).collect::<Vec<_>>(),
|
||||
redo
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -12182,6 +12194,7 @@ fn palette_cancellation_preserves_every_underlying_mode_and_insert_group() {
|
||||
}
|
||||
let snapshot =
|
||||
|state: &Snapshot| (state.lines.clone(), state.targets.clone(), state.primary);
|
||||
let history = |entry: &HistoryEntry| (entry.start, entry.end, snapshot(&entry.before));
|
||||
let lines = editor.active.lines.clone();
|
||||
let targets = editor.active.view.targets.clone();
|
||||
let primary = editor.active.view.primary;
|
||||
@@ -12190,8 +12203,8 @@ fn palette_cancellation_preserves_every_underlying_mode_and_insert_group() {
|
||||
let search = editor.active.view.search_query.clone();
|
||||
let found = editor.active.view.search_match;
|
||||
let message = editor.message.clone();
|
||||
let undo: Vec<_> = editor.active.undo.iter().map(&snapshot).collect();
|
||||
let redo: Vec<_> = editor.active.redo.iter().map(&snapshot).collect();
|
||||
let undo: Vec<_> = editor.active.undo.iter().map(&history).collect();
|
||||
let redo: Vec<_> = editor.active.redo.iter().map(&history).collect();
|
||||
let insert = editor.active.view.insert_start.as_ref().map(snapshot);
|
||||
|
||||
editor.handle(Input::Ctrl('p'));
|
||||
@@ -12218,11 +12231,11 @@ fn palette_cancellation_preserves_every_underlying_mode_and_insert_group() {
|
||||
assert_eq!(editor.active.view.search_match, found);
|
||||
assert_eq!(editor.message, message);
|
||||
assert_eq!(
|
||||
editor.active.undo.iter().map(&snapshot).collect::<Vec<_>>(),
|
||||
editor.active.undo.iter().map(&history).collect::<Vec<_>>(),
|
||||
undo
|
||||
);
|
||||
assert_eq!(
|
||||
editor.active.redo.iter().map(&snapshot).collect::<Vec<_>>(),
|
||||
editor.active.redo.iter().map(&history).collect::<Vec<_>>(),
|
||||
redo
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -13489,6 +13502,56 @@ fn insert_sessions_and_open_lines_are_single_undo_actions() {
|
||||
assert_eq!(editor.context().text, "hello\n世界");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_buffer_history_retains_changed_rows_and_round_trips_all_groups() {
|
||||
let directory = Directory::new();
|
||||
let path = directory.path("large.txt");
|
||||
let source = format!("head\n{}tail\n", "unchanged contents\n".repeat(10_000));
|
||||
fs::write(&path, &source).unwrap();
|
||||
let mut editor = Editor::open(Some(path), vec![]).unwrap();
|
||||
for _ in 0..12 {
|
||||
editor.handle(Input::Char('i'));
|
||||
editor.handle(Input::Char('界'));
|
||||
editor.handle(Input::Escape);
|
||||
}
|
||||
assert_eq!(editor.lines()[0], format!("{}head", "界".repeat(12)));
|
||||
let retained_bytes: usize = editor
|
||||
.active
|
||||
.undo
|
||||
.iter()
|
||||
.flat_map(|entry| &entry.before.lines)
|
||||
.map(String::len)
|
||||
.sum();
|
||||
assert!(
|
||||
retained_bytes < 4096,
|
||||
"small edits retained {retained_bytes} bytes of history"
|
||||
);
|
||||
for _ in 0..12 {
|
||||
editor.handle(Input::Char('u'));
|
||||
}
|
||||
assert_eq!(editor.lines().join("\n"), source);
|
||||
assert!(!editor.dirty());
|
||||
for _ in 0..12 {
|
||||
editor.handle(Input::Char('U'));
|
||||
}
|
||||
assert_eq!(editor.lines()[0], format!("{}head", "界".repeat(12)));
|
||||
assert_eq!(&editor.lines()[1..], &split_lines(&source)[1..]);
|
||||
assert!(editor.dirty());
|
||||
editor.handle(Input::Char('u'));
|
||||
editor.handle(Input::Char('i'));
|
||||
editor.handle(Input::Enter);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.active.redo.is_empty());
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.lines()[0], format!("{}head", "界".repeat(11)));
|
||||
editor.execute("w");
|
||||
assert!(!editor.dirty());
|
||||
assert_eq!(
|
||||
fs::read_to_string(editor.path().unwrap()).unwrap(),
|
||||
format!("{}{source}", "界".repeat(11))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grapheme_navigation_and_deletion_do_not_split_unicode() {
|
||||
let mut editor = editor();
|
||||
@@ -14076,6 +14139,52 @@ fn saves_preserve_empty_files_lf_trailing_newlines_and_dirty_history() {
|
||||
assert!(editor.should_quit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dirty_cache_tracks_saved_text_across_edits_undo_restore_and_reload() {
|
||||
let directory = Directory::new();
|
||||
let path = directory.path("file.txt");
|
||||
fs::write(&path, "saved\n").unwrap();
|
||||
let mut editor = Editor::open(Some(path.clone()), vec![]).unwrap();
|
||||
editor.set_clipboard_mode("separate").unwrap();
|
||||
assert!(!editor.dirty());
|
||||
|
||||
editor
|
||||
.apply(vec![Effect::Insert("changed".into())])
|
||||
.unwrap();
|
||||
assert!(editor.dirty());
|
||||
for _ in 0..3 {
|
||||
assert!(editor.dirty());
|
||||
assert!(editor.active.info(true).dirty);
|
||||
assert!(editor.active.document().dirty);
|
||||
assert!(!editor.active.refresh_file().unwrap());
|
||||
}
|
||||
editor.handle(Input::Char('u'));
|
||||
assert!(!editor.dirty());
|
||||
editor.handle(Input::Char('U'));
|
||||
assert!(editor.dirty());
|
||||
|
||||
editor
|
||||
.apply(vec![Effect::Replace("saved\n".into())])
|
||||
.unwrap();
|
||||
assert!(!editor.dirty());
|
||||
editor.apply(vec![Effect::Insert("new ".into())]).unwrap();
|
||||
assert!(editor.dirty());
|
||||
let revision = editor.text_revision();
|
||||
editor.execute("w");
|
||||
assert_eq!(editor.text_revision(), revision);
|
||||
assert!(!editor.dirty());
|
||||
editor.handle(Input::Char('u'));
|
||||
assert!(editor.dirty());
|
||||
editor.handle(Input::Char('U'));
|
||||
assert!(!editor.dirty());
|
||||
|
||||
fs::write(&path, "reloaded content\n").unwrap();
|
||||
assert!(editor.active.refresh_file().unwrap());
|
||||
assert_eq!(editor.lines(), ["reloaded content", ""]);
|
||||
assert!(!editor.dirty());
|
||||
assert!(!editor.active.refresh_file().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_as_and_failed_wq_keep_path_dirty_state_and_quit_correct() {
|
||||
let directory = Directory::new();
|
||||
|
||||
@@ -4,6 +4,47 @@ use std::{
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn oversized_repository_text_skips_snapshots_and_recovers_after_shrinking() {
|
||||
let fixture = Fixture::new();
|
||||
let path = fixture.file("buffer.txt", "small\n");
|
||||
let mut session = EditorSession::open(Some(path.clone()), vec![], None).unwrap();
|
||||
let buffer = session.editor.buffer_id();
|
||||
session.editor.set_gutter_changes(
|
||||
buffer,
|
||||
session.editor.text_revision(),
|
||||
&path,
|
||||
vec![Some(crate::gutter::Change::Added)],
|
||||
);
|
||||
assert_eq!(
|
||||
session.editor.gutter_change(0),
|
||||
Some(crate::gutter::Change::Added)
|
||||
);
|
||||
fs::write(&path, "x".repeat(crate::gutter::FILE_LIMIT + 1)).unwrap();
|
||||
assert!(session.editor.refresh_files());
|
||||
session.poll_gutter_at(Instant::now());
|
||||
session.synchronize_documents();
|
||||
let source = session
|
||||
.gutter_sources
|
||||
.iter()
|
||||
.find(|source| source.buffer == buffer)
|
||||
.unwrap();
|
||||
assert!(source.contents.is_none());
|
||||
assert!(session.blame_worker.is_none());
|
||||
assert_eq!(session.editor.gutter_change(0), None);
|
||||
fs::write(&path, "small again\n").unwrap();
|
||||
assert!(session.editor.refresh_files());
|
||||
session.poll_gutter_at(Instant::now());
|
||||
session.synchronize_documents();
|
||||
let source = session
|
||||
.gutter_sources
|
||||
.iter()
|
||||
.find(|source| source.buffer == buffer)
|
||||
.unwrap();
|
||||
assert_eq!(source.contents.as_deref(), Some("small again\n"));
|
||||
assert!(session.blame_worker.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolver_load_errors_return_through_real_session_without_touching_the_draft() {
|
||||
let fixture = Fixture::new();
|
||||
|
||||
@@ -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