From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Alan Mackenzie Newsgroups: gmane.emacs.devel Subject: Re: Stop frames stealing eachothers' minibuffers! Date: Fri, 30 Oct 2020 22:09:17 +0000 Message-ID: <20201030220917.GA17594@ACM> References: <838sc8zqjj.fsf@gnu.org> <20201014184523.GC7651@ACM> <83y2k8y6qs.fsf@gnu.org> <20201014194904.GD7651@ACM> <83sgafy56d.fsf@gnu.org> <20201015180143.GA10229@ACM> <83wnzrwdy5.fsf@gnu.org> <20201021151945.GA19276@ACM> <20201021200438.GF19276@ACM> <83h7qmkzla.fsf@gnu.org> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="8202"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Fri Oct 30 23:10:02 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kYcav-0001yV-Sy for ged-emacs-devel@m.gmane-mx.org; Fri, 30 Oct 2020 23:10:02 +0100 Original-Received: from localhost ([::1]:51266 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kYcau-0004uE-Uk for ged-emacs-devel@m.gmane-mx.org; Fri, 30 Oct 2020 18:10:00 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:43216) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kYcaK-0004To-VH for emacs-devel@gnu.org; Fri, 30 Oct 2020 18:09:24 -0400 Original-Received: from colin.muc.de ([193.149.48.1]:44670 helo=mail.muc.de) by eggs.gnu.org with smtp (Exim 4.90_1) (envelope-from ) id 1kYcaG-0004E8-WD for emacs-devel@gnu.org; Fri, 30 Oct 2020 18:09:24 -0400 Original-Received: (qmail 41709 invoked by uid 3782); 30 Oct 2020 22:09:18 -0000 Original-Received: from acm.muc.de (p4fe15a6b.dip0.t-ipconnect.de [79.225.90.107]) by localhost.muc.de (tmda-ofmipd) with ESMTP; Fri, 30 Oct 2020 23:09:17 +0100 Original-Received: (qmail 31764 invoked by uid 1000); 30 Oct 2020 22:09:17 -0000 Content-Disposition: inline In-Reply-To: <83h7qmkzla.fsf@gnu.org> X-Delivery-Agent: TMDA/1.1.12 (Macallan) X-Primary-Address: acm@muc.de Received-SPF: pass client-ip=193.149.48.1; envelope-from=acm@muc.de; helo=mail.muc.de X-detected-operating-system: by eggs.gnu.org: First seen = 2020/10/30 18:09:18 X-ACL-Warn: Detected OS = FreeBSD 9.x or newer [fuzzy] X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=unavailable autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:258565 Archived-At: Hello, Eli. On Thu, Oct 22, 2020 at 19:14:09 +0300, Eli Zaretskii wrote: > > Date: Wed, 21 Oct 2020 20:04:38 +0000 > > Cc: emacs-devel@gnu.org > > From: Alan Mackenzie > > Here's a corrected version of the patch, incorporating fixes and > > suggestions from Stefan and Drew: And below is a further amended patch. > Thanks, I have a few minor comments: > > +the user option @code{minibuffer-follows-frame} to @code{nil}, then > I'd prefer to name the option minibuffer-follows-selected-frame. OK. I've changed it. > > ++++ > > +** Switching frames when a minibuffer is active has been rationalized. > "Rationalized"? How about > Minibuffer behavior when selected frame changes can now be controlled. > ? What I've put in is ** Improved handling of minibuffers on switching frames. . How about that? > > +By default, the active minibuffer is moved to the newly selected > > +frame. When the current command is continued (by completing the > > +minibuffer action), it takes effect in the frame where the minibuffer > > +was first opened. An alternative behavior is available by customizing > > +'minibuffer-follows-frame' to nil. Here, the minibuffer stays in the > > +frame where it was first opened, and you must switch back to this > > +frame to continue or abort the current command. The old (pre Emacs > > +28.1) somewhat unsystematic behavior is no longer available. > Can you reword to use less of passive tense here? DONE. > I may be missing something: where does this code handles the case of > minibuffer-only frames? The previous version didn't, really, very much. The current version of the patch contains quite a few changes, for example to suppress the display of a ghost cursor in what had been a miniwindow. I've tested a little bit with an unusual configuration, 4 frames, of which 2 are without minibuffers, one is a MB, and the last is a "normal" frame. I don't see anything untoward except, perhaps, the action of C-x o when there are minibuffers open. But that wasn't entirely predictable on the unchanged master branch either. Here's the latest version of the patch. Comments would be appreciated: diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index 54f046a7e0..ede95a28d4 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -69,6 +69,17 @@ Basic Minibuffer 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/etc/NEWS b/etc/NEWS index 4cc66aef6b..2489706d89 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -85,6 +85,17 @@ useful on systems such as FreeBSD which ships only with "etc/termcap". * Changes in Emacs 28.1 ++++ +** 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 function. 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 6927b6df6b..04fb1dc6d0 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -394,6 +394,7 @@ minibuffer-prompt-properties--setter ;; (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 5a41e2f30b..9d57a817b2 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -701,7 +701,7 @@ minibuffer-message 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 7c377da445..512aaf5f45 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 45353fbef3..548eebc9c7 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 f957b2ae17..11b5eec236 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,73 @@ 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 (minibuf_follows_frame ()) + minibuf_window = sf->minibuffer_window; + else if (minibuf_level) + { + Lisp_Object buffer = get_minibuffer (minibuf_level); + Lisp_Object tail, frame; + + minibuf_window = sf->minibuffer_window; + FOR_EACH_FRAME (tail, frame) + if (EQ (XWINDOW (XFRAME (frame)->minibuffer_window)->contents, + buffer)) + { + minibuf_window = XFRAME (frame)->minibuffer_window; + break; + } + } + else + minibuf_window = Qnil; } - /* 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, @@ -262,13 +304,15 @@ read_minibuf_noninteractive (Lisp_Object prompt, bool expflag, } 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 +t will be returned only if BUFFER is an active minibuffer. */) + (Lisp_Object buffer, Lisp_Object live) { Lisp_Object tem; + EMACS_INT i; if (NILP (buffer)) buffer = Fcurrent_buffer (); @@ -277,8 +321,22 @@ BUFFER can be a buffer or a buffer name. */) else CHECK_BUFFER (buffer); - tem = Fmemq (buffer, Vminibuffer_list); - return ! NILP (tem) ? Qt : Qnil; + /* tem = Fmemq (buffer, Vminibuffer_list); */ + /* return (!NILP (tem) && !EQ (tem, Vminibuffer_list)) ? Qt : Qnil; */ + if (!NILP (live)) + return !NILP (Fmemq (buffer, Vminibuffer_list)) ? Qt : Qnil; + if (EQ (buffer, Fcar (Vminibuffer_list))) + /* *Minibuf-0* is never active. */ + return Qnil; + tem = Fcdr (Vminibuffer_list); + for (i = 1; i <= minibuf_level; i++) + { + if (NILP (tem)) + return Qnil; + if (EQ (Fcar (tem), buffer)) + return Qt; + } + return Qnil; } DEFUN ("minibuffer-prompt-end", Fminibuffer_prompt_end, @@ -362,9 +420,6 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, Lisp_Object histstring; Lisp_Object histval; - Lisp_Object empty_minibuf; - Lisp_Object dummy, frame; - specbind (Qminibuffer_default, defalt); specbind (Qinhibit_read_only, Qnil); @@ -416,11 +471,12 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, { Lisp_Object str = build_string ("Command attempted to use minibuffer while in minibuffer"); - if (EQ (selected_window, minibuf_window)) - Fsignal (Quser_error, (list1 (str))); + if (!minibuf_follows_frame () + || EQ (selected_window, minibuf_window)) + Fsignal (Quser_error, (list1 (str))); else - /* If we're in another window, cancel the minibuffer that's active. */ - Fthrow (Qexit, str); + /* If we're in another window, cancel the minibuffer that's active. */ + Fthrow (Qexit, str); } if ((noninteractive @@ -433,6 +489,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 +542,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,23 +623,6 @@ 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. - Set them to point to ` *Minibuf-0*', which is always empty. */ - empty_minibuf = get_minibuffer (0); - - FOR_EACH_FRAME (dummy, frame) - { - Lisp_Object root_window = Fframe_root_window (frame); - Lisp_Object mini_window = XWINDOW (root_window)->next; - - 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); - } - /* 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). */ @@ -714,6 +754,16 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, return val; } +/* Returns whether a Lisp_Object 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 +825,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 +860,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 +877,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 +1964,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 +2009,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 an open minibuffer will move to a newly 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 e7433969d2..2eba509727 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 0e5dffbe00..0dfe34a011 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -31220,7 +31220,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; -- Alan Mackenzie (Nuremberg, Germany).