Use line-based and word-based diff when reloading and formatting buffers (#25129)
Closes https://github.com/zed-industries/zed/issues/10122 Closes https://github.com/zed-industries/zed/issues/25034 When formatting buffers or reloading them after they change on disk, we performed a diff between the buffer's current contents and the new content. We need this diff in order preserve the positions of cursors and other decorations when updating the buffer's text. In order to handle changes within lines, we would previously compute a *character-wise* diff. This was extremely expensive for large files. This PR gets rid of the character-wise diff, and instead performs a normal line-wise diff. Then, for certain replace hunks, we compute a secondary word-based diff. Also, I've switched to the [`imara-diff`](https://github.com/pascalkuthe/imara-diff) crate, instead of `similar`. Release Notes: - Fixed a hang that could occur when large files were changed on disk or formatted.
This commit is contained in:
parent
1087e05da4
commit
0fdad0c0d6
19 changed files with 429 additions and 343 deletions
|
@ -100,10 +100,10 @@ use language::{
|
|||
language_settings::{
|
||||
self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
|
||||
},
|
||||
point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, Diagnostic, DiskState, EditPredictionsMode, EditPreview, HighlightedText,
|
||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
|
||||
TransactionId, TreeSitterOptions,
|
||||
point_from_lsp, text_diff_with_options, AutoindentMode, BracketPair, Buffer, Capability,
|
||||
CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, DiskState, EditPredictionsMode,
|
||||
EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
|
||||
};
|
||||
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
|
||||
use linked_editing_ranges::refresh_linked_ranges;
|
||||
|
@ -112,7 +112,6 @@ use persistence::DB;
|
|||
pub use proposed_changes_editor::{
|
||||
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use std::iter::Peekable;
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
|
@ -202,7 +201,7 @@ pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
|
|||
#[doc(hidden)]
|
||||
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
|
||||
pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
|
||||
|
@ -7829,6 +7828,7 @@ impl Editor {
|
|||
}
|
||||
|
||||
let start = Point::new(start_row, 0);
|
||||
let start_offset = start.to_offset(&buffer);
|
||||
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
|
||||
let selection_text = buffer.text_for_range(start..end).collect::<String>();
|
||||
let Some(lines_without_prefixes) = selection_text
|
||||
|
@ -7858,44 +7858,21 @@ impl Editor {
|
|||
|
||||
// TODO: should always use char-based diff while still supporting cursor behavior that
|
||||
// matches vim.
|
||||
let diff = match is_vim_mode {
|
||||
IsVimMode::Yes => TextDiff::from_lines(&selection_text, &wrapped_text),
|
||||
IsVimMode::No => TextDiff::from_chars(&selection_text, &wrapped_text),
|
||||
};
|
||||
let mut offset = start.to_offset(&buffer);
|
||||
let mut moved_since_edit = true;
|
||||
let mut diff_options = DiffOptions::default();
|
||||
if is_vim_mode == IsVimMode::Yes {
|
||||
diff_options.max_word_diff_len = 0;
|
||||
diff_options.max_word_diff_line_count = 0;
|
||||
} else {
|
||||
diff_options.max_word_diff_len = usize::MAX;
|
||||
diff_options.max_word_diff_line_count = usize::MAX;
|
||||
}
|
||||
|
||||
for change in diff.iter_all_changes() {
|
||||
let value = change.value();
|
||||
match change.tag() {
|
||||
ChangeTag::Equal => {
|
||||
offset += value.len();
|
||||
moved_since_edit = true;
|
||||
}
|
||||
ChangeTag::Delete => {
|
||||
let start = buffer.anchor_after(offset);
|
||||
let end = buffer.anchor_before(offset + value.len());
|
||||
|
||||
if moved_since_edit {
|
||||
edits.push((start..end, String::new()));
|
||||
} else {
|
||||
edits.last_mut().unwrap().0.end = end;
|
||||
}
|
||||
|
||||
offset += value.len();
|
||||
moved_since_edit = false;
|
||||
}
|
||||
ChangeTag::Insert => {
|
||||
if moved_since_edit {
|
||||
let anchor = buffer.anchor_after(offset);
|
||||
edits.push((anchor..anchor, value.to_string()));
|
||||
} else {
|
||||
edits.last_mut().unwrap().1.push_str(value);
|
||||
}
|
||||
|
||||
moved_since_edit = false;
|
||||
}
|
||||
}
|
||||
for (old_range, new_text) in
|
||||
text_diff_with_options(&selection_text, &wrapped_text, diff_options)
|
||||
{
|
||||
let edit_start = buffer.anchor_after(start_offset + old_range.start);
|
||||
let edit_end = buffer.anchor_after(start_offset + old_range.end);
|
||||
edits.push((edit_start..edit_end, new_text));
|
||||
}
|
||||
|
||||
rewrapped_row_ranges.push(start_row..=end_row);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue