unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#69993: Wrap window buffers while cycling
@ 2024-03-25  7:42 Juri Linkov
  2024-03-25  9:41 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-25  7:42 UTC (permalink / raw)
  To: 69993; +Cc: martin rudalics

[-- Attachment #1: Type: text/plain, Size: 611 bytes --]

Often users complain there is no way to wrap window buffers
while cycling them with 'C-x C-<left>' (previous-buffer)
and 'C-x C-<right>' (next-buffer).  One of the latest examples is
https://old.reddit.com/r/emacs/comments/1barvo2/perwindow_buffer_ordering/

This problem becomes apparent when a list of prev/next window buffers
is visualized by the tab-line.

So here is the patch that finally solves this problem
by adding a new option 'switch-to-prev-buffer-wrap'
disabled by default.

When its value is t, it wraps to the first/last buffer.
But when the value is 'stop', it stops at the first/last buffer.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: switch-to-prev-buffer-wrap.patch --]
[-- Type: text/x-diff, Size: 7194 bytes --]

diff --git a/lisp/window.el b/lisp/window.el
index df55a7ca673..4d727fb827c 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4542,6 +4542,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 +4686,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))
@@ -4711,7 +4721,7 @@ switch-to-prev-buffer
       ;; 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
+      (unless (or window-side switch-to-prev-buffer-wrap)
         (dolist (buffer (if bury-or-kill
                             (buffer-list frame)
                           (nreverse (buffer-list frame))))
@@ -4729,7 +4739,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 new-buffer nil))
+      (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))
@@ -4744,6 +4756,7 @@ switch-to-prev-buffer
             (if (switch-to-prev-buffer-skip-p skip window buffer bury-or-kill)
 	        (setq skipped (or skipped buffer))
 	      (setq new-buffer buffer)
+	      (setq wrapped t)
 	      (set-window-buffer-start-and-point
 	       window new-buffer (nth 1 entry) (nth 2 entry))
 	      (throw 'found t)))))
@@ -4770,8 +4783,23 @@ switch-to-prev-buffer
 	     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))
+          (set-window-next-buffers
+           window (cons old-buffer (delq old-buffer next-buffers)))
+        ;; Restore the right order of previous buffers.
+        (unless (eq wrapped 'stop)
+          (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)))))
 
     ;; Remove killed buffers from WINDOW's previous and next buffers.
     (when killed-buffers
@@ -4812,7 +4840,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 +4867,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,19 +4884,22 @@ 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 new-buffer nil)
+        (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)))
         ;; Show first skipped buffer, unless skip was a function.
@@ -4876,7 +4907,13 @@ switch-to-next-buffer
 	(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))
