├── Makefile ├── README.md ├── diffview.el └── screenshots ├── diffview-after.png └── diffview-before.png /Makefile: -------------------------------------------------------------------------------- 1 | README.md: make-readme-markdown.el diffview.el 2 | emacs --script $< < diffview.el >$@ 3 | 4 | ifeq ($(LOCAL),1) 5 | make-readme-markdown.el: 6 | cp -v ../make-readme-markdown/make-readme-markdown.el . 7 | else 8 | make-readme-markdown.el: 9 | wget -q -O $@ https://raw.github.com/mgalgs/make-readme-markdown/master/make-readme-markdown.el 10 | endif 11 | 12 | .INTERMEDIATE: make-readme-markdown.el 13 | .PHONY: README.md 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Emacs Logo 2 | ## diffview.el 3 | *View diffs in side-by-side format* 4 | 5 | --- 6 | [![License GPLv3](https://img.shields.io/badge/license-GPL_v3-green.svg)](http://www.gnu.org/licenses/gpl-3.0.html) 7 | [![MELPA](http://melpa.org/packages/diffview-badge.svg)](http://melpa.org/#/diffview) 8 | [![MELPA Stable](http://stable.melpa.org/packages/diffview-badge.svg)](http://stable.melpa.org/#/diffview) 9 | 10 | Render a unified diff (top/bottom) in an easy-to-comprehend side-by-side 11 | format. This comes in handy for reading patches from mailing lists (or 12 | from whencever you might acquire them). 13 | 14 | ### Installation 15 | 16 | 17 | M-x package-install diffview 18 | 19 | ### Usage 20 | 21 | 22 | The following functions are provided for launching a side-by-side diff: 23 | 24 | * `diffview-current` : View the current diff buffer side-by-side 25 | * `diffview-region` : View the current diff region side-by-side 26 | * `diffview-message` : View the current email message (which presumably 27 | contains a patch) side-by-side 28 | 29 | ### Keybindings 30 | 31 | 32 | * `}` : Next file 33 | * `{` : Previous file 34 | * `l` : Align windows 35 | * `q` : Quit 36 | 37 | ### Screenshots 38 | 39 | 40 | Before: 41 | 42 | 43 | After: 44 | 45 | 46 | 47 | 48 | ### Function and Macro Documentation 49 | 50 | #### `(diffview-current)` 51 | 52 | Show current diff buffer in a side-by-side view. 53 | 54 | #### `(diffview-region)` 55 | 56 | Show current diff region in a side-by-side view. 57 | 58 | #### `(diffview-message)` 59 | 60 | Show `message-mode` buffer in a side-by-side view. 61 | This is useful for reading patches from mailing lists. 62 | 63 | ----- 64 |
65 | Markdown README file generated by 66 | make-readme-markdown.el 67 |
68 | -------------------------------------------------------------------------------- /diffview.el: -------------------------------------------------------------------------------- 1 | ;;; diffview.el --- View diffs in side-by-side format -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2013-2016 Free Software Foundation, Inc. 4 | 5 | ;; Author: Mitchel Humpherys 6 | ;; Maintainer: Mitchel Humpherys 7 | ;; Keywords: convenience, diff 8 | ;; Version: 1.0 9 | ;; URL: https://github.com/mgalgs/diffview-mode 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | ;; 26 | ;; Render a unified diff (top/bottom) in an easy-to-comprehend side-by-side 27 | ;; format. This comes in handy for reading patches from mailing lists (or 28 | ;; from whencever you might acquire them). 29 | ;; 30 | ;;; Installation: 31 | ;; 32 | ;; M-x package-install diffview 33 | ;; 34 | ;;; Usage: 35 | ;; 36 | ;; The following functions are provided for launching a side-by-side diff: 37 | ;; 38 | ;; o `diffview-current' : View the current diff buffer side-by-side 39 | ;; o `diffview-region' : View the current diff region side-by-side 40 | ;; o `diffview-message' : View the current email message (which presumably 41 | ;; contains a patch) side-by-side 42 | ;; 43 | ;;; Keybindings 44 | ;; 45 | ;; o `}' : Next file 46 | ;; o `{' : Previous file 47 | ;; o `l' : Align windows 48 | ;; o `q' : Quit 49 | ;; 50 | ;;; Screenshots: 51 | ;; 52 | ;; Before: 53 | ;; https://raw.github.com/mgalgs/diffview-mode/master/screenshots/diffview-before.png 54 | ;; 55 | ;; After: 56 | ;; https://raw.github.com/mgalgs/diffview-mode/master/screenshots/diffview-after.png 57 | ;; 58 | ;;; Code: 59 | 60 | (require 'message) 61 | 62 | (defun diffview--print-all-lines-to-buffer (lines buffer-name) 63 | "Prints each line in `LINES' to a buffer named `BUFFER-NAME'." 64 | (let ((old-temp-buffer (get-buffer buffer-name))) 65 | ;; (with-output-to-temp-buffer buffer-name 66 | (when old-temp-buffer 67 | (kill-buffer old-temp-buffer)) 68 | (with-current-buffer (get-buffer-create buffer-name) 69 | (erase-buffer) 70 | (dolist (line lines) 71 | (insert line "\n"))))) 72 | 73 | (defvar diffview--minus-bufname "*side-by-side-1*") 74 | (defvar diffview--plus-bufname "*side-by-side-2*") 75 | (defvar diffview--saved-wincfg nil) 76 | (defvar diffview--regexp-is-plus-line "^\\+\\([^+]\\{1\\}\\|$\\)" 77 | "A + followed by one non + or the end of the line.") 78 | (defvar diffview--regexp-is-minus-line "^-\\([^-]\\{1\\}\\|$\\)" 79 | "A - followed by one non - or the end of the line.") 80 | 81 | (defun diffview--view-string (input-string) 82 | "Displays `INPUT-STRING' (a diff) in a side-by-side view." 83 | (setq diffview--saved-wincfg (current-window-configuration)) 84 | (delete-other-windows) 85 | (let (plus-lines 86 | minus-lines 87 | tmp-line 88 | (current-state 'in-common) 89 | (last-state 'in-common) 90 | (current-lines-in-plus 0) 91 | (current-lines-in-minus 0) 92 | (total-lines 0) 93 | (all-lines (split-string input-string "\n"))) 94 | (dolist (line all-lines) 95 | (cond 96 | ((string-match diffview--regexp-is-plus-line line) 97 | (push line plus-lines) 98 | (setq current-state 'in-plus) 99 | (setq current-lines-in-plus (1+ current-lines-in-plus))) 100 | ((string-match diffview--regexp-is-minus-line line) 101 | (push line minus-lines) 102 | (setq current-state 'in-minus) 103 | (setq current-lines-in-minus (1+ current-lines-in-minus))) 104 | ;; everything else must be common 105 | (t 106 | (push line plus-lines) 107 | (push line minus-lines) 108 | (setq current-state 'in-common))) 109 | 110 | (setq total-lines (1+ total-lines)) 111 | 112 | ;; Process hunk state transitions 113 | (when (not (equal current-state last-state)) 114 | ;; there's been a state change 115 | (when (equal current-state 'in-common) 116 | ;; we're transitioning out the +/- part of a hunk. We would 117 | ;; like both sides to have the same number lines for this 118 | ;; hunk, so we might need to fill one side or the other with 119 | ;; empty lines. 120 | (cond 121 | ((> current-lines-in-plus current-lines-in-minus) 122 | ;; need to fill minus 123 | (setq tmp-line (pop minus-lines)) 124 | (dotimes (_ (- current-lines-in-plus current-lines-in-minus)) 125 | (push "" minus-lines)) 126 | (push tmp-line minus-lines)) 127 | ((< current-lines-in-plus current-lines-in-minus) 128 | ;; need to fill plus 129 | (setq tmp-line (pop plus-lines)) 130 | (dotimes (_ (- current-lines-in-minus current-lines-in-plus)) 131 | (push "" plus-lines)) 132 | (push tmp-line plus-lines))) 133 | 134 | (setq current-lines-in-plus 0 135 | current-lines-in-minus 0))) 136 | 137 | (setq last-state current-state)) 138 | 139 | (diffview--print-all-lines-to-buffer (reverse minus-lines) diffview--minus-bufname) 140 | (diffview--print-all-lines-to-buffer (reverse plus-lines) diffview--plus-bufname) 141 | 142 | (switch-to-buffer diffview--minus-bufname nil t) 143 | (goto-char (point-min)) 144 | (diffview-mode) 145 | 146 | (split-window-right) 147 | (other-window 1) 148 | 149 | (switch-to-buffer diffview--plus-bufname nil t) 150 | (goto-char (point-min)) 151 | (diffview-mode) 152 | 153 | (scroll-all-mode))) 154 | 155 | ;;;###autoload 156 | (defun diffview-current () 157 | "Show current diff buffer in a side-by-side view." 158 | (interactive) 159 | (diffview--view-string (buffer-string))) 160 | 161 | ;;;###autoload 162 | (defun diffview-region () 163 | "Show current diff region in a side-by-side view." 164 | (interactive) 165 | (diffview--view-string (buffer-substring (point) (mark)))) 166 | 167 | ;;;###autoload 168 | (defun diffview-message () 169 | "Show `message-mode' buffer in a side-by-side view. 170 | 171 | This is useful for reading patches from mailing lists." 172 | (interactive) 173 | (let (beg end) 174 | (save-excursion 175 | (message-goto-body) 176 | (search-forward-regexp "^---$") 177 | (setq beg (1+ (point))) 178 | (search-forward-regexp "^-- $") 179 | (setq end (1+ (point))) 180 | (diffview--view-string (buffer-substring beg end))))) 181 | 182 | (defvar diffview--mode-map 183 | (let ((km (make-sparse-keymap))) 184 | (define-key km (kbd "l") 'diffview--align-windows) 185 | (define-key km (kbd "}") 'diffview--next-file) 186 | (define-key km (kbd "{") 'diffview--prev-file) 187 | (define-key km (kbd "q") 'diffview--quit) 188 | km) 189 | "Special keymap for `diffview--mode-map'.") 190 | 191 | (easy-menu-define 192 | diffview--menu diffview--mode-map "diffview menu" 193 | '("Diffview" 194 | ["Align windows" diffview--align-windows] 195 | ["Next file" diffview--next-file] 196 | ["Prev file" diffview--prev-file] 197 | ["Quit" diffview--quit])) 198 | 199 | ;;; You probably don't want to invoke `diffview-mode' directly. Just use 200 | ;;; one of the autoload functions above. 201 | 202 | (define-derived-mode diffview-mode special-mode "Diffview" 203 | "Mode for viewing diffs side-by-side" 204 | (make-local-variable 'font-lock-defaults) 205 | (setq font-lock-defaults '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil))) 206 | (use-local-map diffview--mode-map)) 207 | 208 | (defun diffview--quit () 209 | "Quit diffview and clean up diffview buffers." 210 | (interactive) 211 | (delete-other-windows) 212 | (scroll-all-mode 0) 213 | (let ((plusbuf (get-buffer diffview--plus-bufname)) 214 | (minusbuf (get-buffer diffview--minus-bufname))) 215 | (if plusbuf (kill-buffer plusbuf)) 216 | (if minusbuf (kill-buffer minusbuf))) 217 | (set-window-configuration diffview--saved-wincfg)) 218 | 219 | (defun diffview--next-file (&optional arg) 220 | "Move to next diff file start. Move to previous diff file start 221 | with prefix ARG." 222 | (interactive "P") 223 | (let* ((updown (if arg -1 1)) 224 | (next-file-line-num (save-excursion 225 | (save-restriction 226 | (widen) 227 | (let ((old-start-re "^--- ") 228 | (new-start-re "^\\+\\+\\+ ")) 229 | (beginning-of-line) 230 | (when (looking-at (if (= updown 1) old-start-re new-start-re)) 231 | (forward-line updown)) 232 | (when (looking-at (if (= updown 1) new-start-re old-start-re)) 233 | (forward-line updown)) 234 | (while (and (not (if (= updown 1) (eobp) (bobp))) 235 | (not (looking-at new-start-re))) 236 | (forward-line updown)) 237 | (line-number-at-pos)))))) 238 | (let ((n-lines (- next-file-line-num (line-number-at-pos)))) 239 | (when 240 | (and (not (= n-lines 0)) 241 | (cond 242 | ((string= (buffer-name (current-buffer)) 243 | diffview--minus-bufname) 244 | (forward-line n-lines) 245 | (switch-to-buffer-other-window diffview--plus-bufname)) 246 | ((string= (buffer-name (current-buffer)) 247 | diffview--plus-bufname) 248 | (forward-line n-lines) 249 | (switch-to-buffer-other-window diffview--minus-bufname))) 250 | (forward-line n-lines) 251 | (other-window 1)))))) 252 | 253 | (defun diffview--prev-file () 254 | "Move to prev diff file start" 255 | (interactive) 256 | (diffview--next-file t)) 257 | 258 | (defun diffview--align-windows () 259 | (interactive) 260 | (let ((align-to-line (line-number-at-pos)) 261 | (align-from-top (- (line-number-at-pos (point)) 262 | (line-number-at-pos (window-start))))) 263 | (when 264 | (cond 265 | ((string= (buffer-name (current-buffer)) 266 | diffview--minus-bufname) 267 | (switch-to-buffer-other-window diffview--plus-bufname)) 268 | ((string= (buffer-name (current-buffer)) 269 | diffview--plus-bufname) 270 | (switch-to-buffer-other-window diffview--minus-bufname))) 271 | (goto-char (point-min)) 272 | (forward-line (1- align-to-line)) 273 | (recenter align-from-top) 274 | (other-window 1)))) 275 | 276 | (provide 'diffview) 277 | ;;; diffview.el ends here 278 | ;; 279 | -------------------------------------------------------------------------------- /screenshots/diffview-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgalgs/diffview-mode/8f07c0ff4a1acef990589df0d3e32288f19c9d71/screenshots/diffview-after.png -------------------------------------------------------------------------------- /screenshots/diffview-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgalgs/diffview-mode/8f07c0ff4a1acef990589df0d3e32288f19c9d71/screenshots/diffview-before.png --------------------------------------------------------------------------------