-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
goggles.el
193 lines (155 loc) · 6.22 KB
/
goggles.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
;;; goggles.el --- Pulse modified regions -*- lexical-binding: t -*-
;; Copyright (C) 2020-2024 Daniel Mendler
;; Author: Daniel Mendler <[email protected]>
;; Maintainer: Daniel Mendler <[email protected]>
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Created: 2020
;; Version: 0.4
;; Package-Requires: ((emacs "27.1"))
;; URL: https://github.com/minad/goggles
;; Keywords: convenience, text
;; This file is not part of GNU Emacs.
;; 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 <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Pulse modified regions
;;; Code:
(require 'pulse)
;;;; Faces
(defgroup goggles nil
"Pulse modified regions."
:link '(url-link :tag "Website" "https://github.com/minad/goggles")
:link '(emacs-library-link :tag "Library Source" "goggles.el")
:group 'editing
:prefix "goggles-")
(defface goggles-changed
'((((class color) (min-colors 88) (background dark))
:background "#1a0f44")
(((class color) (min-colors 88) (background light))
:background "#f2f0ff")
(t :background "blue"))
"Face for highlighting changed text.")
(defface goggles-removed
'((default :extend t)
(((class color) (min-colors 88) (background dark))
:background "#31101f")
(((class color) (min-colors 88) (background light))
:background "#ffedf2")
(t :background "red"))
"Face for highlighting removed text.")
(defface goggles-added
'((((class color) (min-colors 88) (background dark))
:background "#1f461a")
(((class color) (min-colors 88) (background light))
:background "#e0ffe0")
(t :background "green"))
"Face for highlighting added text.")
;;;; Customization
(defcustom goggles-pulse-iterations pulse-iterations
"Number of iterations in a pulse operation."
:type 'number)
(defcustom goggles-pulse-delay pulse-delay
"Delay between face lightening iterations."
:type 'number)
(defcustom goggles-pulse t
"Enable pulsing."
:type 'boolean)
;;;; Internal variables
(defvar-local goggles--active nil
"Goggles are active.")
(defvar-local goggles--changes nil
"List of changed regions (change log).
Each element is a pair of start/end markers.
In order to show the highlighting, the change log is used
to compute the overall start and end position.")
(defvar-local goggles--delta 0
"Total number of changed characters.
Positive if characters have been added.
Negative if characters have been deleted.
Zero if characters have been modified.")
;;;; Hooks for logging the changes and pulsing the changed region
(defun goggles--post-command ()
"Highlight change after command."
(when goggles--changes
(let ((start most-positive-fixnum)
(end 0)
(pulse-delay goggles-pulse-delay)
(pulse-iterations goggles-pulse-iterations)
(pulse-flag goggles-pulse))
(dolist (change goggles--changes)
(setq start (min start (car change))
end (max end (cdr change)))
(set-marker (car change) nil)
(set-marker (cdr change) nil))
(pulse-momentary-highlight-region
start end
(cond
((> goggles--delta 0) 'goggles-added)
((< goggles--delta 0) 'goggles-removed)
(t 'goggles-changed)))
(setq goggles--changes nil
goggles--delta 0))))
(defun goggles--after-change (start end len)
"Remember changed region between START and END.
The endpoints of the changed region are pushed to
the change log `goggles--changes'.
LEN is the length of the replaced string."
(when goggles--active
(setq goggles--delta (+ goggles--delta (- end start len)))
(when (and (/= len 0) (= start end))
(when (> start (buffer-size))
(setq start (- start 1)))
(setq end (1+ start)))
(push (cons (copy-marker start) (copy-marker end)) goggles--changes)))
(defun goggles--advice(&rest args)
"Advice around original function with ARGS."
(let ((goggles--active t))
(apply args)))
;;;; Define goggles
(defmacro goggles-define (name &rest funs)
"Define goggles with NAME which is activated by the functions FUNS.
For example (goggles-define kill `kill-region') defines
the goggles function `goggles-kill' which is only activated
by the `kill-region' operation.
The function `goggles-kill' takes an optional argument DISABLE.
If called without argument, the goggles are activated,
if called with the argument t, the goggles are deactivated.
This allows to individually define goggles based on operations
and activate/deactivate them separately."
(let ((name (intern (format "goggles-%s" name))))
`(progn
(defun ,name (&optional disable)
(interactive)
(if disable
,(macroexp-progn (mapcar (lambda (f) `(advice-remove #',f #'goggles--advice)) funs))
,@(mapcar (lambda (f) `(advice-add #',f :around #'goggles--advice)) funs))
nil)
(,name))))
;;;; Define some standard goggles
(goggles-define undo primitive-undo)
(goggles-define yank yank yank-pop)
(goggles-define kill kill-region)
(goggles-define delete delete-region)
(goggles-define transpose transpose-words transpose-chars transpose-lines)
;;;; Goggles mode which activates all the defined goggles
;;;###autoload
(define-minor-mode goggles-mode
"The goggles local minor mode pulses modified regions.
The defined goggles (see `goggles-define') can be enabled/disabled individually
in case you prefer to have goggles only for certain operations."
:lighter " Goggles"
(remove-hook 'post-command-hook #'goggles--post-command 'local)
(remove-hook 'after-change-functions #'goggles--after-change 'local)
(when goggles-mode
(add-hook 'post-command-hook #'goggles--post-command nil 'local)
(add-hook 'after-change-functions #'goggles--after-change nil 'local)))
(provide 'goggles)
;;; goggles.el ends here