From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Jonas Bernoulli Newsgroups: gmane.emacs.bugs Subject: bug#61176: post-command-hook is not run if minibuffer input is aborted Date: Thu, 02 Feb 2023 15:36:15 +0100 Message-ID: <87sffojfs0.fsf@bernoul.li> References: <87y1pk2h8t.fsf@bernoul.li> Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="19614"; mail-complaints-to="usenet@ciao.gmane.io" Cc: 61176@debbugs.gnu.org To: Stefan Monnier Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Feb 02 15:37:25 2023 Return-path: Envelope-to: geb-bug-gnu-emacs@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 1pNaiI-0004nl-Kf for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 02 Feb 2023 15:37:22 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNai1-0000xo-Mm; Thu, 02 Feb 2023 09:37:05 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pNahz-0000xY-9s for bug-gnu-emacs@gnu.org; Thu, 02 Feb 2023 09:37:03 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1pNahz-0007kX-1y for bug-gnu-emacs@gnu.org; Thu, 02 Feb 2023 09:37:03 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1pNahy-000436-Gp for bug-gnu-emacs@gnu.org; Thu, 02 Feb 2023 09:37:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Jonas Bernoulli Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 02 Feb 2023 14:37:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 61176 X-GNU-PR-Package: emacs Original-Received: via spool by 61176-submit@debbugs.gnu.org id=B61176.167534858015506 (code B ref 61176); Thu, 02 Feb 2023 14:37:02 +0000 Original-Received: (at 61176) by debbugs.gnu.org; 2 Feb 2023 14:36:20 +0000 Original-Received: from localhost ([127.0.0.1]:33001 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pNahI-000422-6r for submit@debbugs.gnu.org; Thu, 02 Feb 2023 09:36:20 -0500 Original-Received: from mail.hostpark.net ([212.243.197.30]:37448) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1pNahG-00041t-4F for 61176@debbugs.gnu.org; Thu, 02 Feb 2023 09:36:19 -0500 Original-Received: from localhost (localhost [127.0.0.1]) by mail.hostpark.net (Postfix) with ESMTP id BF657167E0; Thu, 2 Feb 2023 15:36:15 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=bernoul.li; h= content-type:content-type:mime-version:message-id:date:date :references:in-reply-to:subject:subject:from:from:received :received; s=sel2011a; t=1675348575; bh=XzbCabR5Fslcbqt5vYa2Kz8u WmRv7F6dAfQMoC14v4I=; b=TY1jHPmSAJcdr/reR0W7ZNA9Wq8FtVFIEL5z3sCU uhrm31z0mKIqfj0UlSVEQ7BUVQ2pb+YREfj45G5ezIfBq58cAGBJKd3lJVs6y8IA mrWk/hmFl+ug8WfKywl4m0iR8K+JP/9OC9MN6setwVlUekHOPWelSwuuwKFhyF5j oyU= X-Virus-Scanned: by Hostpark/NetZone Mailprotection at hostpark.net Original-Received: from mail.hostpark.net ([127.0.0.1]) by localhost (mail1.hostpark.net [127.0.0.1]) (amavisd-new, port 10224) with ESMTP id 9vW2fgu3xlFB; Thu, 2 Feb 2023 15:36:15 +0100 (CET) Original-Received: from customer (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mail.hostpark.net (Postfix) with ESMTPSA id 8C00016782; Thu, 2 Feb 2023 15:36:15 +0100 (CET) In-Reply-To: X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:254659 Archived-At: Stefan Monnier writes: >> - However, when the command reads from the minibuffer and the user >> aborts that, then post-command-hook is NOT run a second time AFTER >> the command. > > Could you clarify what you mean here? > Let's say in the following scenario: > > - The user hits a key like `M-x` which causes a minibuffer to be entered. > - the user hits C-g. > - Emacs exits the minibuffer and doesn't even call the command because > the interactive args could not be gathered. > > When do you expect `post-command-hook` to be run? Going back to the examples I provided, if the user does NOT abort, then this happens: >> ;; -setup ([f1] -command) >> ;; -post ([] -command) >> ;; -post ([97] self-insert-command) >> ;; -exit ([return] exit-minibuffer) >> ;; -command >> ;; -post ([f1] -command) And if the user DOES abort, I would like the behavior to be changed like so: >> ;; -setup ([f1] -command) >> ;; -post ([] -command) >> ;; -exit ([7] abort-minibuffers) >> ;; Quit >> ;; -post ([] abort-minibuffers) *NEW* -post ([f1] -command) > There's also the case where the command is called and it enters the > minibuffer (rather than doing it within the interactive spec). Not sure > if it makes a significant difference. The sequence of events is the same as in the above case. I posted the wrong log in the examples I provided. I posted two instances of the user not aborting instead providing the output for when they abort, which is: >> ;; -command >> ;; -setup ([f1] -command) >> ;; -post ([] -command) >> ;; -exit ([7] abort-minibuffers) >> ;; Quit >> ;; -post ([] abort-minibuffers) So it seems that iff a command uses the minibuffer, then post-command-hook is ALWAYS run for it right before the minibuffer is entered, regardless of where the recursive edit is entered; and iff the user aborts the minibuffer, then the post-command-hook is NEVER run "after"/"post" the outer command. > Also it would help to know what you need `post-command-hook` for. This is relevant for the Transient package. The following is a simplified description of relevant parts of what it does. Calling a transient prefix command installs a transient keymap and adds functions to `pre-command-hook' and `post-command-hook'. The pre-command function is responsible for determining whether a subsequently called (suffix) command should exit the transient state. If we are about to exit the transient, then this does also set some global variables to nil, which are only relevant while the transient is still active. However, it does not and cannot unset all variables and most importantly it does not remove the transient keymap and the pre- and post-command functions. This function may (or may not) set other global variables, so that the command that is about to be called has access to the arguments set in the transient command. This is similar to how prefix arguments are implemented. However, if I remember correctly, in the case of prefix arguments, there is some C code that takes care of unsetting the prefix argument, if the next command is aborted. Transient on the other hand has to do that in Elisp, and it used the post-command-hook for that (among other things). Then the command's interactive spec is processed and then the command is called with the arguments thus determined. Once that is done, then the post-command function is called. It is responsible for redisplaying the transient buffer that displays the available suffix commands. Or if the suffix command should exit the transient, then it has to remove the transient map and the pre- and post functions, and unset the variables that serve a similar role to prefix-arg, as well as some internal variables. The suffix command may use the minibuffer inside interactive and/or in its body. If that happens, then transient has to suspend the transient keymap and pre- and post-command functions, while the minibuffer is in use. There are two kinds of suffix commands that may (or may not) use the minibuffer: (1) Commands that are specifically designed to be used to set the value of some argument in the transient command. These commands are fully under our control and are designed to handle the suspension and resuming of the transient map and the pre- and post-command hooks, using minibuffer-setup-hook, minibuffer-exit-hook, and unwind-protect. (2) Arbitrary commands, which may have been written with Transient in mind, but which more likely do nothing to account for the needs of Transient, i.e., any command that exists in Emacs. When a (2) command is invoked and that uses the minibuffer, then our post-command function detects that because it is called with this-command being the suffix command and this-command-keys-vector being an empty vector. The transient map and pre- and post-command hooks are then suspended like when a (1) command uses the minibuffer, but it is not possible to use unwind-protect to ensure that these things are reinstated (or the transient is fully exited), even if the user aborts while the minibuffer is active. If post-command-hook were run ("post" command) regardless of whether the user aborts the minibuffer, then such aborts would not have to be handled specifically. Since that is not the case, the pre-mature invocation of the post-command function, has to delay the resume-or-exit work until some other event occurs. Currently that is being done by adding a new minibuffer-exit function and *another* post-command function. The first is designed perform the resume/exit behavior if the minibuffer is aborted, and the second function is designed to perform the resume/exit behavior if the first function did not end up doing that, i.e., if the minibuffer was not aborted. This is fragile. Heuristics have to be used to determine whether the minibuffer is exited normally or if it was aborted. (There is only minibuffer-exit-hook, and as far as I can tell, there is no reliable way to determine whether that was called because the minibuffer was exited normally or was aborted.) This approach works more or less, but a few times I already though I had finally tweaked it enough to handle all edge-cases, only to later learn that was not so. Currently there is one case where it doesn't work as intended. And if a third-party completion framework were used that exits the minibuffer in some highly unexpected way then it would also not work (but currently no such framework does that, I believe)). > [ There are several "alternatives" to `post-command-hook` plus there > are cases where code is executed not via a command, yet it can be > viewed as a command execution as well (e.g. opening a file via > `emacsclient`), so over the years ad-hoc calls to `post-command-hook` > have been sprinkled outside of the "command-loop", which makes this > whole business even more muddy. ] What alternatives are you thinking about? Piuu, that got a bit long. I left out details, but I hope it became clear why I need the post-command-hook to always be run *post* command (and that that is a legitimate need). > - The user hits a key like `M-x` which causes a minibuffer to be entered. > - the user hits C-g. > - Emacs exits the minibuffer and doesn't even call the command because > the interactive args could not be gathered. That could be used as an argument as to why post-command-hook should not be run when the interactive minibuffer is aborted: if we never go "inside" the command, there also is no point in going "post" command. However, it does not appear that this is actually the reason why post-command-hook is not being run "post" command, not from a design perspective at least. If the user aborts the minibuffer usage of the following command, which uses the minibuffer in its body, then the post-command-hook also isn't being run "post" command, even though we clearly made it "into" the command: >> (defun -command () >> (interactive) >> (message ";; -command") >> (read-string "-command: ")) >> >> ;; -command >> ;; -setup ([f1] -command) >> ;; -post ([] -command) >> ;; -exit ([7] abort-minibuffers) >> ;; Quit >> ;; -post ([] abort-minibuffers) Thanks for looking into this! Jonas