unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: martin rudalics via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org>
To: bjorn.bidar@thaodan.de, 59862@debbugs.gnu.org
Cc: juri@linkov.net
Subject: bug#59862: quit-restore per window buffer
Date: Wed, 17 Jul 2024 11:23:31 +0200	[thread overview]
Message-ID: <9dfeeabb-f421-4cb3-863c-6411e7c4b8a5@gmx.at> (raw)
In-Reply-To: <871q3tc7da.fsf@>

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

 > I meant window. Same as you explain further below in your message
 > I call quit-window on a dedicate window the frame also dies with it.

Note that if a window is dedicated, 'quit-restore-window' will already
do its best to delete the window together with its frame.

 > I think if the window inside of a frame didn't contain any other buffer
 > than the initial buffer and a new buffer that uses the same window
 > there could be the argument that the window should die and thus the
 > frame too.

I attach a patch (including all previous changes) that tries to do that.
In particular, evaluating

(custom-set-variables
  '(display-buffer-alist '(("\\*info\\*" display-buffer-pop-up-window)))
  '(quit-restore-window-no-switch t)
  '(frame-auto-hide-function 'delete-frame))

and doing C-x 5 2 then C-h i then C-x 0 and finally q, will delete the
second frame.

Alternatively, evaluating

(custom-set-variables
  '(display-buffer-alist '(("\\*info\\*" display-buffer-same-window)))
  '(quit-restore-window-no-switch 'skip-initial)
  '(frame-auto-hide-function 'delete-frame))

and doing C-x 5 2 then C-h i and q, will delete the second frame too.

Finally, evaluating

(custom-set-variables
  '(display-buffer-alist '(("\\*info\\*" display-buffer-same-window)))
  '(quit-restore-window-no-switch t)
  '(frame-auto-hide-function 'delete-frame))

and doing C-x 5 2 then C-h i then C-x k *scratch* and finally q will
also delete the second frame.

martin

