diff options
| author | Stefan Monnier | 2007-10-19 15:59:13 +0000 |
|---|---|---|
| committer | Stefan Monnier | 2007-10-19 15:59:13 +0000 |
| commit | cd62539fc7ce9529619880b309276ca9d6c6ce13 (patch) | |
| tree | 4590d0dc29f5a13ee1cabfecf97de3eadda95e07 | |
| parent | fc7793831bdef46fef2cb400f2839fd846987110 (diff) | |
| download | emacs-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/ChangeLog | 9 | ||||
| -rw-r--r-- | lisp/smerge-mode.el | 173 |
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 @@ | |||
| 1 | 2007-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 | |||
| 1 | 2007-10-19 Juanma Barranquero <lekktu@gmail.com> | 10 | 2007-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. | ||
| 652 | You can set it to `forward-char' to get char-level granularity. | ||
| 653 | Its 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. | ||
| 666 | I.e. each atomic element (e.g. word) will be copied as many times (on different | ||
| 667 | lines) 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. | ||
| 673 | It 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. |
| 650 | Save the result into FILE. | 695 | Save the result into FILE. |
| 651 | If non-nil, PREPROC is called with no argument in a buffer that contains | 696 | If non-nil, PREPROC is called with no argument in a buffer that contains |
| 652 | a copy of the text, just before chopping it up. It can be used to replace | 697 | a copy of the text, just before chopping it up. It can be used to replace |
| 653 | chars to try and eliminate some spurious differences." | 698 | chars 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 | |||
| 697 | a copy of a region, just before preparing it to for `diff'. It can be used to | 766 | a copy of a region, just before preparing it to for `diff'. It can be used to |
| 698 | replace chars to try and eliminate some spurious differences." | 767 | replace 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 | ||