You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
6.5 KiB
200 lines
6.5 KiB
;;; diffview.el --- View diffs in side-by-side format
|
|
|
|
;; Copyright (C) 2013-2015, Mitchel Humpherys
|
|
|
|
;; Author: Mitchel Humpherys <mitch.special@gmail.com>
|
|
;; Keywords: convenience, diff
|
|
;; Package-Version: 1.0
|
|
;; Package-Commit: 471dc36af93e68849bf2da0db991e186283b3546
|
|
;; Version: 1.0
|
|
;; URL: https://github.com/mgalgs/diffview-mode
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
;;
|
|
;; Render a unified diff (top/bottom) in an easy-to-comprehend side-by-side
|
|
;; format. This comes in handy for reading patches from mailing lists (or
|
|
;; from whencever you might acquire them).
|
|
;;
|
|
;;; Installation:
|
|
;;
|
|
;; M-x package-install diffview
|
|
;;
|
|
;;; Usage:
|
|
;;
|
|
;; The following functions are provided for launching a side-by-side diff:
|
|
;;
|
|
;; o `diffview-current' : View the current diff buffer side-by-side
|
|
;; o `diffview-region' : View the current diff region side-by-side
|
|
;; o `diffview-message' : View the current email message (which presumably
|
|
;; contains a patch) side-by-side
|
|
;;
|
|
;;
|
|
;;; Screenshots:
|
|
;;
|
|
;; Before:<br>
|
|
;; <img src="https://raw.github.com/mgalgs/diffview-mode/master/screenshots/diffview-before.png"><br>
|
|
;; After:<br>
|
|
;; <img src="https://raw.github.com/mgalgs/diffview-mode/master/screenshots/diffview-after.png"><br>
|
|
;;
|
|
;;; Code:
|
|
|
|
(require 'message)
|
|
|
|
(defun diffview--print-all-lines-to-buffer (lines buffer-name)
|
|
"Prints each line in `LINES' to a buffer named `BUFFER-NAME'."
|
|
(let ((old-temp-buffer (get-buffer buffer-name)))
|
|
;; (with-output-to-temp-buffer buffer-name
|
|
(when old-temp-buffer
|
|
(kill-buffer old-temp-buffer))
|
|
(with-current-buffer (get-buffer-create buffer-name)
|
|
(erase-buffer)
|
|
(dolist (line lines)
|
|
(insert line "\n")))))
|
|
|
|
(defvar diffview--minus-bufname "*side-by-side-1*")
|
|
(defvar diffview--plus-bufname "*side-by-side-2*")
|
|
(defvar diffview--saved-wincfg nil)
|
|
(defvar diffview--regexp-is-plus-line "^\\+\\([^+]\\{1\\}\\|$\\)"
|
|
"A + followed by one non + or the end of the line.")
|
|
(defvar diffview--regexp-is-minus-line "^-\\([^-]\\{1\\}\\|$\\)"
|
|
"A - followed by one non - or the end of the line.")
|
|
|
|
(defun diffview--view-string (input-string)
|
|
"Displays `INPUT-STRING' (a diff) in a side-by-side view."
|
|
(setq diffview--saved-wincfg (current-window-configuration))
|
|
(delete-other-windows)
|
|
(let (plus-lines
|
|
minus-lines
|
|
tmp-line
|
|
(current-state 'in-common)
|
|
(last-state 'in-common)
|
|
(current-lines-in-plus 0)
|
|
(current-lines-in-minus 0)
|
|
(total-lines 0)
|
|
(all-lines (split-string input-string "\n")))
|
|
(dolist (line all-lines)
|
|
(cond
|
|
((string-match diffview--regexp-is-plus-line line)
|
|
(push line plus-lines)
|
|
(setq current-state 'in-plus)
|
|
(setq current-lines-in-plus (1+ current-lines-in-plus)))
|
|
((string-match diffview--regexp-is-minus-line line)
|
|
(push line minus-lines)
|
|
(setq current-state 'in-minus)
|
|
(setq current-lines-in-minus (1+ current-lines-in-minus)))
|
|
;; everything else must be common
|
|
(t
|
|
(push line plus-lines)
|
|
(push line minus-lines)
|
|
(setq current-state 'in-common)))
|
|
|
|
(setq total-lines (1+ total-lines))
|
|
|
|
;; Process hunk state transitions
|
|
(when (not (equal current-state last-state))
|
|
;; there's been a state change
|
|
(when (equal current-state 'in-common)
|
|
;; we're transitioning out the +/- part of a hunk. We would
|
|
;; like both sides to have the same number lines for this
|
|
;; hunk, so we might need to fill one side or the other with
|
|
;; empty lines.
|
|
(cond
|
|
((> current-lines-in-plus current-lines-in-minus)
|
|
;; need to fill minus
|
|
(setq tmp-line (pop minus-lines))
|
|
(dotimes (i (- current-lines-in-plus current-lines-in-minus))
|
|
(push "" minus-lines))
|
|
(push tmp-line minus-lines))
|
|
((< current-lines-in-plus current-lines-in-minus)
|
|
;; need to fill plus
|
|
(setq tmp-line (pop plus-lines))
|
|
(dotimes (i (- current-lines-in-minus current-lines-in-plus))
|
|
(push "" plus-lines))
|
|
(push tmp-line plus-lines)))
|
|
|
|
(setq current-lines-in-plus 0
|
|
current-lines-in-minus 0)))
|
|
|
|
(setq last-state current-state))
|
|
|
|
(diffview--print-all-lines-to-buffer (reverse minus-lines) diffview--minus-bufname)
|
|
(diffview--print-all-lines-to-buffer (reverse plus-lines) diffview--plus-bufname)
|
|
|
|
(switch-to-buffer diffview--minus-bufname nil t)
|
|
(goto-char (point-min))
|
|
(diffview-mode)
|
|
|
|
(split-window-right)
|
|
(other-window 1)
|
|
|
|
(switch-to-buffer diffview--plus-bufname nil t)
|
|
(goto-char (point-min))
|
|
(diffview-mode)
|
|
|
|
(scroll-all-mode)))
|
|
|
|
;;;###autoload
|
|
(defun diffview-current ()
|
|
"Show current diff buffer in a side-by-side view."
|
|
(interactive)
|
|
(diffview--view-string (buffer-string)))
|
|
|
|
;;;###autoload
|
|
(defun diffview-region ()
|
|
"Show current diff region in a side-by-side view."
|
|
(interactive)
|
|
(diffview--view-string (buffer-substring (point) (mark))))
|
|
|
|
;;;###autoload
|
|
(defun diffview-message ()
|
|
"Show `message-mode' buffer in a side-by-side view.
|
|
|
|
This is useful for reading patches from mailing lists."
|
|
(interactive)
|
|
(let (beg end)
|
|
(save-excursion
|
|
(message-goto-body)
|
|
(search-forward-regexp "^---$")
|
|
(setq beg (1+ (point)))
|
|
(search-forward-regexp "^-- $")
|
|
(setq end (1+ (point)))
|
|
(diffview--view-string (buffer-substring beg end)))))
|
|
|
|
|
|
|
|
;;; You probably don't want to invoke `diffview-mode' directly. Just use
|
|
;;; one of the autoload functions above.
|
|
|
|
(define-derived-mode diffview-mode special-mode "Diffview"
|
|
"Mode for viewing diffs side-by-side"
|
|
(setq font-lock-defaults '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil))))
|
|
|
|
(defun diffview--quit ()
|
|
"Quit diffview and clean up diffview buffers."
|
|
(interactive)
|
|
(delete-other-windows)
|
|
(scroll-all-mode 0)
|
|
(let ((plusbuf (get-buffer diffview--plus-bufname))
|
|
(minusbuf (get-buffer diffview--minus-bufname)))
|
|
(if plusbuf (kill-buffer plusbuf))
|
|
(if minusbuf (kill-buffer minusbuf)))
|
|
(set-window-configuration diffview--saved-wincfg))
|
|
|
|
(define-key diffview-mode-map (kbd "q") 'diffview--quit)
|
|
|
|
(provide 'diffview)
|
|
;;; diffview.el ends here
|
|
;;
|