Skip to content

Commit 2de80fa

Browse files
committed
Incrementally diff using changesets
1 parent bf5b5a5 commit 2de80fa

File tree

1 file changed

+61
-7
lines changed

1 file changed

+61
-7
lines changed

helix-view/src/document.rs

+61-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::{anyhow, bail, Context, Error};
22
use helix_core::auto_pairs::AutoPairs;
3+
use helix_core::diff::compare_ropes;
34
use helix_vcs::LineDiffs;
45
use serde::de::{self, Deserialize, Deserializer};
56
use serde::Serialize;
@@ -122,7 +123,11 @@ pub struct Document {
122123

123124
diagnostics: Vec<Diagnostic>,
124125
language_server: Option<Arc<helix_lsp::Client>>,
126+
125127
version_control: Option<Rc<RefCell<helix_vcs::Git>>>,
128+
/// The changes that should be applied to the current text in the doc
129+
/// to get the diff base (eg. checked in version of the file in git).
130+
diff_changes: Option<ChangeSet>,
126131
line_diffs: LineDiffs,
127132
}
128133

@@ -365,6 +370,7 @@ impl Document {
365370
modified_since_accessed: false,
366371
language_server: None,
367372
version_control: None,
373+
diff_changes: None,
368374
line_diffs: LineDiffs::new(),
369375
}
370376
}
@@ -623,12 +629,42 @@ impl Document {
623629
}
624630

625631
pub fn diff_with_vcs(&mut self) {
626-
let vcs = self
632+
let mut vcs = self
627633
.version_control
628634
.as_ref()
629635
.and_then(|v| v.try_borrow_mut().ok());
630-
if let Some((mut vcs, path)) = vcs.zip(self.path()) {
631-
self.line_diffs = vcs.line_diff_with_head(path, self.text());
636+
let diff_base = self
637+
.path()
638+
.and_then(|path| vcs.as_mut()?.read_file_from_head(path));
639+
if let Some(diff_base) = diff_base {
640+
let changes = compare_ropes(diff_base, self.text()).changes().clone();
641+
let changes = changes.invert(diff_base);
642+
self.diff_changes = Some(changes);
643+
drop(vcs);
644+
self.diff_with_base();
645+
}
646+
}
647+
648+
pub fn diff_with_base(&mut self) {
649+
let changes = match &self.diff_changes {
650+
Some(c) => c,
651+
None => return,
652+
};
653+
self.line_diffs.clear();
654+
655+
for (from, to, replacement) in changes.changes_iter() {
656+
let from_line = self.text().char_to_line(from);
657+
let to_line = self.text().char_to_line(to);
658+
let mut mark_with = |diff_tag| {
659+
for line_idx in from_line..=to_line {
660+
self.line_diffs.entry(line_idx).or_insert(diff_tag);
661+
}
662+
};
663+
match replacement {
664+
None if from_line == to_line => mark_with(helix_vcs::LineDiff::Modified),
665+
None => mark_with(helix_vcs::LineDiff::Added),
666+
Some(_) => mark_with(helix_vcs::LineDiff::Deleted),
667+
}
632668
}
633669
}
634670

@@ -668,12 +704,32 @@ impl Document {
668704

669705
if !transaction.changes().is_empty() {
670706
self.version += 1;
707+
let reverted_tx = transaction.invert(&old_doc);
708+
709+
// Consider H to be the text of the file in git HEAD, and B₁ to be
710+
// the text when buffer initially loads. H → B₁ is the changeset
711+
// that describes the diff between HEAD and buffer text. Inverting
712+
// this produces B₁ → H, which is initially saved to `diff_changes`.
713+
// In the next edit, buffer text changes to B₂. The transaction
714+
// describes the change B₁ → B₂. Inverting this gives B₂ → B₁.
715+
// Composing this with the saved `diff_changes` gives us
716+
// (B₂ → B₁) → (B₁ → H) = B₂ → H. Whenever examining a change X₁ → X₂,
717+
// we need to know the contents of the text at state X₁ to know where
718+
// to apply the operations in the changeset. The advantage of inverting and
719+
// composing this way instead of simply composing (which would give
720+
// us H → B₂ in this example) is that we no longer need the HEAD text
721+
// and we can instead use the current text in the buffer.
722+
if let Some(changes) = self.diff_changes.take() {
723+
let reverted_changes = reverted_tx.changes().clone();
724+
let changes = reverted_changes.compose(changes);
725+
self.diff_changes = Some(changes);
726+
self.diff_with_base();
727+
}
671728

672729
// generate revert to savepoint
673730
if self.savepoint.is_some() {
674731
take_with(&mut self.savepoint, |prev_revert| {
675-
let revert = transaction.invert(&old_doc);
676-
Some(revert.compose(prev_revert.unwrap()))
732+
Some(reverted_tx.compose(prev_revert.unwrap()))
677733
});
678734
}
679735

@@ -707,8 +763,6 @@ impl Document {
707763
tokio::spawn(notify);
708764
}
709765
}
710-
711-
self.diff_with_vcs();
712766
}
713767
success
714768
}

0 commit comments

Comments
 (0)