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: Sat, 13 Mar 2021 18:23:33 +0000 Message-ID: References: <87wnvkixrv.fsf@miha-pc> <874kinakv2.fsf@miha-pc> <87sg6690vc.fsf@miha-pc> 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="24005"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Stefan Monnier , jakanakaevangeli Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sat Mar 13 19:24:18 2021 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 1lL8vx-00066g-ML for ged-emacs-devel@m.gmane-mx.org; Sat, 13 Mar 2021 19:24:17 +0100 Original-Received: from localhost ([::1]:43000 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lL8vw-0006mz-On for ged-emacs-devel@m.gmane-mx.org; Sat, 13 Mar 2021 13:24:16 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:44278) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lL8vN-0006Kn-WB for emacs-devel@gnu.org; Sat, 13 Mar 2021 13:23:42 -0500 Original-Received: from colin.muc.de ([193.149.48.1]:10608 helo=mail.muc.de) by eggs.gnu.org with smtp (Exim 4.90_1) (envelope-from ) id 1lL8vK-0005Dw-Iu for emacs-devel@gnu.org; Sat, 13 Mar 2021 13:23:41 -0500 Original-Received: (qmail 39393 invoked by uid 3782); 13 Mar 2021 18:23:34 -0000 Original-Received: from acm.muc.de (p4fe15e36.dip0.t-ipconnect.de [79.225.94.54]) (using STARTTLS) by colin.muc.de (tmda-ofmipd) with ESMTP; Sat, 13 Mar 2021 19:23:34 +0100 Original-Received: (qmail 7701 invoked by uid 1000); 13 Mar 2021 18:23:33 -0000 Content-Disposition: inline In-Reply-To: X-Submission-Agent: TMDA/1.3.x (Ph3nix) 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-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=ham 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:266436 Archived-At: Hello, Stefan and jakanakaevangeli. On Thu, Feb 11, 2021 at 09:29:44 -0500, Stefan Monnier wrote: > > This is more involved. What do we want to happen when a frame with open > > minibuffers is deleted? I would say that these minibuffers should, > > except in the (eq minibuffer-follows-selected-frame t) case, be aborted, > > together with any others in other frames whose nesting level makes this > > necessary. > I vote for moving those minibuffers elsewhere (anywhere else is fine by > me, really). I assume it's no more complicated code-wise, and it should > suffer less from the risk of losing information. Just a quick recapitulation of the problem: when minibuffer-follows-selected-frame is nil, and enable-recursive-minibuffers t, it is possible to have several open minibuffers on several frames. If one of these frames is deleted, its minibuffers continue to exist, but are wholly inaccessible - the only thing to do with them is to abort them, e.g. with C-]. This is suboptimal. The patch below tries to solve this by, when such a frame gets deleted, "zipping" its minibuffers into those of another frame. The idea behind the patch is to use the mini-window's w->prev_buffers component to hold the list of its minibuffers. This mini-window component was unused by the rest of Emacs, apart from accidentally by window--before-delete-windows, which I have now amended. I would be grateful indeed if either of you (or indeed, anybody else), would apply the patch and try it out, or indeed comment on it. Thanks! diff --git a/doc/emacs/mini.texi b/doc/emacs/mini.texi index f81e64bdf9..7da0a48b7c 100644 --- a/doc/emacs/mini.texi +++ b/doc/emacs/mini.texi @@ -82,7 +82,9 @@ Basic Minibuffer (@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 diff --git a/lisp/window.el b/lisp/window.el index cfd9876ed0..fb2ea4a985 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -4158,7 +4158,7 @@ window--before-delete-windows 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 @@ record-window-buffer 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)) + (string-match "^ \\*Minibuf" (buffer-name buffer))) ;; Add an entry for buffer to WINDOW's previous buffers. (with-current-buffer buffer (let ((start (window-start window)) diff --git a/src/frame.c b/src/frame.c index a62347c1fb..b9df5739dd 100644 --- a/src/frame.c +++ b/src/frame.c @@ -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; } diff --git a/src/lisp.h b/src/lisp.h index b95f389b89..2f4e6377cb 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4348,7 +4348,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 *, int); extern bool is_minibuffer (EMACS_INT, Lisp_Object); extern EMACS_INT this_minibuffer_depth (Lisp_Object); extern EMACS_INT minibuf_level; diff --git a/src/minibuf.c b/src/minibuf.c index 4b1f4b1ff7..53ed8d5ef8 100644 --- a/src/minibuf.c +++ b/src/minibuf.c @@ -67,6 +67,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 +79,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 +87,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,83 +115,137 @@ 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); - } - } +/* Get the next live buffer entry from a w->prev_buffer, setting the pertinent + variables of `zip_minibuffer_stacks'. The parameter P is either "d" for + the destination structures, or "s" for the source structures. When the + ->prev_buffers list is exhausted, set di/si to -1. */ +#define NEXT_BUFFER_ENTRY(p) \ + do \ + { \ + if (NILP (p##_bufs)) \ + { \ + p##b = Qnil; \ + p##_ent = Qnil; \ + } \ + else \ + { \ + p##_ent = Fcar (p##_bufs); \ + p##b = Fcar (p##_ent); \ + p##_bufs = Fcdr (p##_bufs); \ + } \ + if (!NILP (p##b)) \ + p##i = this_minibuffer_depth (p##b); \ + else \ + p##i = -1; \ + } while (p##i == 0) + +/* 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 d_bufs, s_bufs; /* Lists of minibuffer entries */ + Lisp_Object acc = Qnil; + Lisp_Object d_ent, s_ent; /* Entries from dw/sw->prev_buffers */ + Lisp_Object db, sb; /* (Mini)buffers from the above */ + EMACS_INT di, si; /* Indices in the minibuffer list of db and sb */ + + if (!live_minibuffer_p (dw->contents) + && NILP (dw->prev_buffers)) + { + 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; + } + + if (live_minibuffer_p (dw->contents)) + call1 (Qrecord_window_buffer, dest_window); + if (live_minibuffer_p (sw->contents)) + call1 (Qrecord_window_buffer, source_window); + + d_bufs = Fnreverse (dw->prev_buffers); + s_bufs = Fnreverse (sw->prev_buffers); + + NEXT_BUFFER_ENTRY (d); + NEXT_BUFFER_ENTRY (s); + + while (di != -1 && si != -1) + if (di < si) + { + acc = Fcons (d_ent, acc); + NEXT_BUFFER_ENTRY (d); + } + else + { + acc = Fcons (s_ent, acc); + NEXT_BUFFER_ENTRY (s); + } + while (di != -1) + { + acc = Fcons (d_ent, acc); + NEXT_BUFFER_ENTRY (d); + } + while (si != -1) + { + acc = Fcons (s_ent, acc); + NEXT_BUFFER_ENTRY (s); + } + 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); } +#undef NEXT_BUFFER_ENTRY -/* 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 `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 self explanatory. */ +void +move_minibuffers_onto_frame (struct frame *of, int for_deletion) { - if (!minibuf_level) - return; - if (!minibuf_follows_frame ()) + if (!for_deletion && (!minibuf_level || !minibuf_follows_frame ())) return; if (FRAMEP (selected_frame) && FRAME_LIVE_P (XFRAME (selected_frame)) - && !EQ (minibuf_window, XFRAME (selected_frame)->minibuffer_window)) + && (for_deletion + || !EQ (minibuf_window, + XFRAME (selected_frame)->minibuffer_window))) { - EMACS_INT i; struct frame *sf = XFRAME (selected_frame); - Lisp_Object old_frame = XWINDOW (minibuf_window)->frame; - struct frame *of = XFRAME (old_frame); - - /* 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 mini_frame = XWINDOW (minibuf_window)->frame; + struct frame *mf = XFRAME (mini_frame); + if (minibuf_follows_frame () || for_deletion) + zip_minibuffer_stacks (sf->minibuffer_window, + of->minibuffer_window); + if (minibuf_window != sf->minibuffer_window) { Lisp_Object temp = get_minibuffer (0); - set_window_buffer (of->minibuffer_window, temp, 0, 0); + set_window_buffer (mf->minibuffer_window, temp, 0, 0); set_minibuffer_mode (temp, 0); + minibuf_window = sf->minibuffer_window; } } } @@ -221,6 +278,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 +602,7 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, Lisp_Object histval; Lisp_Object empty_minibuf; - Lisp_Object dummy, frame; + Lisp_Object old_minibuf_window = minibuf_window; specbind (Qminibuffer_default, defalt); specbind (Qinhibit_read_only, Qnil); @@ -626,17 +684,23 @@ read_minibuf (Lisp_Object map, Lisp_Object initial, Lisp_Object prompt, mini_frame = WINDOW_FRAME (XWINDOW (minibuf_window)); if (minibuf_level > 1 + && (!EQ (minibuf_window, old_minibuf_window)) && minibuf_moves_frame_when_opened () - && (!minibuf_follows_frame () - || (!EQ (mini_frame, selected_frame)))) + && (!minibuf_follows_frame ())) { - EMACS_INT i; + Lisp_Object old_frame = XWINDOW (old_minibuf_window)->frame; + struct frame *of = XFRAME (old_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, old_minibuf_window); + /* The frame's minibuffer can be on a different frame. */ + if (MINI_WINDOW_P (XWINDOW (FRAME_SELECTED_WINDOW (of)))) + Fset_frame_selected_window (old_frame, + Fframe_first_window (old_frame), Qnil); } + 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 +835,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 +955,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); @@ -1127,20 +1176,57 @@ 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); + } } + +/* Replace the expired minibuffer in the selected frame with the next + less nested minibuffer, if any, stored in the minibuffer window. + Otherwise, replace it with the null minibuffer. MINIBUF_WINDOW + gets set to the selected frame's minibuffer. */ +static void +minibuffer_unwind (void) +{ + struct frame *sf = XFRAME (selected_frame); + struct window *w; + Lisp_Object entry; + + if (FRAMEP (selected_frame) + && FRAME_LIVE_P (sf)) + { + minibuf_window = sf->minibuffer_window; + w = XWINDOW (minibuf_window); + if (!NILP (w->prev_buffers)) + { + entry = Fcar (w->prev_buffers); + w->prev_buffers = Fcdr (w->prev_buffers); + set_window_buffer (minibuf_window, Fcar (entry), 0, 0); + Fset_window_start (minibuf_window, Fcar (Fcdr (entry)), Qnil); + Fset_window_point (minibuf_window, Fcar (Fcdr (Fcdr (entry)))); + /* set-window-configuration may/will have unselected the + mini-window as the selected window. Restore it. */ + if (EQ (w->frame, selected_frame)) + Fset_frame_selected_window (selected_frame, minibuf_window, Qnil); + } + else + set_window_buffer (minibuf_window, nth_minibuffer (0), 0, 0); + } +} + void diff --git a/src/window.c b/src/window.c index eb16e2a433..cde53e8059 100644 --- a/src/window.c +++ b/src/window.c @@ -6958,7 +6960,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); diff --git a/src/xdisp.c b/src/xdisp.c index cc0a689ba3..a405d51f80 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -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; > Stefan -- Alan Mackenzie (Nuremberg, Germany).