+        (set-window-next-buffers window (delq new-buffer next-buffers))
+      (unless (eq wrapped 'stop)
+        (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

^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-25  7:42 bug#69993: Wrap window buffers while cycling Juri Linkov
@ 2024-03-25  9:41 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-03-25 17:16   ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-25  9:41 UTC (permalink / raw)
  To: Juri Linkov, 69993

 > Often users complain there is no way to wrap window buffers
 > while cycling them with 'C-x C-<left>' (previous-buffer)
 > and 'C-x C-<right>' (next-buffer).  One of the latest examples is
 > https://old.reddit.com/r/emacs/comments/1barvo2/perwindow_buffer_ordering/
 >
 > This problem becomes apparent when a list of prev/next window buffers
 > is visualized by the tab-line.
 >
 > So here is the patch that finally solves this problem
 > by adding a new option 'switch-to-prev-buffer-wrap'
 > disabled by default.
 >
 > When its value is t, it wraps to the first/last buffer.
 > But when the value is 'stop', it stops at the first/last buffer.

Good idea.  You probably should emphasize the point that if this option
is non-nil, the buffer switched to must have appeared in the window
before.

martin






^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-25  9:41 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-03-25 17:16   ` Juri Linkov
  2024-03-26  9:56     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-25 17:16 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> So here is the patch that finally solves this problem
>> by adding a new option 'switch-to-prev-buffer-wrap'
>> disabled by default.
>
> Good idea.  You probably should emphasize the point that if this option
> is non-nil, the buffer switched to must have appeared in the window
> before.

Thanks for the suggestion, will add to the documentation.

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.
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.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-25 17:16   ` Juri Linkov
@ 2024-03-26  9:56     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-03-27  7:20       ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-26  9:56 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > 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.

 > 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.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-26  9:56     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-03-27  7:20       ` Juri Linkov
  2024-03-27  8:48         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-27  7:20 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

[-- Attachment #1: Type: text/plain, Size: 1974 bytes --]

>> 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':


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: switch-to-prev-buffer-wrap.patch --]
[-- Type: text/x-diff, Size: 10723 bytes --]

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

^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-27  7:20       ` Juri Linkov
@ 2024-03-27  8:48         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-03-28  7:54           ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-27  8:48 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> 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.

What is the "fixed order"?  When I use C-x b, that buffer becomes the
one most recently shown in that window.

 > 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.

Then give 'record-window-buffer' a second argument - the new buffer to
be shown.  I'm a bit reluctant to work in this area - the introduction
of 'push-window-buffer-onto-prev' has obfuscated the code considerably
for no apparent use (at least one that I could understand).

 > 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':

I see no problems with it.  After C-x b *foo* I want to return to *foo*
via 'previous-buffer' after switching to *bar* via a second C-x b.  What
would you want to see instead?  Maybe I still misunderstand you.

I think 'switch-to-prev-buffer-wrap' already confuses things.  Wrapping,
for me, means to wrap around like when navigating on a ring of buffers.
Whether this should include buffers never shown in the window before is
a different issue IMO.  And whether C-x b should change the order is yet
another issue.  So maybe we need three options instead of one...

martin






^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-27  8:48         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-03-28  7:54           ` Juri Linkov
  2024-03-28  9:19             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-28  7:54 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

[-- Attachment #1: Type: text/plain, Size: 3303 bytes --]

>>> 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.
>
> What is the "fixed order"?  When I use C-x b, that buffer becomes the
> one most recently shown in that window.

The fixed order is similar to how tabs work in web browsers.
It would be unexpected when switching to a tab will always
move it to the end of the tab line.  This is why it's unexpected
for C-x b to move tabs when tab-line-mode is used.

>> 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.
>
> Then give 'record-window-buffer' a second argument - the new buffer to
> be shown.  I'm a bit reluctant to work in this area - the introduction
> of 'push-window-buffer-onto-prev' has obfuscated the code considerably
> for no apparent use (at least one that I could understand).

Ok, let's add a second argument to 'record-window-buffer'.
I'll do this in a separate feature request for a new option
that will keep the fixed order for C-x b
since it's quite different from the wrapping option.

> I see no problems with it.  After C-x b *foo* I want to return to *foo*
> via 'previous-buffer' after switching to *bar* via a second C-x b.  What
> would you want to see instead?  Maybe I still misunderstand you.

Selecting a buffer via C-x b still uses the sorting order of buffers
by the most-recently-used.  So after C-x b *bar* you still can easily
return to *foo* by C-x b RET.  But the proposed change makes sense
when using tab-line-mode where C-x b messes up buffer tabs.

> I think 'switch-to-prev-buffer-wrap' already confuses things.  Wrapping,
> for me, means to wrap around like when navigating on a ring of buffers.
> Whether this should include buffers never shown in the window before is
> a different issue IMO.  And whether C-x b should change the order is yet
> another issue.  So maybe we need three options instead of one...

I can't imagine why anyone would need wrapping when C-x C-left
will visit hundreds of buffers never shown in the window.
So we need only two options: wrapping buffers shown in the window,
and to keep the fixed order of C-x b.  So I will create a new request
for the fixed order of C-x b.  And here is the final patch for wrapping:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: switch-to-prev-buffer-wrap.patch --]
[-- Type: text/x-diff, Size: 8569 bytes --]

diff --git a/lisp/window.el b/lisp/window.el
index df55a7ca673..ff08b0bcfc9 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4542,6 +4546,22 @@ set-window-buffer-start-and-point
     (when point
       (set-window-point window point))))
 
+(defcustom switch-to-prev-buffer-wrap nil
+  "Wrap to the first or last window buffer while cycling.
+The value t means wrapping around while cycling buffers appeared in the
+window before.  So when the commands that switch buffers in the selected
+window `previous-buffer' and `next-buffer' reach the first or the last
+buffer (these buffers are visible when using `tab-line-mode'),
+then wrap around to another end of the list of previous/next buffers.
+
+When the value is `stop', stop at the first or last buffer
+in the list of previous/next buffers, but don't wrap around."
+  :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 +4696,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 +4730,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 +4749,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 +4765,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 +4791,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 +4853,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 +4880,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 +4897,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

^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-28  7:54           ` Juri Linkov
@ 2024-03-28  9:19             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-03-28 17:57               ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-28  9:19 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> What is the "fixed order"?  When I use C-x b, that buffer becomes the
 >> one most recently shown in that window.
 >
 > The fixed order is similar to how tabs work in web browsers.
 > It would be unexpected when switching to a tab will always
 > move it to the end of the tab line.  This is why it's unexpected
 > for C-x b to move tabs when tab-line-mode is used.

But the lists of previous and next buffers have to reflect the order in
which these buffers were shown in a window.  You probably  can't map them
directly to tabs.

 >> I think 'switch-to-prev-buffer-wrap' already confuses things.  Wrapping,
 >> for me, means to wrap around like when navigating on a ring of buffers.
 >> Whether this should include buffers never shown in the window before is
 >> a different issue IMO.  And whether C-x b should change the order is yet
 >> another issue.  So maybe we need three options instead of one...
 >
 > I can't imagine why anyone would need wrapping when C-x C-left
 > will visit hundreds of buffers never shown in the window.

Emacs "wrapped" in that case ever since (with at least two buffers).

 > So we need only two options: wrapping buffers shown in the window,
 > and to keep the fixed order of C-x b.  So I will create a new request
 > for the fixed order of C-x b.  And here is the final patch for wrapping:

I still don't agree with it.  IMHO we have to cater for two cases:

(1) The classic behavior where switching may show a buffer never shown
     in the window before.  I suppose you mean that
     'switch-to-prev-buffer-wrap' does not affect it.  If that's the
     case, please say so.

(2) The new behavior where switching may only show buffers shown in that
     window before.  For this you want to either wrap or not.  So the
     option 'switch-to-prev-buffer-wrap' will affect (2) only.  Right?

I think there's no clean way to provide one single option to choose
between (1) and (2) and whether to wrap.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-28  9:19             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-03-28 17:57               ` Juri Linkov
  2024-03-29  8:45                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-28 17:57 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>> What is the "fixed order"?  When I use C-x b, that buffer becomes the
>>> one most recently shown in that window.
>>
>> The fixed order is similar to how tabs work in web browsers.
>> It would be unexpected when switching to a tab will always
>> move it to the end of the tab line.  This is why it's unexpected
>> for C-x b to move tabs when tab-line-mode is used.
>
> But the lists of previous and next buffers have to reflect the order in
> which these buffers were shown in a window.  You probably  can't map them
> directly to tabs.

They are already mapped to tabs, and users happily used them for 5 years.
The only remaining problem the users complain about is that switching
buffers messes up the order.  There is no problem in using a fixed order
because buffers will be still ordered by the order they were shown in a window.
Only need a way to switch between buffers already shown in a window.

For users of tab-line-mode there is no difference whether to use
'C-x C-left' or 'C-x b' to switch to the previous buffer.

>>> I think 'switch-to-prev-buffer-wrap' already confuses things.  Wrapping,
>>> for me, means to wrap around like when navigating on a ring of buffers.
>>> Whether this should include buffers never shown in the window before is
>>> a different issue IMO.  And whether C-x b should change the order is yet
>>> another issue.  So maybe we need three options instead of one...
>>
>> I can't imagine why anyone would need wrapping when C-x C-left
>> will visit hundreds of buffers never shown in the window.
>
> Emacs "wrapped" in that case ever since (with at least two buffers).

The case of 'emacs -Q' has no practical significance.

>> So we need only two options: wrapping buffers shown in the window,
>> and to keep the fixed order of C-x b.  So I will create a new request
>> for the fixed order of C-x b.  And here is the final patch for wrapping:
>
> I still don't agree with it.  IMHO we have to cater for two cases:
>
> (1) The classic behavior where switching may show a buffer never shown
>     in the window before.  I suppose you mean that
>     'switch-to-prev-buffer-wrap' does not affect it.  If that's the
>     case, please say so.

Indeed, 'switch-to-prev-buffer-wrap' does not affect it.
Switching to a buffer that was never shown in the window
should still reset the list of next-buffers to nil.

> (2) The new behavior where switching may only show buffers shown in that
>     window before.  For this you want to either wrap or not.  So the
>     option 'switch-to-prev-buffer-wrap' will affect (2) only.  Right?

'switch-to-prev-buffer-wrap' will affect only 'C-x C-left' and 'C-x C-right'
cycling buffers shown in that window before.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-28 17:57               ` Juri Linkov
@ 2024-03-29  8:45                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-03-29 16:35                   ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-29  8:45 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> (1) The classic behavior where switching may show a buffer never shown
 >>      in the window before.  I suppose you mean that
 >>      'switch-to-prev-buffer-wrap' does not affect it.  If that's the
 >>      case, please say so.
 >
 > Indeed, 'switch-to-prev-buffer-wrap' does not affect it.
 > Switching to a buffer that was never shown in the window
 > should still reset the list of next-buffers to nil.
 >
 >> (2) The new behavior where switching may only show buffers shown in that
 >>      window before.  For this you want to either wrap or not.  So the
 >>      option 'switch-to-prev-buffer-wrap' will affect (2) only.  Right?
 >
 > 'switch-to-prev-buffer-wrap' will affect only 'C-x C-left' and 'C-x C-right'
 > cycling buffers shown in that window before.

I'm still confused.  Do you mean that C-x b, when it switches to a
buffer previously shown in that window, should not change the ordering
of buffers previously shown in that window?  And behave so if and only
if 'switch-to-prev-buffer-wrap' is non-nil?  Then what would users do if
they (1) want to use your new option but (2) still want C-x b or C-x
C-left make that buffer the most previously used one in that window?

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-29  8:45                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-03-29 16:35                   ` Juri Linkov
  2024-03-30  9:37                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-29 16:35 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>> (1) The classic behavior where switching may show a buffer never shown
>>>      in the window before.  I suppose you mean that
>>>      'switch-to-prev-buffer-wrap' does not affect it.  If that's the
>>>      case, please say so.
>>
>> Indeed, 'switch-to-prev-buffer-wrap' does not affect it.
>> Switching to a buffer that was never shown in the window
>> should still reset the list of next-buffers to nil.
>>
>>> (2) The new behavior where switching may only show buffers shown in that
>>>      window before.  For this you want to either wrap or not.  So the
>>>      option 'switch-to-prev-buffer-wrap' will affect (2) only.  Right?
>>
>> 'switch-to-prev-buffer-wrap' will affect only 'C-x C-left' and 'C-x C-right'
>> cycling buffers shown in that window before.
>
> I'm still confused.  Do you mean that C-x b, when it switches to a
> buffer previously shown in that window, should not change the ordering
> of buffers previously shown in that window?  And behave so if and only
> if 'switch-to-prev-buffer-wrap' is non-nil?  Then what would users do if
> they (1) want to use your new option but (2) still want C-x b or C-x
> C-left make that buffer the most previously used one in that window?

Sorry for the confusion.  This feature request was only about C-x C-left.
C-x b was just speculation about a possible separate option.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-29 16:35                   ` Juri Linkov
@ 2024-03-30  9:37                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-03-30 18:24                       ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-30  9:37 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > Sorry for the confusion.  This feature request was only about C-x C-left.

... and about C-x C-right presumably.

 > C-x b was just speculation about a possible separate option.

Please can we start again from scratch please:

- I do like the idea of having an option to constrain buffers switched
   to via C-x <left> and C-x <right> to those shown in that window
   before.

- I do like the idea of having an option controlling whether C-x <left>
   and C-x <right> should wrap to the last/first buffer (regardless of
   whether buffers switched to appeared in the window before or not).

- I agree that the orders of tabs shown by 'tab-line-mode' should not
   change when a buffer that already appeared in a window is shown again
   in that window - the window's buffer is already highlighted
   appropriately in the tab line in that case.

But this last requirement of 'tab-line-mode' should not affect the order
of buffers in a window's list of previous and next buffers.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-30  9:37                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-03-30 18:24                       ` Juri Linkov
  2024-03-31  8:32                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-03-30 18:24 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

> - I do like the idea of having an option to constrain buffers switched
>   to via C-x <left> and C-x <right> to those shown in that window
>   before.
>
> - I do like the idea of having an option controlling whether C-x <left>
>   and C-x <right> should wrap to the last/first buffer (regardless of
>   whether buffers switched to appeared in the window before or not).
>
> - I agree that the orders of tabs shown by 'tab-line-mode' should not
>   change when a buffer that already appeared in a window is shown again
>   in that window - the window's buffer is already highlighted
>   appropriately in the tab line in that case.

When you say that a buffer is shown again in that window,
do you mean showing the buffer with C-x <left> and C-x <right>
or with C-x b?

> But this last requirement of 'tab-line-mode' should not affect the order
> of buffers in a window's list of previous and next buffers.

When the tab-line will show the same order of prev/next buffers
after C-x <left> and C-x <right> or even after C-x b, does this
mean the order of prev/next buffers is not affected?





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-30 18:24                       ` Juri Linkov
@ 2024-03-31  8:32                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-02  6:37                           ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-03-31  8:32 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> - I do like the idea of having an option to constrain buffers switched
 >>    to via C-x <left> and C-x <right> to those shown in that window
 >>    before.
 >>
 >> - I do like the idea of having an option controlling whether C-x <left>
 >>    and C-x <right> should wrap to the last/first buffer (regardless of
 >>    whether buffers switched to appeared in the window before or not).
 >>
 >> - I agree that the orders of tabs shown by 'tab-line-mode' should not
 >>    change when a buffer that already appeared in a window is shown again
 >>    in that window - the window's buffer is already highlighted
 >>    appropriately in the tab line in that case.
 >
 > When you say that a buffer is shown again in that window,
 > do you mean showing the buffer with C-x <left> and C-x <right>
 > or with C-x b?

The Elisp manual says about 'switch-to-prev-buffer'

      The previous buffer is usually the buffer shown before the buffer
      currently shown in WINDOW.  However, a buffer that has been buried
      or killed, or has been already shown by a recent invocation of
      ‘switch-to-prev-buffer’, does not qualify as previous buffer.

and about 'switch-to-next-buffer'

      This command switches to the next buffer in WINDOW, thus undoing
      the effect of the last ‘switch-to-prev-buffer’ command in WINDOW.
      The argument WINDOW must be a live window and defaults to the
      selected one.

Admittedly, "recent" is not very precise.  The idea is, among others,
that an intervening C-x b will make "recent invocations" appear as if
they never happened.

 >> But this last requirement of 'tab-line-mode' should not affect the order
 >> of buffers in a window's list of previous and next buffers.
 >
 > When the tab-line will show the same order of prev/next buffers
 > after C-x <left> and C-x <right> or even after C-x b, does this
 > mean the order of prev/next buffers is not affected?

No.  All of these affect the order, albeit in distinct ways.  I think
the following is problematic:

   (defun tab-line-switch-to-prev-tab (&optional event)
     "Switch to the previous tab's buffer.
   Its effect is the same as using the `previous-buffer' command
   (\\[previous-buffer])."

If the "previous tab" does not show the buffer 'switch-to-prev-buffer'
would switch to, then the doc is wrong.  I'm not sure whether
'tab-line-tabs-window-buffers' can guarantee that this chooses the same
buffer 'switch-to-prev-buffer' would switch to, though.  If it doesn't,
then the effect should be that of C-x b switching to a buffer earlier
shown in that window.  BTW, burying a buffer removes it from the tab
line but does not prevent 'switch-to-prev-buffer' from switching to it -
it just makes it very unlikely IIRC.

martin

^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-03-31  8:32                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-02  6:37                           ` Juri Linkov
  2024-04-02  8:22                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-02  6:37 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

> The Elisp manual says about 'switch-to-prev-buffer'
>
>      The previous buffer is usually the buffer shown before the buffer
>      currently shown in WINDOW.  However, a buffer that has been buried
>      or killed, or has been already shown by a recent invocation of
>      ‘switch-to-prev-buffer’, does not qualify as previous buffer.

I see that the Elisp manual also says about 'switch-to-prev-buffer':

 -- Command: bury-buffer &optional buffer-or-name
     ...
     (*note Quitting Windows::).  Otherwise, it calls
     ‘switch-to-prev-buffer’ (*note Window History::)
     to show another buffer in that window.

This means that it reuses switch-to-prev-buffer to show any available
buffer, even the buffers that were never shown in that window.

This means that such calls should be wrapped with let-bind
to handle the case when the list of prev/next-buffers becomes empty
to switch to any other buffer not shown in that window before:

@@ -4994,7 +5042,8 @@ bury-buffer
      (t
       ;; Switch to another buffer in window.
       (set-window-dedicated-p nil nil)
-      (switch-to-prev-buffer nil 'bury)))
+      (let ((switch-to-prev-buffer-wrap nil))
+        (switch-to-prev-buffer nil 'bury))))
     ;; Always return nil.
     nil))

Also for:

 -- Command: replace-buffer-in-windows &optional buffer-or-name
    ...
     The replacement buffer in each window is chosen via
     ‘switch-to-prev-buffer’ (*note Window History::).

need the same:

@@ -5145,7 +5194,8 @@ replace-buffer-in-windows
             (when (or dedicated-side (not (window--delete window t t)))
               ;; Switch to another buffer in that window.
               (set-window-dedicated-p window nil)
-              (if (switch-to-prev-buffer window 'kill)
+              (if (let ((switch-to-prev-buffer-wrap nil))
+                    (switch-to-prev-buffer window 'kill))
                   (and dedicated-side (set-window-dedicated-p window 'side))
                 (window--delete window nil 'kill))))
 	;; Unrecord BUFFER in WINDOW.

And for:

 -- Function: quit-restore-window &optional window bury-or-kill
    ...
    As a consequence, if WINDOW is not deleted, invoking
    ‘switch-to-prev-buffer’ will usually show the buffer again.

need the same:

@@ -5292,7 +5340,8 @@ quit-restore-window
       (set-window-dedicated-p window nil)
       ;; Try to switch to a previous buffer.  Delete the window only if
       ;; that is not possible (Bug#48367).
-      (if (switch-to-prev-buffer window bury-or-kill)
+      (if (let ((switch-to-prev-buffer-wrap nil))
+            (switch-to-prev-buffer window bury-or-kill))
           (when (eq dedicated 'side)
             (set-window-dedicated-p window 'side))
         (window--delete window nil (eq bury-or-kill 'kill))

The Elisp manual doesn't mention that delete-windows-on
uses switch-to-prev-buffer, unlike it mentions other functions:

  The ‘switch-to-prev-buffer’ command, in particular, is
  used by ‘replace-buffer-in-windows’, ‘bury-buffer’ and ‘quit-window’ to
  find a replacement buffer for a window.

but delete-windows-on still needs the same:

@@ -5116,7 +5164,8 @@ delete-windows-on
 	     (t
 	      ;; In window switch to previous buffer.
 	      (set-window-dedicated-p window nil)
-	      (switch-to-prev-buffer window 'bury)
+	      (let ((switch-to-prev-buffer-wrap nil))
+                (switch-to-prev-buffer window 'bury))
               ;; Restore the dedicated 'side' flag.
               (when (eq dedicated 'side)
                 (set-window-dedicated-p window 'side)))))

> Admittedly, "recent" is not very precise.  The idea is, among others,
> that an intervening C-x b will make "recent invocations" appear as if
> they never happened.

'C-x b' is not different from 'C-x <left>' and 'C-x <right>'
when the buffer selected for 'C-x b' was already shown in the window.

> I think the following is problematic:
>
>   (defun tab-line-switch-to-prev-tab (&optional event)
>     "Switch to the previous tab's buffer.
>   Its effect is the same as using the `previous-buffer' command
>   (\\[previous-buffer])."
>
> If the "previous tab" does not show the buffer 'switch-to-prev-buffer'
> would switch to, then the doc is wrong.  I'm not sure whether
> 'tab-line-tabs-window-buffers' can guarantee that this chooses the same
> buffer 'switch-to-prev-buffer' would switch to, though.  If it doesn't,
> then the effect should be that of C-x b switching to a buffer earlier
> shown in that window.  BTW, burying a buffer removes it from the tab
> line but does not prevent 'switch-to-prev-buffer' from switching to it -
> it just makes it very unlikely IIRC.

tab-line-switch-to-prev-tab doesn't choose buffers itself:
for tab-line-tabs-window-buffers it just delegates the task
to switch-to-prev-buffer.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02  6:37                           ` Juri Linkov
@ 2024-04-02  8:22                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-02 16:28                               ` Juri Linkov
                                                 ` (2 more replies)
  0 siblings, 3 replies; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-02  8:22 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > I see that the Elisp manual also says about 'switch-to-prev-buffer':
 >
 >   -- Command: bury-buffer &optional buffer-or-name
 >       ...
 >       (*note Quitting Windows::).  Otherwise, it calls
 >       ‘switch-to-prev-buffer’ (*note Window History::)
 >       to show another buffer in that window.
 >
 > This means that it reuses switch-to-prev-buffer to show any available
 > buffer, even the buffers that were never shown in that window.

Even buffers that were never shown anywhere.  That's the way Emacs
traditionally behaves and we are not supposed to change that.

 > This means that such calls should be wrapped with let-bind
 > to handle the case when the list of prev/next-buffers becomes empty
 > to switch to any other buffer not shown in that window before:
 >
 > @@ -4994,7 +5042,8 @@ bury-buffer
 >        (t
 >         ;; Switch to another buffer in window.
 >         (set-window-dedicated-p nil nil)
 > -      (switch-to-prev-buffer nil 'bury)))
 > +      (let ((switch-to-prev-buffer-wrap nil))
 > +        (switch-to-prev-buffer nil 'bury))))
 >       ;; Always return nil.
 >       nil))

You mean to avoid a "Could not replace buffer ..." error?  Can't we
handle this problem in 'switch-to-prev-buffer' when BURY-OR-KILL and
'switch-to-prev-buffer-wrap' are non-nil, here and in the other calls
you cite?

 > 'C-x b' is not different from 'C-x <left>' and 'C-x <right>'
 > when the buffer selected for 'C-x b' was already shown in the window.

But then we would have to handle any call of set_window_buffer that
replaces a window's buffer with one that has been previously shown in
that window.  Think only of 'switch-to-buffer-obey-display-actions'.

 >> I think the following is problematic:
 >>
 >>    (defun tab-line-switch-to-prev-tab (&optional event)
 >>      "Switch to the previous tab's buffer.
 >>    Its effect is the same as using the `previous-buffer' command
 >>    (\\[previous-buffer])."
 >>
 >> If the "previous tab" does not show the buffer 'switch-to-prev-buffer'
 >> would switch to, then the doc is wrong.  I'm not sure whether
 >> 'tab-line-tabs-window-buffers' can guarantee that this chooses the same
 >> buffer 'switch-to-prev-buffer' would switch to, though.  If it doesn't,
 >> then the effect should be that of C-x b switching to a buffer earlier
 >> shown in that window.  BTW, burying a buffer removes it from the tab
 >> line but does not prevent 'switch-to-prev-buffer' from switching to it -
 >> it just makes it very unlikely IIRC.
 >
 > tab-line-switch-to-prev-tab doesn't choose buffers itself:
 > for tab-line-tabs-window-buffers it just delegates the task
 > to switch-to-prev-buffer.

But what is the "previous tab"?  IIUC it is the one on the left of the
current tab in the tab line.  But that tab is not necessarily the one
'switch-to-prev-buffer' will switch to.

martin

^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02  8:22                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-02 16:28                               ` Juri Linkov
  2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-02 16:34                               ` Juri Linkov
  2024-04-02 16:40                               ` Juri Linkov
  2 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-02 16:28 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

[-- Attachment #1: Type: text/plain, Size: 899 bytes --]

>> This means that such calls should be wrapped with let-bind
>> to handle the case when the list of prev/next-buffers becomes empty
>> to switch to any other buffer not shown in that window before:
>>
>> @@ -4994,7 +5042,8 @@ bury-buffer
>>        (t
>>         ;; Switch to another buffer in window.
>>         (set-window-dedicated-p nil nil)
>> -      (switch-to-prev-buffer nil 'bury)))
>> +      (let ((switch-to-prev-buffer-wrap nil))
>> +        (switch-to-prev-buffer nil 'bury))))
>>       ;; Always return nil.
>>       nil))
>
> You mean to avoid a "Could not replace buffer ..." error?  Can't we
> handle this problem in 'switch-to-prev-buffer' when BURY-OR-KILL and
> 'switch-to-prev-buffer-wrap' are non-nil, here and in the other calls
> you cite?

Thanks, so the patch below ignores 'switch-to-prev-buffer-wrap' in
'switch-to-prev-buffer' when its argument BURY-OR-KILL is non-nil.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: switch-to-prev-buffer-wrap.patch --]
[-- Type: text/x-diff, Size: 8501 bytes --]

diff --git a/lisp/window.el b/lisp/window.el
index df55a7ca673..f1486b15e0a 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4542,6 +4542,22 @@ set-window-buffer-start-and-point
     (when point
       (set-window-point window point))))
 
+(defcustom switch-to-prev-buffer-wrap nil
+  "Wrap to the first or last window buffer while cycling.
+The value t means wrapping around while cycling buffers appeared in the
+window before.  So when the commands that switch buffers in the selected
+window `previous-buffer' and `next-buffer' reach the first or the last
+buffer (these buffers are visible when using `tab-line-mode'),
+then wrap around to another end of the list of previous/next buffers.
+
+When the value is `stop', stop at the first or last buffer
+in the list of previous/next buffers, but don't wrap around."
+  :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 +4692,8 @@ switch-to-prev-buffer
            ((or switch-to-prev-buffer-skip
                 (not switch-to-visible-buffer))
             frame)))
-         entry new-buffer killed-buffers skipped)
+         (wrap (and switch-to-prev-buffer-wrap (not bury-or-kill)))
+         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 +4727,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 wrap)
         (dolist (buffer (if bury-or-kill
                             (buffer-list frame)
                           (nreverse (buffer-list frame))))
@@ -4729,7 +4746,8 @@ switch-to-prev-buffer
               (set-window-buffer-start-and-point window new-buffer)
               (throw 'found t)))))
 
-      (unless bury-or-kill
+      (when (eq wrap 'stop) (setq wrapped 'stop))
+      (unless (or bury-or-kill (eq wrap 'stop))
 	;; Scan reverted next buffers last (must not use nreverse
 	;; here!).
 	(dolist (buffer (reverse next-buffers))
@@ -4743,12 +4761,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 +4787,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 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 +4849,8 @@ switch-to-next-buffer
            ((or switch-to-prev-buffer-skip
                 (not switch-to-visible-buffer))
             frame)))
-	 new-buffer entry killed-buffers skipped)
+	 (wrap switch-to-prev-buffer-wrap)
+	 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 +4877,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 wrap)
         (dolist (buffer (buffer-list frame))
           (when (and (buffer-live-p buffer)
                      (not (eq buffer old-buffer))
@@ -4856,27 +4894,37 @@ 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 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 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

^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02  8:22                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-02 16:28                               ` Juri Linkov
@ 2024-04-02 16:34                               ` Juri Linkov
  2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-04  6:22                                 ` Juri Linkov
  2024-04-02 16:40                               ` Juri Linkov
  2 siblings, 2 replies; 45+ messages in thread
From: Juri Linkov @ 2024-04-02 16:34 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> 'C-x b' is not different from 'C-x <left>' and 'C-x <right>'
>> when the buffer selected for 'C-x b' was already shown in the window.

You can also see how 'tab-line-select-tab-buffer' emulates
'switch-to-buffer' to keep the fixed order by using
'switch-to-prev-buffer':

     ((and (eq tab-line-tabs-function #'tab-line-tabs-window-buffers)
           (memq buffer prev-buffers))
      (dotimes (_ (1+ (seq-position prev-buffers buffer)))
        (switch-to-prev-buffer window)))

> But then we would have to handle any call of set_window_buffer that
> replaces a window's buffer with one that has been previously shown in
> that window.

Indeed, this is why I asked to add a new hook after set-window-buffer,
like the hook record-window-buffer is called before set-window-buffer.

Then 'tab-line-select-tab-buffer' will just let-bind a new variable
like 'switch-to-prev-buffer-wrap' that will keep the fixed order
with a hook above, and use 'set-window-buffer'.

> Think only of 'switch-to-buffer-obey-display-actions'.

Thanks for reminding about 'switch-to-buffer-obey-display-actions'.
This means that all uses of 'switch-to-buffer' in tab-line.el are wrong
and should be replaced with 'set-window-buffer'.

Or 'switch-to-buffer' still does necessary things?
Then maybe better to wrap 'switch-to-buffer' calls with

  (let ((switch-to-buffer-obey-display-actions nil))
    (switch-to-buffer buffer))





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02  8:22                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-02 16:28                               ` Juri Linkov
  2024-04-02 16:34                               ` Juri Linkov
@ 2024-04-02 16:40                               ` Juri Linkov
  2 siblings, 0 replies; 45+ messages in thread
From: Juri Linkov @ 2024-04-02 16:40 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

[-- Attachment #1: Type: text/plain, Size: 1365 bytes --]

>>> I think the following is problematic:
>>>
>>>    (defun tab-line-switch-to-prev-tab (&optional event)
>>>      "Switch to the previous tab's buffer.
>>>    Its effect is the same as using the `previous-buffer' command
>>>    (\\[previous-buffer])."
>>>
>>> If the "previous tab" does not show the buffer 'switch-to-prev-buffer'
>>> would switch to, then the doc is wrong.  I'm not sure whether
>>> 'tab-line-tabs-window-buffers' can guarantee that this chooses the same
>>> buffer 'switch-to-prev-buffer' would switch to, though.  If it doesn't,
>>> then the effect should be that of C-x b switching to a buffer earlier
>>> shown in that window.  BTW, burying a buffer removes it from the tab
>>> line but does not prevent 'switch-to-prev-buffer' from switching to it -
>>> it just makes it very unlikely IIRC.
>>
>> tab-line-switch-to-prev-tab doesn't choose buffers itself:
>> for tab-line-tabs-window-buffers it just delegates the task
>> to switch-to-prev-buffer.
>
> But what is the "previous tab"?  IIUC it is the one on the left of the
> current tab in the tab line.  But that tab is not necessarily the one
> 'switch-to-prev-buffer' will switch to.

It's the same tab buffer that 'switch-to-prev-buffer' will switch to,
while using tab-line-tabs-window-buffers that is the default behavior.

So here are changes in the documentation that explain this:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: tab-line-switch-cycling.patch --]
[-- Type: text/x-diff, Size: 2617 bytes --]

diff --git a/lisp/tab-line.el b/lisp/tab-line.el
index cc60f94c9c5..12414f0e06f 100644
--- a/lisp/tab-line.el
+++ b/lisp/tab-line.el
@@ -835,31 +849,48 @@
         (switch-to-buffer buffer))))))
 
 (defcustom tab-line-switch-cycling nil
-  "Enable cycling tab switch.
+  "Wrap tabs on tab switch while cycling.
 If non-nil, `tab-line-switch-to-prev-tab' in the first tab
 switches to the last tab and `tab-line-switch-to-next-tab' in the
-last tab switches to the first tab.  This variable is not consulted
-when `tab-line-tabs-function' is `tab-line-tabs-window-buffers'."
+last tab switches to the first tab.
+
+When `tab-line-tabs-function' is `tab-line-tabs-window-buffers',
+then you can customize `switch-to-prev-buffer-wrap' to t
+to achieve the same wrapping effect."
   :type 'boolean
   :group 'tab-line
   :version "28.1")
 
 (defun tab-line-switch-to-prev-tab (&optional event)
   "Switch to the previous tab's buffer.
-Its effect is the same as using the `previous-buffer' command
-(\\[previous-buffer])."
+When `tab-line-tabs-function' is `tab-line-tabs-window-buffers',
+its effect is the same as using the `previous-buffer' command
+\(\\[previous-buffer]).  To wrap buffer cycling in this case,
+you can customize `switch-to-prev-buffer-wrap'.
+
+For other values of `tab-line-tabs-function' this command
+switches to the previous buffer in the sequence defined by
+`tab-line-tabs-function'.  To wrap buffer cycling in this case,
+you can customize `tab-line-switch-cycling'."
   (interactive (list last-nonmenu-event))
   (let ((window (and (listp event) (posn-window (event-start event)))))
     (if (eq tab-line-tabs-function #'tab-line-tabs-window-buffers)
@@ -835,31 +849,48 @@
 
 (defun tab-line-switch-to-next-tab (&optional event)
   "Switch to the next tab's buffer.
-Its effect is the same as using the `next-buffer' command
-(\\[next-buffer])."
+When `tab-line-tabs-function' is `tab-line-tabs-window-buffers',
+its effect is the same as using the `next-buffer' command
+\(\\[next-buffer]).  To wrap buffer cycling in this case,
+you can customize `switch-to-prev-buffer-wrap'.
+
+For other values of `tab-line-tabs-function' this command
+switches to the next buffer in the sequence defined by
+`tab-line-tabs-function'.  To wrap buffer cycling in this case,
+you can customize `tab-line-switch-cycling'."
   (interactive (list last-nonmenu-event))
   (let ((window (and (listp event) (posn-window (event-start event)))))
     (if (eq tab-line-tabs-function #'tab-line-tabs-window-buffers)
         (switch-to-next-buffer window)
       (with-selected-window (or window (selected-window))

^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02 16:28                               ` Juri Linkov
@ 2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-03 17:45                                   ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-03  8:24 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > Thanks, so the patch below ignores 'switch-to-prev-buffer-wrap' in
 > 'switch-to-prev-buffer' when its argument BURY-OR-KILL is non-nil.

If I correctly caught your intentions, I'd rather write

   (defcustom switch-to-prev-buffer-wrap nil
     "Non-nil means switching to previous or next buffers behaves specially.
   If this is non-nil, the commands `switch-to-prev-buffer' and
   `switch-to-next-buffer' restrict their lists of candidate buffers
   to those that were previously visible in the window specified by
   their WINDOW argument.  Buffers never shown in that window are
   ignored.

   If this is t, candidate buffers form a ring and these commands
   will always show another buffer, provided there are at least two
   candidates.  If this is the symbol 'stop', these commands will
   stop to show another buffer when reaching the first or last
   candidate buffer.

   If this option is nil or the argument BURY-OR-KILL of
   `switch-to-prev-buffer' or `switch-to-next-buffer' is non-nil,
   these commands may choose a buffer never shown in that window
   before."

But I still think that the '-wrap' postfix creates a misnomer.  The
primary intention of this option is to restrict the set of candidate
buffers ...

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02 16:34                               ` Juri Linkov
@ 2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-03 17:44                                   ` Juri Linkov
  2024-04-04  6:22                                 ` Juri Linkov
  1 sibling, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-03  8:24 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> But then we would have to handle any call of set_window_buffer that
 >> replaces a window's buffer with one that has been previously shown in
 >> that window.
 >
 > Indeed, this is why I asked to add a new hook after set-window-buffer,
 > like the hook record-window-buffer is called before set-window-buffer.

Are you sure that 'window-buffer-change-functions' can't catch this
already?  IIUC all you need is to to know whether the current buffer of
a window does not equal the value returned by 'window-old-buffer' for
that window.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-03 17:44                                   ` Juri Linkov
  0 siblings, 0 replies; 45+ messages in thread
From: Juri Linkov @ 2024-04-03 17:44 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>> But then we would have to handle any call of set_window_buffer that
>>> replaces a window's buffer with one that has been previously shown in
>>> that window.
>>
>> Indeed, this is why I asked to add a new hook after set-window-buffer,
>> like the hook record-window-buffer is called before set-window-buffer.
>
> Are you sure that 'window-buffer-change-functions' can't catch this
> already?  IIUC all you need is to to know whether the current buffer of
> a window does not equal the value returned by 'window-old-buffer' for
> that window.

Thanks for the suggestion, will try.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-03 17:45                                   ` Juri Linkov
  2024-04-04  8:03                                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-03 17:45 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> Thanks, so the patch below ignores 'switch-to-prev-buffer-wrap' in
>> 'switch-to-prev-buffer' when its argument BURY-OR-KILL is non-nil.
>
> If I correctly caught your intentions, I'd rather write
>
>   (defcustom switch-to-prev-buffer-wrap nil
>     "Non-nil means switching to previous or next buffers behaves specially.
>   If this is non-nil, the commands `switch-to-prev-buffer' and
>   `switch-to-next-buffer' restrict their lists of candidate buffers
>   to those that were previously visible in the window specified by
>   their WINDOW argument.  Buffers never shown in that window are
>   ignored.
>
>   If this is t, candidate buffers form a ring and these commands
>   will always show another buffer, provided there are at least two
>   candidates.  If this is the symbol 'stop', these commands will
>   stop to show another buffer when reaching the first or last
>   candidate buffer.
>
>   If this option is nil or the argument BURY-OR-KILL of
>   `switch-to-prev-buffer' or `switch-to-next-buffer' is non-nil,
>   these commands may choose a buffer never shown in that window
>   before."

The only problem is that this doesn't mention wrapping.

> But I still think that the '-wrap' postfix creates a misnomer.  The
> primary intention of this option is to restrict the set of candidate
> buffers ...

The real misnomer is 'tab-line-switch-cycling'.
It should be renamed 'tab-line-switch-wrap'.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-02 16:34                               ` Juri Linkov
  2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-04  6:22                                 ` Juri Linkov
  1 sibling, 0 replies; 45+ messages in thread
From: Juri Linkov @ 2024-04-04  6:22 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> Think only of 'switch-to-buffer-obey-display-actions'.
>
> Thanks for reminding about 'switch-to-buffer-obey-display-actions'.
> This means that all uses of 'switch-to-buffer' in tab-line.el are wrong
> and should be replaced with 'set-window-buffer'.
>
> Or 'switch-to-buffer' still does necessary things?
> Then maybe better to wrap 'switch-to-buffer' calls with
>
>   (let ((switch-to-buffer-obey-display-actions nil))
>     (switch-to-buffer buffer))

Ok, now fixed this in tab-line.el.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-03 17:45                                   ` Juri Linkov
@ 2024-04-04  8:03                                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-05  6:45                                       ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-04  8:03 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >>    (defcustom switch-to-prev-buffer-wrap nil
 >>      "Non-nil means switching to previous or next buffers behaves specially.
 >>    If this is non-nil, the commands `switch-to-prev-buffer' and
 >>    `switch-to-next-buffer' restrict their lists of candidate buffers
 >>    to those that were previously visible in the window specified by
 >>    their WINDOW argument.  Buffers never shown in that window are
 >>    ignored.
 >>
 >>    If this is t, candidate buffers form a ring and these commands
 >>    will always show another buffer, provided there are at least two
 >>    candidates.  If this is the symbol 'stop', these commands will
 >>    stop to show another buffer when reaching the first or last
 >>    candidate buffer.
 >>
 >>    If this option is nil or the argument BURY-OR-KILL of
 >>    `switch-to-prev-buffer' or `switch-to-next-buffer' is non-nil,
 >>    these commands may choose a buffer never shown in that window
 >>    before."
 >
 > The only problem is that this doesn't mention wrapping.

Isn't the paragraph starting with "If this is t, ..." enough?

 >> But I still think that the '-wrap' postfix creates a misnomer.  The
 >> primary intention of this option is to restrict the set of candidate
 >> buffers ...
 >
 > The real misnomer is 'tab-line-switch-cycling'.
 > It should be renamed 'tab-line-switch-wrap'.

For me "cycling" and "wrapping" stand for the same effect.  The major
difference is that when the above option is made non-nil, the set of
candidate buffers changes.  OTOH, when it's nil, Emacs wraps/cycles
while when it's 'stop' it doesn't.  That's confusing.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-04  8:03                                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-05  6:45                                       ` Juri Linkov
  2024-04-05  9:08                                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-05  6:45 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>>    (defcustom switch-to-prev-buffer-wrap nil
>>
>> The only problem is that this doesn't mention wrapping.
>
> Isn't the paragraph starting with "If this is t, ..." enough?

No, because this feature is about wrapping buffers
visible on the tab-line.

>>> But I still think that the '-wrap' postfix creates a misnomer.  The
>>> primary intention of this option is to restrict the set of candidate
>>> buffers ...
>>
>> The real misnomer is 'tab-line-switch-cycling'.
>> It should be renamed 'tab-line-switch-wrap'.
>
> For me "cycling" and "wrapping" stand for the same effect.  The major
> difference is that when the above option is made non-nil, the set of
> candidate buffers changes.  OTOH, when it's nil, Emacs wraps/cycles
> while when it's 'stop' it doesn't.  That's confusing.

Ok, since this feature is intended for the users of the tab-line, who
intuitively understand what it's doing, so it's not confusing for them,
here is what I'm going to do: I'll rebind 'C-x C-left' and 'C-x C-right'
in 'tab-line-mode' and will use tab-line functions to implement wrapping
of visible tabs/buffers.  Basically this means just adding a new map:

  (defvar-keymap tab-line-mode-map
    "C-x <left>" #'tab-line-switch-to-prev-tab
    "C-x C-<left>" #'tab-line-switch-to-prev-tab
    "C-x <right>" #'tab-line-switch-to-next-tab
    "C-x C-<right>" #'tab-line-switch-to-next-tab)

So no problem, tab-line.el is the right place since later I could
also implement drag'n'drop of tabs to allow manually changing
the order of buffers in the window buffers list.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-05  6:45                                       ` Juri Linkov
@ 2024-04-05  9:08                                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-05 16:32                                           ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-05  9:08 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > So no problem, tab-line.el is the right place since later I could
 > also implement drag'n'drop of tabs to allow manually changing
 > the order of buffers in the window buffers list.

Your are throwing out the child with the bathwater.  I think your
earlier patches for 'switch-to-prev-buffer' and 'switch-to-next-buffer'
were good.  Just the doc of the option was too tab-line specific.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-05  9:08                                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-05 16:32                                           ` Juri Linkov
  2024-04-06 18:43                                             ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-05 16:32 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> So no problem, tab-line.el is the right place since later I could
>> also implement drag'n'drop of tabs to allow manually changing
>> the order of buffers in the window buffers list.
>
> Your are throwing out the child with the bathwater.  I think your
> earlier patches for 'switch-to-prev-buffer' and 'switch-to-next-buffer'
> were good.  Just the doc of the option was too tab-line specific.

I don't know, this whole feature was intended to be tab-line specific,
since the window prev/next-buffers are visualized by the tab-line.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-05 16:32                                           ` Juri Linkov
@ 2024-04-06 18:43                                             ` Juri Linkov
  2024-04-07  8:23                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-06 18:43 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>> So no problem, tab-line.el is the right place since later I could
>>> also implement drag'n'drop of tabs to allow manually changing
>>> the order of buffers in the window buffers list.
>>
>> Your are throwing out the child with the bathwater.  I think your
>> earlier patches for 'switch-to-prev-buffer' and 'switch-to-next-buffer'
>> were good.  Just the doc of the option was too tab-line specific.
>
> I don't know, this whole feature was intended to be tab-line specific,
> since the window prev/next-buffers are visualized by the tab-line.

The decision whether to support wrapping for 'switch-to-prev-buffer'
could depend on whether users who don't use the tab-line need it?





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-06 18:43                                             ` Juri Linkov
@ 2024-04-07  8:23                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-09  6:35                                                 ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-07  8:23 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > The decision whether to support wrapping for 'switch-to-prev-buffer'
 > could depend on whether users who don't use the tab-line need it?

Currently, 'switch-to-prev-buffer' always wraps.  So it misses two
features:

- Inhibit wrapping.

- Restrict itself to the buffers shown in that window only.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-07  8:23                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-09  6:35                                                 ` Juri Linkov
  2024-04-09  9:04                                                   ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-09  6:35 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> The decision whether to support wrapping for 'switch-to-prev-buffer'
>> could depend on whether users who don't use the tab-line need it?
>
> Currently, 'switch-to-prev-buffer' always wraps.  So it misses two
> features:
>
> - Inhibit wrapping.
>
> - Restrict itself to the buffers shown in that window only.

The problem is that from the point of view of tab-line users
currently there is no wrapping because the tab-line already
restricts itself to the buffers shown in that window only.

But if you think that 'switch-to-prev-buffer-wrap' should be renamed
to something that makes no sense for tab-line users such as
'switch-to-prev-buffer-restrict', then still it can be used
from tab-line.el like this:

(when tab-line-switch-cycling
  (let-bind ((switch-to-prev-buffer-restrict tab-line-switch-cycling))
    (switch-to-prev-buffer window)))





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-09  6:35                                                 ` Juri Linkov
@ 2024-04-09  9:04                                                   ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-09 16:37                                                     ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-09  9:04 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > The problem is that from the point of view of tab-line users
 > currently there is no wrapping because the tab-line already
 > restricts itself to the buffers shown in that window only.

I'm afraid we still misunderstand each other: For me "wrap" means that
when I'm at the beginning of a list and want to go to its previous
element, I go to the last element of the list.  When I'm at the end of a
list and want to go to its next element, I go to the first element of
the list.

I think that if we added a 'switch-to-prev-buffer-wrap' option, we would
emulate the behavior sketched above regardless of whether it's triggered
by 'switch-to-prev-buffer' or 'tab-line-switch-to-prev-tab'.  Currently,
'switch-to-prev-buffer' always wraps while 'tab-line-switch-to-prev-tab'
obeys the value of 'tab-line-switch-cycling'.

'tab-line-switch-cycling' could then be used to override the more
general 'switch-to-prev-buffer-wrap' when calling
'tab-line-switch-to-prev-tab'.  Or we could alias
'tab-line-switch-cycling' to 'switch-to-prev-buffer-wrap'.  And we could
use the term 'switch-to-prev-buffer-cycle' instead.

 > But if you think that 'switch-to-prev-buffer-wrap' should be renamed
 > to something that makes no sense for tab-line users such as
 > 'switch-to-prev-buffer-restrict', then still it can be used
 > from tab-line.el like this:
 >
 > (when tab-line-switch-cycling
 >    (let-bind ((switch-to-prev-buffer-restrict tab-line-switch-cycling))
 >      (switch-to-prev-buffer window)))

An option like say 'switch-to-prev-buffer-restrict' should effectively
affect 'switch-to-prev-buffer' only.  'tab-line-switch-to-prev-tab'
would not switch to a buffer never shown in that window and could be
implemented as you sketched above.

To sum up, I think that we should not mingle the concepts of
wrapping/cycling and restricting into one and the same option but
provide two options instead.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-09  9:04                                                   ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-09 16:37                                                     ` Juri Linkov
  2024-04-10  8:46                                                       ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-09 16:37 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> The problem is that from the point of view of tab-line users
>> currently there is no wrapping because the tab-line already
>> restricts itself to the buffers shown in that window only.
>
> I'm afraid we still misunderstand each other: For me "wrap" means that
> when I'm at the beginning of a list and want to go to its previous
> element, I go to the last element of the list.  When I'm at the end of a
> list and want to go to its next element, I go to the first element of
> the list.
>
> I think that if we added a 'switch-to-prev-buffer-wrap' option, we would
> emulate the behavior sketched above regardless of whether it's triggered
> by 'switch-to-prev-buffer' or 'tab-line-switch-to-prev-tab'.  Currently,
> 'switch-to-prev-buffer' always wraps while 'tab-line-switch-to-prev-tab'
> obeys the value of 'tab-line-switch-cycling'.

The users won't understand how currently 'switch-to-prev-buffer' always wraps,
when going to the previous element at the beginning of a list
doesn't go to the last element of the tab-line.

> 'tab-line-switch-cycling' could then be used to override the more
> general 'switch-to-prev-buffer-wrap' when calling
> 'tab-line-switch-to-prev-tab'.  Or we could alias
> 'tab-line-switch-cycling' to 'switch-to-prev-buffer-wrap'.  And we could
> use the term 'switch-to-prev-buffer-cycle' instead.

I think "cycle" is a wrong name.  Cycling is using
'switch-to-prev-buffer'.  But wrapping is going
from the beginning to the end of the tab-line.

>> But if you think that 'switch-to-prev-buffer-wrap' should be renamed
>> to something that makes no sense for tab-line users such as
>> 'switch-to-prev-buffer-restrict', then still it can be used
>> from tab-line.el like this:
>>
>> (when tab-line-switch-cycling
>>    (let-bind ((switch-to-prev-buffer-restrict tab-line-switch-cycling))
>>      (switch-to-prev-buffer window)))
>
> An option like say 'switch-to-prev-buffer-restrict' should effectively
> affect 'switch-to-prev-buffer' only.  'tab-line-switch-to-prev-tab'
> would not switch to a buffer never shown in that window and could be
> implemented as you sketched above.
>
> To sum up, I think that we should not mingle the concepts of
> wrapping/cycling and restricting into one and the same option but
> provide two options instead.

Mixing wrapping/cycling and restricting is unavoidable:
you can see how the previous patch reorders prev/next buffers
to make the order of tabs stay the same after wrapping.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-09 16:37                                                     ` Juri Linkov
@ 2024-04-10  8:46                                                       ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-10 17:45                                                         ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-10  8:46 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> I think that if we added a 'switch-to-prev-buffer-wrap' option, we would
 >> emulate the behavior sketched above regardless of whether it's triggered
 >> by 'switch-to-prev-buffer' or 'tab-line-switch-to-prev-tab'.  Currently,
 >> 'switch-to-prev-buffer' always wraps while 'tab-line-switch-to-prev-tab'
 >> obeys the value of 'tab-line-switch-cycling'.
 >
 > The users won't understand how currently 'switch-to-prev-buffer' always wraps,
 > when going to the previous element at the beginning of a list
 > doesn't go to the last element of the tab-line.

Suppose I'm at the first element of a tab list.  With wrapping/cycling
'switch-to-prev-buffer' will select some buffer and that buffer will be
shown as current in the tab list.  With restricting it will be the last
element of the tab list, without restricting it may be a buffer that's
new on the tab list, probably shown at its beginning.  Since
'tab-line-switch-to-prev-tab' restricts by default, it will always
select the last buffer on the tab list.  What am I missing?

 >> 'tab-line-switch-cycling' could then be used to override the more
 >> general 'switch-to-prev-buffer-wrap' when calling
 >> 'tab-line-switch-to-prev-tab'.  Or we could alias
 >> 'tab-line-switch-cycling' to 'switch-to-prev-buffer-wrap'.  And we could
 >> use the term 'switch-to-prev-buffer-cycle' instead.
 >
 > I think "cycle" is a wrong name.  Cycling is using
 > 'switch-to-prev-buffer'.  But wrapping is going
 > from the beginning to the end of the tab-line.

In my understanding, the elements of the tab line are a visual
representation of a "window-local" subset of all buffers.  This subset
is ordered in a way that does not necessarily match the order used by
'switch-to-prev-buffer' - when C-x b-ing to a buffer already on the tab
line, the tab line order does not change while the order of buffers used
by C-x <left> and C-x <right> may change.  Other than that, cycling and
wrapping behave identically wrt their respective sets.

 > Mixing wrapping/cycling and restricting is unavoidable:
 > you can see how the previous patch reorders prev/next buffers
 > to make the order of tabs stay the same after wrapping.

The order of tabs is one thing, that of buffers on the buffer list
another and that of buffers to be visited by 'switch-to-prev-buffer' and
'switch-to-next-buffer' a third one.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-10  8:46                                                       ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-10 17:45                                                         ` Juri Linkov
  2024-04-11  9:18                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-10 17:45 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

> Suppose I'm at the first element of a tab list.  With wrapping/cycling
> 'switch-to-prev-buffer' will select some buffer and that buffer will be
> shown as current in the tab list.

The selected buffer will be the first element of a tab list, not the last
as wrapping should do.  So currently 'switch-to-prev-buffer' doesn't do
wrapping.  Before 'switch-to-prev-buffer' the leftmost tab is selected,
and after 'switch-to-prev-buffer' the leftmost tab will remain selected.
Whereas the proper wrapping should select the rightmost tab/buffer.

> With restricting it will be the last element of the tab list, without
> restricting it may be a buffer that's new on the tab list, probably
> shown at its beginning.

When a buffer not previously displayed in the window appears at the
beginning, this is not wrapping.  Also when all available buffers were
already displayed in the window, and so restriction has no effect,
and an already displayed buffer appears at the beginning, this is
not wrapping too.

> Since 'tab-line-switch-to-prev-tab' restricts by default, it will
> always select the last buffer on the tab list.  What am I missing?

Unfortunately 'tab-line-switch-to-prev-tab' doesn't restrict by default.
This is a big problem for users that needs to be fixed.  Therefore
the wrapping option was proposed either for 'switch-to-prev-buffer'
or for 'tab-line-switch-to-prev-tab'.

>>> 'tab-line-switch-cycling' could then be used to override the more
>>> general 'switch-to-prev-buffer-wrap' when calling
>>> 'tab-line-switch-to-prev-tab'.  Or we could alias
>>> 'tab-line-switch-cycling' to 'switch-to-prev-buffer-wrap'.  And we could
>>> use the term 'switch-to-prev-buffer-cycle' instead.
>>
>> I think "cycle" is a wrong name.  Cycling is using
>> 'switch-to-prev-buffer'.  But wrapping is going
>> from the beginning to the end of the tab-line.
>
> In my understanding, the elements of the tab line are a visual
> representation of a "window-local" subset of all buffers.  This subset
> is ordered in a way that does not necessarily match the order used by
> 'switch-to-prev-buffer' - when C-x b-ing to a buffer already on the tab
> line, the tab line order does not change while the order of buffers used
> by C-x <left> and C-x <right> may change.  Other than that, cycling and
> wrapping behave identically wrt their respective sets.

By default the tab-line uses the order of window prev/next buffers.
So maybe it would be easier to keep the order in tab-line.el.

>> Mixing wrapping/cycling and restricting is unavoidable:
>> you can see how the previous patch reorders prev/next buffers
>> to make the order of tabs stay the same after wrapping.
>
> The order of tabs is one thing, that of buffers on the buffer list
> another and that of buffers to be visited by 'switch-to-prev-buffer' and
> 'switch-to-next-buffer' a third one.

Currently the first thing is the same as the third thing,
and this works fine.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-10 17:45                                                         ` Juri Linkov
@ 2024-04-11  9:18                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-12  6:35                                                             ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-11  9:18 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> Suppose I'm at the first element of a tab list.  With wrapping/cycling
 >> 'switch-to-prev-buffer' will select some buffer and that buffer will be
 >> shown as current in the tab list.
 >
 > The selected buffer will be the first element of a tab list, not the last
 > as wrapping should do.  So currently 'switch-to-prev-buffer' doesn't do
 > wrapping.  Before 'switch-to-prev-buffer' the leftmost tab is selected,
 > and after 'switch-to-prev-buffer' the leftmost tab will remain selected.
 > Whereas the proper wrapping should select the rightmost tab/buffer.

I meant 'switch-to-prev-buffer' which must be allowed to switch to the
previous buffer it finds and that could be _any_ buffer on the tab line
and, without "restricting", a buffer that is not even on the tab line.

 >> Since 'tab-line-switch-to-prev-tab' restricts by default, it will
 >> always select the last buffer on the tab list.  What am I missing?
 >
 > Unfortunately 'tab-line-switch-to-prev-tab' doesn't restrict by default.

I thought we agreed that it should do so with an appropriate binding.

 > This is a big problem for users that needs to be fixed.  Therefore
 > the wrapping option was proposed either for 'switch-to-prev-buffer'
 > or for 'tab-line-switch-to-prev-tab'.

Once it uses the binding it can either wrap or not.  I see no problem
here.

 > By default the tab-line uses the order of window prev/next buffers.
 > So maybe it would be easier to keep the order in tab-line.el.

But this would mean to change the ordering of elements on the tab line
whenever C-x b switches to a buffer already present on the tab line.

 >> The order of tabs is one thing, that of buffers on the buffer list
 >> another and that of buffers to be visited by 'switch-to-prev-buffer' and
 >> 'switch-to-next-buffer' a third one.
 >
 > Currently the first thing is the same as the third thing,
 > and this works fine.

IIUC it isn't, given the C-x b example.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-11  9:18                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-12  6:35                                                             ` Juri Linkov
  2024-04-12  8:37                                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-12  6:35 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> By default the tab-line uses the order of window prev/next buffers.
>> So maybe it would be easier to keep the order in tab-line.el.
>
> But this would mean to change the ordering of elements on the tab line
> whenever C-x b switches to a buffer already present on the tab line.

Indeed, some hook is needed to restore the previous order after C-x b.
Maybe 'window-buffer-change-functions' like you suggested.

>>> The order of tabs is one thing, that of buffers on the buffer list
>>> another and that of buffers to be visited by 'switch-to-prev-buffer' and
>>> 'switch-to-next-buffer' a third one.
>>
>> Currently the first thing is the same as the third thing,
>> and this works fine.
>
> IIUC it isn't, given the C-x b example.

So keeping the stable order of window prev/next buffers after C-x b
with a hook should be implemented in tab-line.el, not in window.el?





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-12  6:35                                                             ` Juri Linkov
@ 2024-04-12  8:37                                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-12 16:23                                                                 ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-12  8:37 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 >> But this would mean to change the ordering of elements on the tab line
 >> whenever C-x b switches to a buffer already present on the tab line.
 >
 > Indeed, some hook is needed to restore the previous order after C-x b.
 > Maybe 'window-buffer-change-functions' like you suggested.

And I think that in such case 'tab-line-switch-to-prev-tab' and
'switch-to-prev-buffer' should simply show different buffers.  That is,
'tab-line-switch-to-prev-tab' should _not_ use 'window-prev-buffers' to
get the buffer to switch to but simply use the buffer represented by the
tab visually preceding the current one on the tab line (stopping or
using the last one on the tab line if there is no preceding one).

 > So keeping the stable order of window prev/next buffers after C-x b
 > with a hook should be implemented in tab-line.el, not in window.el?

I don't know how you currently handle C-x <left>, C-x b or
'rename-buffer' or whether a buffer is modified on the tab line so I
can't tell whether you would need a hook for these.  But this issue is
IMHO not connected to whether getting the previous or next buffer should
wrap or be restricted to buffers previously shown in a window.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-12  8:37                                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-12 16:23                                                                 ` Juri Linkov
  2024-04-14 16:15                                                                   ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-12 16:23 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>> But this would mean to change the ordering of elements on the tab line
>>> whenever C-x b switches to a buffer already present on the tab line.
>>
>> Indeed, some hook is needed to restore the previous order after C-x b.
>> Maybe 'window-buffer-change-functions' like you suggested.
>
> And I think that in such case 'tab-line-switch-to-prev-tab' and
> 'switch-to-prev-buffer' should simply show different buffers.  That is,
> 'tab-line-switch-to-prev-tab' should _not_ use 'window-prev-buffers' to
> get the buffer to switch to but simply use the buffer represented by the
> tab visually preceding the current one on the tab line (stopping or
> using the last one on the tab line if there is no preceding one).

'C-x <left>/<right>' is too nice keybinding to lose ;-)
Therefore attempting to use it for navigating tab buffers.

>> So keeping the stable order of window prev/next buffers after C-x b
>> with a hook should be implemented in tab-line.el, not in window.el?
>
> I don't know how you currently handle C-x <left>, C-x b or
> 'rename-buffer' or whether a buffer is modified on the tab line so I
> can't tell whether you would need a hook for these.  But this issue is
> IMHO not connected to whether getting the previous or next buffer should
> wrap or be restricted to buffers previously shown in a window.

Handling 'C-x b' and 'rename-buffer' is not yet implemented.
Probably need to add a new window parameter to keep a list
of tab buffers and sync it with window prev/next buffers.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-12 16:23                                                                 ` Juri Linkov
@ 2024-04-14 16:15                                                                   ` Juri Linkov
  2024-04-16  6:38                                                                     ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-14 16:15 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

[-- Attachment #1: Type: text/plain, Size: 738 bytes --]

>>> So keeping the stable order of window prev/next buffers after C-x b
>>> with a hook should be implemented in tab-line.el, not in window.el?
>>
>> I don't know how you currently handle C-x <left>, C-x b or
>> 'rename-buffer' or whether a buffer is modified on the tab line so I
>> can't tell whether you would need a hook for these.  But this issue is
>> IMHO not connected to whether getting the previous or next buffer should
>> wrap or be restricted to buffers previously shown in a window.
>
> Handling 'C-x b' and 'rename-buffer' is not yet implemented.
> Probably need to add a new window parameter to keep a list
> of tab buffers and sync it with window prev/next buffers.

Now handling 'C-x b' is completely implemented here:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: tab-line-buffer-change.patch --]
[-- Type: text/x-diff, Size: 3678 bytes --]

diff --git a/lisp/tab-line.el b/lisp/tab-line.el
index 54e9ee16243..776d33de3e2 100644
--- a/lisp/tab-line.el
+++ b/lisp/tab-line.el
@@ -843,26 +857,9 @@ tab-line-select-tab
               (force-mode-line-update))))))))
 
 (defun tab-line-select-tab-buffer (buffer &optional window)
-  (let* ((window-buffer (window-buffer window))
-         (next-buffers (seq-remove (lambda (b) (eq b window-buffer))
-                                   (window-next-buffers window)))
-         (prev-buffers (seq-remove (lambda (b) (eq b window-buffer))
-                                   (mapcar #'car (window-prev-buffers window))))
-         ;; Remove next-buffers from prev-buffers
-         (prev-buffers (seq-difference prev-buffers next-buffers)))
-    (cond
-     ((and (eq tab-line-tabs-function #'tab-line-tabs-window-buffers)
-           (memq buffer next-buffers))
-      (dotimes (_ (1+ (seq-position next-buffers buffer)))
-        (switch-to-next-buffer window)))
-     ((and (eq tab-line-tabs-function #'tab-line-tabs-window-buffers)
-           (memq buffer prev-buffers))
-      (dotimes (_ (1+ (seq-position prev-buffers buffer)))
-        (switch-to-prev-buffer window)))
-     (t
-      (with-selected-window window
-        (let ((switch-to-buffer-obey-display-actions nil))
-          (switch-to-buffer buffer)))))))
+  (with-selected-window window
+    (let ((switch-to-buffer-obey-display-actions nil))
+      (switch-to-buffer buffer))))
 
 (defcustom tab-line-switch-cycling nil
   "Enable cycling tab switch.
@@ -919,6 +916,26 @@ tab-line-switch-to-next-tab
             (let ((switch-to-buffer-obey-display-actions nil))
               (switch-to-buffer buffer))))))))
 
