diff options
| author | F. Jason Park | 2022-12-09 22:00:59 -0800 |
|---|---|---|
| committer | F. Jason Park | 2023-04-08 14:23:51 -0700 |
| commit | e7992d2adbc50ba8a3b0fb18b9afe22a2a539b1d (patch) | |
| tree | e61cf2ee00dd7d568326a366e81ac1f0a00ea903 | |
| parent | ba7fe88b782ad516b4cbb5e99fb108f57a9235e2 (diff) | |
| download | emacs-e7992d2adbc50ba8a3b0fb18b9afe22a2a539b1d.tar.gz emacs-e7992d2adbc50ba8a3b0fb18b9afe22a2a539b1d.zip | |
Add option to show visual erc-keep-place indicator
* lisp/erc/erc-goodies.el (erc-keep-place-indicator-style,
erc-keep-place-indicator-buffer-type,
erc-keep-place-indicator-follow): New options for anchoring kept place
visually.
(erc-keep-place-indicator-line, erc-keep-place-indicator-arrow): New
faces.
(erc--keep-place-indicator-overlay): New internal variable.
(erc--keep-place-indicator-on-window-configuration-change): New
function to subscribe to `window-configuration-change-hook' and maybe
update kept-place indicator.
(erc--keep-place-indicator-setup): New function to initialize buffer
for local module `keep-place-indicator'.
(erc-keep-place-indicator-mode, erc-keep-place-indicator-enable,
erc-keep-place-indicator-disable): New local ERC module. Depends on
"parent" module `keep-place'. Like `fill-wrap', this is (for now)
also deliberately left out of the widget menu for `erc-modules'.
(erc-keep-place-move, erc-keep-place-goto): Add new commands for
manually updating and jumping to keep-place indicator.
(erc-keep-place): Move `erc--keep-place-overlay' when applicable.
* test/lisp/erc/erc-goodies-tests.el (erc-keep-place-indicator-mode):
Add test.
(Bug#59943.)
| -rw-r--r-- | lisp/erc/erc-goodies.el | 157 | ||||
| -rw-r--r-- | test/lisp/erc/erc-goodies-tests.el | 81 |
2 files changed, 238 insertions, 0 deletions
diff --git a/lisp/erc/erc-goodies.el b/lisp/erc/erc-goodies.el index 7ea6c42ec65..6235de5f1c0 100644 --- a/lisp/erc/erc-goodies.el +++ b/lisp/erc/erc-goodies.el | |||
| @@ -32,6 +32,10 @@ | |||
| 32 | (eval-when-compile (require 'cl-lib)) | 32 | (eval-when-compile (require 'cl-lib)) |
| 33 | (require 'erc) | 33 | (require 'erc) |
| 34 | 34 | ||
| 35 | (declare-function fringe-columns "fringe" (side &optional real)) | ||
| 36 | (declare-function pulse-available-p "pulse" nil) | ||
| 37 | (declare-function pulse-momentary-highlight-overlay "pulse" (o &optional face)) | ||
| 38 | |||
| 35 | 39 | ||
| 36 | ;;; Automatically scroll to bottom | 40 | ;;; Automatically scroll to bottom |
| 37 | (defcustom erc-input-line-position nil | 41 | (defcustom erc-input-line-position nil |
| @@ -143,6 +147,154 @@ Put this function on `erc-insert-post-hook' and/or `erc-send-post-hook'." | |||
| 143 | ((add-hook 'erc-insert-pre-hook #'erc-keep-place)) | 147 | ((add-hook 'erc-insert-pre-hook #'erc-keep-place)) |
| 144 | ((remove-hook 'erc-insert-pre-hook #'erc-keep-place))) | 148 | ((remove-hook 'erc-insert-pre-hook #'erc-keep-place))) |
| 145 | 149 | ||
| 150 | (defcustom erc-keep-place-indicator-style t | ||
| 151 | "Flavor of visual indicator applied to kept place. | ||
| 152 | For use with the `keep-place-indicator' module. A value of `arrow' | ||
| 153 | displays an arrow in the left fringe or margin. When it's | ||
| 154 | `face', ERC adds the face `erc-keep-place-indicator-line' to the | ||
| 155 | appropriate line. A value of t does both." | ||
| 156 | :group 'erc | ||
| 157 | :package-version '(ERC . "5.6") | ||
| 158 | :type '(choice (const t) (const server) (const target))) | ||
| 159 | |||
| 160 | (defcustom erc-keep-place-indicator-buffer-type t | ||
| 161 | "ERC buffer type in which to display `keep-place-indicator'. | ||
| 162 | A value of t means \"all\" ERC buffers." | ||
| 163 | :group 'erc | ||
| 164 | :package-version '(ERC . "5.6") | ||
| 165 | :type '(choice (const t) (const server) (const target))) | ||
| 166 | |||
| 167 | (defcustom erc-keep-place-indicator-follow nil | ||
| 168 | "Whether to sync visual kept place to window's top when reading. | ||
| 169 | For use with `erc-keep-place-indicator-mode'." | ||
| 170 | :group 'erc | ||
| 171 | :package-version '(ERC . "5.6") | ||
| 172 | :type 'boolean) | ||
| 173 | |||
| 174 | (defface erc-keep-place-indicator-line | ||
| 175 | '((((class color) (min-colors 88) (background light) | ||
| 176 | (supports :underline (:style wave))) | ||
| 177 | (:underline (:color "PaleGreen3" :style wave))) | ||
| 178 | (((class color) (min-colors 88) (background dark) | ||
| 179 | (supports :underline (:style wave))) | ||
| 180 | (:underline (:color "PaleGreen1" :style wave))) | ||
| 181 | (t :underline t)) | ||
| 182 | "Face for option `erc-keep-place-indicator-style'." | ||
| 183 | :group 'erc-faces) | ||
| 184 | |||
| 185 | (defface erc-keep-place-indicator-arrow | ||
| 186 | '((((class color) (min-colors 88) (background light)) | ||
| 187 | (:foreground "PaleGreen3")) | ||
| 188 | (((class color) (min-colors 88) (background dark)) | ||
| 189 | (:foreground "PaleGreen1")) | ||
| 190 | (t :inherit fringe)) | ||
| 191 | "Face for arrow value of option `erc-keep-place-indicator-style'." | ||
| 192 | :group 'erc-faces) | ||
| 193 | |||
| 194 | (defvar-local erc--keep-place-indicator-overlay nil | ||
| 195 | "Overlay for `erc-keep-place-indicator-mode'.") | ||
| 196 | |||
| 197 | (defun erc--keep-place-indicator-on-window-configuration-change () | ||
| 198 | "Maybe sync `erc--keep-place-indicator-overlay'. | ||
| 199 | Specifically, do so unless switching to or from another window in | ||
| 200 | the active frame." | ||
| 201 | (when erc-keep-place-indicator-follow | ||
| 202 | (unless (or (minibuffer-window-active-p (minibuffer-window)) | ||
| 203 | (eq (window-old-buffer) (current-buffer))) | ||
| 204 | (when (< (overlay-end erc--keep-place-indicator-overlay) | ||
| 205 | (window-start) | ||
| 206 | erc-insert-marker) | ||
| 207 | (erc-keep-place-move (window-start)))))) | ||
| 208 | |||
| 209 | (defun erc--keep-place-indicator-setup () | ||
| 210 | "Initialize buffer for maintaining `erc--keep-place-indicator-overlay'." | ||
| 211 | (require 'fringe) | ||
| 212 | (setq erc--keep-place-indicator-overlay | ||
| 213 | (if-let* ((vars (or erc--server-reconnecting erc--target-priors)) | ||
| 214 | ((alist-get 'erc-keep-place-indicator-mode vars))) | ||
| 215 | (alist-get 'erc--keep-place-indicator-overlay vars) | ||
| 216 | (make-overlay 0 0))) | ||
| 217 | (add-hook 'window-configuration-change-hook | ||
| 218 | #'erc--keep-place-indicator-on-window-configuration-change nil t) | ||
| 219 | (when-let* (((memq erc-keep-place-indicator-style '(t arrow))) | ||
| 220 | (display (if (zerop (fringe-columns 'left)) | ||
| 221 | `((margin left-margin) ,overlay-arrow-string) | ||
| 222 | '(left-fringe right-triangle | ||
| 223 | erc-keep-place-indicator-arrow))) | ||
| 224 | (bef (propertize " " 'display display))) | ||
| 225 | (overlay-put erc--keep-place-indicator-overlay 'before-string bef)) | ||
| 226 | (when (memq erc-keep-place-indicator-style '(t face)) | ||
| 227 | (overlay-put erc--keep-place-indicator-overlay 'face | ||
| 228 | 'erc-keep-place-indicator-line))) | ||
| 229 | |||
| 230 | ;;;###autoload(put 'keep-place-indicator 'erc--feature 'erc-goodies) | ||
| 231 | (define-erc-module keep-place-indicator nil | ||
| 232 | "`keep-place' with a fringe arrow and/or highlighted face." | ||
| 233 | ((unless erc-keep-place-mode | ||
| 234 | (unless (memq 'keep-place erc-modules) | ||
| 235 | ;; FIXME use `erc-button--display-error-notice-with-keys' | ||
| 236 | ;; to display this message when bug#60933 is ready. | ||
| 237 | (erc-display-error-notice | ||
| 238 | nil (concat | ||
| 239 | "Local module `keep-place-indicator' needs module `keep-place'." | ||
| 240 | " Enabling now. This will affect \C-]all\C-] ERC sessions." | ||
| 241 | " Add `keep-place' to `erc-modules' to silence this message."))) | ||
| 242 | (erc-keep-place-mode +1)) | ||
| 243 | (if (pcase erc-keep-place-indicator-buffer-type | ||
| 244 | ('target erc--target) | ||
| 245 | ('server (not erc--target)) | ||
| 246 | ('t t)) | ||
| 247 | (erc--keep-place-indicator-setup) | ||
| 248 | (setq erc-keep-place-indicator-mode nil))) | ||
| 249 | ((when erc--keep-place-indicator-overlay | ||
| 250 | (delete-overlay erc--keep-place-indicator-overlay) | ||
| 251 | (remove-hook 'window-configuration-change-hook | ||
| 252 | #'erc--keep-place-indicator-on-window-configuration-change t) | ||
| 253 | (kill-local-variable 'erc--keep-place-indicator-overlay))) | ||
| 254 | 'local) | ||
| 255 | |||
| 256 | (defun erc-keep-place-move (pos) | ||
| 257 | "Move keep-place indicator to current line or POS. | ||
| 258 | For use with `keep-place-indicator' module. When called | ||
| 259 | interactively, interpret POS as an offset. Specifically, when | ||
| 260 | POS is a raw prefix arg, like (4), move the indicator to the | ||
| 261 | window's last line. When it's the minus sign, put it on the | ||
| 262 | window's first line. Interpret an integer as an offset in lines." | ||
| 263 | (interactive | ||
| 264 | (progn | ||
| 265 | (unless erc-keep-place-indicator-mode | ||
| 266 | (user-error "`erc-keep-place-indicator-mode' not enabled")) | ||
| 267 | (list (pcase current-prefix-arg | ||
| 268 | ((and (pred integerp) v) | ||
| 269 | (save-excursion | ||
| 270 | (let ((inhibit-field-text-motion t)) | ||
| 271 | (forward-line v) | ||
| 272 | (point)))) | ||
| 273 | (`(,_) (1- (min erc-insert-marker (window-end)))) | ||
| 274 | ('- (min (1- erc-insert-marker) (window-start))))))) | ||
| 275 | (save-excursion | ||
| 276 | (let ((inhibit-field-text-motion t)) | ||
| 277 | (when pos | ||
| 278 | (goto-char pos)) | ||
| 279 | (move-overlay erc--keep-place-indicator-overlay | ||
| 280 | (line-beginning-position) | ||
| 281 | (line-end-position))))) | ||
| 282 | |||
| 283 | (defun erc-keep-place-goto () | ||
| 284 | "Jump to keep-place indicator. | ||
| 285 | For use with `keep-place-indicator' module." | ||
| 286 | (interactive | ||
| 287 | (prog1 nil | ||
| 288 | (unless erc-keep-place-indicator-mode | ||
| 289 | (user-error "`erc-keep-place-indicator-mode' not enabled")) | ||
| 290 | (deactivate-mark) | ||
| 291 | (push-mark))) | ||
| 292 | (goto-char (overlay-start erc--keep-place-indicator-overlay)) | ||
| 293 | (recenter (truncate (* (window-height) 0.25)) t) | ||
| 294 | (require 'pulse) | ||
| 295 | (when (pulse-available-p) | ||
| 296 | (pulse-momentary-highlight-overlay erc--keep-place-indicator-overlay))) | ||
| 297 | |||
| 146 | (defun erc-keep-place (_ignored) | 298 | (defun erc-keep-place (_ignored) |
| 147 | "Move point away from the last line in a non-selected ERC buffer." | 299 | "Move point away from the last line in a non-selected ERC buffer." |
| 148 | (when (and (not (eq (window-buffer (selected-window)) | 300 | (when (and (not (eq (window-buffer (selected-window)) |
| @@ -151,6 +303,11 @@ Put this function on `erc-insert-post-hook' and/or `erc-send-post-hook'." | |||
| 151 | (deactivate-mark) | 303 | (deactivate-mark) |
| 152 | (goto-char (erc-beg-of-input-line)) | 304 | (goto-char (erc-beg-of-input-line)) |
| 153 | (forward-line -1) | 305 | (forward-line -1) |
| 306 | (when erc-keep-place-indicator-mode | ||
| 307 | (unless (or (minibuffer-window-active-p (selected-window)) | ||
| 308 | (and (frame-visible-p (selected-frame)) | ||
| 309 | (get-buffer-window (current-buffer) (selected-frame)))) | ||
| 310 | (erc-keep-place-move nil))) | ||
| 154 | ;; if `switch-to-buffer-preserve-window-point' is set, | 311 | ;; if `switch-to-buffer-preserve-window-point' is set, |
| 155 | ;; we cannot rely on point being saved, and must commit | 312 | ;; we cannot rely on point being saved, and must commit |
| 156 | ;; it to window-prev-buffers. | 313 | ;; it to window-prev-buffers. |
diff --git a/test/lisp/erc/erc-goodies-tests.el b/test/lisp/erc/erc-goodies-tests.el index 46fcf82401b..a1f53c5bf88 100644 --- a/test/lisp/erc/erc-goodies-tests.el +++ b/test/lisp/erc/erc-goodies-tests.el | |||
| @@ -250,4 +250,85 @@ | |||
| 250 | (when noninteractive | 250 | (when noninteractive |
| 251 | (kill-buffer))))) | 251 | (kill-buffer))))) |
| 252 | 252 | ||
| 253 | |||
| 254 | ;; Among other things, this test also asserts that a local module's | ||
| 255 | ;; minor-mode toggle is allowed to disable its mode variable as | ||
| 256 | ;; needed. | ||
| 257 | |||
| 258 | (ert-deftest erc-keep-place-indicator-mode () | ||
| 259 | ;; FIXME remove after adding | ||
| 260 | (unless (fboundp 'erc--initialize-markers) | ||
| 261 | (ert-skip "Missing required function")) | ||
| 262 | (with-current-buffer (get-buffer-create "*erc-keep-place-indicator-mode*") | ||
| 263 | (erc-mode) | ||
| 264 | (erc--initialize-markers (point) nil) | ||
| 265 | (let ((assert-off | ||
| 266 | (lambda () | ||
| 267 | (should-not erc-keep-place-indicator-mode) | ||
| 268 | (should-not (local-variable-p 'window-configuration-change-hook)) | ||
| 269 | (should-not erc--keep-place-indicator-overlay))) | ||
| 270 | (assert-on | ||
| 271 | (lambda () | ||
| 272 | (should erc--keep-place-indicator-overlay) | ||
| 273 | (should (local-variable-p 'window-configuration-change-hook)) | ||
| 274 | (should window-configuration-change-hook) | ||
| 275 | (should erc-keep-place-mode))) | ||
| 276 | ;; | ||
| 277 | erc-insert-pre-hook | ||
| 278 | erc-modules) | ||
| 279 | |||
| 280 | (funcall assert-off) | ||
| 281 | |||
| 282 | (ert-info ("Value t") | ||
| 283 | (should (eq erc-keep-place-indicator-buffer-type t)) | ||
| 284 | (erc-keep-place-indicator-mode +1) | ||
| 285 | (funcall assert-on) | ||
| 286 | (goto-char (point-min)) | ||
| 287 | (should (search-forward "Enabling" nil t)) | ||
| 288 | (should (memq 'keep-place erc-modules))) | ||
| 289 | |||
| 290 | (erc-keep-place-indicator-mode -1) | ||
| 291 | (funcall assert-off) | ||
| 292 | |||
| 293 | (ert-info ("Value `target'") | ||
| 294 | (let ((erc-keep-place-indicator-buffer-type 'target)) | ||
| 295 | (erc-keep-place-indicator-mode +1) | ||
| 296 | (funcall assert-off) | ||
| 297 | (setq erc--target (erc--target-from-string "#chan")) | ||
| 298 | (erc-keep-place-indicator-mode +1) | ||
| 299 | (funcall assert-on))) | ||
| 300 | |||
| 301 | (erc-keep-place-indicator-mode -1) | ||
| 302 | (funcall assert-off) | ||
| 303 | |||
| 304 | (ert-info ("Value `server'") | ||
| 305 | (let ((erc-keep-place-indicator-buffer-type 'server)) | ||
| 306 | (erc-keep-place-indicator-mode +1) | ||
| 307 | (funcall assert-off) | ||
| 308 | (setq erc--target nil) | ||
| 309 | (erc-keep-place-indicator-mode +1) | ||
| 310 | (funcall assert-on))) | ||
| 311 | |||
| 312 | ;; Populate buffer | ||
| 313 | (erc-display-message nil 'notice (current-buffer) | ||
| 314 | "This buffer is for text that is not saved") | ||
| 315 | (erc-display-message nil 'notice (current-buffer) | ||
| 316 | "and for lisp evaluation") | ||
| 317 | (should (search-forward "saved" nil t)) | ||
| 318 | (erc-keep-place-move nil) | ||
| 319 | (goto-char erc-input-marker) | ||
| 320 | |||
| 321 | (ert-info ("Indicator survives reconnect") | ||
| 322 | (let ((erc--server-reconnecting (buffer-local-variables))) | ||
| 323 | (cl-letf (((symbol-function 'erc-server-connect) #'ignore)) | ||
| 324 | (erc-open "localhost" 6667 "tester" "Tester" 'connect | ||
| 325 | nil nil nil nil nil "tester" nil))) | ||
| 326 | (funcall assert-on) | ||
| 327 | (should (= (point) erc-input-marker)) | ||
| 328 | (goto-char (overlay-start erc--keep-place-indicator-overlay)) | ||
| 329 | (should (looking-at (rx "*** This buffer is for text"))))) | ||
| 330 | |||
| 331 | (when noninteractive | ||
| 332 | (kill-buffer)))) | ||
| 333 | |||
| 253 | ;;; erc-goodies-tests.el ends here | 334 | ;;; erc-goodies-tests.el ends here |