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: Juri Linkov <juri@linkov.net>
Cc: 59862@debbugs.gnu.org
Subject: bug#59862: quit-restore per window buffer
Date: Mon, 8 Jul 2024 18:49:52 +0200	[thread overview]
Message-ID: <ca1388d2-3481-49c4-8721-42792a399340@gmx.at> (raw)
In-Reply-To: <86plsgrfvu.fsf@mail.linkov.net>

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

It took me some time to rewrite the code for this since I found a couple
of leaks in the window handling code which I tried to plug now.  In a
nutshell, the new patch is supposed to fix the following issues.

- I introduced a new variable called 'kill-buffer-quit-windows'.  If
   non-nil, it makes killing a buffer call 'quit-restore-window' for each
   window showing the buffer thus implementing the behavior you want.  If
   nil, killing a buffer works as before apart from the fixes I added
   below.

- The new window parameter type 'quit-restore-prev' that was already in
   the previous patch handles most scenarios of traversing an entire
   chain of buffer display operations back to the one that initially
   created a window or a frame.  The only thing it does not handle is to
   restore the previously selected window for all but the first and last
   such operation.

- As in the previous patch I remove a buffer from all live windows'
   previous/next buffers and quit-restore(-prev) parameters when killing
   it.  In the present patch I remove it also from dead windows (windows
   presumably stored in window configurations if they survived at least
   one collection cycle after their deletion).  This should eliminate the
   necessity to scan these lists separately in the mark phase of the
   collector.

- The old code erroneously has an internal window share the lists of
   previous/next buffers of a live window whenever that internal window
   is created by splitting the live window.  If the live window is
   subsequently deleted but the internal window persists, these lists
   will continue to exist until the internal window is deleted.  Since
   you cannot switch buffers in internal windows, these lists are
   completely useless while increasing memory overhead.  The patch fixes
   this leak.

Dead windows are kept in a weak hash table, adding a window when it is
deleted and removing it when restoring it from a saved window
configuration.  The collector is supposed to remove all dead windows
whose configurations have died in its finalization phase.  For testing
purposes, this hash table is accessible from Lisp via the variable
'window-dead-windows-table'.

In earlier versions of Emacs, window configurations were mostly used to
handle window excursions and were consequently rather short-lived.  Your
tab bar code has changed that.  Hence we should try to avoid any leaks
introduced by long-lived configurations.

The hash table based code should also work for the incremental (MPS
based) collector once it is capable of handling weak hash tables
correctly.  The present approach to emulate the above mentioned separate
mark steps in Elisp is completely inadequate in this regard since it
cannot identify dead windows.

Please test the patch intensively and report any problems you see.  I
used the forms below to test the various features of the patch.

martin

(display-buffer (get-buffer-create "*foo*"))
(display-buffer (get-buffer-create "*bar*"))

(setq kill-buffer-quit-windows t)

(kill-buffer "*bar*")
(kill-buffer "*foo*")

(setq foo (current-window-configuration))
(set-window-configuration foo)
(setq foo nil) ; To reclaim all dead windows of foo

(garbage-collect)

(hash-table-count window-dead-windows-table)

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

diff --git a/lisp/window.el b/lisp/window.el
index 9fc2e5d65e8..0a82aa9989d 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."
@@ -5249,7 +5300,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 +5323,30 @@ 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)))
+	   (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)
 	   ;; 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 +5384,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)))
@@ -5359,13 +5415,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 +5463,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,9 +6787,11 @@ 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)
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-08 16:49 UTC|newest]

Thread overview: 32+ 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 [this message]
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

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=ca1388d2-3481-49c4-8721-42792a399340@gmx.at \
    --to=bug-gnu-emacs@gnu.org \
    --cc=59862@debbugs.gnu.org \
    --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).