From: Alan Mackenzie Date: Thu, 5 Nov 2020 19:27:43 +0000 (+0000) Subject: Allow minibuffer to stay in its original frame. Tidy up this area. X-Git-Tag: emacs-28.0.90~5237 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=2ecbf4cfae7bd504fbdca28e1e51ee2574fe5d12;p=emacs.git Allow minibuffer to stay in its original frame. Tidy up this area. * doc/emacs/mini.texi (Basic Minibuffer): Add an entry for minibuffer-follows-selected-frame. * doc/lispref/minibuf.texi (Minibuffer Misc): Describe the new parameter to minibufferp, LIVE. * etc/NEWS: Add an entry describing the new minibuffer strategy. * lisp/cus-start.el (minibuffer-prompt-properties--setter): Add an entry for minibuffer-follows-selected-frame. * lisp/minibuffer.el (minibuffer-message): Check for the current buffer being an _active_ minibuffer rather than merely a minibuffer. * src/frame.c (do_switch_frame): Call move_minibuffer_onto_frame. * src/lisp.h (Top level): Add prototypes for move_minibuffer_onto_frame and is_minibuffer. * src/minibuf.c (minibuf_follows_frame): New function which ignores local and let-bound values of minibuffer-follows-selected-frame. (choose_minibuf_frame): Reformulate this function to reuse a minibuffer window where possible, and to ensure no other frame has its minibuffer current, but only when `minibuffer-follows-selected-frame'. (move_minibuffer_onto_frame): New function. (live_minibuffer_p): New function. (Fminibufferp): Add a new &optional parameter LIVE. Reformulate, possibly calling live_minibuffer_p. (read_minibuf): move the incrementation of minibuf_level to before the call of choose_minibuf_frame. Empty the miniwindows of frames without an active minibuffer, rather than of all but the current frame. (is_minibuffer): New function. (read_minibuf_unwind): Note the miniwindow being restored and resize all other miniwindows to zero size. (minibuffer-follows-selected-frame): New configuration variable. * src/window.c (candidate_window_p): In some scenarios, check the miniwindow holds an active minibuffer. * src/xdisp.c (get_window_cursor_type): Suppress the cursor for non-active miniwindows, regardless of minibuf_level. --- diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index 54f046a7e05..ede95a28d4e 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -69,6 +69,17 @@ the minibuffer for a few seconds, or until you type something; then the minibuffer comes back. While the minibuffer is in use, Emacs does not echo keystrokes. +@vindex minibuffer-follows-selected-frame + While using the minibuffer, you can switch to a different frame, +perhaps to note text you need to enter (@pxref{Frame Commands}). By +default, the active minibuffer moves to this new frame. If you set +the user option @code{minibuffer-follows-selected-frame} to +@code{nil}, then the minibuffer stays in the frame where you opened +it, and you must switch back to that frame in order to complete (or +abort) the current command. 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. + @node Minibuffer File @section Minibuffers for File Names diff --git a/doc/lispref/minibuf.texi b/doc/lispref/minibuf.texi index e5a0233b3c7..b6a3434d15e 100644 --- a/doc/lispref/minibuf.texi +++ b/doc/lispref/minibuf.texi @@ -2586,10 +2586,12 @@ The minibuffer command @code{next-matching-history-element} (normally @node Minibuffer Misc @section Minibuffer Miscellany -@defun minibufferp &optional buffer-or-name +@defun minibufferp &optional buffer-or-name live This function returns non-@code{nil} if @var{buffer-or-name} is a -minibuffer. If @var{buffer-or-name} is omitted, it tests the current -buffer. +minibuffer. If @var{buffer-or-name} is omitted or @code{nil}, it +tests the current buffer. When @var{live} is non-@code{nil}, the +function returns non-@code{nil} only when @var{buffer-or-name} is an +active minibuffer. @end defun @defvar minibuffer-setup-hook diff --git a/etc/NEWS b/etc/NEWS index d15f3ed1ae3..7ef832e6033 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -88,6 +88,17 @@ useful on systems such as FreeBSD which ships only with "etc/termcap". ** Minibuffer scrolling is now conservative by default. This is controlled by the new variable 'scroll-minibuffer-conservatively'. ++++ +** Improved handling of minibuffers on switching frames. +By default, an active minibuffer now moves to a newly selected frame. +When continuing the current command (by completing the minibuffer +action), the effect happens in the frame where the minibuffer was +first opened. An alternative behavior is available by customizing +'minibuffer-follows-selected-frame' to nil. Here, the minibuffer +stays in the frame where you first opened it, and you must switch back +to this frame to continue or abort its command. The old, somewhat +unsystematic behavior is no longer available. + +++ ** New system for displaying documentation for groups of functions. This can either be used by saying 'M-x shortdoc-display-group' and diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 6927b6df6b8..04fb1dc6d06 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -394,6 +394,7 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of ;; (directory :format "%v")))) (load-prefer-newer lisp boolean "24.4") ;; minibuf.c + (minibuffer-follows-selected-frame minibuffer boolean "28.1") (enable-recursive-minibuffers minibuffer boolean) (history-length minibuffer (choice (const :tag "Infinite" t) integer) diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 5a41e2f30bd..9d57a817b25 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -701,7 +701,7 @@ The text is displayed for `minibuffer-message-timeout' seconds, or until the next input event arrives, whichever comes first. Enclose MESSAGE in [...] if this is not yet the case. If ARGS are provided, then pass MESSAGE through `format-message'." - (if (not (minibufferp (current-buffer))) + (if (not (minibufferp (current-buffer) t)) (progn (if args (apply #'message message args) diff --git a/src/frame.c b/src/frame.c index 7c377da4456..512aaf5f45c 100644 --- a/src/frame.c +++ b/src/frame.c @@ -1482,6 +1482,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 (); return frame; } diff --git a/src/lisp.h b/src/lisp.h index a3cfb5044d4..cf33031342d 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4336,6 +4336,8 @@ 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 bool is_minibuffer (EMACS_INT, Lisp_Object); extern Lisp_Object get_minibuffer (EMACS_INT); extern void init_minibuf_once (void); extern void syms_of_minibuf (void); diff --git a/src/minibuf.c b/src/minibuf.c index f957b2ae173..ebc00ae4e4e 100644 --- a/src/minibuf.c +++ b/src/minibuf.c @@ -64,6 +64,12 @@ static Lisp_Object minibuf_prompt; static ptrdiff_t minibuf_prompt_width; +static bool +minibuf_follows_frame (void) +{ + return !NILP (Fdefault_toplevel_value (Qminibuffer_follows_selected_frame)); +} + /* Put minibuf on currently selected frame's minibuffer. We do this whenever the user starts a new minibuffer or when a minibuffer exits. */ @@ -76,37 +82,72 @@ choose_minibuf_frame (void) && !EQ (minibuf_window, XFRAME (selected_frame)->minibuffer_window)) { struct frame *sf = XFRAME (selected_frame); - Lisp_Object buffer; - /* I don't think that any frames may validly have a null minibuffer window anymore. */ if (NILP (sf->minibuffer_window)) emacs_abort (); - /* Under X, we come here with minibuf_window being the - minibuffer window of the unused termcap window created in - init_window_once. That window doesn't have a buffer. */ - buffer = XWINDOW (minibuf_window)->contents; - if (BUFFERP (buffer)) - /* Use set_window_buffer instead of Fset_window_buffer (see - discussion of bug#11984, bug#12025, bug#12026). */ - set_window_buffer (sf->minibuffer_window, buffer, 0, 0); minibuf_window = sf->minibuffer_window; + /* If we've still got another minibuffer open, use its mini-window + instead. */ + if (minibuf_level && !minibuf_follows_frame ()) + { + 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; + } + } } - /* 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; + if (minibuf_follows_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; + + FOR_EACH_FRAME (tail, frame) + if (MINI_WINDOW_P (XWINDOW (FRAME_SELECTED_WINDOW (XFRAME (frame)))) + && !(EQ (frame, selected_frame) + && minibuf_level > 0)) + Fset_frame_selected_window (frame, Fframe_first_window (frame), + Qnil); + } +} - FOR_EACH_FRAME (tail, frame) - if (MINI_WINDOW_P (XWINDOW (FRAME_SELECTED_WINDOW (XFRAME (frame)))) - && !(EQ (frame, selected_frame) - && minibuf_level > 0)) - Fset_frame_selected_window (frame, Fframe_first_window (frame), Qnil); - } +/* If `minibuffer_follows_selected_frame' 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 (!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)) + { + struct frame *sf = XFRAME (selected_frame); + Lisp_Object old_frame = XWINDOW (minibuf_window)->frame; + struct frame *of = XFRAME (old_frame); + Lisp_Object buffer = XWINDOW (minibuf_window)->contents; + + set_window_buffer (sf->minibuffer_window, buffer, 0, 0); + minibuf_window = sf->minibuffer_window; + if (XWINDOW (minibuf_window)->frame == selected_frame) + /* The minibuffer might be on another frame. */ + Fset_frame_selected_window (selected_frame, sf->minibuffer_window, + Qnil); + set_window_buffer (of->minibuffer_window, get_minibuffer (0), 0, 0); + } } DEFUN ("active-minibuffer-window", Factive_minibuffer_window, @@ -261,15 +302,31 @@ read_minibuf_noninteractive (Lisp_Object prompt, bool expflag, return val; } +/* Return true when BUFFER is an active minibuffer. */ +static bool +live_minibuffer_p (Lisp_Object buffer) +{ + Lisp_Object tem; + EMACS_INT i; + + if (EQ (buffer, Fcar (Vminibuffer_list))) + /* *Minibuf-0* is never active. */ + return false; + tem = Fcdr (Vminibuffer_list); + for (i = 1; i <= minibuf_level; i++, tem = Fcdr (tem)) + if (EQ (Fcar (tem), buffer)) + return true; + return false; +} + DEFUN ("minibufferp", Fminibufferp, - Sminibufferp, 0, 1, 0, + Sminibufferp, 0, 2, 0, doc: /* Return t if BUFFER is a minibuffer. No argument or nil as argument means use current buffer as BUFFER. -BUFFER can be a buffer or a buffer name. */) - (Lisp_Object buffer) +BUFFER can be a buffer or a buffer name. If LIVE is non-nil, then +return t only if BUFFER is an active minibuffer. */) + (Lisp_Object buffer, Lisp_Object live) { - Lisp_Object tem; - if (NILP (buffer)) buffer = Fcurrent_buffer (); else if (STRINGP (buffer)) @@ -277,8 +334,10 @@ BUFFER can be a buffer or a buffer name. */) else CHECK_BUFFER (buffer); - tem = Fmemq (buffer, Vminibuffer_list); - return ! NILP (tem) ? Qt : Qnil; + return (NILP (live) + ? !NILP (Fmemq (buffer, Vminibuffer_list)) + : live_minibuffer_p (buffer)) + ? Qt : Qnil; } DEFUN ("minibuffer-prompt-end", Fminibuffer_prompt_end, @@ -433,6 +492,8 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, return unbind_to (count, val); } + minibuf_level++; /* Before calling choose_minibuf_frame. */ + /* Choose the minibuffer window and frame, and take action on them. */ /* Prepare for restoring the current buffer since choose_minibuf_frame @@ -484,7 +545,6 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, = Fcons (Fthis_command_keys_vector (), minibuf_save_list); record_unwind_protect_void (read_minibuf_unwind); - minibuf_level++; /* We are exiting the minibuffer one way or the other, so run the hook. It should be run before unwinding the minibuf settings. Do it separately from read_minibuf_unwind because we need to make sure that @@ -566,8 +626,8 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, if (minibuf_level == 1 || !EQ (minibuf_window, selected_window)) minibuf_selected_window = selected_window; - /* Empty out the minibuffers of all frames other than the one - where we are going to display one now. + /* Empty out the minibuffers of all frames, except those frames + where there is an active minibuffer. Set them to point to ` *Minibuf-0*', which is always empty. */ empty_minibuf = get_minibuffer (0); @@ -575,12 +635,17 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, { 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))) - /* 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); + 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. */ @@ -714,6 +779,16 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, return val; } +/* Return true if BUF is a particular existing minibuffer. */ +bool +is_minibuffer (EMACS_INT depth, Lisp_Object buf) +{ + Lisp_Object tail = Fnthcdr (make_fixnum (depth), Vminibuffer_list); + return + !NILP (tail) + && EQ (Fcar (tail), buf); +} + /* Return a buffer to be used as the minibuffer at depth `depth'. depth = 0 is the lowest allowed argument, and that is the value used for nonrecursive minibuffer invocations. */ @@ -775,6 +850,7 @@ read_minibuf_unwind (void) { Lisp_Object old_deactivate_mark; Lisp_Object window; + Lisp_Object future_mini_window; /* If this was a recursive minibuffer, tie the minibuffer window back to the outer level minibuffer buffer. */ @@ -809,6 +885,7 @@ read_minibuf_unwind (void) if (FRAME_LIVE_P (XFRAME (WINDOW_FRAME (XWINDOW (temp))))) minibuf_window = temp; #endif + future_mini_window = Fcar (minibuf_save_list); minibuf_save_list = Fcdr (minibuf_save_list); /* Erase the minibuffer we were using at this level. */ @@ -825,7 +902,8 @@ read_minibuf_unwind (void) /* When we get to the outmost level, make sure we resize the mini-window back to its normal size. */ - if (minibuf_level == 0) + if (minibuf_level == 0 + || !EQ (selected_frame, WINDOW_FRAME (XWINDOW (future_mini_window)))) resize_mini_window (XWINDOW (window), 0); /* Deal with frames that should be removed when exiting the @@ -1911,6 +1989,8 @@ syms_of_minibuf (void) staticpro (&minibuf_prompt); staticpro (&minibuf_save_list); + DEFSYM (Qminibuffer_follows_selected_frame, + "minibuffer-follows-selected-frame"); DEFSYM (Qcompletion_ignore_case, "completion-ignore-case"); DEFSYM (Qminibuffer_default, "minibuffer-default"); Fset (Qminibuffer_default, Qnil); @@ -1954,6 +2034,14 @@ For example, `eval-expression' uses this. */); The function is called with the arguments passed to `read-buffer'. */); Vread_buffer_function = Qnil; + DEFVAR_BOOL ("minibuffer-follows-selected-frame", minibuffer_follows_selected_frame, + doc: /* Non-nil means the active minibuffer always displays on the selected frame. +Nil means that a minibuffer will appear only in the frame which created it. + +Any buffer local or dynamic binding of this variable is ignored. Only the +default top level value is used. */); + minibuffer_follows_selected_frame = 1; + DEFVAR_BOOL ("read-buffer-completion-ignore-case", read_buffer_completion_ignore_case, doc: /* Non-nil means completion ignores case when reading a buffer name. */); diff --git a/src/window.c b/src/window.c index e7433969d29..a6de34f3db6 100644 --- a/src/window.c +++ b/src/window.c @@ -2643,8 +2643,10 @@ candidate_window_p (Lisp_Object window, Lisp_Object owindow, /* To qualify as candidate, it's not sufficient for WINDOW's frame to just share the minibuffer window - it must be active as well (see Bug#24500). */ - candidate_p = (EQ (XWINDOW (all_frames)->frame, w->frame) - || EQ (XWINDOW (all_frames)->frame, FRAME_FOCUS_FRAME (f))); + candidate_p = ((EQ (XWINDOW (all_frames)->frame, w->frame) + || (EQ (f->minibuffer_window, all_frames) + && EQ (XWINDOW (all_frames)->frame, FRAME_FOCUS_FRAME (f)))) + && !is_minibuffer (0, XWINDOW (all_frames)->contents)); else if (FRAMEP (all_frames)) candidate_p = EQ (all_frames, w->frame); diff --git a/src/xdisp.c b/src/xdisp.c index 618ec688e89..bff14e584de 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -31224,7 +31224,9 @@ get_window_cursor_type (struct window *w, struct glyph *glyph, int *width, { *active_cursor = false; - if (MINI_WINDOW_P (w) && minibuf_level == 0) + if (MINI_WINDOW_P (w) && + (minibuf_level == 0 + || is_minibuffer (0, w->contents))) return NO_CURSOR; non_selected = true;