|
1 | 1 | use anyhow::{anyhow, bail, Context, Error};
|
2 | 2 | use helix_core::auto_pairs::AutoPairs;
|
| 3 | +use helix_core::diff::compare_ropes; |
3 | 4 | use helix_vcs::LineDiffs;
|
4 | 5 | use serde::de::{self, Deserialize, Deserializer};
|
5 | 6 | use serde::Serialize;
|
@@ -122,7 +123,11 @@ pub struct Document {
|
122 | 123 |
|
123 | 124 | diagnostics: Vec<Diagnostic>,
|
124 | 125 | language_server: Option<Arc<helix_lsp::Client>>,
|
| 126 | + |
125 | 127 | 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>, |
126 | 131 | line_diffs: LineDiffs,
|
127 | 132 | }
|
128 | 133 |
|
@@ -365,6 +370,7 @@ impl Document {
|
365 | 370 | modified_since_accessed: false,
|
366 | 371 | language_server: None,
|
367 | 372 | version_control: None,
|
| 373 | + diff_changes: None, |
368 | 374 | line_diffs: LineDiffs::new(),
|
369 | 375 | }
|
370 | 376 | }
|
@@ -623,12 +629,42 @@ impl Document {
|
623 | 629 | }
|
624 | 630 |
|
625 | 631 | pub fn diff_with_vcs(&mut self) {
|
626 |
| - let vcs = self |
| 632 | + let mut vcs = self |
627 | 633 | .version_control
|
628 | 634 | .as_ref()
|
629 | 635 | .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 | + } |
632 | 668 | }
|
633 | 669 | }
|
634 | 670 |
|
@@ -668,12 +704,32 @@ impl Document {
|
668 | 704 |
|
669 | 705 | if !transaction.changes().is_empty() {
|
670 | 706 | 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 | + } |
671 | 728 |
|
672 | 729 | // generate revert to savepoint
|
673 | 730 | if self.savepoint.is_some() {
|
674 | 731 | 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())) |
677 | 733 | });
|
678 | 734 | }
|
679 | 735 |
|
@@ -707,8 +763,6 @@ impl Document {
|
707 | 763 | tokio::spawn(notify);
|
708 | 764 | }
|
709 | 765 | }
|
710 |
| - |
711 |
| - self.diff_with_vcs(); |
712 | 766 | }
|
713 | 767 | success
|
714 | 768 | }
|
|
0 commit comments