aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2007-10-19 15:59:13 +0000
committerStefan Monnier2007-10-19 15:59:13 +0000
commitcd62539fc7ce9529619880b309276ca9d6c6ce13 (patch)
tree4590d0dc29f5a13ee1cabfecf97de3eadda95e07
parentfc7793831bdef46fef2cb400f2839fd846987110 (diff)
downloademacs-cd62539fc7ce9529619880b309276ca9d6c6ce13.tar.gz
emacs-cd62539fc7ce9529619880b309276ca9d6c6ce13.zip
Add word-granularity refinement.
(smerge-refine-forward-function, smerge-refine-ignore-whitespace) (smerge-refine-weight-hack): New vars. (smerge-refine-forward): New fun. (smerge-refine-chopup-region, smerge-refine-highlight-change): Use them. (smerge-refine-subst): Use them as well. Preserve point.
-rw-r--r--lisp/ChangeLog9
-rw-r--r--lisp/smerge-mode.el173
2 files changed, 144 insertions, 38 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 9c399eb6de2..a410410ffe1 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,12 @@
12007-10-19 Stefan Monnier <monnier@iro.umontreal.ca>
2
3 * smerge-mode.el: Add word-granularity refinement.
4 (smerge-refine-forward-function, smerge-refine-ignore-whitespace)
5 (smerge-refine-weight-hack): New vars.
6 (smerge-refine-forward): New fun.
7 (smerge-refine-chopup-region, smerge-refine-highlight-change): Use them.
8 (smerge-refine-subst): Use them as well. Preserve point.
9
12007-10-19 Juanma Barranquero <lekktu@gmail.com> 102007-10-19 Juanma Barranquero <lekktu@gmail.com>
2 11
3 * follow.el (follow-unload-function): New function. 12 * follow.el (follow-unload-function): New function.
diff --git a/lisp/smerge-mode.el b/lisp/smerge-mode.el
index 5d4400958d6..a33d21925b7 100644
--- a/lisp/smerge-mode.el
+++ b/lisp/smerge-mode.el
@@ -645,50 +645,119 @@ Point is moved to the end of the conflict."
645 (error nil))) 645 (error nil)))
646 found)) 646 found))
647 647
648;;; Refined change highlighting
649
650(defvar smerge-refine-forward-function 'smerge-refine-forward
651 "Function used to determine an \"atomic\" element.
652You can set it to `forward-char' to get char-level granularity.
653Its behavior has mainly two restrictions:
654- if this function encounters a newline, it's important that it stops right
655 after the newline.
656 This only matters if `smerge-refine-ignore-whitespace' is nil.
657- it needs to be unaffected by changes performed by the `preproc' argument
658 to `smerge-refine-subst'.
659 This only matters if `smerge-refine-weight-hack' is nil.")
660
661(defvar smerge-refine-ignore-whitespace t
662 "If non-nil,Indicate that smerge-refine should try to ignore change in whitespace.")
663
664(defvar smerge-refine-weight-hack t
665 "If non-nil, pass to diff as many lines as there are chars in the region.
666I.e. each atomic element (e.g. word) will be copied as many times (on different
667lines) as it has chars. This has 2 advantages:
668- if `diff' tries to minimize the number *lines* (rather than chars)
669 added/removed, this adjust the weights so that adding/removing long
670 symbols is considered correspondingly more costly.
671- `smerge-refine-forward-function' only needs to be called when chopping up
672 the regions, and `forward-char' can be used afterwards.
673It has the following disadvantages:
674- cannot use `diff -w' because the weighting causes added spaces in a line
675 to be represented as added copies of some line, so `diff -w' can't do the
676 right thing any more.
677- may in degenerate cases take a 1KB input region and turn it into a 1MB
678 file to pass to diff.")
679
680(defun smerge-refine-forward (n)
681 (let ((case-fold-search nil)
682 (re "[[:upper:]]?[[:lower:]]+\\|[[:upper:]]+\\|[[:digit:]]+\\|.\\|\n"))
683 (when (and smerge-refine-ignore-whitespace
684 ;; smerge-refine-weight-hack causes additional spaces to
685 ;; appear as additional lines as well, so even if diff ignore
686 ;; whitespace changes, it'll report added/removed lines :-(
687 (not smerge-refine-weight-hack))
688 (setq re (concat "[ \t]*\\(?:" re "\\)")))
689 (dotimes (i n)
690 (unless (looking-at re) (error "Smerge refine internal error"))
691 (goto-char (match-end 0)))))
692
648(defun smerge-refine-chopup-region (beg end file &optional preproc) 693(defun smerge-refine-chopup-region (beg end file &optional preproc)
649 "Chopup the region into small elements, one per line. 694 "Chopup the region into small elements, one per line.
650Save the result into FILE. 695Save the result into FILE.
651If non-nil, PREPROC is called with no argument in a buffer that contains 696If non-nil, PREPROC is called with no argument in a buffer that contains
652a copy of the text, just before chopping it up. It can be used to replace 697a copy of the text, just before chopping it up. It can be used to replace
653chars to try and eliminate some spurious differences." 698chars to try and eliminate some spurious differences."
654 ;; ediff chops up into words, where the definition of a word is 699 ;; We used to chop up char-by-char rather than word-by-word like ediff
655 ;; customizable. Instead we here keep only one char per line. 700 ;; does. It had the benefit of simplicity and very fine results, but it
656 ;; The advantages are that there's nothing to configure, that we get very 701 ;; often suffered from problem that diff would find correlations where
657 ;; fine results, and that it's trivial to map the line numbers in the 702 ;; there aren't any, so the resulting "change" didn't make much sense.
658 ;; output of diff back into buffer positions. The disadvantage is that it 703 ;; You can still get this behavior by setting
659 ;; can take more time to compute the diff and that the result is sometimes 704 ;; `smerge-refine-forward-function' to `forward-char'.
660 ;; too fine. I'm not too concerned about the slowdown because conflicts
661 ;; are usually significantly smaller than the whole file. As for the
662 ;; problem of too-fine-refinement, I have found it to be unimportant
663 ;; especially when you consider the cases where the fine-grain is just
664 ;; what you want.
665 (let ((buf (current-buffer))) 705 (let ((buf (current-buffer)))
666 (with-temp-buffer 706 (with-temp-buffer
667 (insert-buffer-substring buf beg end) 707 (insert-buffer-substring buf beg end)
668 (when preproc (goto-char (point-min)) (funcall preproc)) 708 (when preproc (goto-char (point-min)) (funcall preproc))
709 (when smerge-refine-ignore-whitespace
710 ;; It doesn't make much of a difference for diff-fine-highlight
711 ;; because we still have the _/+/</>/! prefix anyway. Can still be
712 ;; useful in other circumstances.
713 (subst-char-in-region (point-min) (point-max) ?\n ?\s))
669 (goto-char (point-min)) 714 (goto-char (point-min))
670 (while (not (eobp)) 715 (while (not (eobp))
671 (forward-char 1) 716 (funcall smerge-refine-forward-function 1)
672 ;; We add \n after each char except after \n, so we get one line per 717 (let ((s (if (prog2 (forward-char -1) (bolp) (forward-char 1))
673 ;; text char, where each line contains just one char, except for \n 718 nil
674 ;; chars which are represented by the empty line. 719 (buffer-substring (line-beginning-position) (point)))))
675 (unless (eq (char-before) ?\n) (insert ?\n))) 720 ;; We add \n after each char except after \n, so we get
721 ;; one line per text char, where each line contains
722 ;; just one char, except for \n chars which are
723 ;; represented by the empty line.
724 (unless (eq (char-before) ?\n) (insert ?\n))
725 ;; HACK ALERT!!
726 (if smerge-refine-weight-hack
727 (dotimes (i (1- (length s))) (insert s "\n")))))
728 (unless (bolp) (error "Smerge refine internal error"))
676 (let ((coding-system-for-write 'emacs-mule)) 729 (let ((coding-system-for-write 'emacs-mule))
677 (write-region (point-min) (point-max) file nil 'nomessage))))) 730 (write-region (point-min) (point-max) file nil 'nomessage)))))
678 731
679(defun smerge-refine-highlight-change (buf beg match-num1 match-num2 props) 732(defun smerge-refine-highlight-change (buf beg match-num1 match-num2 props)
680 (let* ((startline (string-to-number (match-string match-num1))) 733 (with-current-buffer buf
681 (ol (make-overlay 734 (goto-char beg)
682 (+ beg startline -1) 735 (let* ((startline (- (string-to-number match-num1) 1))
683 (+ beg (if (match-end match-num2) 736 (beg (progn (funcall (if smerge-refine-weight-hack
684 (string-to-number (match-string match-num2)) 737 'forward-char
685 startline)) 738 smerge-refine-forward-function)
686 buf 739 startline)
687 ;; Make them tend to shrink rather than spread when editing. 740 (point)))
688 'front-advance nil))) 741 (end (progn (funcall (if smerge-refine-weight-hack
689 (overlay-put ol 'evaporate t) 742 'forward-char
690 (dolist (x props) 743 smerge-refine-forward-function)
691 (overlay-put ol (car x) (cdr x))))) 744 (if match-num2
745 (- (string-to-number match-num2)
746 startline)
747 1))
748 (point))))
749 (when smerge-refine-ignore-whitespace
750 (skip-chars-backward " \t\n" beg) (setq end (point))
751 (goto-char beg)
752 (skip-chars-forward " \t\n" end) (setq beg (point)))
753 (when (> end beg)
754 (let ((ol (make-overlay
755 beg end nil
756 ;; Make them tend to shrink rather than spread when editing.
757 'front-advance nil)))
758 (overlay-put ol 'evaporate t)
759 (dolist (x props) (overlay-put ol (car x) (cdr x)))
760 ol)))))
692 761
693(defun smerge-refine-subst (beg1 end1 beg2 end2 props &optional preproc) 762(defun smerge-refine-subst (beg1 end1 beg2 end2 props &optional preproc)
694 "Show fine differences in the two regions BEG1..END1 and BEG2..END2. 763 "Show fine differences in the two regions BEG1..END1 and BEG2..END2.
@@ -697,9 +766,9 @@ If non-nil, PREPROC is called with no argument in a buffer that contains
697a copy of a region, just before preparing it to for `diff'. It can be used to 766a copy of a region, just before preparing it to for `diff'. It can be used to
698replace chars to try and eliminate some spurious differences." 767replace chars to try and eliminate some spurious differences."
699 (let* ((buf (current-buffer)) 768 (let* ((buf (current-buffer))
769 (pos (point))
700 (file1 (make-temp-file "diff1")) 770 (file1 (make-temp-file "diff1"))
701 (file2 (make-temp-file "diff2"))) 771 (file2 (make-temp-file "diff2")))
702
703 ;; Chop up regions into smaller elements and save into files. 772 ;; Chop up regions into smaller elements and save into files.
704 (smerge-refine-chopup-region beg1 end1 file1 preproc) 773 (smerge-refine-chopup-region beg1 end1 file1 preproc)
705 (smerge-refine-chopup-region beg2 end2 file2 preproc) 774 (smerge-refine-chopup-region beg2 end2 file2 preproc)
@@ -710,21 +779,49 @@ replace chars to try and eliminate some spurious differences."
710 (let ((coding-system-for-read 'emacs-mule)) 779 (let ((coding-system-for-read 'emacs-mule))
711 ;; Don't forget -a to make sure diff treats it as a text file 780 ;; Don't forget -a to make sure diff treats it as a text file
712 ;; even if it contains \0 and such. 781 ;; even if it contains \0 and such.
713 (call-process diff-command nil t nil "-a" file1 file2)) 782 (call-process diff-command nil t nil
783 (if (and smerge-refine-ignore-whitespace
784 (not smerge-refine-weight-hack))
785 "-aw" "-a")
786 file1 file2))
714 ;; Process diff's output. 787 ;; Process diff's output.
715 (goto-char (point-min)) 788 (goto-char (point-min))
716 (while (not (eobp)) 789 (let ((last1 nil)
717 (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$")) 790 (last2 nil))
718 (error "Unexpected patch hunk header: %s" 791 (while (not (eobp))
719 (buffer-substring (point) (line-end-position))) 792 (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$"))
720 (let ((op (char-after (match-beginning 3)))) 793 (error "Unexpected patch hunk header: %s"
794 (buffer-substring (point) (line-end-position))))
795 (let ((op (char-after (match-beginning 3)))
796 (m1 (match-string 1))
797 (m2 (match-string 2))
798 (m4 (match-string 4))
799 (m5 (match-string 5)))
721 (when (memq op '(?d ?c)) 800 (when (memq op '(?d ?c))
722 (smerge-refine-highlight-change buf beg1 1 2 props)) 801 (setq last1
802 (smerge-refine-highlight-change buf beg1 m1 m2 props)))
723 (when (memq op '(?a ?c)) 803 (when (memq op '(?a ?c))
724 (smerge-refine-highlight-change buf beg2 4 5 props))) 804 (setq last2
805 (smerge-refine-highlight-change buf beg2 m4 m5 props))))
725 (forward-line 1) ;Skip hunk header. 806 (forward-line 1) ;Skip hunk header.
726 (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body. 807 (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body.
727 (goto-char (match-beginning 0)))))) 808 (goto-char (match-beginning 0))))
809 ;; (assert (or (null last1) (< (overlay-start last1) end1)))
810 ;; (assert (or (null last2) (< (overlay-start last2) end2)))
811 (if smerge-refine-weight-hack
812 (progn
813 ;; (assert (or (null last1) (<= (overlay-end last1) end1)))
814 ;; (assert (or (null last2) (<= (overlay-end last2) end2)))
815 )
816 ;; smerge-refine-forward-function when calling in chopup may
817 ;; have stopped because it bumped into EOB whereas in
818 ;; smerge-refine-weight-hack it may go a bit further.
819 (if (and last1 (> (overlay-end last1) end1))
820 (move-overlay last1 (overlay-start last1) end1))
821 (if (and last2 (> (overlay-end last2) end2))
822 (move-overlay last2 (overlay-start last2) end2))
823 )))
824 (goto-char pos)
728 (delete-file file1) 825 (delete-file file1)
729 (delete-file file2)))) 826 (delete-file file2))))
730 827