1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-12-25 10:47:00 +00:00

Prevent open minibuffers getting lost when their frame gets deleted

This happened with minibuffer-follows-selected-frame set to t.

* doc/emacs/mini.texi (Basic Minibuffer): State where a command's action takes
place when a minibuffer's frame has been deleted.

* lisp/window.el (window--before-delete-windows, record-window-buffer): Take
into account that minibuffers are now recorded on w->prev_buffers field.

* src/fns.c (merge_c): New version of `merge' taking a C function, rather than
a Lisp function as the comparison function.

* src/frame.c (do_switch_frame): Pass arguments sf and for_deletion to
move_minibuffers_onnto_frame.

* src/lisp.h (top level): Declare merge_c and
move_minibuffers_onto_selected_frame.

* src/minibuf.c (MB_frame): New Lisp_Object recording the minibuffer's frame.
(choose_minibuf_frame): Remove all code except that which sets minibuf_window
to the current frame's minibuffer.
(minibuffer_ent_greater): New comparison function, passed to merge_c.
(zip_minibuffer_stacks): New function.
(move_minibuffers_onto_frame): Renamed from `move_minibuffer_onto_frame' given
two arguments, the old frame and for_deletion, and simplified.  Minibuffers
are now stacked in the mini-window's ->prev_buffers field.
(read_minibuf): Several detailed amendments.
(exp_MB_frame): New Lisp_Object, the expired minibuffer's frame.
(read_minibuf_unwind): Search for the expired minibuffer's frame, rather than
taking it from (unreliable) variables.  Switch temporarily to this frame for
tidying up operations.
(minibuffer_unwind): New function which pops a stacked minibuffer.
(syms_of_minibuf): Call staticpro for the two new Lisp variables.

* src/window.c (Fset_window_configuration): Don't record minibuffers with
record-window-buffer.

* src/xdisp.c (gui_consider_frame_title): Remove redundant Fselect_window,
which caused an unwanted frame switch.  Amend the arguments to
format_mode_line_unwind_data to match.
This commit is contained in:
Alan Mackenzie 2021-03-21 16:54:31 +00:00
parent e276810dff
commit 7c2ebf6e23
8 changed files with 243 additions and 117 deletions

View File

@ -82,7 +82,9 @@ after a recursive minibuffer has been opened in the current command
(@pxref{Recursive Mini,,, elisp}). This option is mainly to retain
(approximately) the behavior prior to Emacs 28.1. Note that the
effect of the command, when you finally finish using the minibuffer,
always takes place in the frame where you first opened it.
always takes place in the frame where you first opened it. The sole
exception is that when that frame no longer exists, the action takes
place in the currently selected frame.
@node Minibuffer File
@section Minibuffers for File Names

View File

@ -4158,7 +4158,7 @@ returned by `window-start' and `window-point' respectively.
This function is called only if `switch-to-buffer-preserve-window-point'
evaluates non-nil."
(dolist (win (window-list))
(dolist (win (window-list nil 'no-minibuf))
(let* ((buf (window-buffer (or window win)))
(start (window-start win))
(pos (window-point win))
@ -4416,7 +4416,8 @@ WINDOW must be a live window and defaults to the selected one."
window (assq-delete-all buffer (window-prev-buffers window))))
;; Don't record insignificant buffers.
(unless (eq (aref (buffer-name buffer) 0) ?\s)
(when (or (not (eq (aref (buffer-name buffer) 0) ?\s))
(minibufferp buffer))
;; Add an entry for buffer to WINDOW's previous buffers.
(with-current-buffer buffer
(let ((start (window-start window))

View File

@ -2279,6 +2279,52 @@ merge (Lisp_Object org_l1, Lisp_Object org_l2, Lisp_Object pred)
}
}
Lisp_Object
merge_c (Lisp_Object org_l1, Lisp_Object org_l2, bool (*less) (Lisp_Object, Lisp_Object))
{
Lisp_Object l1 = org_l1;
Lisp_Object l2 = org_l2;
Lisp_Object tail = Qnil;
Lisp_Object value = Qnil;
while (1)
{
if (NILP (l1))
{
if (NILP (tail))
return l2;
Fsetcdr (tail, l2);
return value;
}
if (NILP (l2))
{
if (NILP (tail))
return l1;
Fsetcdr (tail, l1);
return value;
}
Lisp_Object tem;
if (less (Fcar (l1), Fcar (l2)))
{
tem = l1;
l1 = Fcdr (l1);
org_l1 = l1;
}
else
{
tem = l2;
l2 = Fcdr (l2);
org_l2 = l2;
}
if (NILP (tail))
value = tem;
else
Fsetcdr (tail, tem);
tail = tem;
}
}
/* This does not check for quits. That is safe since it must terminate. */

View File

@ -1487,7 +1487,7 @@ do_switch_frame (Lisp_Object frame, int track, int for_deletion, Lisp_Object nor
#endif
internal_last_event_frame = Qnil;
move_minibuffer_onto_frame ();
move_minibuffers_onto_frame (sf, for_deletion);
return frame;
}

View File

@ -3610,6 +3610,7 @@ extern void validate_subarray (Lisp_Object, Lisp_Object, Lisp_Object,
extern Lisp_Object substring_both (Lisp_Object, ptrdiff_t, ptrdiff_t,
ptrdiff_t, ptrdiff_t);
extern Lisp_Object merge (Lisp_Object, Lisp_Object, Lisp_Object);
extern Lisp_Object merge_c (Lisp_Object, Lisp_Object, bool (*) (Lisp_Object, Lisp_Object));
extern Lisp_Object do_yes_or_no_p (Lisp_Object);
extern int string_version_cmp (Lisp_Object, Lisp_Object);
extern Lisp_Object concat2 (Lisp_Object, Lisp_Object);
@ -4348,7 +4349,7 @@ extern void clear_regexp_cache (void);
extern Lisp_Object Vminibuffer_list;
extern Lisp_Object last_minibuf_string;
extern void move_minibuffer_onto_frame (void);
extern void move_minibuffers_onto_frame (struct frame *, bool);
extern bool is_minibuffer (EMACS_INT, Lisp_Object);
extern EMACS_INT this_minibuffer_depth (Lisp_Object);
extern EMACS_INT minibuf_level;

View File

@ -59,6 +59,12 @@ Lisp_Object last_minibuf_string;
static Lisp_Object minibuf_prompt;
/* The frame containinug the most recently opened Minibuffer. This is
used only when `minibuffer-follows-selected-frame' is neither nil
nor t. */
static Lisp_Object MB_frame;
/* Width of current mini-buffer prompt. Only set after display_line
of the line that contains the prompt. */
@ -67,6 +73,7 @@ static ptrdiff_t minibuf_prompt_width;
static Lisp_Object nth_minibuffer (EMACS_INT depth);
static EMACS_INT minibuf_c_loop_level (EMACS_INT depth);
static void set_minibuffer_mode (Lisp_Object buf, EMACS_INT depth);
static bool live_minibuffer_p (Lisp_Object);
/* Return TRUE when a frame switch causes a minibuffer on the old
@ -78,6 +85,7 @@ minibuf_follows_frame (void)
Qt);
}
#if 0
/* Return TRUE when a minibuffer always remains on the frame where it
was first invoked. */
static bool
@ -85,6 +93,7 @@ minibuf_stays_put (void)
{
return NILP (Fdefault_toplevel_value (Qminibuffer_follows_selected_frame));
}
#endif
/* Return TRUE when opening a (recursive) minibuffer causes
minibuffers on other frames to move to the selected frame. */
@ -112,84 +121,85 @@ choose_minibuf_frame (void)
emacs_abort ();
minibuf_window = sf->minibuffer_window;
/* If we've still got another minibuffer open, use its mini-window
instead. */
if (minibuf_level > 1 && minibuf_stays_put ())
{
Lisp_Object buffer = get_minibuffer (minibuf_level);
Lisp_Object tail, frame;
FOR_EACH_FRAME (tail, frame)
if (EQ (XWINDOW (XFRAME (frame)->minibuffer_window)->contents,
buffer))
{
minibuf_window = XFRAME (frame)->minibuffer_window;
break;
}
}
}
if (minibuf_moves_frame_when_opened ()
&& FRAMEP (selected_frame)
&& FRAME_LIVE_P (XFRAME (selected_frame)))
/* Make sure no other frame has a minibuffer as its selected window,
because the text would not be displayed in it, and that would be
confusing. Only allow the selected frame to do this,
and that only if the minibuffer is active. */
{
Lisp_Object tail, frame;
struct frame *of;
FOR_EACH_FRAME (tail, frame)
if (!EQ (frame, selected_frame)
&& minibuf_level > 1
/* The frame's minibuffer can be on a different frame. */
&& ! EQ (XWINDOW ((of = XFRAME (frame))->minibuffer_window)->frame,
selected_frame))
{
if (MINI_WINDOW_P (XWINDOW (FRAME_SELECTED_WINDOW (of))))
Fset_frame_selected_window (frame, Fframe_first_window (frame),
Qnil);
if (!EQ (XWINDOW (of->minibuffer_window)->contents,
nth_minibuffer (0)))
set_window_buffer (of->minibuffer_window,
nth_minibuffer (0), 0, 0);
}
}
}
/* If `minibuffer_follows_selected_frame' is t and we have a
minibuffer, move it from its current frame to the selected frame.
This function is intended to be called from `do_switch_frame' in
frame.c. */
void move_minibuffer_onto_frame (void)
/* If ENT1 has a higher minibuffer index than ENT2, return true. More
precisely, compare the buffer components of each window->prev_buffers
entry. */
static bool
minibuffer_ent_greater (Lisp_Object ent1, Lisp_Object ent2)
{
if (!minibuf_level)
return;
if (!minibuf_follows_frame ())
return;
if (FRAMEP (selected_frame)
&& FRAME_LIVE_P (XFRAME (selected_frame))
&& !EQ (minibuf_window, XFRAME (selected_frame)->minibuffer_window))
return this_minibuffer_depth (Fcar (ent1))
> this_minibuffer_depth (Fcar (ent2)) ;
}
/* Move the ordered "stack" of minibuffers from SOURCE_WINDOW to
DEST_WINDOW, interleaving those minibuffers with any in DEST_WINDOW
to produce an ordered combination. The ordering is by minibuffer
depth. A stack of minibuffers consists of the minibuffer currently
in DEST/SOURCE_WINDOW together with any recorded in the
->prev_buffers field of the struct window. */
static void
zip_minibuffer_stacks (Lisp_Object dest_window, Lisp_Object source_window)
{
struct window *dw = XWINDOW (dest_window);
struct window *sw = XWINDOW (source_window);
Lisp_Object acc;
Lisp_Object d_ent; /* Entry from dw->prev_buffers */
if (!live_minibuffer_p (dw->contents)
&& NILP (dw->prev_buffers))
{
EMACS_INT i;
struct frame *sf = XFRAME (selected_frame);
Lisp_Object old_frame = XWINDOW (minibuf_window)->frame;
struct frame *of = XFRAME (old_frame);
set_window_buffer (dest_window, sw->contents, 0, 0);
Fset_window_start (dest_window, Fwindow_start (source_window), Qnil);
Fset_window_point (dest_window, Fwindow_point (source_window));
dw->prev_buffers = sw->prev_buffers;
set_window_buffer (source_window, get_minibuffer (0), 0, 0);
sw->prev_buffers = Qnil;
return;
}
/* Stack up all the (recursively) open minibuffers on the selected
mini_window. */
for (i = 1; i <= minibuf_level; i++)
set_window_buffer (sf->minibuffer_window, nth_minibuffer (i), 0, 0);
minibuf_window = sf->minibuffer_window;
if (of != sf)
{
Lisp_Object temp = get_minibuffer (0);
if (live_minibuffer_p (dw->contents))
call1 (Qrecord_window_buffer, dest_window);
if (live_minibuffer_p (sw->contents))
call1 (Qrecord_window_buffer, source_window);
set_window_buffer (of->minibuffer_window, temp, 0, 0);
set_minibuffer_mode (temp, 0);
}
acc = merge_c (dw->prev_buffers, sw->prev_buffers, minibuffer_ent_greater);
if (!NILP (acc))
{
d_ent = Fcar (acc);
acc = Fcdr (acc);
set_window_buffer (dest_window, Fcar (d_ent), 0, 0);
Fset_window_start (dest_window, Fcar (Fcdr (d_ent)), Qnil);
Fset_window_point (dest_window, Fcar (Fcdr (Fcdr (d_ent))));
}
dw->prev_buffers = acc;
sw->prev_buffers = Qnil;
set_window_buffer (source_window, get_minibuffer (0), 0, 0);
}
/* If `minibuffer_follows_selected_frame' is t, or we're about to
delete a frame which potentially "contains" minibuffers, move them
from the old frame to the selected frame. This function is
intended to be called from `do_switch_frame' in frame.c. OF is the
old frame, FOR_DELETION is true if OF is about to be deleted. */
void
move_minibuffers_onto_frame (struct frame *of, bool for_deletion)
{
struct frame *f = XFRAME (selected_frame);
minibuf_window = f->minibuffer_window;
if (!(minibuf_level
&& (for_deletion || minibuf_follows_frame () || FRAME_INITIAL_P (of))))
return;
if (FRAME_LIVE_P (f)
&& !EQ (f->minibuffer_window, of->minibuffer_window))
{
zip_minibuffer_stacks (f->minibuffer_window, of->minibuffer_window);
if (for_deletion && XFRAME (MB_frame) != of)
MB_frame = selected_frame;
}
}
@ -221,6 +231,7 @@ without invoking the usual minibuffer commands. */)
/* Actual minibuffer invocation. */
static void read_minibuf_unwind (void);
static void minibuffer_unwind (void);
static void run_exit_minibuf_hook (void);
@ -544,7 +555,6 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
Lisp_Object histval;
Lisp_Object empty_minibuf;
Lisp_Object dummy, frame;
specbind (Qminibuffer_default, defalt);
specbind (Qinhibit_read_only, Qnil);
@ -626,17 +636,24 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
mini_frame = WINDOW_FRAME (XWINDOW (minibuf_window));
if (minibuf_level > 1
&& !EQ (XWINDOW (XFRAME (selected_frame)->minibuffer_window)->frame,
MB_frame)
&& minibuf_moves_frame_when_opened ()
&& (!minibuf_follows_frame ()
|| (!EQ (mini_frame, selected_frame))))
&& (!minibuf_follows_frame ()))
{
EMACS_INT i;
struct frame *of = XFRAME (MB_frame);
/* Stack up the existing minibuffers on the current mini-window */
for (i = 1; i < minibuf_level; i++)
set_window_buffer (minibuf_window, nth_minibuffer (i), 0, 0);
zip_minibuffer_stacks (minibuf_window, of->minibuffer_window);
/* MB_frame's minibuffer can be on a different frame. */
if (MINI_WINDOW_P (XWINDOW (FRAME_SELECTED_WINDOW (of))))
Fset_frame_selected_window (MB_frame,
Fframe_first_window (MB_frame), Qnil);
}
MB_frame = XWINDOW (XFRAME (selected_frame)->minibuffer_window)->frame;
if (live_minibuffer_p (XWINDOW (minibuf_window)->contents))
call1 (Qrecord_window_buffer, minibuf_window);
record_unwind_protect_void (minibuffer_unwind);
record_unwind_protect (restore_window_configuration,
Fcons (Qt, Fcurrent_window_configuration (Qnil)));
@ -771,23 +788,6 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
empty_minibuf = get_minibuffer (0);
set_minibuffer_mode (empty_minibuf, 0);
FOR_EACH_FRAME (dummy, frame)
{
Lisp_Object root_window = Fframe_root_window (frame);
Lisp_Object mini_window = XWINDOW (root_window)->next;
Lisp_Object buffer;
if (!NILP (mini_window) && !EQ (mini_window, minibuf_window)
&& !NILP (Fwindow_minibuffer_p (mini_window)))
{
buffer = XWINDOW (mini_window)->contents;
if (!live_minibuffer_p (buffer))
/* Use set_window_buffer instead of Fset_window_buffer (see
discussion of bug#11984, bug#12025, bug#12026). */
set_window_buffer (mini_window, empty_minibuf, 0, 0);
}
}
/* Display this minibuffer in the proper window. */
/* Use set_window_buffer instead of Fset_window_buffer (see
discussion of bug#11984, bug#12025, bug#12026). */
@ -908,7 +908,9 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
unbind_to (count, Qnil);
/* Switch the frame back to the calling frame. */
if (!EQ (selected_frame, calling_frame)
if ((!EQ (selected_frame, calling_frame)
|| !EQ (XWINDOW (XFRAME (calling_frame)->minibuffer_window)->frame,
calling_frame))
&& FRAMEP (calling_frame)
&& FRAME_LIVE_P (XFRAME (calling_frame)))
call2 (intern ("select-frame-set-input-focus"), calling_frame, Qnil);
@ -1026,6 +1028,14 @@ run_exit_minibuf_hook (void)
safe_run_hooks (Qminibuffer_exit_hook);
}
/* This variable records the expired minibuffer's frame between the
calls of `read_minibuf_unwind' and `minibuffer_unwind'. It should
be used only by these two functions. Note that the same search
method for the MB's frame won't always work in `minibuffer_unwind'
because the intervening `restore-window-configuration' will have
changed the buffer in the mini-window. */
static Lisp_Object exp_MB_frame;
/* This function is called on exiting minibuffer, whether normally or
not, and it restores the current window, buffer, etc. */
@ -1036,6 +1046,28 @@ read_minibuf_unwind (void)
Lisp_Object calling_frame;
Lisp_Object calling_window;
Lisp_Object future_mini_window;
Lisp_Object saved_selected_frame = selected_frame;
Lisp_Object window, frames;
struct window *w;
struct frame *f;
/* Locate the expired minibuffer. */
FOR_EACH_FRAME (frames, exp_MB_frame)
{
f = XFRAME (exp_MB_frame);
window = f->minibuffer_window;
w = XWINDOW (window);
if (EQ (w->frame, exp_MB_frame)
&& EQ (w->contents, nth_minibuffer (minibuf_level)))
goto found;
}
return; /* expired minibuffer not found. Maybe we should output an
error, here. */
found:
if (!EQ (exp_MB_frame, saved_selected_frame))
do_switch_frame (exp_MB_frame, 0, 0, Qt); /* This also sets
minibuff_window */
/* To keep things predictable, in case it matters, let's be in the
minibuffer when we reset the relevant variables. Don't depend on
@ -1127,20 +1159,61 @@ read_minibuf_unwind (void)
away from the expired minibuffer window, both in the current
minibuffer's frame and the original calling frame. */
choose_minibuf_frame ();
if (!EQ (WINDOW_FRAME (XWINDOW (minibuf_window)), calling_frame))
{
Lisp_Object prev = Fprevious_window (minibuf_window, Qnil, Qnil);
/* PREV can be on a different frame when we have a minibuffer only
frame, the other frame's minibuffer window is MINIBUF_WINDOW,
and its "focus window" is also MINIBUF_WINDOW. */
if (!EQ (prev, minibuf_window)
&& EQ (WINDOW_FRAME (XWINDOW (prev)),
WINDOW_FRAME (XWINDOW (minibuf_window))))
Fset_frame_selected_window (selected_frame, prev, Qnil);
}
else
Fset_frame_selected_window (calling_frame, calling_window, Qnil);
if (NILP (XWINDOW (minibuf_window)->prev_buffers))
{
if (!EQ (WINDOW_FRAME (XWINDOW (minibuf_window)), calling_frame))
{
Lisp_Object prev = Fprevious_window (minibuf_window, Qnil, Qnil);
/* PREV can be on a different frame when we have a minibuffer only
frame, the other frame's minibuffer window is MINIBUF_WINDOW,
and its "focus window" is also MINIBUF_WINDOW. */
if (!EQ (prev, minibuf_window)
&& EQ (WINDOW_FRAME (XWINDOW (prev)),
WINDOW_FRAME (XWINDOW (minibuf_window))))
Fset_frame_selected_window (selected_frame, prev, Qnil);
}
else
Fset_frame_selected_window (calling_frame, calling_window, Qnil);
}
/* Restore the selected frame. */
if (!EQ (exp_MB_frame, saved_selected_frame))
do_switch_frame (saved_selected_frame, 0, 0, Qt);
}
/* Replace the expired minibuffer in frame exp_MB_frame with the next less
nested minibuffer in that frame, if any. Otherwise, replace it
with the null minibuffer. MINIBUF_WINDOW is not changed. */
static void
minibuffer_unwind (void)
{
struct frame *f;
struct window *w;
Lisp_Object window;
Lisp_Object entry;
f = XFRAME (exp_MB_frame);
window = f->minibuffer_window;
w = XWINDOW (window);
if (FRAME_LIVE_P (f))
{
/* minibuf_window = sf->minibuffer_window; */
if (!NILP (w->prev_buffers))
{
entry = Fcar (w->prev_buffers);
w->prev_buffers = Fcdr (w->prev_buffers);
set_window_buffer (window, Fcar (entry), 0, 0);
Fset_window_start (window, Fcar (Fcdr (entry)), Qnil);
Fset_window_point (window, Fcar (Fcdr (Fcdr (entry))));
/* set-window-configuration may/will have unselected the
mini-window as the selected window. Restore it. */
Fset_frame_selected_window (exp_MB_frame, window, Qnil);
}
else
set_window_buffer (window, nth_minibuffer (0), 0, 0);
}
}
void
@ -2213,6 +2286,9 @@ syms_of_minibuf (void)
{
staticpro (&minibuf_prompt);
staticpro (&minibuf_save_list);
staticpro (&MB_frame);
MB_frame = Qnil;
staticpro (&exp_MB_frame);
DEFSYM (Qminibuffer_follows_selected_frame,
"minibuffer-follows-selected-frame");

View File

@ -6958,7 +6958,8 @@ the return value is nil. Otherwise the value is t. */)
if (BUFFERP (w->contents)
&& !EQ (w->contents, p->buffer)
&& BUFFER_LIVE_P (XBUFFER (p->buffer)))
&& BUFFER_LIVE_P (XBUFFER (p->buffer))
&& (NILP (Fminibufferp (p->buffer, Qnil))))
/* If a window we restore gets another buffer, record the
window's old buffer. */
call1 (Qrecord_window_buffer, window);

View File

@ -12650,9 +12650,8 @@ gui_consider_frame_title (Lisp_Object frame)
mode_line_noprop_buf; then display the title. */
record_unwind_protect (unwind_format_mode_line,
format_mode_line_unwind_data
(f, current_buffer, selected_window, false));
(NULL, current_buffer, Qnil, false));
Fselect_window (f->selected_window, Qt);
set_buffer_internal_1
(XBUFFER (XWINDOW (f->selected_window)->contents));
fmt = FRAME_ICONIFIED_P (f) ? Vicon_title_format : Vframe_title_format;