1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-04-27 08:43:40 -07:00

Add frame-use-time, get-mru-frame, use mru frame in delete-frame (bug#80397)

* lisp/frame.el (get-mru-frame): New defun.
* src/frame.c (delete_frame): Call 'get-mru-frame' (when force
is not Qnoelisp) to select the most recently used frame that is
not the deleted frame as the candidate to select.
(syms_of_frame): Qget_mru_frame new DEFSYM.
* doc/lispref/frames.texi: Document the new functions.
* etc/NEWS: Announce the new functions.
This commit is contained in:
Stéphane Marks 2026-02-11 19:01:44 -05:00 committed by Juri Linkov
parent f31c61a9aa
commit 54d0764665
4 changed files with 181 additions and 36 deletions

View file

@ -3188,6 +3188,55 @@ could switch to a different terminal without switching back when
you're done.
@end deffn
@cindex most recently used frame
@defun get-mru-frame &optional all-frames exclude
This function is like @code{get-mru-window}, but it returns the most
recently used frame. The @dfn{most recently used frame} is the frame
with the most recently selected window as computed by
@code{get-mru-window}. (@pxref{Selecting Windows})
This function will return @code{nil} if no candidate frames are found,
for example, if there is only one frame. Tooltip, minibuffer only, and
child frames are never candidates.
Since in practice the most recently used frame is always the selected
one, it usually makes sense to call this function with a non-@code{nil}
@var{exclude} argument with, for example, the selected frame.
The optional argument @var{all-frames} specifies which frames to
consider:
@itemize @bullet
@item @code{visible}
means to consider all visible frames on the current terminal or
@var{exclude}'s terminal.
@item 0
means to consider all visible or iconified frames on the current
terminal or @var{exclude}'s terminal.
@item Anything else
means to consider all frames.
@end itemize
If @code{get-mru-frame} is called from within the body of
@code{with-selected-frame}, it may yield unexpected results as the most
recently used window will not necessarily match the selected one in the
temporarily selected frame. (@pxref{Selecting Windows})
@end defun
@cindex frame use time
@cindex use time of frame
@cindex frame order by time of last use
@defun frame-use-time &optional frame
This function returns the use time of frame @var{frame}. @var{frame}
must be a live frame and defaults to the selected one. The result is
the @code{window-use-time} of @var{frame}'s most recently used window
computed by @code{get-mru-window}. (@pxref{Selecting Windows})
@end defun
@deffn Command select-frame-by-id id &optional noerror
This function searches open and undeletable frames for a matching frame
identifier @var{id} (@pxref{Frames}). If found, its frame is undeleted,

View file

@ -494,6 +494,27 @@ fail to delete its argument FRAME or might signal an error. It is
therefore advisable to use this function as part of a condition that
determines whether to call 'delete-frame'.
+++
*** New function 'frame-use-time'.
This function is the frame equivalent of the function 'window-use-time'
for a window. The result is the `window-use-time' of a frame's most
recently used window.
+++
*** New function 'get-mru-frame'.
This function returns the most recently used frame from among all
frames, or those visible or iconified on the same terminal as the
selected frame. A frame can be excluded, for example, the selected
frame, as it will always be the most recently used frame on the current
terminal.
---
*** After deleting, 'delete-frame' now selects the most recently used frame.
Previously, after deleting a specified frame, 'delete-frame' would
select the next eligible frame in the order of the frame list. This is
often unrelated to frames' recent use and can appear somewhat random to
users.
+++
*** New value 'force' for user option 'frame-inhibit-implied-resize'.
This will inhibit implied resizing while a new frame is made and can be

View file

@ -2610,6 +2610,60 @@ for FRAME."
(or (/= (window-old-pixel-width root) (window-pixel-width root))
(/= (+ (window-old-pixel-height root) mini-old-height)
(+ (window-pixel-height root) mini-height)))))
(defun frame-use-time (&optional frame)
"Return FRAME's last use time.
The result is the `window-use-time' of FRAME's most recently used window
computed by `get-mru-window'. If optional FRAME is nil, use the
selected frame."
(window-use-time
;; Arguments to `get-mru-window' consider all windows.
(get-mru-window (window-normalize-frame frame) t t t)))
(defun get-mru-frame (&optional all-frames exclude)
"Return the most recently used frame on frames specified by ALL-FRAMES.
Compute the result from each frame's most recently used window computed
by `get-mru-window', which see. Return nil if no candidate frames are
found, for example, if there is only one frame. Tooltip, minibuffer
only, and child frames are never candidates. Optional argument EXCLUDE,
when non-nil, is a frame to exclude, for example, the selected frame.
The following non-nil values of the optional argument ALL-FRAMES
have special meanings:
- `visible' means consider all visible frames on the current terminal
or EXCLUDE's terminal if non-nil.
- 0 (the number zero) means consider all all visible and iconified
frames on the current terminal or EXCLUDE's terminal if non-nil.
Any other value of ALL-FRAMES means consider all frames."
(setq all-frames (or all-frames t))
(let* ((mru-frame)
(time)
(best-time 0)
(terminal (frame-terminal (or exclude (selected-frame))))
(frame-list
(seq-remove (lambda (frame)
(or (eq frame exclude)
(eq (frame-parameter frame 'minibuffer) 'only)
(frame-parent frame)))
(cond
((eq all-frames 'visible)
(seq-filter (lambda (frame)
(eq (frame-terminal frame) terminal))
(visible-frame-list)))
((eq all-frames 0)
(seq-filter (lambda (frame)
(eq (frame-terminal frame) terminal))
(frame-list)))
(t (frame-list))))))
(dolist (frame frame-list)
(setq time (frame-use-time frame))
(when (> time best-time)
(setq best-time time)
(setq mru-frame frame)))
mru-frame))
;;;; Frame/display capabilities.

View file

@ -2748,57 +2748,77 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
do_switch_frame (mru_rooted_frame (f), 0, 1, Qnil);
else
{
Lisp_Object tail;
eassume (CONSP (Vframe_list));
/* Look for another visible frame on the same terminal.
Do not call next_frame here because it may loop forever.
See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=15025. */
FOR_EACH_FRAME (tail, frame1)
frame1 = Qnil;
if (!EQ (force, Qnoelisp))
{
struct frame *f1 = XFRAME (frame1);
if (!EQ (frame, frame1)
&& !FRAME_TOOLTIP_P (f1)
&& FRAME_TERMINAL (f) == FRAME_TERMINAL (f1)
&& FRAME_VISIBLE_P (f1))
break;
/* Find the most recently used visible frame among all
frames on the same terminal as FRAME, excluding FRAME
which we are about to delete. */
frame1 = calln (Qget_mru_frame, Qvisible, frame);
if (!NILP (frame1))
{
struct frame *f1 = XFRAME (frame1);
if (FRAME_TOOLTIP_P (f1)
|| FRAME_TERMINAL (f) != FRAME_TERMINAL (f1)
|| !FRAME_VISIBLE_P (f1))
frame1 = Qnil;
}
}
/* If there is none, find *some* other frame. */
if (NILP (frame1) || EQ (frame1, frame))
if (NILP (frame1))
{
Lisp_Object tail;
eassume (CONSP (Vframe_list));
/* Look for another visible frame on the same terminal.
Do not call next_frame here because it may loop forever.
See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=15025. */
FOR_EACH_FRAME (tail, frame1)
{
struct frame *f1 = XFRAME (frame1);
if (!EQ (frame, frame1)
&& FRAME_LIVE_P (f1)
&& !FRAME_TOOLTIP_P (f1))
{
if (FRAME_TERMCAP_P (f1) || FRAME_MSDOS_P (f1))
{
Lisp_Object top_frame = FRAME_TTY (f1)->top_frame;
&& !FRAME_TOOLTIP_P (f1)
&& FRAME_TERMINAL (f) == FRAME_TERMINAL (f1)
&& FRAME_VISIBLE_P (f1))
break;
}
if (!EQ (top_frame, frame))
frame1 = top_frame;
/* If there is none, find *some* other frame. */
if (NILP (frame1) || EQ (frame1, frame))
{
FOR_EACH_FRAME (tail, frame1)
{
struct frame *f1 = XFRAME (frame1);
if (!EQ (frame, frame1)
&& FRAME_LIVE_P (f1)
&& !FRAME_TOOLTIP_P (f1))
{
if (FRAME_TERMCAP_P (f1) || FRAME_MSDOS_P (f1))
{
Lisp_Object top_frame = FRAME_TTY (f1)->top_frame;
if (!EQ (top_frame, frame))
frame1 = top_frame;
}
break;
}
break;
}
}
}
#ifdef NS_IMPL_COCOA
else
{
/* Under NS, there is no system mechanism for choosing a new
window to get focus -- it is left to application code.
So the portion of THIS application interfacing with NS
needs to make the frame we switch to the key window. */
struct frame *f1 = XFRAME (frame1);
if (FRAME_NS_P (f1))
ns_make_frame_key_window (f1);
}
else
{
/* Under NS, there is no system mechanism for choosing a new
window to get focus -- it is left to application code.
So the portion of THIS application interfacing with NS
needs to make the frame we switch to the key window. */
struct frame *f1 = XFRAME (frame1);
if (FRAME_NS_P (f1))
ns_make_frame_key_window (f1);
}
#endif
}
do_switch_frame (frame1, 0, 1, Qnil);
sf = SELECTED_FRAME ();
@ -7185,6 +7205,7 @@ syms_of_frame (void)
DEFSYM (Qframe_monitor_attributes, "frame-monitor-attributes");
DEFSYM (Qwindow__pixel_to_total, "window--pixel-to-total");
DEFSYM (Qmake_initial_minibuffer_frame, "make-initial-minibuffer-frame");
DEFSYM (Qget_mru_frame, "get-mru-frame");
DEFSYM (Qexplicit_name, "explicit-name");
DEFSYM (Qheight, "height");
DEFSYM (Qicon, "icon");