bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#69993: Wrap window buffers while cycling


From: Juri Linkov
Subject: bug#69993: Wrap window buffers while cycling
Date: Wed, 27 Mar 2024 09:20:32 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/30.0.50 (x86_64-pc-linux-gnu)

>> There is a remaining problem, and I can't find a way to fix it.
>> When a buffer already appeared in the window before,
>> then switching to that buffer with e.g. 'C-x b'
>> moves it to the end of the list.  Technically
>> this means that window-next-buffers is set to nil.
>
> I'm not sure I understand.  IIRC 'window-next-buffers' always returns
> nil unless you invoked 'switch-to-prev-buffer' before.  It serves to
> "navigate" a window's buffer list, in particular, to "undo" preceding
> 'previous-buffer' calls when overshooting.  'switch-to-buffer' is not
> part of such a scenario.

A new option should always keep the fixed order, even when users use C-x b
to visit a buffer that appeared in the window before.

The problem is that there is no function that is called after
set-window-buffer to reset the order of prev/next-buffers.

set-window-buffer works that way that before changing the window buffer
it calls record-window-buffer.  But record-window-buffer has
no information about new-buffer.  So it can't reorder prev/next-buffers
based on new-buffer that will be displayed in this window.

Then later set-window-buffer sets window's buffer,
but after that it doesn't call any function like
record-window-buffer that could reorder prev/next-buffers.

Then maybe possible to add such reordering after calling
set-window-buffer?  I mean such places as after calling
set-window-buffer in window--display-buffer, and after calling
set-window-buffer in switch-to-buffer.

>> However, I can't find code that does this.  Could you help
>> to find it?  I already found one occurrence of
>> (set-window-next-buffers window nil) in record-window-buffer.
>> But after adding a condition with switch-to-prev-buffer-wrap
>> it still moves the switched buffer to the end.
>
> If you get me the patch you currently use and a scenario, I'll try.

Ok, here is the current patch that supports the fixed order
for 'C-x C-left' and 'C-x C-right' but still not for 'C-x b':

diff --git a/lisp/window.el b/lisp/window.el
index df55a7ca673..5fc346571a8 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4475,24 +4475,26 @@ push-window-buffer-onto-prev
   (let* ((window (window-normalize-window window t))
          (buffer (window-buffer window))
          (w-list (window-prev-buffers window))
-         (entry (assq buffer w-list)))
-    (when entry
-      (setq w-list (assq-delete-all buffer w-list)))
-    (let ((start (window-start window))
-          (point (window-point window)))
-      (setq entry
-            (cons buffer
-                  (with-current-buffer buffer
-                    (if entry
-                        ;; We have an entry, update marker positions.
-                        (list (set-marker (nth 1 entry) start)
-                              (set-marker (nth 2 entry) point))
-                      (list (copy-marker start)
-                            (copy-marker
-                             ;; Preserve window-point-insertion-type
-                             ;; (Bug#12855)
-                             point window-point-insertion-type))))))
-      (set-window-prev-buffers window (cons entry w-list)))))
+         (entry (assq buffer w-list))
+         (start (window-start window))
+         (point (window-point window))
+         (start-point
+          (with-current-buffer buffer
+            (if entry
+                ;; We have an entry, update marker positions.
+                (list (set-marker (nth 1 entry) start)
+                      (set-marker (nth 2 entry) point))
+              (list (copy-marker start)
+                    (copy-marker
+                     ;; Preserve window-point-insertion-type
+                     ;; (Bug#12855)
+                     point window-point-insertion-type))))))
+    (if (and switch-to-prev-buffer-wrap entry)
+        (setf (alist-get buffer w-list) start-point)
+      (when entry
+        (setq w-list (assq-delete-all buffer w-list)))
+      (set-window-prev-buffers
+       window (cons (cons buffer start-point) w-list)))))
 
 (defun record-window-buffer (&optional window)
   "Record WINDOW's buffer.
@@ -4501,7 +4503,9 @@ record-window-buffer
          (buffer (window-buffer window)))
     ;; Reset WINDOW's next buffers.  If needed, they are resurrected by
     ;; `switch-to-prev-buffer' and `switch-to-next-buffer'.
