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

countvajhula / buffer-ring / 78

09 Oct 2025 05:55AM UTC coverage: 73.661% (-0.9%) from 74.561%
78

push

github

countvajhula
ci: use new approach of defining recipes

165 of 224 relevant lines covered (73.66%)

283.55 hits per line

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

73.66
/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.4.1
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.4.1")
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)))
456✔
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,500✔
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,706✔
130

131
(defvar-local buffer-ring-rings
132
  nil
133
  "A ring attached to each buffer containing buffer rings that it is part of.
134

135
The values are the *names* of rings rather than the rings themselves.
136
It is initialized to nil as, if it were bound to a ring here, that
137
bound ring would be the same value separately bound in every buffer.
138
As it is mutable, the same ring would thus be mutated in every buffer,
139
defeating the purpose of having it be buffer local.
140

141
Each buffer will initialize this variable to a fresh dynaring when it
142
is added to a ring.")
143

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

147
BUFFER could be either be the name of the buffer (a string)
148
or a buffer object, or nil.  In the last case, this evaluates to
149
the current buffer."
150
  (if buffer
1,876✔
151
      (if (bufferp buffer)
1,760✔
152
          buffer
1,759✔
153
        (get-buffer buffer))
1✔
154
    (current-buffer)))
116✔
155

156
(defun buffer-ring-get-rings (&optional buffer)
157
  "All rings that BUFFER is part of."
158
  (with-current-buffer (buffer-ring--parse-buffer buffer)
903✔
159
    (unless buffer-ring-rings
903✔
160
      (setq buffer-ring-rings (dynaring-make)))
1✔
161
    (seq-map #'buffer-ring-torus--find-ring
903✔
162
             (seq-reverse (dynaring-values buffer-ring-rings)))))
903✔
163

164
(defun buffer-ring-rings-add-ring (buffer bfr-ring)
165
  "Add BFR-RING to BUFFER's ring of buffer rings.
166

167
If BFR-RING is already present, it is promoted to the head of the ring."
168
  (with-current-buffer buffer
1,182✔
169
    (unless buffer-ring-rings
1,182✔
170
      (setq buffer-ring-rings (dynaring-make)))
313✔
171
    (dynaring-break-insert buffer-ring-rings
1,182✔
172
                           (buffer-ring-ring-name bfr-ring))))
1,182✔
173

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

177
This does NOT delete the buffer from the buffer ring, only the ring
178
identifier from the buffer.  It should only be called either as part
179
of doing the former or when deleting the ring entirely."
180
  (with-current-buffer buffer
413✔
181
    (unless buffer-ring-rings
413✔
182
      (setq buffer-ring-rings (dynaring-make)))
×
183
    (dynaring-delete buffer-ring-rings
413✔
184
                     (buffer-ring-ring-name bfr-ring))))
413✔
185

186
(defun buffer-ring-rings-drop-ring (bfr-ring)
187
  "Drop BFR-RING from the buffer's ring of buffer rings.
188

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

194
;;
195
;; buffer ring interface
196
;;
197

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

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

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

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

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

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

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

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

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

272
Not to be confused with the little-known evil cousin
273
to the koala buffer."
274
  (interactive)
275
  (let ((buffer (current-buffer)))
313✔
276
    (save-excursion
313✔
277
      (dolist (bfr-ring (buffer-ring-get-rings buffer))
313✔
278
        ;; TODO: this may muddle torus recency
279
        (buffer-ring-torus-switch-to-ring (buffer-ring-ring-name bfr-ring))
313✔
280
        (buffer-ring-delete buffer)))
313✔
281
    ;; delete the buffer's ring of buffer rings
282
    (dynaring-destroy buffer-ring-rings)
313✔
283
    (setq buffer-ring-rings nil)
313✔
284
    (remove-hook 'kill-buffer-hook #'buffer-ring-drop-buffer t)))
313✔
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)))
508✔
392
      (buffer-ring-rings-add-ring buffer bfr-ring))))
508✔
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))
540✔
400
        (bfr-rings (buffer-ring-get-rings buffer)))
540✔
401
    ;; if the buffer isn't on any rings, this is a no-op
402
    (dolist (bring bfr-rings)
540✔
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)
540✔
408
                             buffer))))
540✔
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)))
453✔
417
    (dynaring-insert buffer-ring-torus bfr-ring)
453✔
418
    bfr-ring))
453✔
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,661✔
423
                                         (lambda (r)
424
                                           (string= name
2,270✔
425
                                                    (buffer-ring-ring-name r))))))
1,661✔
426
    (when segment
1,661✔
427
      (dynaring-segment-value segment))))
1,208✔
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)))
473✔
434
    (if found-ring
473✔
435
        (progn
20✔
436
          (message "Found existing ring: %s" name)
20✔
437
          found-ring)
20✔
438
      (message "Creating a new ring \"%s\"" name)
453✔
439
      (buffer-ring-torus--create-ring name))))
453✔
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
469✔
450
                                         (lambda (r)
451
                                           (string= name
746✔
452
                                                    (buffer-ring-ring-name r))))))
469✔
453
    (when segment
469✔
454
      (let ((bfr-ring (dynaring-segment-value segment)))
468✔
455
        ;; insert the ring at the head of the torus
456
        (dynaring-break-insert buffer-ring-torus
468✔
457
                               bfr-ring)
468✔
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)
468✔
461
        bfr-ring))))
468✔
462

463
(defun buffer-ring-current-ring ()
464
  "Get the current (active) buffer ring."
465
  (dynaring-value buffer-ring-torus))
1,857✔
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)))
415✔
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))))
521✔
480

481
(defun buffer-ring-switch-to-buffer (buffer)
482
  "Switch to BUFFER while keeping rings consistent."
483
  (switch-to-buffer buffer)
481✔
484
  (buffer-ring-surface-buffer buffer)
481✔
485
  buffer)
481✔
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)))
505✔
495
    (when head-buffer
505✔
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)
503✔
508
                                    (current-buffer))
503✔
509
               (not (eq (current-buffer) head-buffer)))
440✔
510
          (buffer-ring-surface-buffer)
58✔
511
        (buffer-ring-switch-to-buffer head-buffer))))
445✔
512
  ;; surface the ring in all of its member buffers
513
  ;; so it reflects as most recent
514
  (buffer-ring-surface-ring bfr-ring))
505✔
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-rings-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