[-- Attachment #2: windows-and-buffers.diff --]
[-- Type: text/x-patch, Size: 28470 bytes --]

diff --git a/lisp/window.el b/lisp/window.el
index 9fc2e5d65e8..ad552f5139e 100644
--- a/lisp/window.el
+++ b/lisp/window.el
@@ -4542,14 +4542,18 @@ record-window-buffer
       (push-window-buffer-onto-prev window)
       (run-hooks 'buffer-list-update-hook))))
 
+;; A version in C that handles dead windows (that presumably are part of
+;; a saved window configuration) lives in window.c and is called
+;; window_discard_buffer_from_dead_window.  We could call that from here
+;; but it seems more practical to handle live windows entirely in Elisp.
 (defun unrecord-window-buffer (&optional window buffer)
   "Unrecord BUFFER in WINDOW.
-WINDOW must be a live window and defaults to the selected one.
-BUFFER must be a live buffer and defaults to the buffer of
-WINDOW.
+WINDOW must be a live window and defaults to the selected one.  BUFFER
+must be a live buffer and defaults to the buffer of WINDOW (although
+that default hardly makes any sense).
 
-Make BUFFER disappear from all variables mentioned by the object of
-WINDOW.  This includes the buffers previously shwon in WINDOW as well as
+Make BUFFER disappear from most components specified by the object of
+WINDOW.  This includes the buffers previously shown in WINDOW as well as
 any buffers mentioned by WINDOW's `quit-restore' and `quit-restore-prev'
 parameters."
   (let* ((window (window-normalize-window window t))
@@ -4562,7 +4566,13 @@ unrecord-window-buffer
        window (assq-delete-all buffer (window-prev-buffers window)))
       (set-window-next-buffers
        window (delq buffer (window-next-buffers window)))
-
+      ;; If the fourth elements of the 'quit-restore' or
+      ;; 'quit-restore-prev' parameters equal BUFFER, these parameters
+      ;; become useless - in 'quit-restore-window' the fourth element
+      ;; must equal the buffer of WINDOW in order to use that parameter.
+      ;; If BUFFER is mentioned in the second element of the parameter,
+      ;; 'quit-restore-window' cannot possibly show BUFFER instead; so
+      ;; this parameter becomes useless too.
       (when (or (eq buffer (nth 3 quit-restore-prev))
 		(and (listp (setq quad (nth 1 quit-restore-prev)))
 		     (eq (car quad) buffer)))
@@ -4572,7 +4582,8 @@ unrecord-window-buffer
 		(and (listp (setq quad (nth 1 quit-restore)))
 		     (eq (car quad) buffer)))
 	(set-window-parameter
-	 window 'quit-restore (window-parameter window 'quit-restore-prev))))))
+	 window 'quit-restore (window-parameter window 'quit-restore-prev))
+	(set-window-parameter window 'quit-restore-prev nil)))))
 
 (defun set-window-buffer-start-and-point (window buffer &optional start point)
   "Set WINDOW's buffer to BUFFER.
@@ -5091,6 +5102,18 @@ previous-buffer
                  (not (or executing-kbd-macro noninteractive)))
         (user-error "No previous buffer"))))))
 
+(defcustom kill-buffer-quit-windows nil
+  "Non-nil means killing buffers shall quit windows.
+If this is nil, killing a buffer may delete dedicated windows only.  If
+this is non-nil, `kill-buffer' (and `replace-buffer-in-windows' in
+consequence) have `quit-restore-window' deal with any window showing the
+buffer to be killed which may delete the window if it's not dedicated to
+its buffer.  Also, `delete-windows-on' will use `quit-restore-window' as
+fallback when a window cannot be deleted otherwise."
+  :type 'boolean
+  :version "31.1"
+  :group 'windows)
+
 (defun delete-windows-on (&optional buffer-or-name frame)
   "Delete all windows showing BUFFER-OR-NAME.
 BUFFER-OR-NAME may be a buffer or the name of an existing buffer
@@ -5128,8 +5151,10 @@ delete-windows-on
 terminal, delete that frame.  Otherwise, do not delete a frame's root
 window if it shows the buffer specified by BUFFER-OR-NAME and do not
 delete any frame's main window showing that buffer either.  Rather, in
-any such case, call `quit-restore-window' to show another buffer in that
-window and make sure the window is no more dedicated to its buffer.
+any such case, call either `quit-restore-window' (provided
+`kill-buffer-quit-windows' is non-nil) or `switch-to-prev-buffer' to
+show another buffer in that window and make sure the window is no more
+dedicated to its buffer.
 
 If the buffer specified by BUFFER-OR-NAME is shown in a minibuffer
 window, do nothing for that window.  For any window that does not show
@@ -5166,9 +5191,18 @@ delete-windows-on
 	     ((eq deletable t)
 	      ;; Delete window.
 	      (delete-window window))
+	     (kill-buffer-quit-windows
+	      (quit-restore-window window 'bury)
+	      (when (window-live-p window)
+		;; Unrecord BUFFER in this window.
+		(unrecord-window-buffer window buffer)))
 	     (t
-	      (when (eq (window-buffer window) buffer)
-		(quit-restore-window window 'bury))
+	      ;; In window switch to previous buffer.
+	      (set-window-dedicated-p window nil)
+	      (switch-to-prev-buffer window 'bury)
+	      ;; Restore the dedicated 'side' flag.
+	      (when (eq dedicated 'side)
+                (set-window-dedicated-p window 'side))
 	      (when (window-live-p window)
 		;; Unrecord BUFFER in this window.
 		(unrecord-window-buffer window buffer)))))
@@ -5177,29 +5211,46 @@ delete-windows-on
 
 (defun replace-buffer-in-windows (&optional buffer-or-name)
   "Replace BUFFER-OR-NAME with some other buffer in all windows showing it.
-BUFFER-OR-NAME may be a buffer or the name of an existing buffer
-and defaults to the current buffer.  Minibuffer windows are not
-considered.
-
-With the exception of side windows, when a window showing BUFFER-OR-NAME
-is dedicated, that window is deleted.  If that window is the only window
-on its frame, the frame is deleted too when there are other frames left.
-If there are no other frames left, some other buffer is displayed in that
+BUFFER-OR-NAME may be a buffer or the name of an existing buffer and
+defaults to the current buffer.  Minibuffer windows are not considered.
+
+If the option `kill-buffer-quit-windows' is nil, behave as follows: With
+the exception of side windows, when a window showing BUFFER-OR-NAME is
+dedicated, delete that window.  If that window is the only window on its
+frame, delete its frame when there are other frames left.  In any other
+case, call `switch-to-prev-buffer' to display some other buffer in that
 window.
 
-This function removes the buffer denoted by BUFFER-OR-NAME from all
-window-local buffer lists and removes any `quit-restore' or
-`quit-restore-prev' parameters mentioning it."
+If `kill-buffer-quit-windows' is non-nil, call `quit-restore-window' for
+any window showing BUFFER-OR-NAME with the argument BURY-OR-KILL set to
+`killing' to avoid that the latter kills the buffer prematurely.
+
+In either case, remove the buffer denoted by BUFFER-OR-NAME from the
+lists of previous and next buffers of all windows and remove any
+`quit-restore' or `quit-restore-prev' parameters mentioning it.
+
+This function is called by `kill-buffer' which kills the buffer
+specified by `buffer-or-name' afterwards.  It never kills a buffer by
+itself."
   (interactive "bBuffer to replace: ")
   (let ((buffer (window-normalize-buffer buffer-or-name)))
-    ;; Scan all windows.  We have to unrecord BUFFER in those not
-    ;; showing it.
+    ;; Scan all windows.  We have to unrecord BUFFER-OR-NAME in those
+    ;; not showing it.
     (dolist (window (window-list-1 nil nil t))
       (when (eq (window-buffer window) buffer)
-	(quit-restore-window window))
-      (when (window-live-p window)
-	;; Unrecord BUFFER in this window.
-	(unrecord-window-buffer window buffer)))))
+	(if kill-buffer-quit-windows
+	    (quit-restore-window window 'killing)
+	  (let ((dedicated-side (eq (window-dedicated-p window) 'side)))
+            (when (or dedicated-side (not (window--delete window t 'kill)))
+	      ;; Switch to another buffer in that window.
+	      (set-window-dedicated-p window nil)
+	      (if (switch-to-prev-buffer window 'kill)
+                  (and dedicated-side (set-window-dedicated-p window 'side))
+		(window--delete window nil 'kill))))))
+
+	(when (window-live-p window)
+	  ;; Unrecord BUFFER in this window.
+	  (unrecord-window-buffer window buffer)))))
 
 (defcustom quit-window-hook nil
   "Hook run before performing any other actions in the `quit-window' command."
@@ -5207,6 +5258,23 @@ quit-window-hook
   :version "27.1"
   :group 'windows)
 
+(defcustom quit-restore-window-no-switch nil
+  "Non-nil means `quit-restore-window' preferably won't switch buffers.
+If this is nil, `quit-restore-window' will call `switch-to-prev-buffer'
+unless the window has been made by `display-buffer'.  If this is t,
+`quit-restore-window' will switch to a previous buffer only if a live
+buffer exists that was previously shown in that window.  If this is the
+symbol `skip-initial', it will not switch to the initial buffer of the
+window's `quit-restore' parameter.
+
+In either case, if `quit-restore-window' doesn't switch to a previous
+buffer, it will try to delete the window (and maybe its frame) instead.
+Note also that if a window is dedicated, `quit-restore-window' will
+usually not switch to a previous buffer in it."
+  :type 'boolean
+  :version "31.1"
+  :group 'windows)
+
 (defun window--quit-restore-select-window (window)
   "Select WINDOW after having quit another one.
 Do not select an inactive minibuffer window."
@@ -5227,9 +5295,10 @@ quit-restore-window
 used to restore the previously shown buffer.  See Info node `(elisp)
 Quitting Windows' for more details.
 
-If WINDOW's dedicated flag is t, try to delete WINDOW.  If it
-equals the value `side', restore that value when WINDOW is not
-deleted.
+If WINDOW's dedicated flag is t, try to delete WINDOW.  If it equals the
+value `side', restore that value when WINDOW is not deleted.  Whether
+WINDOW or its frame get deleted can be further controlled via the option
+`quit-restore-window-no-switch'.
 
 Optional second argument BURY-OR-KILL tells how to proceed with
 the buffer of WINDOW.  The following values are handled:
@@ -5249,7 +5318,14 @@ quit-restore-window
   most reliable remedy to not have `switch-to-prev-buffer' switch
   to this buffer again without killing the buffer.
 
-`kill' means to kill WINDOW's buffer."
+`kill' means to kill WINDOW's buffer.
+
+`killing' is like `kill' but means that WINDOW's buffer will get killed
+elsewhere.  This value is used by `replace-buffer-in-windows' and
+`quit-windows-on'.
+
+`burying' is like `bury' but means that WINDOW's buffer will get buried
+elsewhere.  This value is used by `quit-windows-on'."
   (setq window (window-normalize-window window t))
   (let* ((buffer (window-buffer window))
 	 (quit-restore (window-parameter window 'quit-restore))
@@ -5265,32 +5341,38 @@ quit-restore-window
     (cond
      ;; First try to delete dedicated windows that are not side windows.
      ((and dedicated (not (eq dedicated 'side))
-           (window--delete window 'dedicated (eq bury-or-kill 'kill)))
+           (window--delete
+	    window 'dedicated (memq bury-or-kill '(kill killing))))
       ;; If the previously selected window is still alive, select it.
       (window--quit-restore-select-window quit-restore-2))
      ((and (not prev-buffer)
 	   (eq (nth 1 quit-restore) 'tab)
-	   (eq (nth 3 quit-restore) buffer))
+	   (eq (nth 3 quit-restore) buffer)
+	   (< (seq-count (lambda (w) (window-parameter w 'quit-restore))
+		         (window-list-1 nil 'nomini))
+              2))
       (tab-bar-close-tab)
       ;; If the previously selected window is still alive, select it.
       (window--quit-restore-select-window quit-restore-2))
-     ((and (not prev-buffer)
-	   (or (and (or (eq (nth 1 quit-restore) 'frame)
-			(and (eq (nth 1 quit-restore) 'window)
-			     ;; If the window has been created on an
-			     ;; existing frame and ended up as the sole
-			     ;; window on that frame, do not delete it
-			     ;; (Bug#12764).
-			     (not (eq window (frame-root-window window)))))
-		    (eq (nth 3 quit-restore) buffer))
-	       (and (or (eq (nth 1 quit-restore-prev) 'frame)
-			(and (eq (nth 1 quit-restore-prev) 'window)
-			     (not (eq window (frame-root-window window)))))
-		    (eq (nth 3 quit-restore-prev) buffer)
-		    ;; Use selected window from quit-restore-prev.
-		    (setq quit-restore-2 quit-restore-prev-2)))
+     ((and (or (not prev-buffer)
+	       ;; Ignore first of the previous buffers if
+	       ;; 'quit-restore-window-no-switch' says so.
+	       (and (eq quit-restore-window-no-switch 'skip-initial)
+		    (not (cdr (window-prev-buffers window)))))
+	   (or (eq (nth 1 quit-restore) 'frame)
+	       ;; If the window has been created on an existing
+	       ;; frame and ended up as the sole window on that
+	       ;; frame, do not delete it (Bug#12764).
+	       (and (eq (nth 1 quit-restore) 'window)
+		    (not (eq window (frame-root-window window))))
+	       ;; But always allow deleting a frame or window if
+	       ;; 'quit-restore-window-no-switch' says so.
+	       quit-restore-window-no-switch)
+	   (or (eq (nth 3 quit-restore) buffer)
+	       quit-restore-window-no-switch)
 	   ;; Delete WINDOW if possible.
-	   (window--delete window nil (eq bury-or-kill 'kill)))
+	   (window--delete
+	    window nil (memq bury-or-kill '(kill killing))))
       ;; If the previously selected window is still alive, select it.
       (window--quit-restore-select-window quit-restore-2))
      ((or (and (listp (setq quad (nth 1 quit-restore-prev)))
@@ -5328,7 +5410,7 @@ quit-restore-window
       ;; Deal with the buffer we just removed from WINDOW.
       (setq entry (and (eq bury-or-kill 'append)
 		       (assq buffer (window-prev-buffers window))))
-      (when bury-or-kill
+      (when (memq bury-or-kill '(bury burying kill killing))
 	;; Remove buffer from WINDOW's previous and next buffers.
 	(set-window-prev-buffers
 	 window (assq-delete-all buffer (window-prev-buffers window)))
@@ -5346,7 +5428,6 @@ quit-restore-window
 	;; If quit-restore-prev was not used, reset the quit-restore
 	;; parameter
 	(set-window-parameter window 'quit-restore nil))
-      ;; Select old window.
       ;; If the previously selected window is still alive, select it.
       (window--quit-restore-select-window quit-restore-2))
      (t
@@ -5359,13 +5440,14 @@ quit-restore-window
       (if (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)))))
+        (window--delete
+	 window nil (memq bury-or-kill '(kill killing))))))
     ;; Deal with the buffer.
     (cond
      ((not (buffer-live-p buffer)))
      ((eq bury-or-kill 'kill)
       (kill-buffer buffer))
-     (bury-or-kill
+     ((eq bury-or-kill 'bury)
       (bury-buffer-internal buffer)))))
 
 (defun quit-window (&optional kill window)
@@ -5406,11 +5488,18 @@ quit-windows-on
 	(frames (cond ((not frame) t) ((eq frame t) nil) (t frame))))
     (dolist (window (window-list-1 nil nil frames))
       (when (eq (window-buffer window) buffer)
-	(quit-restore-window window kill))
+	(quit-restore-window
+	 window (if kill 'killing 'burying)))
 
       (when (window-live-p window)
 	;; Unrecord BUFFER in this window.
-	(unrecord-window-buffer window buffer)))))
+	(unrecord-window-buffer window buffer)))
+
+    ;; Deal with BUFFER-OR-NAME.
+    (cond
+     ((not (buffer-live-p buffer)))
+     (kill (kill-buffer buffer))
+     (t (bury-buffer-internal buffer)))))
 \f
 (defun window--combination-resizable (parent &optional horizontal)
   "Return number of pixels recoverable from height of window PARENT.
@@ -6723,36 +6812,38 @@ display-buffer-record-window
 
 If TYPE is `reuse', BUFFER is different from the one currently displayed
 in WINDOW, and WINDOW already has a `quit-restore' parameter, install or
-update a `quit-restore-prev' parameter for this window that allows to
-quit WINDOW in a similar fashion but remembers the very first in a
-series of buffer display operations for this window."
+update a `quit-restore-prev' parameter for this window.  This allows for
+quitting WINDOW in a similar fashion but also keeps the very first
+`quit-restore' parameter stored for this window around.  Consequently,
+WINDOW (or its frame) can be eventually deleted by `quit-restore-widow'
+if that parameter's fourth element equals WINDOW's buffer."
   (cond
    ((eq type 'reuse)
-    (if (eq (window-buffer window) buffer)
-	;; WINDOW shows BUFFER already.  Update WINDOW's quit-restore
-	;; parameter, if any.
-	(let ((quit-restore (window-parameter window 'quit-restore)))
+    (let ((quit-restore (window-parameter window 'quit-restore)))
+      (if (eq (window-buffer window) buffer)
+	  ;; WINDOW shows BUFFER already.  Update WINDOW's quit-restore
+	  ;; parameter, if any.
 	  (when (consp quit-restore)
 	    (setcar quit-restore 'same)
 	    ;; The selected-window might have changed in
 	    ;; between (Bug#20353).
 	    (unless (or (eq window (selected-window))
-                        (eq window (nth 2 quit-restore)))
-	      (setcar (cddr quit-restore) (selected-window)))))
-      ;; WINDOW shows another buffer.
-      (with-current-buffer (window-buffer window)
-	(set-window-parameter
-	 window 'quit-restore-prev
-	 (list 'other
-	       ;; A quadruple of WINDOW's buffer, start, point and height.
-	       (list (current-buffer) (window-start window)
-		     ;; Preserve window-point-insertion-type (Bug#12855).
-		     (copy-marker
-		      (window-point window) window-point-insertion-type)
-		     (if (window-combined-p window)
-                         (window-total-height window)
-                       (window-total-width window)))
-	       (selected-window) buffer)))))
+			(eq window (nth 2 quit-restore)))
+	      (setcar (cddr quit-restore) (selected-window))))
+	;; WINDOW shows another buffer.
+	(with-current-buffer (window-buffer window)
+	  (set-window-parameter
+	   window (if quit-restore 'quit-restore-prev 'quit-restore)
+	   (list 'other
+		 ;; A quadruple of WINDOW's buffer, start, point and height.
+		 (list (current-buffer) (window-start window)
+		       ;; Preserve window-point-insertion-type (Bug#12855).
+		       (copy-marker
+			(window-point window) window-point-insertion-type)
+		       (if (window-combined-p window)
+                           (window-total-height window)
+			 (window-total-width window)))
+		 (selected-window) buffer))))))
    ((eq type 'window)
     ;; WINDOW has been created on an existing frame.
     (set-window-parameter
diff --git a/src/alloc.c b/src/alloc.c
index 666f77bfce1..b955651f5c0 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -6998,33 +6998,6 @@ mark_face_cache (struct face_cache *c)
     }
 }
 
-/* Remove killed buffers or items whose car is a killed buffer from
-   LIST, and mark other items.  Return changed LIST, which is marked.  */
-
-static Lisp_Object
-mark_discard_killed_buffers (Lisp_Object list)
-{
-  Lisp_Object tail, *prev = &list;
-
-  for (tail = list; CONSP (tail) && !cons_marked_p (XCONS (tail));
-       tail = XCDR (tail))
-    {
-      Lisp_Object tem = XCAR (tail);
-      if (CONSP (tem))
-	tem = XCAR (tem);
-      if (BUFFERP (tem) && !BUFFER_LIVE_P (XBUFFER (tem)))
-	*prev = XCDR (tail);
-      else
-	{
-	  set_cons_marked (XCONS (tail));
-	  mark_object (XCAR (tail));
-	  prev = xcdr_addr (tail);
-	}
-    }
-  mark_object (tail);
-  return list;
-}
-
 static void
 mark_frame (struct Lisp_Vector *ptr)
 {
@@ -7079,15 +7052,6 @@ mark_window (struct Lisp_Vector *ptr)
       mark_glyph_matrix (w->current_matrix);
       mark_glyph_matrix (w->desired_matrix);
     }
-
-  /* Filter out killed buffers from both buffer lists
-     in attempt to help GC to reclaim killed buffers faster.
-     We can do it elsewhere for live windows, but this is the
-     best place to do it for dead windows.  */
-  wset_prev_buffers
-    (w, mark_discard_killed_buffers (w->prev_buffers));
-  wset_next_buffers
-    (w, mark_discard_killed_buffers (w->next_buffers));
 }
 
 /* Entry of the mark stack.  */
diff --git a/src/buffer.c b/src/buffer.c
index 744b0ef5548..6ec40aff646 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2012,6 +2012,13 @@ DEFUN ("kill-buffer", Fkill_buffer, Skill_buffer, 0, 1, "bKill buffer: ",
      buffer (bug#10114).  */
   replace_buffer_in_windows (buffer);
 
+  /* For dead windows that have not been collected yet, remove this
+     buffer from those windows' lists of previously and next shown
+     buffers and remove any 'quit-restore' or 'quit-restore-prev'
+     parameters mentioning the buffer.  */
+  if (XFIXNUM (BVAR (b, display_count)) > 0)
+    window_discard_buffer_from_dead_windows (buffer);
+
   /* Exit if replacing the buffer in windows has killed our buffer.  */
   if (!BUFFER_LIVE_P (b))
     return Qt;
diff --git a/src/window.c b/src/window.c
index ff28bac5306..dd0abe84fdf 100644
--- a/src/window.c
+++ b/src/window.c
@@ -3277,6 +3277,90 @@ window_pixel_to_total (Lisp_Object frame, Lisp_Object horizontal)
 }
 
 
+/* Remove first occurrence of element whose car is BUFFER from ALIST.
+   Return changed ALIST.  */
+static Lisp_Object
+window_discard_buffer_from_alist (Lisp_Object buffer, Lisp_Object alist)
+{
+  Lisp_Object tail, *prev = &alist;
+
+  for (tail = alist; CONSP (tail); tail = XCDR (tail))
+    {
+      Lisp_Object tem = XCAR (tail);
+
+      tem = XCAR (tem);
+
+      if (EQ (tem, buffer))
+	{
+	  *prev = XCDR (tail);
+	  break;
+	}
+      else
+	prev = xcdr_addr (tail);
+    }
+
+  return alist;
+}
+
+/* Remove first occurrence of BUFFER from LIST.  Return changed
+   LIST.  */
+static Lisp_Object
+window_discard_buffer_from_list (Lisp_Object buffer, Lisp_Object list)
+{
+  Lisp_Object tail, *prev = &list;
+
+  for (tail = list; CONSP (tail); tail = XCDR (tail))
+    {
+      if (EQ (XCAR (tail), buffer))
+	{
+	  *prev = XCDR (tail);
+	  break;
+	}
+      else
+	prev = xcdr_addr (tail);
+    }
+
+  return list;
+}
+
+static void
+window_discard_buffer_from_dead_window (Lisp_Object buffer, Lisp_Object window)
+{
+  struct window *w = XWINDOW (window);
+  Lisp_Object quit_restore = window_parameter (w, Qquit_restore);
+  Lisp_Object quit_restore_prev = window_parameter (w, Qquit_restore_prev);
+  Lisp_Object quad;
+
+  wset_prev_buffers
+    (w, window_discard_buffer_from_alist (buffer, w->prev_buffers));
+  wset_next_buffers
+    (w, window_discard_buffer_from_list (buffer, w->next_buffers));
+
+  if (EQ (buffer, Fnth (make_fixnum (3), quit_restore_prev))
+      || (CONSP (quad = Fcar (Fcdr (quit_restore_prev)))
+	  && EQ (Fcar (quad), buffer)))
+    Fset_window_parameter (window, Qquit_restore_prev, Qnil);
+
+  if (EQ (buffer, Fnth (make_fixnum (3), quit_restore))
+      || (CONSP (quad = Fcar (Fcdr (quit_restore)))
+	  && EQ (Fcar (quad), buffer)))
+    {
+      Fset_window_parameter (window, Qquit_restore,
+			     window_parameter (w, Qquit_restore_prev));
+      Fset_window_parameter (window, Qquit_restore_prev, Qnil);
+    }
+}
+
+void
+window_discard_buffer_from_dead_windows (Lisp_Object buffer)
+{
+  struct Lisp_Hash_Table *h = XHASH_TABLE (window_dead_windows_table);
+
+  DOHASH (h, k, v)
+    window_discard_buffer_from_dead_window (buffer, v);
+}
+
+
 DEFUN ("delete-other-windows-internal", Fdelete_other_windows_internal,
        Sdelete_other_windows_internal, 0, 2, "",
        doc: /* Make WINDOW fill its frame.
@@ -4402,6 +4486,10 @@ make_parent_window (Lisp_Object window, bool horflag)
   wset_buffer (p, Qnil);
   wset_combination (p, horflag, window);
   wset_combination_limit (p, Qnil);
+  /* Reset any previous and next buffers of p which have been installed
+     by the memcpy above.  */
+  wset_prev_buffers (p, Qnil);
+  wset_next_buffers (p, Qnil);
   wset_window_parameters (p, Qnil);
 }
 
@@ -4426,10 +4514,6 @@ make_window (void)
   wset_vertical_scroll_bar_type (w, Qt);
   wset_horizontal_scroll_bar_type (w, Qt);
   wset_cursor_type (w, Qt);
-  /* These Lisp fields are marked specially so they're not set to nil by
-     allocate_window.  */
-  wset_prev_buffers (w, Qnil);
-  wset_next_buffers (w, Qnil);
 
   /* Initialize non-Lisp data.  Note that allocate_window zeroes out all
      non-Lisp data, so do it only for slots which should not be zero.  */
@@ -5252,6 +5336,11 @@ DEFUN ("delete-window-internal", Fdelete_window_internal, Sdelete_window_interna
 	  unchain_marker (XMARKER (w->old_pointm));
 	  unchain_marker (XMARKER (w->start));
 	  wset_buffer (w, Qnil);
+	  /* Add WINDOW to table of dead windows so when killing a buffer
+	     WINDOW mentions, all references to that buffer can be removed
+	     and the buffer be collected.  */
+	  Fputhash (make_fixnum (w->sequence_number),
+		    window, window_dead_windows_table);
 	}
 
       if (NILP (s->prev) && NILP (s->next))
@@ -7356,6 +7445,10 @@ DEFUN ("set-window-configuration", Fset_window_configuration,
 		}
 	    }
 
+	  /* Remove window from the table of dead windows.  */
+	  Fremhash (make_fixnum (w->sequence_number),
+		    window_dead_windows_table);
+
 	  if ((NILP (dont_set_miniwindow) || !MINI_WINDOW_P (w))
 	      && BUFFERP (p->buffer) && BUFFER_LIVE_P (XBUFFER (p->buffer)))
 	    /* If saved buffer is alive, install it, unless it's a
@@ -7585,6 +7678,11 @@ delete_all_child_windows (Lisp_Object window)
 	 possible resurrection in Fset_window_configuration.  */
       wset_combination_limit (w, w->contents);
       wset_buffer (w, Qnil);
+      /* Add WINDOW to table of dead windows so when killing a buffer
+	 WINDOW mentions, all references to that buffer can be removed
+	 and the buffer be collected.  */
+      Fputhash (make_fixnum (w->sequence_number),
+		window, window_dead_windows_table);
     }
 
   Vwindow_list = Qnil;
@@ -8594,6 +8692,8 @@ syms_of_window (void)
   DEFSYM (Qconfiguration, "configuration");
   DEFSYM (Qdelete, "delete");
   DEFSYM (Qdedicated, "dedicated");
+  DEFSYM (Qquit_restore, "quit-restore");
+  DEFSYM (Qquit_restore_prev, "quit-restore-prev");
 
   DEFVAR_LISP ("temp-buffer-show-function", Vtemp_buffer_show_function,
 	       doc: /* Non-nil means call as function to display a help buffer.
@@ -8917,6 +9017,17 @@ syms_of_window (void)
 displayed after a scrolling operation to be somewhat inaccurate.  */);
   fast_but_imprecise_scrolling = false;
 
+  DEFVAR_LISP ("window-dead-windows-table", window_dead_windows_table,
+    doc: /* Hash table of dead windows.
+Each entry in this table maps a window number to a window object.
+Entries are added by `delete-window-internal' and are removed by the
+garbage collector.
+
+This table is maintained by code in window.c and is made visible in
+Elisp for testing purposes only.  */);
+  window_dead_windows_table
+    = CALLN (Fmake_hash_table, QCweakness, Qt);
+
   defsubr (&Sselected_window);
   defsubr (&Sold_selected_window);
   defsubr (&Sminibuffer_window);
diff --git a/src/window.h b/src/window.h
index 86932181252..335e0a3453e 100644
--- a/src/window.h
+++ b/src/window.h
@@ -142,6 +142,12 @@ #define WINDOW_H_INCLUDED
        as well.  */
     Lisp_Object contents;
 
+    /* A list of <buffer, window-start, window-point> triples listing
+       buffers previously shown in this window.  */
+    Lisp_Object prev_buffers;
+    /* List of buffers re-shown in this window.  */
+    Lisp_Object next_buffers;
+
     /* The old buffer of this window, set to this window's buffer by
        run_window_change_functions every time it sees this window.
        Unused for internal windows.  */
@@ -218,14 +224,6 @@ #define WINDOW_H_INCLUDED
     struct glyph_matrix *current_matrix;
     struct glyph_matrix *desired_matrix;
 
-    /* The two Lisp_Object fields below are marked in a special way,
-       which is why they're placed after `current_matrix'.  */
-    /* A list of <buffer, window-start, window-point> triples listing
-       buffers previously shown in this window.  */
-    Lisp_Object prev_buffers;
-    /* List of buffers re-shown in this window.  */
-    Lisp_Object next_buffers;
-
     /* Number saying how recently window was selected.  */
     EMACS_INT use_time;
 
@@ -1228,6 +1226,7 @@ #define CHECK_LIVE_WINDOW(WINDOW)				\
 extern void wset_buffer (struct window *, Lisp_Object);
 extern bool window_outdated (struct window *);
 extern ptrdiff_t window_point (struct window *w);
+extern void window_discard_buffer_from_dead_windows (Lisp_Object);
 extern void init_window_once (void);
 extern void init_window (void);
 extern void syms_of_window (void);

  parent reply	other threads:[~2024-07-17  9:23 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-06 17:32 bug#59862: quit-restore per window buffer Juri Linkov
2024-06-02  6:45 ` Juri Linkov
2024-06-03  9:34   ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-03  9:53     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-03 16:09       ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-04  6:53         ` Juri Linkov
2024-06-05 16:56           ` Juri Linkov
2024-06-11  6:52           ` Juri Linkov
2024-06-12  8:57             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-13  6:47               ` Juri Linkov
2024-06-13  8:21                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-14 17:35                   ` Juri Linkov
2024-06-15  8:41                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-06-16 16:50                       ` Juri Linkov
2024-06-17 14:48                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-08 16:49                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-09  6:58                           ` Juri Linkov
2024-07-09  8:52                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-10  6:50                               ` Juri Linkov
2024-07-10  9:16                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-11  6:47                                   ` Juri Linkov
2024-07-11  8:36                                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-12  6:54                                       ` Juri Linkov
2024-07-12  8:20                                         ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-14  7:49                                           ` Juri Linkov
2024-07-15  7:32                                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-16  6:09                                               ` Björn Bidar via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-16  6:09                                               ` Björn Bidar via Bug reports for GNU Emacs, the Swiss army knife of text editors
     [not found]                                               ` <87frs9kgve.fsf@>
2024-07-16  8:22                                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-16 22:14                                                   ` Björn Bidar via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-16 22:14                                                   ` Björn Bidar via Bug reports for GNU Emacs, the Swiss army knife of text editors
     [not found]                                                   ` <871q3tc7da.fsf@>
2024-07-17  9:23                                                     ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors [this message]
2024-07-30  8:20                                             ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-07-31 17:30                                               ` Juri Linkov
2024-08-01  6:37                                                 ` Juri Linkov
2024-08-01  7:50                                                   ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-08-01  7:49                                                 ` martin rudalics via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-08-01 16:01                                                   ` Juri Linkov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9dfeeabb-f421-4cb3-863c-6411e7c4b8a5@gmx.at \
    --to=bug-gnu-emacs@gnu.org \
    --cc=59862@debbugs.gnu.org \
    --cc=bjorn.bidar@thaodan.de \
    --cc=juri@linkov.net \
    --cc=rudalics@gmx.at \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).