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: Wed, 21 Oct 2020 15:19:45 +0000 Message-ID: <20201021151945.GA19276@ACM> References: <20201014160240.GA7651@ACM> <83d01kzswk.fsf@gnu.org> <20201014163534.GB7651@ACM> <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> 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="25292"; mail-complaints-to="usenet@ciao.gmane.io" Cc: ghe@sdf.org, emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Wed Oct 21 17:34:37 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 1kVG8K-0006Rt-Bo for ged-emacs-devel@m.gmane-mx.org; Wed, 21 Oct 2020 17:34:36 +0200 Original-Received: from localhost ([::1]:47806 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kVG8J-0003Ac-CU for ged-emacs-devel@m.gmane-mx.org; Wed, 21 Oct 2020 11:34:35 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:35832) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kVFu6-00062F-NH for emacs-devel@gnu.org; Wed, 21 Oct 2020 11:19:54 -0400 Original-Received: from colin.muc.de ([193.149.48.1]:13808 helo=mail.muc.de) by eggs.gnu.org with smtp (Exim 4.90_1) (envelope-from ) id 1kVFu2-0002IR-SS for emacs-devel@gnu.org; Wed, 21 Oct 2020 11:19:54 -0400 Original-Received: (qmail 60845 invoked by uid 3782); 21 Oct 2020 15:19:46 -0000 Original-Received: from acm.muc.de (p4fe159f6.dip0.t-ipconnect.de [79.225.89.246]) by localhost.muc.de (tmda-ofmipd) with ESMTP; Wed, 21 Oct 2020 17:19:45 +0200 Original-Received: (qmail 19706 invoked by uid 1000); 21 Oct 2020 15:19:45 -0000 Content-Disposition: inline In-Reply-To: <83wnzrwdy5.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/21 11:19:46 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:258233 Archived-At: Hello, Eli. On Thu, Oct 15, 2020 at 21:18:10 +0300, Eli Zaretskii wrote: > > Date: Thu, 15 Oct 2020 18:01:43 +0000 > > Cc: ghe@sdf.org, emacs-devel@gnu.org > > From: Alan Mackenzie > > > The latter is correct, except for the last step: the active minibuffer > > > should have switched to F2, which is now the selected frame. > > OK, but I disagree. We seem to have different mental models of the > > minibuffer. For you, the MB, I think, is what demands immediate > > attention, therefore should be on the selected frame. For me, the MB is > > an integral part of the frame on which it was opened. > > Is there any chance we could implement an option for this (which has been > > mentioned already)? > I'm okay with such an option. I think I already said that. I too think you did. There's a proposed patch below. [ .... ] > > Anyhow, back to practicalities. I think we agreed last night (talking > > about the "hybrid" option) that the current way of doing things isn't > > very good. I think, but I'm not sure, that you're saying the MB, if > > open, should always be present on the currently selected frame and > > nowhere else. If I'm wrong here, could you possibly give a precise > > description of when you say the MB should be moved to a different frame. > IMO, the minibuffer should _always_ be on the selected frame, unless > it has its own minibuffer-only frame (in which case it is always > there). OK. I've implemented a new variable `minibuffer-follows-frame' which is t by default. For safety's sake, only the default-toplevel-value of this variable is consulted, to stop funny things happening should it be dynamically bound or if there might be a buffer local value. For minibuffer-follows-frame nil, the error "Command attempted to use minibuffer while in minibuffer" no longer aborts an enclosing minibuffer command. There doesn't seem any need or benefit - the enclosing command can carry on later. Here's my patch (including amendments to the Emacs manual and a NEWS entry). Comments would be most appreciated. diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index 54f046a7e0..0e6d86f3b8 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -69,6 +69,16 @@ Basic Minibuffer the minibuffer comes back. While the minibuffer is in use, Emacs does not echo keystrokes. + Whilst using the minibuffer, you can switch to a different frame, +perhaps to note text you need to enter (@pxref{Frame Commands}). By +default, the minibuffer moves to this new frame. If you set the user +option @code{minibuffer-follows-frame} to @code{nil}, then the +minibuffer stays in the frame where it was opened, 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 f3e3d9a1b6..95b5d7ffd0 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -86,6 +86,19 @@ useful on systems such as FreeBSD which ships only with "etc/termcap". * Changes in Emacs 28.1 +++ +** Switching frames when a minibuffer is active has been rationalized. +By default, the 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 the minibuffer was first opened +in. An alternative behavior is available by customizing +'minibuffer-follows-frame' to nil; here, the minibuffer stays on the +frame it was first opened on, and you must switch back to this frame +to continue or abort the current command. The old (pre 28.1), +somewhat chaotic behavior is no longer available. + ++++ +*** A new system for displaying documentation for groups of function is added. + ** New system for displaying documentation for groups of function. This can either be used by saying 'M-x shortdoc-display-group' and choosing a group, or clicking a button in the *Help* buffers when diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 3fd6ac031c..2120f5a627 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-frame minibuffer boolean "28.1") (enable-recursive-minibuffers minibuffer boolean) (history-length minibuffer (choice (const :tag "Infinite" t) integer) diff --git a/src/frame.c b/src/frame.c index 0b707c2af8..361a7119c5 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..5b00252f24 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4336,6 +4336,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 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..61863af538 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_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,74 @@ 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; + + FOR_EACH_FRAME (tail, frame) + { + if (EQ (XWINDOW (XFRAME (frame)->minibuffer_window)->contents, + buffer)) + { + minibuf_window = XFRAME (frame)->minibuffer_window; + goto after_set; + } + minibuf_window = sf->minibuffer_window; + after_set: ; + } + } + 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_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; + 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, @@ -362,9 +405,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 +456,15 @@ 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))); + /* OLD STOUGH, 2020-10-21 */ + 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); + /* END OF OLD STOUGH */ + Fsignal (Quser_error, (list1 (str))); } if ((noninteractive @@ -433,6 +477,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 +530,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 +611,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). */ @@ -1911,6 +1939,8 @@ syms_of_minibuf (void) staticpro (&minibuf_prompt); staticpro (&minibuf_save_list); + DEFSYM (Qminibuffer_follows_frame, + "minibuffer-follows-frame"); DEFSYM (Qcompletion_ignore_case, "completion-ignore-case"); DEFSYM (Qminibuffer_default, "minibuffer-default"); Fset (Qminibuffer_default, Qnil); @@ -1954,6 +1984,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-frame", minibuffer_follows_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_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. */); -- Alan Mackenzie (Nuremberg, Germany).