/ diff-hl-margin.el
diff-hl-margin.el
1 ;;; diff-hl-margin.el --- Highlight buffer changes on margins -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2012-2025 Free Software Foundation, Inc. 4 5 ;; This file is part of GNU Emacs. 6 7 ;; GNU Emacs is free software: you can redistribute it and/or modify 8 ;; it under the terms of the GNU General Public License as published by 9 ;; the Free Software Foundation, either version 3 of the License, or 10 ;; (at your option) any later version. 11 12 ;; GNU Emacs is distributed in the hope that it will be useful, 13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 ;; GNU General Public License for more details. 16 17 ;; You should have received a copy of the GNU General Public License 18 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 19 20 ;;; Commentary: 21 22 ;; This is a global mode, it modifies `diff-hl-mode' to use the margin 23 ;; instead of the fringe. To toggle, type `M-x diff-hl-margin-mode'. 24 ;; 25 ;; Compared to the default behavior, this makes `diff-hl-mode' 26 ;; indicators show up even when Emacs is running in a terminal. 27 ;; 28 ;; On the flip side, the indicators look simpler, and they are 29 ;; incompatible with `linum-mode' or any other mode that uses the 30 ;; margin. 31 ;; 32 ;; You might want to enable it conditionally in your init file 33 ;; depending on whether Emacs is running in graphical mode: 34 ;; 35 ;; (unless (window-system) (diff-hl-margin-mode)) 36 37 (require 'cl-lib) 38 (require 'diff-hl) 39 (require 'diff-hl-dired) 40 41 (defvar diff-hl-margin-old-highlight-function nil) 42 43 (defvar diff-hl-margin-old-highlight-ref-function nil) 44 45 (defvar diff-hl-margin-old-width nil) 46 47 (defgroup diff-hl-margin nil 48 "Highlight buffer changes on margin" 49 :group 'diff-hl) 50 51 (defface diff-hl-margin-insert 52 '((default :inherit diff-hl-insert)) 53 "Face used to highlight inserted lines on the margin.") 54 55 (defface diff-hl-margin-delete 56 '((default :inherit diff-hl-delete)) 57 "Face used to highlight deleted lines on the margin.") 58 59 (defface diff-hl-margin-change 60 '((default :inherit diff-hl-change)) 61 "Face used to highlight changed lines on the margin.") 62 63 (defface diff-hl-margin-ignored 64 '((default :inherit dired-ignored)) 65 "Face used to highlight changed lines on the margin.") 66 67 (defface diff-hl-margin-unknown 68 '((default :inherit dired-ignored)) 69 "Face used to highlight changed lines on the margin.") 70 71 (defface diff-hl-margin-reference-insert 72 '((default :inherit diff-hl-reference-insert)) 73 "Face used to highlight lines inserted since reference rev on the margin.") 74 75 (defface diff-hl-margin-reference-delete 76 '((default :inherit diff-hl-reference-delete)) 77 "Face used to highlight lines deleted since reference rev on the margin.") 78 79 (defface diff-hl-margin-reference-change 80 '((default :inherit diff-hl-reference-change)) 81 "Face used to highlight changed since reference rev on the margin.") 82 83 (defcustom diff-hl-margin-symbols-alist 84 '((insert . "+") (delete . "-") (change . "!") 85 (unknown . "?") (ignored . "i") (reference . " ")) 86 "Associative list from symbols to strings." 87 :type '(alist :key-type symbol 88 :value-type string 89 :options (insert delete change unknown ignored reference)) 90 :set (lambda (symbol value) 91 (defvar diff-hl-margin-spec-cache) 92 (set-default symbol value) 93 (setq diff-hl-margin-spec-cache nil))) 94 95 ;;;###autoload 96 (define-minor-mode diff-hl-margin-mode 97 "Toggle displaying `diff-hl-mode' highlights on the margin." 98 :lighter "" :global t 99 (if diff-hl-margin-mode 100 (progn 101 (add-hook 'diff-hl-mode-on-hook 'diff-hl-margin-local-mode) 102 (add-hook 'diff-hl-mode-off-hook 'diff-hl-margin-local-mode-off) 103 (add-hook 'diff-hl-dired-mode-on-hook 'diff-hl-margin-local-mode) 104 (add-hook 'diff-hl-dired-mode-off-hook 'diff-hl-margin-local-mode-off)) 105 (remove-hook 'diff-hl-mode-on-hook 'diff-hl-margin-local-mode) 106 (remove-hook 'diff-hl-mode-off-hook 'diff-hl-margin-local-mode-off) 107 (remove-hook 'diff-hl-dired-mode-on-hook 'diff-hl-margin-local-mode) 108 (remove-hook 'diff-hl-dired-mode-off-hook 'diff-hl-margin-local-mode-off)) 109 (dolist (buf (buffer-list)) 110 (with-current-buffer buf 111 (cond 112 (diff-hl-mode 113 (diff-hl-margin-local-mode (if diff-hl-margin-mode 1 -1)) 114 (diff-hl-update)) 115 (diff-hl-dired-mode 116 (diff-hl-margin-local-mode (if diff-hl-margin-mode 1 -1)) 117 (diff-hl-dired-update)))))) 118 119 ;;;###autoload 120 (define-minor-mode diff-hl-margin-local-mode 121 "Toggle displaying `diff-hl-mode' highlights on the margin locally. 122 You probably shouldn't use this function directly." 123 :lighter "" 124 (let ((width-var (intern (format "%s-margin-width" diff-hl-side)))) 125 (if diff-hl-margin-local-mode 126 (progn 127 (setq-local diff-hl-margin-old-highlight-function 128 diff-hl-highlight-function) 129 (setq-local diff-hl-margin-old-highlight-ref-function 130 diff-hl-highlight-reference-function) 131 (setq-local diff-hl-highlight-function 132 #'diff-hl-highlight-on-margin) 133 (setq-local diff-hl-highlight-reference-function 134 #'diff-hl-highlight-on-margin-flat) 135 (setq-local diff-hl-margin-old-width (symbol-value width-var)) 136 (set width-var 1)) 137 (when diff-hl-margin-old-highlight-function 138 (setq diff-hl-highlight-function diff-hl-margin-old-highlight-function 139 diff-hl-highlight-reference-function diff-hl-margin-old-highlight-ref-function 140 diff-hl-margin-old-highlight-function nil)) 141 (set width-var diff-hl-margin-old-width) 142 (kill-local-variable 'diff-hl-margin-old-width))) 143 (dolist (win (get-buffer-window-list)) 144 (set-window-buffer win (current-buffer)))) 145 146 (defun diff-hl-margin-local-mode-off () 147 (diff-hl-margin-local-mode -1)) 148 149 (defvar diff-hl-margin-spec-cache nil) 150 151 (defun diff-hl-margin-spec-cache () 152 (or diff-hl-margin-spec-cache 153 (setq diff-hl-margin-spec-cache 154 (diff-hl-margin-build-spec-cache)))) 155 156 (defun diff-hl-margin-build-spec-cache () 157 (nconc 158 (cl-loop for (type . char) in diff-hl-margin-symbols-alist 159 unless (eq type 'reference) 160 nconc 161 (cl-loop for side in '(left right) 162 collect 163 (cons 164 (cons type side) 165 (propertize 166 " " 'display 167 `((margin ,(intern (format "%s-margin" side))) 168 ,(propertize char 'face 169 (intern (format "diff-hl-margin-%s" type)))))))) 170 (cl-loop for char = (or (assoc-default 'reference diff-hl-margin-symbols-alist) 171 " ") 172 for type in '(insert delete change) 173 nconc 174 (cl-loop for side in '(left right) 175 collect 176 (cons 177 (list type side 'reference) 178 (propertize 179 " " 'display 180 `((margin ,(intern (format "%s-margin" side))) 181 ,(propertize char 'face 182 (intern (format "diff-hl-margin-reference-%s" type)))))))))) 183 184 (defun diff-hl-margin-ensure-visible () 185 (let ((width-var (intern (format "%s-margin-width" diff-hl-side)))) 186 (when (zerop (symbol-value width-var)) 187 (set width-var 1) 188 (dolist (win (get-buffer-window-list)) 189 (set-window-buffer win (current-buffer)))))) 190 191 (defun diff-hl-highlight-on-margin (ovl type _shape) 192 (diff-hl-margin-ensure-visible) 193 (let ((spec (cdr (assoc (cons type diff-hl-side) 194 (diff-hl-margin-spec-cache))))) 195 (overlay-put ovl 'before-string spec))) 196 197 (defun diff-hl-highlight-on-margin-flat (ovl type _shape) 198 (let ((spec (cdr (assoc (list type diff-hl-side 'reference) 199 (diff-hl-margin-spec-cache))))) 200 (overlay-put ovl 'before-string spec))) 201 202 (provide 'diff-hl-margin) 203 204 ;;; diff-hl-margin.el ends here