• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

countvajhula / buffer-ring / 73

16 Sep 2025 01:26AM UTC coverage: 74.561%. Remained the same
73

push

github

countvajhula
address byte compile error on older versions of emacs

170 of 228 relevant lines covered (74.56%)

320.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.56
/buffer-ring.el
1
;;; buffer-ring.el --- Rings and tori for buffer navigation -*- lexical-binding: t -*-
2

3
;; Author: Mike Mattie <codermattie@gmail.com>
4
;;         Sid Kasivajhula <sid@countvajhula.com>
5
;; Maintainer: Sid Kasivajhula <sid@countvajhula.com>
6
;; URL: https://github.com/countvajhula/buffer-ring
7
;; Created: 2009-4-16
8
;; Version: 0.3.4
9
;; Package-Requires: ((emacs "25.1") (dynaring "0.3"))
10

11
;; This file is NOT a part of Gnu Emacs.
12

13
;; This work is "part of the world."  You are free to do whatever you
14
;; like with it and it isn't owned by anybody, not even the
15
;; creators.  Attribution would be appreciated and is a valuable
16
;; contribution in itself, but it is not strictly necessary nor
17
;; required.  If you'd like to learn more about this way of doing
18
;; things and how it could lead to a peaceful, efficient, and creative
19
;; world, and how you can help, visit https://drym.org.
20
;;
21
;; This paradigm transcends traditional legal and economic systems, but
22
;; for the purposes of any such systems within which you may need to
23
;; operate:
24
;;
25
;; This is free and unencumbered software released into the public domain.
26
;; The authors relinquish any copyright claims on this work.
27

28
;;; Commentary:
29

30
;; Rings of buffers and tori of buffer rings.
31

32
;;; Code:
33

34
(defconst buffer-ring-version "0.3.4")
35

