Personal emacs config
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

  1. ;;; diffview.el --- View diffs in side-by-side format
  2. ;; Copyright (C) 2013-2015, Mitchel Humpherys
  3. ;; Author: Mitchel Humpherys <mitch.special@gmail.com>
  4. ;; Keywords: convenience, diff
  5. ;; Package-Version: 1.0
  6. ;; Package-Commit: 471dc36af93e68849bf2da0db991e186283b3546
  7. ;; Version: 1.0
  8. ;; URL: https://github.com/mgalgs/diffview-mode
  9. ;; This program is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or
  12. ;; (at your option) any later version.
  13. ;; This program is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. ;;; Commentary:
  20. ;;
  21. ;; Render a unified diff (top/bottom) in an easy-to-comprehend side-by-side
  22. ;; format. This comes in handy for reading patches from mailing lists (or
  23. ;; from whencever you might acquire them).
  24. ;;
  25. ;;; Installation:
  26. ;;
  27. ;; M-x package-install diffview
  28. ;;
  29. ;;; Usage:
  30. ;;
  31. ;; The following functions are provided for launching a side-by-side diff:
  32. ;;
  33. ;; o `diffview-current' : View the current diff buffer side-by-side
  34. ;; o `diffview-region' : View the current diff region side-by-side
  35. ;; o `diffview-message' : View the current email message (which presumably
  36. ;; contains a patch) side-by-side
  37. ;;
  38. ;;
  39. ;;; Screenshots:
  40. ;;
  41. ;; Before:<br>
  42. ;; <img src="https://raw.github.com/mgalgs/diffview-mode/master/screenshots/diffview-before.png"><br>
  43. ;; After:<br>
  44. ;; <img src="https://raw.github.com/mgalgs/diffview-mode/master/screenshots/diffview-after.png"><br>
  45. ;;
  46. ;;; Code:
  47. (require 'message)
  48. (defun diffview--print-all-lines-to-buffer (lines buffer-name)
  49. "Prints each line in `LINES' to a buffer named `BUFFER-NAME'."
  50. (let ((old-temp-buffer (get-buffer buffer-name)))
  51. ;; (with-output-to-temp-buffer buffer-name
  52. (when old-temp-buffer
  53. (kill-buffer old-temp-buffer))
  54. (with-current-buffer (get-buffer-create buffer-name)
  55. (erase-buffer)
  56. (dolist (line lines)
  57. (insert line "\n")))))
  58. (defvar diffview--minus-bufname "*side-by-side-1*")
  59. (defvar diffview--plus-bufname "*side-by-side-2*")
  60. (defvar diffview--saved-wincfg nil)
  61. (defvar diffview--regexp-is-plus-line "^\\+\\([^+]\\{1\\}\\|$\\)"
  62. "A + followed by one non + or the end of the line.")
  63. (defvar diffview--regexp-is-minus-line "^-\\([^-]\\{1\\}\\|$\\)"
  64. "A - followed by one non - or the end of the line.")
  65. (defun diffview--view-string (input-string)
  66. "Displays `INPUT-STRING' (a diff) in a side-by-side view."
  67. (setq diffview--saved-wincfg (current-window-configuration))
  68. (delete-other-windows)
  69. (let (plus-lines
  70. minus-lines
  71. tmp-line
  72. (current-state 'in-common)
  73. (last-state 'in-common)
  74. (current-lines-in-plus 0)
  75. (current-lines-in-minus 0)
  76. (total-lines 0)
  77. (all-lines (split-string input-string "\n")))
  78. (dolist (line all-lines)
  79. (cond
  80. ((string-match diffview--regexp-is-plus-line line)
  81. (push line plus-lines)
  82. (setq current-state 'in-plus)
  83. (setq current-lines-in-plus (1+ current-lines-in-plus)))
  84. ((string-match diffview--regexp-is-minus-line line)
  85. (push line minus-lines)
  86. (setq current-state 'in-minus)
  87. (setq current-lines-in-minus (1+ current-lines-in-minus)))
  88. ;; everything else must be common
  89. (t
  90. (push line plus-lines)
  91. (push line minus-lines)
  92. (setq current-state 'in-common)))
  93. (setq total-lines (1+ total-lines))
  94. ;; Process hunk state transitions
  95. (when (not (equal current-state last-state))
  96. ;; there's been a state change
  97. (when (equal current-state 'in-common)
  98. ;; we're transitioning out the +/- part of a hunk. We would
  99. ;; like both sides to have the same number lines for this
  100. ;; hunk, so we might need to fill one side or the other with
  101. ;; empty lines.
  102. (cond
  103. ((> current-lines-in-plus current-lines-in-minus)
  104. ;; need to fill minus
  105. (setq tmp-line (pop minus-lines))
  106. (dotimes (i (- current-lines-in-plus current-lines-in-minus))
  107. (push "" minus-lines))
  108. (push tmp-line minus-lines))
  109. ((< current-lines-in-plus current-lines-in-minus)
  110. ;; need to fill plus
  111. (setq tmp-line (pop plus-lines))
  112. (dotimes (i (- current-lines-in-minus current-lines-in-plus))
  113. (push "" plus-lines))
  114. (push tmp-line plus-lines)))
  115. (setq current-lines-in-plus 0
  116. current-lines-in-minus 0)))
  117. (setq last-state current-state))
  118. (diffview--print-all-lines-to-buffer (reverse minus-lines) diffview--minus-bufname)
  119. (diffview--print-all-lines-to-buffer (reverse plus-lines) diffview--plus-bufname)
  120. (switch-to-buffer diffview--minus-bufname nil t)
  121. (goto-char (point-min))
  122. (diffview-mode)
  123. (split-window-right)
  124. (other-window 1)
  125. (switch-to-buffer diffview--plus-bufname nil t)
  126. (goto-char (point-min))
  127. (diffview-mode)
  128. (scroll-all-mode)))
  129. ;;;###autoload
  130. (defun diffview-current ()
  131. "Show current diff buffer in a side-by-side view."
  132. (interactive)
  133. (diffview--view-string (buffer-string)))
  134. ;;;###autoload
  135. (defun diffview-region ()
  136. "Show current diff region in a side-by-side view."
  137. (interactive)
  138. (diffview--view-string (buffer-substring (point) (mark))))
  139. ;;;###autoload
  140. (defun diffview-message ()
  141. "Show `message-mode' buffer in a side-by-side view.
  142. This is useful for reading patches from mailing lists."
  143. (interactive)
  144. (let (beg end)
  145. (save-excursion
  146. (message-goto-body)
  147. (search-forward-regexp "^---$")
  148. (setq beg (1+ (point)))
  149. (search-forward-regexp "^-- $")
  150. (setq end (1+ (point)))
  151. (diffview--view-string (buffer-substring beg end)))))
  152. ;;; You probably don't want to invoke `diffview-mode' directly. Just use
  153. ;;; one of the autoload functions above.
  154. (define-derived-mode diffview-mode special-mode "Diffview"
  155. "Mode for viewing diffs side-by-side"
  156. (setq font-lock-defaults '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil))))
  157. (defun diffview--quit ()
  158. "Quit diffview and clean up diffview buffers."
  159. (interactive)
  160. (delete-other-windows)
  161. (scroll-all-mode 0)
  162. (let ((plusbuf (get-buffer diffview--plus-bufname))
  163. (minusbuf (get-buffer diffview--minus-bufname)))
  164. (if plusbuf (kill-buffer plusbuf))
  165. (if minusbuf (kill-buffer minusbuf)))
  166. (set-window-configuration diffview--saved-wincfg))
  167. (define-key diffview-mode-map (kbd "q") 'diffview--quit)
  168. (provide 'diffview)
  169. ;;; diffview.el ends here
  170. ;;