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))))) (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 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 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);