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

countvajhula / buffer-ring / 69

15 Sep 2025 04:47PM UTC coverage: 74.561%. Remained the same
69

push

github

countvajhula
ci config: gitignore, empty external dependencies module

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") (s "1.12.0") (ht "2.0"))
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)
37
(require 'dynaring)
38
(require 's)
39
(require 'ht)
40

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

43
;;
44
;; default keymap
45
;;
46

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

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

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

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

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

111
;;
112
;;  buffer ring structure
113
;;
114

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

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

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

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

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

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

140
(defun buffer-ring-registry-get-key (buffer)
141
  "Key to use for BUFFER in the buffer registry."
142
  (buffer-name buffer))
2,870✔
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,910✔
151
      (if (bufferp buffer)
1,790✔
152
          buffer
1,789✔
153
        (get-buffer buffer))
1✔
154
    (current-buffer)))
120✔
155

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

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

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

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

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

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

196
;;
197
;; buffer ring interface
198
;;
199

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

411
;;
412
;; buffer torus interface
413
;;
414

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

588
(provide 'buffer-ring)
589
;;; 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