-    (set-window-next-buffers window nil)
+    (unless (and switch-to-prev-buffer-wrap
+                 (assq buffer (window-prev-buffers window)))
+      (set-window-next-buffers window nil))
 
     ;; Don't record insignificant buffers.
     (when (not (eq (aref (buffer-name buffer) 0) ?\s))
@@ -4542,6 +4546,16 @@ set-window-buffer-start-and-point
     (when point
       (set-window-point window point))))
 
+(defcustom switch-to-prev-buffer-wrap nil
+  "Wrap to the first/last window-local buffer while cycling.
+When t, wrap to the first/last buffer.
+When the value is `stop', stop at the first/last buffer."
+  :type '(choice (const :tag "Never wrap" nil)
+                 (const :tag "Stop at window-local buffers" stop)
+                 (const :tag "Wrap to window-local buffers" t))
+  :version "30.1"
+  :group 'windows)
+
 (defcustom switch-to-visible-buffer t
   "If non-nil, allow switching to an already visible buffer.
 If this variable is non-nil, `switch-to-prev-buffer' and
@@ -4676,7 +4690,7 @@ switch-to-prev-buffer
            ((or switch-to-prev-buffer-skip
                 (not switch-to-visible-buffer))
             frame)))
-         entry new-buffer killed-buffers skipped)
+         entry new-buffer killed-buffers skipped wrapped)
     (when (window-minibuffer-p window)
       ;; Don't switch in minibuffer window.
       (unless (setq window (minibuffer-selected-window))
@@ -4710,8 +4724,8 @@ switch-to-prev-buffer
       ;; a buried buffer instead.  Otherwise, we must reverse the global
       ;; buffer list in order to make sure that switching to the
       ;; previous/next buffer traverse it in opposite directions.  Skip
-      ;; this step for side windows.
-      (unless window-side
+      ;; this step for side windows or when wrapping.
+      (unless (or window-side switch-to-prev-buffer-wrap)
         (dolist (buffer (if bury-or-kill
                             (buffer-list frame)
                           (nreverse (buffer-list frame))))
@@ -4729,7 +4743,9 @@ switch-to-prev-buffer
               (set-window-buffer-start-and-point window new-buffer)
               (throw 'found t)))))
 
-      (unless bury-or-kill
+      (when (eq switch-to-prev-buffer-wrap 'stop)
+        (setq wrapped 'stop))
+      (unless (or bury-or-kill (eq switch-to-prev-buffer-wrap 'stop))
        ;; Scan reverted next buffers last (must not use nreverse
        ;; here!).
        (dolist (buffer (reverse next-buffers))
@@ -4743,12 +4759,13 @@ switch-to-prev-buffer
                     (setq entry (assq buffer (window-prev-buffers window))))
             (if (switch-to-prev-buffer-skip-p skip window buffer bury-or-kill)
                (setq skipped (or skipped buffer))
-             (setq new-buffer buffer)
+             (setq new-buffer buffer wrapped t)
              (set-window-buffer-start-and-point
               window new-buffer (nth 1 entry) (nth 2 entry))
              (throw 'found t)))))
 
-      (when (and skipped (not (functionp switch-to-prev-buffer-skip)))
+      (when (and skipped (not (functionp switch-to-prev-buffer-skip))
+                 (not wrapped))
         ;; Show first skipped buffer, unless skip was a function.
        (setq new-buffer skipped)
        (set-window-buffer-start-and-point window new-buffer)))
@@ -4768,10 +4785,28 @@ switch-to-prev-buffer
            ;; it.
            (set-window-prev-buffers
             window (append (window-prev-buffers window) (list entry)))))
-      ;; Move `old-buffer' to head of WINDOW's restored list of next
-      ;; buffers.
-      (set-window-next-buffers
-       window (cons old-buffer (delq old-buffer next-buffers))))
+      (if (not (and switch-to-prev-buffer-wrap wrapped))
+          ;; Move `old-buffer' to head of WINDOW's restored list of next
+          ;; buffers.
+          (set-window-next-buffers
+           window (cons old-buffer (delq old-buffer next-buffers)))
+        (if (eq wrapped 'stop)
+            (setq new-buffer nil)
+          ;; Restore the right order of previous buffers.
+          (let ((prev-buffers (window-prev-buffers window)))
+            ;; Use the same sorting order as was in next-buffers
+            ;; with old-buffer at the bottom.
+            (setq prev-buffers
+                  (sort prev-buffers
+                        (lambda (a b)
+                          (cond
+                           ((eq (car a) old-buffer) nil)
+                           ((eq (car b) old-buffer) t)
+                           (t (< (length (memq (car a) next-buffers))
+                                 (length (memq (car b) next-buffers))))))))
+            (set-window-prev-buffers window prev-buffers)
+            ;; When record-window-buffer doesn't reset next-buffers.
+            (set-window-next-buffers window nil)))))
 
     ;; Remove killed buffers from WINDOW's previous and next buffers.
     (when killed-buffers
@@ -4812,7 +4847,7 @@ switch-to-next-buffer
            ((or switch-to-prev-buffer-skip
                 (not switch-to-visible-buffer))
             frame)))
-        new-buffer entry killed-buffers skipped)
+        new-buffer entry killed-buffers skipped wrapped)
     (when (window-minibuffer-p window)
       ;; Don't switch in minibuffer window.
       (unless (setq window (minibuffer-selected-window))
@@ -4839,7 +4874,7 @@ switch-to-next-buffer
            (throw 'found t))))
       ;; Scan the buffer list of WINDOW's frame next, skipping previous
       ;; buffers entries.  Skip this step for side windows.
-      (unless window-side
+      (unless (or window-side switch-to-prev-buffer-wrap)
         (dolist (buffer (buffer-list frame))
           (when (and (buffer-live-p buffer)
                      (not (eq buffer old-buffer))
@@ -4856,27 +4891,38 @@ switch-to-next-buffer
               (throw 'found t)))))
       ;; Scan WINDOW's reverted previous buffers last (must not use
       ;; nreverse here!)
-      (dolist (entry (reverse (window-prev-buffers window)))
-       (when (and (not (eq new-buffer (car entry)))
-                   (not (eq old-buffer (car entry)))
-                   (setq new-buffer (car entry))
-                  (or (buffer-live-p new-buffer)
-                      (not (setq killed-buffers
-                                 (cons new-buffer killed-buffers))))
-                   (or (null pred) (funcall pred new-buffer)))
-          (if (switch-to-prev-buffer-skip-p skip window new-buffer)
-             (setq skipped (or skipped new-buffer))
-           (set-window-buffer-start-and-point
-            window new-buffer (nth 1 entry) (nth 2 entry))
-           (throw 'found t))))
+      (if (eq switch-to-prev-buffer-wrap 'stop)
+          (setq wrapped 'stop)
+        (dolist (entry (reverse (window-prev-buffers window)))
+          (when (and (not (eq new-buffer (car entry)))
+                     (not (eq old-buffer (car entry)))
+                     (setq new-buffer (car entry))
+                     (or (buffer-live-p new-buffer)
+                         (not (setq killed-buffers
+                                    (cons new-buffer killed-buffers))))
+                     (or (null pred) (funcall pred new-buffer)))
+            (if (switch-to-prev-buffer-skip-p skip window new-buffer)
+                (setq skipped (or skipped new-buffer))
+              (setq wrapped t)
+              (set-window-buffer-start-and-point
+               window new-buffer (nth 1 entry) (nth 2 entry))
+              (throw 'found t)))))
 
-      (when (and skipped (not (functionp switch-to-prev-buffer-skip)))
+      (when (and skipped (not (functionp switch-to-prev-buffer-skip))
+                 (not wrapped))
         ;; Show first skipped buffer, unless skip was a function.
        (setq new-buffer skipped)
        (set-window-buffer-start-and-point window new-buffer)))
 
-    ;; Remove `new-buffer' from and restore WINDOW's next buffers.
-    (set-window-next-buffers window (delq new-buffer next-buffers))
+    (if (not (and switch-to-prev-buffer-wrap wrapped))
+        ;; Remove `new-buffer' from and restore WINDOW's next buffers.
+        (set-window-next-buffers window (delq new-buffer next-buffers))
+      (if (eq wrapped 'stop)
+          (setq new-buffer nil)
+        (let ((prev-buffers (window-prev-buffers window)))
+          (setq prev-buffers
+                (nreverse (delq new-buffer (mapcar #'car prev-buffers))))
+          (set-window-next-buffers window prev-buffers))))
 
     ;; Remove killed buffers from WINDOW's previous and next buffers.
     (when killed-buffers

reply via email to

[Prev in Thread] Current Thread [Next in Thread]