7

How do i check if two regions have identical content? Is it possible to do so while ignoring language-specific whitespace?

6
  • 2
    ediff?
    – Dan
    Commented Aug 21, 2015 at 14:08
  • 3
    Have you tried ediff-regions-linewise or ediff-regions-wordwise?
    – glucas
    Commented Aug 21, 2015 at 14:10
  • 3
    Are you looking for a programmatic answer, or one with a ui?
    – zck
    Commented Aug 21, 2015 at 15:54
  • I'm looking for an UI answer. The ediff solutions are nice, if one has climbed ediff's learning curve and you need more than a boolean answer to the question. There is also compare-windows (which works nicely with narrow/widen), but is useless if you trying to compare regions in the same buffer. One could write a function just for this: <code>(defun last-kills-identical () (interactive) (message (if (string= (first kill-ring) (second kill-ring)) "They are the same" "They are different")))</code>
    – Ben Hyde
    Commented Aug 28, 2015 at 13:35
  • @BenHyde maybe indirect buffers will help you when you want to use compare-windows to compare different regions of the same buffer. Either way, ediff is what you really need.
    – suvayu
    Commented Sep 24, 2015 at 20:10

3 Answers 3

5

(As previous mentioned in the comments by glucas), a heavyweight approach to comparing regions is provided by the excellent ediff library:

  • ediff-regions-linewise
  • ediff-regions-wordwise

These commands "run Ediff on a pair of regions in specified buffers".

They prompt you first to specify which buffer(s) the regions come from, and then to mark and/or confirm the region to use in each of those buffers in turn, and then finally the ediff session is started to compare the two.

Obviously you'd only use this approach when the benefits of using ediff outweighed the more-cumbersome initialisation procedure.

4

Interactively, I do this one of a few ways, depending on the scenario.

First, if the regions are small -- e.g., lately I've been comparing some test output with the expected results, and both are just a single line -- I just edit so the lines are next to each other and compare visually.

Another "low tech" solution is to use isearch. If both regions are visible at the same time, I use C-s at the start of one region, then repeatedly use C-w to pick up characters from the region. Then I watch the highlighting of the other region -- when it stops I have found the difference.

For larger regions, I use compare-windows. This has a way to ignore whitespace. If the regions are in the same buffer, just split windows and move point in each window to the start of one of the regions before beginning.

On the other hand if you wanted to do this programmatically, I think the basic idea is to use buffer-substring and then equal (or string=) to compare. I am not sure how you would ignore "language-specific whitespace", but I suppose one way would be to rewrite each string into a canonical form before comparing; for example by running a regular expression over them to strip such whitespace.

2
  • I'm glad you pointed those out. One man's "low tech" is another's "smart tech."
    – Emacs User
    Commented Sep 24, 2015 at 16:59
  • I concur with the isearch and compare-windows approaches :) I use both of those far more regularly than any more heavyweight approach.
    – phils
    Commented Sep 24, 2015 at 23:20
3

Since this solution uses only CLI Diff, it also answers your "ignore whitespace" question, tweak diff's option to do whatever you want. Please man diff.

;; Diff two regions
;; Step 1: Select a region and `M-x diff-region-tag-selected-as-a'
;; Step 2: Select another region and `M-x diff-region-compare-with-b'
(defun diff-region-format-region-boundary (b e)
  "Make sure lines are selected and B is less than E"
  (let (tmp rlt)
    ;; swap b e, make sure b < e
    (when (> b e)
      (setq tmp b)
      (setq b e)
      (set e tmp))

    ;; select lines
    (save-excursion
      ;; Another workaround for evil-visual-line bug:
      ;; In evil-mode, if we use hotkey V or `M-x evil-visual-line` to select line,
      ;; the (line-beginning-position) of the line which is after the last selected
      ;; line is always (region-end)! Don't know why.
      (if (and (> e b)
               (save-excursion (goto-char e) (= e (line-beginning-position)))
               (boundp 'evil-state) (eq evil-state 'visual))
          (setq e (1- e)))
      (goto-char b)
      (setq b (line-beginning-position))
      (goto-char e)
      (setq e (line-end-position)))
    (setq rlt (list b e))
    rlt))

(defun diff-region-tag-selected-as-a ()
  "Select a region to compare"
  (interactive)
  (when (region-active-p)
    (let (tmp buf)
      ;; select lines
      (setq tmp (diff-region-format-region-boundary (region-beginning) (region-end)))
      (setq buf (get-buffer-create "*Diff-regionA*"))
      (save-current-buffer
        (set-buffer buf)
        (erase-buffer))
      (append-to-buffer buf (car tmp) (cadr tmp))))
  (message "Now select other region to compare and run `diff-region-compare-with-b`"))

(defun diff-region-compare-with-b ()
  "Compare current region with region selected by `diff-region-tag-selected-as-a' "
  (interactive)
  (if (region-active-p)
      (let (rlt-buf
            diff-output
            (fa (make-temp-file (expand-file-name "scor"
                                                  (or small-temporary-file-directory
                                                      temporary-file-directory))))
            (fb (make-temp-file (expand-file-name "scor"
                                                  (or small-temporary-file-directory
                                                      temporary-file-directory)))))
        (when fb
          (setq tmp (diff-region-format-region-boundary (region-beginning) (region-end)))
          (write-region (car tmp) (cadr tmp) fb))

        (setq rlt-buf (get-buffer-create "*Diff-region-output*"))
        (when (and fa (file-exists-p fa) fb (file-exists-p fb))
          (save-current-buffer
            (set-buffer (get-buffer-create "*Diff-regionA*"))
            (write-region (point-min) (point-max) fa))
          (setq diff-output (shell-command-to-string (format "diff -Nabur %s %s" fa fb)))
          ;; show the diff output
          (if (string= diff-output "")
              (message "Two regions are SAME!")
              (save-current-buffer
                (switch-to-buffer-other-window rlt-buf)
                (set-buffer rlt-buf)
                (erase-buffer)
                (insert diff-output)
                (diff-mode))))

        (if (and fa (file-exists-p fa))
            (delete-file fa))
        (if (and fb (file-exists-p fb))
            (delete-file fb)))
    (message "Please select region at first!")))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.