+(defun tab-line-buffer-change (_window)
+  (let* ((new-buffer (window-buffer))
+         (old-buffers (window-parameter nil 'tab-line-window-buffers))
+         (prev-buffers (window-prev-buffers))
+         (next-buffers (memq new-buffer old-buffers)))
+    (when next-buffers
+      (set-window-next-buffers nil (seq-filter (lambda (b)
+                                                 (assq b prev-buffers))
+                                               (cdr next-buffers))))
+    (set-window-prev-buffers
+     nil (sort prev-buffers
+               (lambda (a b)
+                 (cond
+                  ((eq (car a) new-buffer) nil)
+                  ((eq (car b) new-buffer) t)
+                  (t (< (length (memq (car a) old-buffers))
+                        (length (memq (car b) old-buffers))))))))
+    (set-window-parameter nil 'tab-line-window-buffers
+                          (tab-line-tabs-window-buffers))))
+
 \f
 (defcustom tab-line-close-tab-function 'bury-buffer
   "What to do upon closing a tab on the tab line.
@@ -1025,13 +1042,17 @@ tab-line-mode
   "Toggle display of tab line in the windows displaying the current buffer."
   :lighter nil
   (let ((default-value '(:eval (tab-line-format))))
-    (if tab-line-mode
-        ;; Preserve the existing tab-line set outside of this mode
-        (unless tab-line-format
-          (setq tab-line-format default-value))
+    (cond
+     (tab-line-mode
+      (add-hook 'window-buffer-change-functions #'tab-line-buffer-change nil t)
+      ;; Preserve the existing tab-line set outside of this mode
+      (unless tab-line-format
+        (setq tab-line-format default-value)))
+     (t
+      (remove-hook 'window-buffer-change-functions #'tab-line-buffer-change t)
       ;; Reset only values set by this mode
       (when (equal tab-line-format default-value)
-        (setq tab-line-format nil)))))
+        (setq tab-line-format nil))))))
 
 (defcustom tab-line-exclude-modes
   '(completion-list-mode)

^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-14 16:15                                                                   ` Juri Linkov
@ 2024-04-16  6:38                                                                     ` Juri Linkov
  2024-04-17 17:56                                                                       ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-16  6:38 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>>>> So keeping the stable order of window prev/next buffers after C-x b
>>>> with a hook should be implemented in tab-line.el, not in window.el?
>>>
>>> I don't know how you currently handle C-x <left>, C-x b or
>>> 'rename-buffer' or whether a buffer is modified on the tab line so I
>>> can't tell whether you would need a hook for these.  But this issue is
>>> IMHO not connected to whether getting the previous or next buffer should
>>> wrap or be restricted to buffers previously shown in a window.
>>
>> Handling 'C-x b' and 'rename-buffer' is not yet implemented.
>> Probably need to add a new window parameter to keep a list
>> of tab buffers and sync it with window prev/next buffers.
>
> Now handling 'C-x b' is completely implemented here:
>
> +    (set-window-prev-buffers
> ...
> +      (add-hook 'window-buffer-change-functions #'tab-line-buffer-change nil t)

Actually using hooks as well as advices in core packages is not a nice
thing to do.  Neither tab-bar.el nor tab-line.el uses hooks and advices.
Also better not to mess with set-window-prev-buffers.  And this is
perfectly possible with the following small patch that supports
everything: wrapping, C-x <left>, C-x b, 'rename-buffer' with
buffers restricted to those that were displayed in the window
while keeping them in the fixed order:

diff --git a/lisp/tab-line.el b/lisp/tab-line.el
index 54e9ee16243..b2f0cbf13ed 100644
--- a/lisp/tab-line.el
+++ b/lisp/tab-line.el
@@ -345,6 +345,8 @@ tab-line-tabs-function
 grouped by `tab-line-tabs-buffer-group-function'."
   :type '(choice (const :tag "Window buffers"
                         tab-line-tabs-window-buffers)
+                 (const :tag "Window buffers with fixed order"
+                        tab-line-tabs-fixed-window-buffers)
                  (const :tag "Same mode buffers"
                         tab-line-tabs-mode-buffers)
                  (const :tag "Grouped buffers"
@@ -515,6 +524,17 @@ tab-line-tabs-window-buffers
             (list buffer)
             next-buffers)))
 
+(defun tab-line-tabs-fixed-window-buffers ()
+  "Like `tab-line-tabs-window-buffers' but keep stable sorting order."
+  (let ((old-buffers (window-parameter nil 'tab-line-fixed-window-buffers))
+        (new-buffers (tab-line-tabs-window-buffers)))
+    (setq new-buffers (sort new-buffers
+                            (lambda (a b)
+                              (> (length (memq a old-buffers))
+                                 (length (memq b old-buffers))))))
+    (set-window-parameter nil 'tab-line-fixed-window-buffers new-buffers)
+    new-buffers))





^ permalink raw reply related	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-16  6:38                                                                     ` Juri Linkov
@ 2024-04-17 17:56                                                                       ` Juri Linkov
  2024-04-24 16:39                                                                         ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-17 17:56 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

close 69993 30.0.50
thanks

> Actually using hooks as well as advices in core packages is not a nice
> thing to do.  Neither tab-bar.el nor tab-line.el uses hooks and advices.
> Also better not to mess with set-window-prev-buffers.  And this is
> perfectly possible with the following small patch that supports
> everything: wrapping, C-x <left>, C-x b, 'rename-buffer' with
> buffers restricted to those that were displayed in the window
> while keeping them in the fixed order:

So now this is added to tab-line.el and closed.

Thanks for helping to arrive at this solution.





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-17 17:56                                                                       ` Juri Linkov
@ 2024-04-24 16:39                                                                         ` Juri Linkov
  2024-04-25  8:31                                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 45+ messages in thread
From: Juri Linkov @ 2024-04-24 16:39 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

> So now this is added to tab-line.el and closed.

There is one problem with this line:

  (add-to-list 'window-persistent-parameters '(tab-line-buffers . writable))

This line is needed to be able to use 'window-swap-states'
because window-state-get/window-state-put should move the window
parameter 'tab-line-buffers' to the swapped window.

But the problem is that it writes to the desktop:

  (parameters (tab-line-buffers "Unprintable entity" "Unprintable entity" ...)

One possible solution would be to save buffer names instead of buffers
in the window parameter 'tab-line-buffers', but will lose the ability
to handle buffer renamings without changing the fixed order.

Another solution would be to add special handling of the window
parameter 'tab-line-buffers' to 'window--state-get-1' like
there is special handling of 'prev-buffers' and 'next-buffers'
that uses 'buffer-name' for them.  Doesn't look right.

A third solution would be to support a lambda in
'window-persistent-parameters' that will process
the parameter before saving.  For example:

  (add-to-list 'window-persistent-parameters
               '(tab-line-buffers . (lambda (p) (mapcar #'buffer-name p))))

Maybe this is the best thing to do?





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-24 16:39                                                                         ` Juri Linkov
@ 2024-04-25  8:31                                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2024-04-25 17:40                                                                             ` Juri Linkov
  0 siblings, 1 reply; 45+ messages in thread
From: martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-04-25  8:31 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 69993

 > There is one problem with this line:
 >
 >    (add-to-list 'window-persistent-parameters '(tab-line-buffers . writable))
 >
 > This line is needed to be able to use 'window-swap-states'
 > because window-state-get/window-state-put should move the window
 > parameter 'tab-line-buffers' to the swapped window.

Why does it have to be "writable"?  'window-swap-states' doesn't care
about "writability".  If you really want something writable too, add a
second parameter, say 'tab-line-buffer-names', with the names in it.

 > A third solution would be to support a lambda in
 > 'window-persistent-parameters' that will process
 > the parameter before saving.  For example:
 >
 >    (add-to-list 'window-persistent-parameters
 >                 '(tab-line-buffers . (lambda (p) (mapcar #'buffer-name p))))
 >
 > Maybe this is the best thing to do?