36
(require 'seq)     ; for `seq-*' on older Emacs
37
(require 'subr-x)  ; for `string-join' on older Emacs
38
(require 'dynaring)
39

40
(defconst buffer-ring-default-ring-name "default")
41

42
;;
43
;; default keymap
44
;;
45

46
;;;###autoload
47
(define-minor-mode buffer-ring-mode
48
  "Minor mode to modulate keybindings in buffer-ring mode."
49
  :lighter " buffer-ring"
50
  :global t
51
  :group 'buffer-ring
52
  :keymap
53
  (let ((buffer-ring-map (make-sparse-keymap)))
54
    (define-key buffer-ring-map (kbd "C-c C-b l") #'buffer-ring-list-buffers)
55
    (define-key buffer-ring-map (kbd "C-c C-b r") #'buffer-ring-torus-list-rings)
56
    (define-key buffer-ring-map (kbd "C-c C-b w") #'buffer-ring-show-name)
57
    (define-key buffer-ring-map (kbd "C-c C-b a") #'buffer-ring-add)
58
    (define-key buffer-ring-map (kbd "C-c C-b d") #'buffer-ring-delete)
59
    (define-key buffer-ring-map (kbd "C-c C-b c") #'buffer-ring-drop-buffer)
60
    (define-key buffer-ring-map (kbd "C-c C-b f") #'buffer-ring-next-buffer)
61
    (define-key buffer-ring-map (kbd "C-c C-b b") #'buffer-ring-prev-buffer)
62
    (define-key buffer-ring-map (kbd "C-c C-b n") #'buffer-ring-torus-next-ring)
63
    (define-key buffer-ring-map (kbd "C-c C-b p") #'buffer-ring-torus-prev-ring)
64
    (define-key buffer-ring-map (kbd "C-c C-b e") #'buffer-ring-torus-delete-ring)
65

66
    buffer-ring-map)
67
  (if buffer-ring-mode
×
68
      (buffer-ring-initialize)
×
69
    (buffer-ring-disable)))
×
70

71
(defvar buffer-ring-torus (dynaring-make)
72
  "A global ring of all the buffer rings.  A torus I believe.")
73

74
(defun buffer-ring-initialize ()
75
  "Set up any hooks needed for buffer rings."
76
  (interactive)
77
  ;; TODO: if we want to automatically maintain a "primary"
78
  ;; ring, we may also need to hook into buffer-list-changed-hook
79
  ;; or maybe find-file-hook in addition here
80
  ;; TODO: should this be buffer-local? in that case it can
81
  ;; be added at the time that the buffer is adding to a ring
82
  (advice-add 'switch-to-buffer
9✔
83
              :after #'buffer-ring-synchronize-buffer)
9✔
84
  (advice-add 'pop-to-buffer
9✔
85
              :after #'buffer-ring-synchronize-buffer)
9✔
86
  (advice-add 'bury-buffer
9✔
87
              :before #'buffer-ring-bury-buffer)
9✔
88
  (advice-add 'quit-window
9✔
89
              :before #'buffer-ring--bury-window-buffer)
9✔
90
  ;; after we bury a buffer, we may arrive at some arbitrary buffer.
91
  ;; as this happens "out of band" wrt the buffer ring interfaces,
92
  ;; we need to explicitly synchronize this arrival buffer with its
93
  ;; buffer rings, just like in the case of a direct visit to that
94
  ;; buffer via switch-to-buffer.
95
  (advice-add 'bury-buffer
9✔
96
              :after #'buffer-ring-synchronize-buffer)
9✔
97
  (advice-add 'quit-window
9✔
98
              :after #'buffer-ring-synchronize-buffer))
9✔
99

100
(defun buffer-ring-disable ()
101
  "Remove hooks, etc."
102
  (interactive)
103
  (advice-remove 'switch-to-buffer #'buffer-ring-synchronize-buffer)
9✔
104
  (advice-remove 'pop-to-buffer #'buffer-ring-synchronize-buffer)
9✔
105
  (advice-remove 'bury-buffer #'buffer-ring-bury-buffer)
9✔
106
  (advice-remove 'quit-window #'buffer-ring--bury-window-buffer)
9✔
107
  (advice-remove 'bury-buffer #'buffer-ring-synchronize-buffer)
9✔
108
  (advice-remove 'quit-window #'buffer-ring-synchronize-buffer))
9✔
109

110
;;
111
;;  buffer ring structure
112
;;
113

114
(defun buffer-ring-make-ring (name)
115
  "Construct a buffer ring with the name NAME.
116

117
A buffer ring is simply a labeled dynamic ring data structure
118
whose members are expected to be buffers."
119
  (cons name (dynaring-make)))
466✔
120

121
(defun buffer-ring-ring-name (buffer-ring)
122
  "An accessor to get the name of a BUFFER-RING."
123
  (car buffer-ring))
5,603✔
124

125
(defun buffer-ring-ring-ring (buffer-ring)
126
  "... Hello?
127

128
An accessor for the dynamic ring component of the BUFFER-RING."
129
  (cdr buffer-ring))
3,772✔
130

131
;;
132
;; buffer rings registry
133
;;
134
;; TODO: use buffer local variables instead?
135
(defvar buffer-ring-rings
136
  (make-hash-table)
137
  "Buffer to rings hash.")
138

139
(defun buffer-ring-registry-get-key (buffer)
140
  "Key to use for BUFFER in the buffer registry."
141
  (buffer-name buffer))
2,870✔
142

143
(defun buffer-ring--parse-buffer (buffer)
144
  "Extract the buffer object indicated by BUFFER.
145

146
BUFFER could be either be the name of the buffer (a string)
147
or a buffer object, or nil.  In the last case, this evaluates to
148
the current buffer."
149
  (if buffer
1,910✔
150
      (if (bufferp buffer)
1,790✔
151
          buffer
1,789✔
152
        (get-buffer buffer))
1✔
153
    (current-buffer)))
120✔
154

155
(defun buffer-ring-get-rings (&optional buffer)
156
  "All rings that BUFFER is part of."
157
  (let* ((buffer (buffer-ring--parse-buffer buffer))
919✔
158
         (ring-names (gethash (buffer-ring-registry-get-key buffer)
919✔
159
                              buffer-ring-rings)))
919✔
160
    (seq-map #'buffer-ring-torus--find-ring ring-names)))
919✔
161

162
(defun buffer-ring-register-ring (buffer bfr-ring)
163
  "Register that BUFFER has been added to BFR-RING."
164
  (let ((key (buffer-ring-registry-get-key buffer))
1,206✔
165
        (ring-name (buffer-ring-ring-name bfr-ring)))
1,206✔
166
    (puthash key
1,206✔
167
             (delete-dups
1,206✔
168
              (cons ring-name
1,206✔
169
                    (gethash key
1,206✔
170
                             buffer-ring-rings)))
1,206✔
171
             buffer-ring-rings)))
1,206✔
172

173
(defun buffer-ring-registry-delete-ring (buffer bfr-ring)
174
  "Delete BFR-RING from the list of rings for BUFFER.
175

176
This does NOT delete the buffer from the ring, only the ring
177
identifier from the buffer.  It should only be called either
178
as part of doing the former or when deleting the ring entirely."
179
  (let ((key (buffer-ring-registry-get-key buffer))
422✔
180
        (ring-name (buffer-ring-ring-name bfr-ring)))
422✔
181
    (puthash key
422✔
182
             (remq ring-name
422✔
183
                   (gethash key
422✔
184
                            buffer-ring-rings))
422✔
185
             buffer-ring-rings)))
422✔
186

187
(defun buffer-ring-registry-drop-ring (bfr-ring)
188
  "Drop BFR-RING from the registry of rings.
189

190
This should only be called when deleting the ring entirely."
191
  (let ((buffers (dynaring-values (buffer-ring-ring-ring bfr-ring))))
2✔
192
    (dolist (buf buffers)
2✔
193
      (buffer-ring-registry-delete-ring buf bfr-ring))))
2✔
194

195
;;
196
;; buffer ring interface
197
;;
198

199
(defun buffer-ring-size (&optional bfr-ring)
200
  "Return the number of buffers in BFR-RING.
201

202
If no buffer ring is specified, this defaults to the current ring.  If
203
there is no active buffer ring, it returns -1 so that you can always
204
use a numeric operator."
205
  (let* ((bfr-ring (or bfr-ring (buffer-ring-current-ring)))
3✔
206
         (ring (buffer-ring-ring-ring bfr-ring)))
3✔
207
    (if ring
3✔
208
        (dynaring-size ring)
3✔
209
      -1)))
3✔
210

211
(defun buffer-ring--add (buffer bfr-ring)
212
  "Add BUFFER to BFR-RING."
213
  (let ((ring (buffer-ring-ring-ring bfr-ring)))
420✔
214
    (dynaring-insert ring buffer)
420✔
215
    (buffer-ring-register-ring buffer bfr-ring)
420✔
216
    (with-current-buffer buffer
420✔
217
      (add-hook 'kill-buffer-hook #'buffer-ring-drop-buffer t t))))
420✔
218

219
(defun buffer-ring-add (ring-name &optional buffer)
220
  "Add the BUFFER to the ring with name RING-NAME.
221

222
It will prompt for the ring to add the buffer to.  If no BUFFER
223
is provided it assumes the current buffer."
224
  (interactive
225
   (list
×
226
    (let ((default-name (or (buffer-ring-current-ring-name)
×
227
                            buffer-ring-default-ring-name)))
×
228
      (read-string (format "Add to which ring [%s]? " default-name)
×
229
                   nil
230
                   nil
231
                   default-name))))
×
232
  (let* ((bfr-ring (buffer-ring-torus-get-ring ring-name))
×
233
         (buffer (buffer-ring--parse-buffer buffer))
×
234
         (ring (buffer-ring-ring-ring bfr-ring))
×
235
         (ring-name (buffer-ring-ring-name bfr-ring)))
×
236
    (let ((result
×
237
           (cond ((dynaring-contains-p ring buffer)
×
238
                  (message "buffer %s is already in ring \"%s\"" (buffer-name buffer)
×
239
                           ring-name)
×
240
                  nil)
241
                 (t (buffer-ring--add buffer bfr-ring)
×
242
                    t))))
×
243
      ;; if we are attempting to add the _current_ buffer to
244
      ;; a ring, switch to the ring in any case, for consistency
245
      (when (eq (current-buffer) buffer)
×
246
        (buffer-ring-torus-switch-to-ring ring-name))
×
247
      result)))
×
248

249
(defun buffer-ring-delete (&optional buffer)
250
  "Delete BUFFER from the current ring.
251

252
If no buffer is specified, it assumes the current buffer.
253

254
This modifies the ring, it does not kill the buffer."
255
  (interactive)
256
  (let ((buffer (buffer-ring--parse-buffer buffer)))
422✔
257
    (if (buffer-ring-current-ring)
422✔
258
        (let ((ring (buffer-ring-ring-ring (buffer-ring-current-ring))))
421✔
259
          (if (dynaring-delete ring buffer)
421✔
260
              (progn
420✔
261
                (buffer-ring-registry-delete-ring buffer (buffer-ring-current-ring))
420✔
262
                (message "Deleted buffer %s from ring %s"
420✔
263
                         buffer
420✔
264
                         (buffer-ring-current-ring-name)))
420✔
265
            (message "This buffer is not in the current ring")
1✔
266
            nil))
421✔
267
      (message "No active buffer ring.")
1✔
268
      nil)))
422✔
269

270
(defun buffer-ring-drop-buffer ()
271
  "Drop buffer from all rings.
272

273
Not to be confused with the little-known evil cousin
274
to the koala buffer."
275
  (interactive)
276
  (let ((buffer (current-buffer)))
320✔
277
    (save-excursion
320✔
278
      (dolist (bfr-ring (buffer-ring-get-rings buffer))
320✔
279
        ;; TODO: this may muddle torus recency
280
        (buffer-ring-torus-switch-to-ring (buffer-ring-ring-name bfr-ring))
320✔
281
        (buffer-ring-delete buffer)))
320✔
282
    ;; remove the buffer from the buffer ring registry
283
    (remhash (buffer-ring-registry-get-key buffer) buffer-ring-rings)
320✔
284
    (remove-hook 'kill-buffer-hook #'buffer-ring-drop-buffer t)))
320✔
285

286
(defun buffer-ring-list-buffers ()
287
  "List the buffers in the current buffer ring."
288
  (interactive)
289
  (let* ((bfr-ring (buffer-ring-current-ring))
×
290
         (ring (buffer-ring-ring-ring bfr-ring)))
×
291
    (if bfr-ring
×
292
        (let ((result (dynaring-traverse-collect ring #'buffer-name)))
×
293
          (if result
×
294
              (message "buffers in [%s]: %s" (buffer-ring-ring-name bfr-ring) result)
×
295
            (message "Buffer ring is empty.")))
×
296
      (message "No active buffer ring."))) )
×
297

298
(defun buffer-ring--rotate (direction)
299
  "Rotate the buffer ring.
300

301
DIRECTION must be a function, either `dynaring-rotate-left` (to rotate
302
left) or `dynaring-rotate-right` (to rotate right)."
303
  (let ((bfr-ring (buffer-ring-current-ring)))
37✔
304
    (when bfr-ring
37✔
305
      (let ((ring (buffer-ring-ring-ring bfr-ring)))
35✔
306
        (unless (dynaring-empty-p ring)
35✔
307
          (when (= 1 (dynaring-size ring))
33✔
308
            (message "There is only one buffer in the ring."))
3✔
309
          (funcall direction ring)
33✔
310
          (buffer-ring-switch-to-buffer (dynaring-value ring)))))))
33✔
311

312
(defun buffer-ring-prev-buffer ()
313
  "Switch to the previous buffer in the buffer ring."
314
  (interactive)
315
  (buffer-ring--rotate #'dynaring-rotate-left))
12✔
316

317
(defun buffer-ring-next-buffer ()
318
  "Switch to the previous buffer in the buffer ring."
319
  (interactive)
320
  (buffer-ring--rotate #'dynaring-rotate-right))
25✔
321

322
(defun buffer-ring-rotate-to-buffer (buffer)
323
  "Rotate the buffer ring until BUFFER is at head.
324

325
This differs from simply switching to the buffer in that the latter
326
results in a change in ordering while the present action preserves the
327
current ordering of buffers in the ring."
328
  (interactive)
329
  (let ((buffer (buffer-ring--parse-buffer buffer))
3✔
330
        (ring (buffer-ring-ring-ring (buffer-ring-current-ring))))
3✔
331
    (dynaring-rotate-until ring
3✔
332
                           #'dynaring-rotate-left
3✔
333
                           (lambda (buf) (eq buf buffer)))
3✔
334
    (buffer-ring-switch-to-buffer (dynaring-value ring))))
3✔
335

336
(defun buffer-ring--bury-window-buffer (&optional _kill window)
337
  "An advice function to move a buffer to the back of the ring.
338

339
This simply adapts the `buffer-ring-bury-buffer` advice function to
340
the `quit-window` interface, so that quiting a WINDOW buries the
341
associated buffer."
342
  (let ((buffer (window-buffer (window-normalize-window window))))
×
343
    (buffer-ring-bury-buffer buffer)))
×
344

345
(defun buffer-ring-bury-buffer (&optional buffer)
346
  "An advice function to move a buffer to the back of the ring.
347

348
When BUFFER is buried (e.g. via `q` on a popup window), this ensures
349
that all rings containing it move it to the \"back\" of the ring, in
350
terms of recency."
351
  (let* ((buffer (buffer-ring--parse-buffer buffer))
×
352
         (bfr-rings (buffer-ring-get-rings buffer)))
×
353
    ;; if it isn't part of any rings, we don't need
354
    ;; to do anything
355
    (when bfr-rings
×
356
      (dolist (bring bfr-rings)
×
357
        (let ((ring (buffer-ring-ring-ring bring)))
×
358
          (dynaring-break-insert ring buffer)
×
359
          (dynaring-rotate-right ring))))))
×
360

361
(defun buffer-ring-synchronize-buffer (&rest _args)
362
  "Keep buffer rings updated when buffers are visited.
363

364
When a buffer is visited directly without rotating to it, this advice
365
function switches to the most recent ring (if any) containing the buffer,
366
promoting both the ring as well as the buffer to the head position in
367
their containing rings, while accounting for recency.
368

369
_ARGS are the arguments that the advised function was invoked with."
370
  (let ((buffer (current-buffer))
56✔
371
        (ring (buffer-ring-ring-ring (buffer-ring-current-ring))))
56✔
372
    ;; if it's already at the head of the current ring,
373
    ;; we probably arrived here via a buffer-ring interface
374
    ;; and don't need to do anything in that case
375
    (unless (eq buffer (dynaring-value ring))
56✔
376
      (let ((bfr-rings (buffer-ring-get-rings buffer)))
13✔
377
        ;; if it isn't part of any rings, we don't need
378
        ;; to do anything
379
        (when bfr-rings
13✔
380
          (let ((most-recent-ring (car bfr-rings)))
12✔
381
            ;; switch to the most recent ring containing the buffer
382
            (buffer-ring-torus-switch-to-ring
12✔
383
             (buffer-ring-ring-name most-recent-ring))))))))
12✔
384

385
(defun buffer-ring-surface-ring (&optional bfr-ring)
386
  "Make BFR-RING the most recent ring in all member buffers.
387

388
We'd want to do this each time the ring becomes current, so that
389
ring recency is consistent across the board."
390
  (let ((bfr-ring (or bfr-ring (buffer-ring-current-ring))))
×
391
    (dolist (buffer (dynaring-values (buffer-ring-ring-ring bfr-ring)))
517✔
392
      (buffer-ring-register-ring buffer bfr-ring))))
517✔
393

394
(defun buffer-ring-surface-buffer (&optional buffer)
395
  "Ensure the buffer is at head position in all rings of which it is a member.
396

397
We'd want to do this each time the BUFFER becomes current, so that
398
buffer recency is consistent across the board."
399
  (let ((buffer (buffer-ring--parse-buffer buffer))
549✔
400
        (bfr-rings (buffer-ring-get-rings buffer)))
549✔
401
    ;; if the buffer isn't on any rings, this is a no-op
402
    (dolist (bring bfr-rings)
549✔
403
      ;; re(break)insert the buffer
404
      ;; in all of its associated rings
405
      ;; note that if the buffer is already at the head,
406
      ;; this will have no effect on the structure of the ring
407
      (dynaring-break-insert (buffer-ring-ring-ring bring)
549✔
408
                             buffer))))
549✔
409

410
;;
411
;; buffer torus interface
412
;;
413

414
(defun buffer-ring-torus--create-ring (name)
415
  "Create ring with name NAME."
416
  (let ((bfr-ring (buffer-ring-make-ring name)))
463✔
417
    (dynaring-insert buffer-ring-torus bfr-ring)
463✔
418
    bfr-ring))
463✔
419

420
(defun buffer-ring-torus--find-ring (name)
421
  "Find a ring with name NAME."
422
  (let ((segment (dynaring-find-forwards buffer-ring-torus
1,691✔
423
                                         (lambda (r)
424
                                           (string= name
2,309✔
425
                                                    (buffer-ring-ring-name r))))))
1,691✔
426
    (when segment
1,691✔
427
      (dynaring-segment-value segment))))
1,228✔
428

429
(defun buffer-ring-torus-get-ring (name)
430
  "Find or create a buffer ring with name NAME.
431

432
The buffer-ring is returned."
433
  (let ((found-ring (buffer-ring-torus--find-ring name)))
483✔
434
    (if found-ring
483✔
435
        (progn
20✔
436
          (message "Found existing ring: %s" name)
20✔
437
          found-ring)
20✔
438
      (message "Creating a new ring \"%s\"" name)
463✔
439
      (buffer-ring-torus--create-ring name))))
463✔
440

441
(defun buffer-ring-torus-switch-to-ring (name)
442
  "Switch to ring NAME.
443

444
Inserts the ring at the head of the torus and \"surfaces\" it
445
in all of its member buffers so it reflects as the most recent.
446
This doesn't perform any tangible actions in connection with
447
the change of ring (that should be done alongside)."
448
  (interactive "sSwitch to ring ? ")
449
  (let ((segment (dynaring-find-forwards buffer-ring-torus
478✔
450
                                         (lambda (r)
451
                                           (string= name
759✔
452
                                                    (buffer-ring-ring-name r))))))
478✔
453
    (when segment
478✔
454
      (let ((bfr-ring (dynaring-segment-value segment)))
477✔
455
        ;; insert the ring at the head of the torus
456
        (dynaring-break-insert buffer-ring-torus
477✔
457
                               bfr-ring)
477✔
458
        ;; take accompanying actions, e.g. switch to the head
459
        ;; buffer and surface the ring in all buffers
460
        (buffer-ring-synchronize-ring bfr-ring)
477✔
461
        bfr-ring))))
477✔
462

463
(defun buffer-ring-current-ring ()
464
  "Get the current (active) buffer ring."
465
  (dynaring-value buffer-ring-torus))
1,893✔
466

467
(defun buffer-ring-current-ring-name ()
468
  "Get the name of the current buffer ring."
469
  (buffer-ring-ring-name (buffer-ring-current-ring)))
424✔
470

471
(defun buffer-ring-show-name ()
472
  "Display name of current ring."
473
  (interactive)
474
  (message (buffer-ring-current-ring-name)))
×
475

476
(defun buffer-ring-current-buffer (&optional bfr-ring)
477
  "Current buffer in BFR-RING."
478
  (let ((bfr-ring (or bfr-ring (buffer-ring-current-ring))))
3✔
479
    (dynaring-value (buffer-ring-ring-ring bfr-ring))))
530✔
480

481
(defun buffer-ring-switch-to-buffer (buffer)
482
  "Switch to BUFFER while keeping rings consistent."
483
  (switch-to-buffer buffer)
488✔
484
  (buffer-ring-surface-buffer buffer)
488✔
485
  buffer)
488✔
486

487
(defun buffer-ring-synchronize-ring (bfr-ring)
488
  "Perform any actions in connection with switching to a new ring.
489

490
At the moment, this switches to the head buffer in BFR-RING
491
\(the new ring), and surfaces that ring in all of its member
492
buffers."
493
  ;; Switch to the head buffer in the new ring.
494
  (let ((head-buffer (buffer-ring-current-buffer bfr-ring)))
514✔
495
    (when head-buffer
514✔
496
      ;; If the new ring is empty, don't switch buffer.
497
      ;; Note that if the original buffer is in the new ring,
498
      ;; it would typically already be at the head due to buffer ↔ ring
499
      ;; synchrony and this would have no effect. But in the case
500
      ;; where (a) we visit a buffer "out of band," i.e. not via a
501
      ;; buffer ring interface, or (b) when the buffer ring is first
502
      ;; created, the rings may not already be synchronized with the
503
      ;; actual buffer state, i.e. the current buffer may not be at
504
      ;; head position in its rings. In such cases, we reorient around
505
      ;; the actual current buffer rather than switch to the ring's
506
      ;; current head buffer.
507
      (if (and (dynaring-contains-p (buffer-ring-ring-ring bfr-ring)
512✔
508
                                    (current-buffer))
512✔
509
               (not (eq (current-buffer) head-buffer)))
449✔
510
          (buffer-ring-surface-buffer)
60✔
511
        (buffer-ring-switch-to-buffer head-buffer))))
452✔
512
  ;; surface the ring in all of its member buffers
513
  ;; so it reflects as most recent
514
  (buffer-ring-surface-ring bfr-ring))
514✔
515

516
(defun buffer-ring-torus--rotate (direction)
517
  "Rotate the buffer ring torus.
518

519
DIRECTION must be a function, either `dynaring-rotate-left` (to rotate
520
left) or `dynaring-rotate-right` (to rotate right)."
521
  (let ((initial-bfr-ring (buffer-ring-current-ring)))
39✔
522
    (cond ((dynaring-empty-p buffer-ring-torus)
39✔
523
           (message "There are no rings in the buffer torus.")
2✔
524
           nil)
525
          ((= 1 (dynaring-size buffer-ring-torus))
37✔
526
           (message "There is only one buffer ring.")
6✔
527
           (unless (dynaring-empty-p (buffer-ring-ring-ring initial-bfr-ring))
6✔
528
             (buffer-ring-synchronize-ring initial-bfr-ring))
4✔
529
           t)
530
          (t
531
           ;; rotate past any empties
532
           (if (dynaring-rotate-until buffer-ring-torus
31✔
533
                                      direction
31✔
534
                                      (lambda (bfr-ring)
535
                                        ;; we want to rotate at least once
536
                                        (and (not (eq initial-bfr-ring
68✔
537
                                                      bfr-ring))
68✔
538
                                             (not (dynaring-empty-p
37✔
539
                                                   (buffer-ring-ring-ring bfr-ring))))))
31✔
540
               (let ((bfr-ring (buffer-ring-current-ring)))
29✔
541
                 (message "switching to ring %s" (buffer-ring-ring-name bfr-ring))
29✔
542
                 (buffer-ring-synchronize-ring bfr-ring)
29✔
543
                 t)
29✔
544
             (message "All of the buffer rings are empty. Keeping the current ring position")
2✔
545
             nil)))))
31✔
546

547
(defun buffer-ring-torus-next-ring ()
548
  "Switch to the previous buffer in the buffer ring."
549
  (interactive)
550
  (buffer-ring-torus--rotate 'dynaring-rotate-right))
21✔
551

552
(defun buffer-ring-torus-prev-ring ()
553
  "Switch to the previous buffer in the buffer ring."
554
  (interactive)
555
  (buffer-ring-torus--rotate 'dynaring-rotate-left))
18✔
556

557
(defun buffer-ring-torus-list-rings ()
558
  "List the buffer rings in the buffer torus."
559
  (interactive)
560
  (let ((rings (dynaring-traverse-collect buffer-ring-torus
×
561
                                          #'buffer-ring-ring-name)))
×
562
    (if rings
×
563
        (message "Buffer rings: %s" (string-join rings ", "))
×
564
      (message "No buffer rings."))))
×
565

566
(defun buffer-ring-torus-delete-ring (&optional ring-name)
567
  "Delete the buffer ring with name RING-NAME.
568

569
If no name is specified, this deletes the current ring."
570
  (interactive
571
   (list
×
572
    (read-string (format "Delete which ring [default: %s]? "
×
573
                         (buffer-ring-current-ring-name))
×
574
                 nil
575
                 nil
576
                 (buffer-ring-current-ring-name))))
×
577
  (let* ((ring-name (or ring-name (buffer-ring-current-ring-name)))
×
578
         (bfr-ring (buffer-ring-torus--find-ring ring-name)))
×
579
    (message "ring name is %s" ring-name)
×
580
    (if bfr-ring
×
581
        (progn (buffer-ring-registry-drop-ring bfr-ring)
×
582
               (dynaring-delete buffer-ring-torus bfr-ring)
×
583
               (message "Ring %s deleted." ring-name))
×
584
      (dynaring-destroy (buffer-ring-ring-ring bfr-ring))
×
585
      (message "No such ring."))))
×
586

587
(provide 'buffer-ring)
588
;;; buffer-ring.el ends here
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc