Skip to content

Commit

Permalink
Make recursive minibuffers and recursive edits work together
Browse files Browse the repository at this point in the history
* lisp/minibuffer.el (exit-minibuffer): When in a minibuffer, throw an error
should the command loop nesting level be wrong.

* src/lisp.h (minibuffer_quit_level): declare as an extern.
(command_loop_level): Move definition from src/window.h

* src/window.h (command_loop_level): move definition to src/lisp.h.

* src/eval.c (minibuffer_quit_level): Move this variable to file level from
being a static inside internal_catch.
(internal_catch): Simplify the logic.

* src/minibuf.c (Vcommand_loop_level_list): New variable.
(move_minibuffer_onto_frame): Set the major mode of  *Minibuf-0*.
(Fminibuffer_innermost_command_loop_p): New primitive.
(Fabort_minibuffers): Check the command loop level before throwing t to 'exit,
and set minibuffer_quit_level too.
(read_minibuf): New variable calling_window.
Before stacking up minibuffers on the current mini-window, check that the
mini-window is not the current one.
Do not call choose_minibuf_frame from read_minibuf's unwinding process.
Bind calling_frame and calling_window over the recursive edit.
Set the new minibuffer's major mode directly.
Remove the switching away from the minibuffer after the recursive edit.
(get_minibuffer): Record the command loop level in new variable
Vcommand_loop_level_list.  No longer set the major mode of a returned
minibuffer.
(minibuf_c_loop_level): New function.
(read_minibuf_unwind): New variables calling_frame, calling_window are unbound
from the binding stack.  Remove old variable `window', which could not be set
reliably to the expired mini-window.
The expired minibuffer is determined as the nth in the list, rather than the
contents of the current or previous mini-window.
Switch the current window away from the mini-window here (moved from
read_minibuf).
  • Loading branch information
Alan Mackenzie committed Feb 11, 2021
1 parent 4f63b4b commit 203e61f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 59 deletions.
13 changes: 7 additions & 6 deletions lisp/minibuffer.el
Original file line number Diff line number Diff line change
Expand Up @@ -2116,18 +2116,19 @@ variables.")
(defun exit-minibuffer ()
"Terminate this minibuffer argument."
(interactive)
(when (or
(innermost-minibuffer-p)
(not (minibufferp)))
(when (minibufferp)
(when (not (minibuffer-innermost-command-loop-p))
(error "%s" "Not in most nested command loop"))
(when (not (innermost-minibuffer-p))
(error "%s" "Not in most nested minibuffer")))
;; If the command that uses this has made modifications in the minibuffer,
;; we don't want them to cause deactivation of the mark in the original
;; buffer.
;; A better solution would be to make deactivate-mark buffer-local
;; (or to turn it into a list of buffers, ...), but in the mean time,
;; this should do the trick in most cases.
(setq deactivate-mark nil)
(throw 'exit nil))
(error "%s" "Not in most nested minibuffer"))
(setq deactivate-mark nil)
(throw 'exit nil))

(defun self-insert-and-exit ()
"Terminate minibuffer input."
Expand Down
32 changes: 14 additions & 18 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1165,21 +1165,23 @@ usage: (catch TAG BODY...) */)
FUNC should return a Lisp_Object.
This is how catches are done from within C code. */

/* MINIBUFFER_QUIT_LEVEL is to handle quitting from nested minibuffers by
throwing t to tag `exit'.
0 means there is no (throw 'exit t) in progress, or it wasn't from
a minibuffer which isn't the most nested;
N > 0 means the `throw' was done from the minibuffer at level N which
wasn't the most nested. */
EMACS_INT minibuffer_quit_level = 0;

Lisp_Object
internal_catch (Lisp_Object tag,
Lisp_Object (*func) (Lisp_Object), Lisp_Object arg)
{
/* MINIBUFFER_QUIT_LEVEL is to handle quitting from nested minibuffers by
throwing t to tag `exit'.
Value -1 means there is no (throw 'exit t) in progress;
0 means the `throw' wasn't done from an active minibuffer;
N > 0 means the `throw' was done from the minibuffer at level N. */
static EMACS_INT minibuffer_quit_level = -1;
/* This structure is made part of the chain `catchlist'. */
struct handler *c = push_handler (tag, CATCHER);

if (EQ (tag, Qexit))
minibuffer_quit_level = -1;
minibuffer_quit_level = 0;

/* Call FUNC. */
if (! sys_setjmp (c->jmp))
Expand All @@ -1194,22 +1196,16 @@ internal_catch (Lisp_Object tag,
Lisp_Object val = handlerlist->val;
clobbered_eassert (handlerlist == c);
handlerlist = handlerlist->next;
if (EQ (tag, Qexit) && EQ (val, Qt))
if (EQ (tag, Qexit) && EQ (val, Qt) && minibuffer_quit_level > 0)
/* If we've thrown t to tag `exit' from within a minibuffer, we
exit all minibuffers more deeply nested than the current
one. */
{
EMACS_INT mini_depth = this_minibuffer_depth (Qnil);
if (mini_depth && mini_depth != minibuffer_quit_level)
{
if (minibuffer_quit_level == -1)
minibuffer_quit_level = mini_depth;
if (minibuffer_quit_level
&& (minibuf_level > minibuffer_quit_level))
Fthrow (Qexit, Qt);
}
if (minibuf_level > minibuffer_quit_level
&& !NILP (Fminibuffer_innermost_command_loop_p (Qnil)))
Fthrow (Qexit, Qt);
else
minibuffer_quit_level = -1;
minibuffer_quit_level = 0;
}
return val;
}
Expand Down
2 changes: 2 additions & 0 deletions src/lisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -4091,6 +4091,7 @@ intern_c_string (const char *str)
}

/* Defined in eval.c. */
extern EMACS_INT minibuffer_quit_level;
extern Lisp_Object Vautoload_queue;
extern Lisp_Object Vrun_hooks;
extern Lisp_Object Vsignaling_function;
Expand Down Expand Up @@ -4369,6 +4370,7 @@ extern void syms_of_casetab (void);

/* Defined in keyboard.c. */

extern EMACS_INT command_loop_level;
extern Lisp_Object echo_message_buffer;
extern struct kboard *echo_kboard;
extern void cancel_echoing (void);
Expand Down
115 changes: 84 additions & 31 deletions src/minibuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
minibuffer recursions are encountered. */

Lisp_Object Vminibuffer_list;
Lisp_Object Vcommand_loop_level_list;

/* Data to remember during recursive minibuffer invocations. */

Expand All @@ -64,6 +65,8 @@ static Lisp_Object minibuf_prompt;
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);


/* Return TRUE when a frame switch causes a minibuffer on the old
Expand Down Expand Up @@ -181,7 +184,12 @@ void move_minibuffer_onto_frame (void)
set_window_buffer (sf->minibuffer_window, nth_minibuffer (i), 0, 0);
minibuf_window = sf->minibuffer_window;
if (of != sf)
set_window_buffer (of->minibuffer_window, get_minibuffer (0), 0, 0);
{
Lisp_Object temp = get_minibuffer (0);

set_window_buffer (of->minibuffer_window, temp, 0, 0);
set_minibuffer_mode (temp, 0);
}
}
}

Expand Down Expand Up @@ -389,6 +397,21 @@ No argument or nil as argument means use the current buffer as BUFFER. */)
: Qnil;
}

DEFUN ("minibuffer-innermost-command-loop-p", Fminibuffer_innermost_command_loop_p,
Sminibuffer_innermost_command_loop_p, 0, 1, 0,
doc: /* Return t if BUFFER is a minibuffer at the current command loop level.
No argument or nil as argument means use the current buffer as BUFFER. */)
(Lisp_Object buffer)
{
EMACS_INT depth;
if (NILP (buffer))
buffer = Fcurrent_buffer ();
depth = this_minibuffer_depth (buffer);
return depth && minibuf_c_loop_level (depth) == command_loop_level
? Qt
: Qnil;
}

/* Return the nesting depth of the active minibuffer BUFFER, or 0 if
BUFFER isn't such a thing. If BUFFER is nil, this means use the current
buffer. */
Expand Down Expand Up @@ -420,12 +443,17 @@ confirm the aborting of the current minibuffer and all contained ones. */)

if (!minibuf_depth)
error ("Not in a minibuffer");
if (NILP (Fminibuffer_innermost_command_loop_p (Qnil)))
error ("Not in most nested command loop");
if (minibuf_depth < minibuf_level)
{
array[0] = fmt;
array[1] = make_fixnum (minibuf_level - minibuf_depth + 1);
if (!NILP (Fyes_or_no_p (Fformat (2, array))))
Fthrow (Qexit, Qt);
{
minibuffer_quit_level = minibuf_depth;
Fthrow (Qexit, Qt);
}
}
else
Fthrow (Qexit, Qt);
Expand Down Expand Up @@ -508,6 +536,7 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
ptrdiff_t count = SPECPDL_INDEX ();
Lisp_Object mini_frame, ambient_dir, minibuffer, input_method;
Lisp_Object calling_frame = selected_frame;
Lisp_Object calling_window = selected_window;
Lisp_Object enable_multibyte;
EMACS_INT pos = 0;
/* String to add to the history. */
Expand Down Expand Up @@ -598,7 +627,8 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,

if (minibuf_level > 1
&& minibuf_moves_frame_when_opened ()
&& !minibuf_follows_frame ())
&& (!minibuf_follows_frame ()
|| (!EQ (mini_frame, selected_frame))))
{
EMACS_INT i;

Expand All @@ -607,8 +637,6 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
set_window_buffer (minibuf_window, nth_minibuffer (i), 0, 0);
}

record_unwind_protect_void (choose_minibuf_frame);

record_unwind_protect (restore_window_configuration,
Fcons (Qt, Fcurrent_window_configuration (Qnil)));

Expand Down Expand Up @@ -640,7 +668,9 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
minibuf_save_list
= Fcons (Voverriding_local_map,
Fcons (minibuf_window,
minibuf_save_list));
Fcons (calling_frame,
Fcons (calling_window,
minibuf_save_list))));
minibuf_save_list
= Fcons (minibuf_prompt,
Fcons (make_fixnum (minibuf_prompt_width),
Expand Down Expand Up @@ -694,6 +724,7 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
/* Switch to the minibuffer. */

minibuffer = get_minibuffer (minibuf_level);
set_minibuffer_mode (minibuffer, minibuf_level);
Fset_buffer (minibuffer);

/* Defeat (setq-default truncate-lines t), since truncated lines do
Expand Down Expand Up @@ -738,6 +769,7 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,
where there is an active minibuffer.
Set them to point to ` *Minibuf-0*', which is always empty. */
empty_minibuf = get_minibuffer (0);
set_minibuffer_mode (empty_minibuf, 0);

FOR_EACH_FRAME (dummy, frame)
{
Expand Down Expand Up @@ -837,20 +869,6 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt,

recursive_edit_1 ();

/* We've exited the recursive edit without an error, so switch the
current window away from the expired minibuffer window. */
{
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. */
while (!EQ (prev, minibuf_window)
&& !EQ (selected_frame, WINDOW_FRAME (XWINDOW (prev))))
prev = Fprevious_window (prev, Qnil, Qnil);
if (!EQ (prev, minibuf_window))
Fset_frame_selected_window (selected_frame, prev, Qnil);
}

/* If cursor is on the minibuffer line,
show the user we have exited by putting it in column 0. */
if (XWINDOW (minibuf_window)->cursor.vpos >= 0
Expand Down Expand Up @@ -959,11 +977,16 @@ Lisp_Object
get_minibuffer (EMACS_INT depth)
{
Lisp_Object tail = Fnthcdr (make_fixnum (depth), Vminibuffer_list);
Lisp_Object cll_tail = Fnthcdr (make_fixnum (depth),
Vcommand_loop_level_list);
if (NILP (tail))
{
tail = list1 (Qnil);
Vminibuffer_list = nconc2 (Vminibuffer_list, tail);
cll_tail = list1 (Qnil);
Vcommand_loop_level_list = nconc2 (Vcommand_loop_level_list, cll_tail);
}
XSETCAR (cll_tail, make_fixnum (depth ? command_loop_level : 0));
Lisp_Object buf = Fcar (tail);
if (NILP (buf) || !BUFFER_LIVE_P (XBUFFER (buf)))
{
Expand All @@ -973,7 +996,6 @@ get_minibuffer (EMACS_INT depth)
buf = Fget_buffer_create (lname, Qnil);
/* Do this before set_minibuffer_mode. */
XSETCAR (tail, buf);
set_minibuffer_mode (buf, depth);
/* Although the buffer's name starts with a space, undo should be
enabled in it. */
Fbuffer_enable_undo (buf);
Expand All @@ -985,12 +1007,19 @@ get_minibuffer (EMACS_INT depth)
while the buffer doesn't know about them any more. */
delete_all_overlays (XBUFFER (buf));
reset_buffer (XBUFFER (buf));
set_minibuffer_mode (buf, depth);
}

return buf;
}

static EMACS_INT minibuf_c_loop_level (EMACS_INT depth)
{
Lisp_Object cll = Fnth (make_fixnum (depth), Vcommand_loop_level_list);
if (FIXNUMP (cll))
return XFIXNUM (cll);
return 0;
}

static void
run_exit_minibuf_hook (void)
{
Expand All @@ -1004,17 +1033,16 @@ static void
read_minibuf_unwind (void)
{
Lisp_Object old_deactivate_mark;
Lisp_Object window;
Lisp_Object calling_frame;
Lisp_Object calling_window;
Lisp_Object future_mini_window;

/* If this was a recursive minibuffer,
tie the minibuffer window back to the outer level minibuffer buffer. */
minibuf_level--;

window = minibuf_window;
/* To keep things predictable, in case it matters, let's be in the
minibuffer when we reset the relevant variables. */
Fset_buffer (XWINDOW (window)->contents);
minibuffer when we reset the relevant variables. Don't depend on
`minibuf_window' here. This could by now be the mini-window of any
frame. */
Fset_buffer (nth_minibuffer (minibuf_level));
minibuf_level--;

/* Restore prompt, etc, from outer minibuffer level. */
Lisp_Object key_vec = Fcar (minibuf_save_list);
Expand Down Expand Up @@ -1042,6 +1070,10 @@ read_minibuf_unwind (void)
#endif
future_mini_window = Fcar (minibuf_save_list);
minibuf_save_list = Fcdr (minibuf_save_list);
calling_frame = Fcar (minibuf_save_list);
minibuf_save_list = Fcdr (minibuf_save_list);
calling_window = Fcar (minibuf_save_list);
minibuf_save_list = Fcdr (minibuf_save_list);

/* Erase the minibuffer we were using at this level. */
{
Expand All @@ -1059,7 +1091,7 @@ read_minibuf_unwind (void)
mini-window back to its normal size. */
if (minibuf_level == 0
|| !EQ (selected_frame, WINDOW_FRAME (XWINDOW (future_mini_window))))
resize_mini_window (XWINDOW (window), 0);
resize_mini_window (XWINDOW (minibuf_window), 0);

/* Deal with frames that should be removed when exiting the
minibuffer. */
Expand Down Expand Up @@ -1090,6 +1122,24 @@ read_minibuf_unwind (void)
to make sure we don't leave around bindings and stuff which only
made sense during the read_minibuf invocation. */
call0 (intern ("minibuffer-inactive-mode"));

/* We've exited the recursive edit, so switch the current windows
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);
}


Expand Down Expand Up @@ -2137,6 +2187,7 @@ void
init_minibuf_once (void)
{
staticpro (&Vminibuffer_list);
staticpro (&Vcommand_loop_level_list);
pdumper_do_now_and_after_load (init_minibuf_once_for_pdumper);
}

Expand All @@ -2150,6 +2201,7 @@ init_minibuf_once_for_pdumper (void)
restore from a dump file. pdumper doesn't try to preserve
frames, windows, and so on, so reset everything related here. */
Vminibuffer_list = Qnil;
Vcommand_loop_level_list = Qnil;
minibuf_level = 0;
minibuf_prompt = Qnil;
minibuf_save_list = Qnil;
Expand Down Expand Up @@ -2380,6 +2432,7 @@ instead. */);

defsubr (&Sminibufferp);
defsubr (&Sinnermost_minibuffer_p);
defsubr (&Sminibuffer_innermost_command_loop_p);
defsubr (&Sabort_minibuffers);
defsubr (&Sminibuffer_prompt_end);
defsubr (&Sminibuffer_contents);
Expand Down
4 changes: 0 additions & 4 deletions src/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -1120,10 +1120,6 @@ void set_window_buffer (Lisp_Object window, Lisp_Object buffer,

extern Lisp_Object echo_area_window;

/* Depth in recursive edits. */

extern EMACS_INT command_loop_level;

/* Non-zero if we should redraw the mode lines on the next redisplay.
Usually set to a unique small integer so we can track the main causes of
full redisplays in `redisplay--mode-lines-cause'. */
Expand Down

0 comments on commit 203e61f

Please sign in to comment.