You would call that function in window.el here

		    (when (and pers (or (not writable) (eq pers 'writable)))
		      (setq list (cons (cons (car par) (cdr par)) list)))))

iff WRITABLE is non-nil and you would not call it here in window.c

		    /* If the window has a value for the parameter,
		       save it.  */
		    p->window_parameters = Fcons (Fcons (XCAR (par), XCDR (par)),
					   p->window_parameters);

I suppose.

martin





^ permalink raw reply	[flat|nested] 45+ messages in thread

* bug#69993: Wrap window buffers while cycling
  2024-04-25  8:31                                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2024-04-25 17:40                                                                             ` Juri Linkov
  0 siblings, 0 replies; 45+ messages in thread
From: Juri Linkov @ 2024-04-25 17:40 UTC (permalink / raw)
  To: martin rudalics; +Cc: 69993

>> There is one problem with this line:
>>
>>    (add-to-list 'window-persistent-parameters '(tab-line-buffers . writable))
>>
>> This line is needed to be able to use 'window-swap-states'
>> because window-state-get/window-state-put should move the window
>> parameter 'tab-line-buffers' to the swapped window.
>
> Why does it have to be "writable"?  'window-swap-states' doesn't care
> about "writability".

Sorry, I don't know why I missed that cdr can be 't'
instead of 'writable'.  Now fixed.

>> A third solution would be to support a lambda in
>> 'window-persistent-parameters' that will process
>> the parameter before saving.  For example:
>>
>>    (add-to-list 'window-persistent-parameters
>>                 '(tab-line-buffers . (lambda (p) (mapcar #'buffer-name p))))
>
> You would call that function in window.el here
>
> 		    (when (and pers (or (not writable) (eq pers 'writable)))
> 		      (setq list (cons (cons (car par) (cdr par)) list)))))
>
> iff WRITABLE is non-nil and you would not call it here in window.c
>
> 		    /* If the window has a value for the parameter,
> 		       save it.  */
> 		    p->window_parameters = Fcons (Fcons (XCAR (par), XCDR (par)),
> 					   p->window_parameters);

This might be needed later for some other window parameters.





^ permalink raw reply	[flat|nested] 45+ messages in thread

end of thread, other threads:[~2024-04-25 17:40 UTC | newest]

Thread overview: 45+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-25  7:42 bug#69993: Wrap window buffers while cycling Juri Linkov
2024-03-25  9:41 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-03-25 17:16   ` Juri Linkov
2024-03-26  9:56     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-03-27  7:20       ` Juri Linkov
2024-03-27  8:48         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-03-28  7:54           ` Juri Linkov
2024-03-28  9:19             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-03-28 17:57               ` Juri Linkov
2024-03-29  8:45                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-03-29 16:35                   ` Juri Linkov
2024-03-30  9:37                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-03-30 18:24                       ` Juri Linkov
2024-03-31  8:32                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-02  6:37                           ` Juri Linkov
2024-04-02  8:22                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-02 16:28                               ` Juri Linkov
2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-03 17:45                                   ` Juri Linkov
2024-04-04  8:03                                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-05  6:45                                       ` Juri Linkov
2024-04-05  9:08                                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-05 16:32                                           ` Juri Linkov
2024-04-06 18:43                                             ` Juri Linkov
2024-04-07  8:23                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-09  6:35                                                 ` Juri Linkov
2024-04-09  9:04                                                   ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-09 16:37                                                     ` Juri Linkov
2024-04-10  8:46                                                       ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-10 17:45                                                         ` Juri Linkov
2024-04-11  9:18                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-12  6:35                                                             ` Juri Linkov
2024-04-12  8:37                                                               ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-12 16:23                                                                 ` Juri Linkov
2024-04-14 16:15                                                                   ` Juri Linkov
2024-04-16  6:38                                                                     ` Juri Linkov
2024-04-17 17:56                                                                       ` Juri Linkov
2024-04-24 16:39                                                                         ` Juri Linkov
2024-04-25  8:31                                                                           ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-25 17:40                                                                             ` Juri Linkov
2024-04-02 16:34                               ` Juri Linkov
2024-04-03  8:24                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-04-03 17:44                                   ` Juri Linkov
2024-04-04  6:22                                 ` Juri Linkov
2024-04-02 16:40                               ` Juri Linkov

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).