From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Robert Weiner Newsgroups: gmane.emacs.devel Subject: Re: Problem quitting properly from transient keymap with one keystroke Date: Thu, 19 Oct 2017 11:40:36 -0400 Message-ID: References: Reply-To: rswgnu@gmail.com NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="001a1141bda23fde41055be82f4c" X-Trace: blaine.gmane.org 1508427716 2654 195.159.176.226 (19 Oct 2017 15:41:56 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Thu, 19 Oct 2017 15:41:56 +0000 (UTC) To: emacs-devel Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Thu Oct 19 17:41:52 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1e5Cx6-0007HS-CW for ged-emacs-devel@m.gmane.org; Thu, 19 Oct 2017 17:41:44 +0200 Original-Received: from localhost ([::1]:49775 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1e5CxA-0001Oq-Oh for ged-emacs-devel@m.gmane.org; Thu, 19 Oct 2017 11:41:48 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:43680) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1e5CwZ-000138-De for emacs-devel@gnu.org; Thu, 19 Oct 2017 11:41:13 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1e5CwV-0001eA-VC for emacs-devel@gnu.org; Thu, 19 Oct 2017 11:41:11 -0400 Original-Received: from fencepost.gnu.org ([2001:4830:134:3::e]:35651) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1e5CwV-0001e3-Pv for emacs-devel@gnu.org; Thu, 19 Oct 2017 11:41:07 -0400 Original-Received: from mail-qt0-f171.google.com ([209.85.216.171]:47029) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_128_CBC_SHA1:128) (Exim 4.82) (envelope-from ) id 1e5CwV-0008Al-Fi for emacs-devel@gnu.org; Thu, 19 Oct 2017 11:41:07 -0400 Original-Received: by mail-qt0-f171.google.com with SMTP id 1so14970959qtn.3 for ; Thu, 19 Oct 2017 08:41:07 -0700 (PDT) X-Gm-Message-State: AMCzsaXIj8lKvCqUtqEI9Jo6NeAFI0bmX/H1fCAKs3huF5InI5rGola+ CEG+DUWVIGoWkAJlDwoBNflwbjkKFVUF1AYlf0M= X-Google-Smtp-Source: ABhQp+QE1NDGD7UyEXDN6S5L+ox80nhZcPWOjr4ivrEwlWKbcCTztAht6n02K5Y/I7AATjmPKSMb0ouOsfIi2XzewT8= X-Received: by 10.200.44.9 with SMTP id d9mr2617928qta.173.1508427666806; Thu, 19 Oct 2017 08:41:06 -0700 (PDT) Original-Received: by 10.200.12.74 with HTTP; Thu, 19 Oct 2017 08:40:36 -0700 (PDT) In-Reply-To: X-Gmail-Original-Message-ID: X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2001:4830:134:3::e X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 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.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:219630 Archived-At: --001a1141bda23fde41055be82f4c Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Wed, Oct 18, 2017 at 11:13 PM, Bob Weiner wrote: > > The problem is that set-transient-map uses pre-command-hook to test the > stay-in-transient-map function and to exit, so if a keystroke sets the > flag read by the stay-in-transient-map function to disable the transient > map, this check is not done until another key is pressed because the > pre-command-hook is not run again until then (so the code doesn't see > the flag change until then). > > I can't just add a post-command-hook that calls the transient map > disable function because calling (keyboard-quit) from the > post-command-hook triggers an error and I imagine that is not a proper > usage scenario. > > Is there any clean way to exit and abort from a transient keymap upon a > single key press? =E2=80=8BI have figured it out though, the resultant code cries out for a simplification in handling =E2=80=8Bexiting from a transient map. The most important part of the solution is that whatever you add to post-command-hook to process each key press in the transient map must also manually call the function that set-transient-map returns to force a quit of the transient map (call this the quit-function). The quit-function removes the transient map setup and then calls the on-exi= t function (optional 3rd argument to set-transient-map) which does application-specific exit processing. The on-exit function is where the full processing of the application's {C-g} and quit command have to go. But even that is not enough. The application's post-command-hook must catch {C-g} and continue its processing; any attempt to do a (keyboard-quit) from post-command-hook will generate an error rather than just doing the quit. The only thing I found that worked was using (top-level) to ensure a clean exit from any recursive-levels when aborting with {C-g}. Here is the resulting abbreviated code for reference. It seems like a lot to setup to allow for aborting and quitting while always ensuring that the quit code is executed. The Elisp manual could use some examples of handling both aborts and quits from transient maps so this is clearer. I guess we could create some macros that would at least make this setup reusable. Bob ----- (defvar hycontrol-frames-mode-map (let ((map (make-sparse-keymap))) (suppress-keymap map) ;; Disable self-inserting keys. (define-key map "\C-g" (lambda () (interactive) (setq hycontrol--exit-status 'abort-from-frames))) (define-key map "q" (lambda () (interactive) (setq hycontrol--exit-status 'quit-from-frames))))) (defvar hycontrol--quit-function nil "Stores function auto-generated by a call to `set-transient-map' to remove the transient-map later.") (defconst hycontrol--frames-prompt "Frames> ") (defun hycontrol-frames-post-command-hook () "Added to `post-command-hook' while in HyControl frames mode." (condition-case () (funcall #'hycontrol-frames-post-command-hook-body) (quit (funcall #'hycontrol-frames-post-command-hook-body)))) (defun hycontrol-frames-post-command-hook-body () (if hycontrol--exit-status (progn (remove-hook 'post-command-hook 'hycontrol-frames-post-command-hook) (funcall hycontrol--quit-function)) (message hycontrol--frames-prompt))) (defun hycontrol-exit-mode () "Run by the HyControl frame or window transient keymap after it is disabled." (setq inhibit-quit nil) (pcase hycontrol--exit-status ('abort-from-frames (progn (ding) (setq hycontrol--exit-status nil) ;; Use of (keyboard-quit) here would ;; trigger an error in post-command-hook, ;; so don't use. (top-level))) ('quit-from-frames (message "Finished controlling frames")))) (defun hycontrol-stay-in-mode () "Return non-nil if HyControl mode should remain active." (null hycontrol--exit-status)) (defun hycontrol-frames () "(Excerpt only) Control frames interactively." (interactive "p") (setq inhibit-quit t hycontrol--exit-status nil) (funcall #'hycontrol-frames-post-command-hook) (add-hook 'post-command-hook 'hycontrol-frames-post-command-hook) ;; Use normal event loop with transient-map until {C-g} or {q} is ;; pressed, then exit. (setq hycontrol--quit-function (set-transient-map hycontrol-frames-mode-map #'hycontrol-stay-in-mode #'hycontrol-exit-mode))) --001a1141bda23fde41055be82f4c Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
On Wed, Oct 18, 2= 017 at 11:13 PM, Bob Weiner <rsw@g= nu.org> wrote:

The problem is that set-transient-map uses pre-command-hook to test the
stay-in-transient-map function and to exit, so if a keystroke sets the
flag read by the stay-in-transient-map function to disable the transient map, this check is not done until another key is pressed because the
pre-command-hook is not run again until then (so the code doesn't see the flag change until then).

I can't just add a post-command-hook that calls the transient map
disable function because calling (keyboard-quit) from the
post-command-hook triggers an error and I imagine that is not a proper
usage scenario.

Is there any clean way to exit and abort from a transient keymap upon a
single key press?

=E2=80=8BI have figured it out tho= ugh, the resultant code cries out for a simplification
in handling =E2=80= =8Bexiting from a transient map.=C2=A0 The most important part
of the solu= tion is that whatever you add to post-command-hook to process
each key pre= ss in the transient map must also manually call the function
that set-tran= sient-map returns to force a quit of the transient map (call
this the quit= -function).

The quit-function removes the transient map setup and th= en calls the on-exit
function (optional 3rd argument to set-transient-map)= which does application-specific
exit processing.=C2=A0 The on-exit functi= on is where the full processing of the
application's {C-g} and quit co= mmand have to go.=C2=A0 But even that is not enough.
The application's= post-command-hook must catch {C-g} and continue its processing;
any attem= pt to do a (keyboard-quit) from post-command-hook will generate an error
r= ather than just doing the quit.=C2=A0 The only thing I found that worked wa= s using
(top-level) to ensure a clean exit from any recursive-levels when = aborting with
{C-g}.

Here is the resulting abbreviated code for ref= erence.=C2=A0 It seems like a lot to setup to allow
for aborting and quitt= ing while always ensuring that the quit code is executed.=C2=A0 The Elisp
= manual could use some examples of handling both aborts and quits from trans= ient maps so this
is clearer.=C2=A0 I guess we could create some macros th= at would at least make this setup reusable.

Bob

-----

(defvar hycontrol-frames-mode-map
=C2=A0 (let ((ma= p (make-sparse-keymap)))
=C2=A0 =C2=A0 (suppress-keymap map) ;; Disable s= elf-inserting keys.
=C2=A0 =C2=A0 (define-key map "\C-g"=C2=A0 = (lambda () (interactive) (setq hycontrol--exit-status 'abort-from-frame= s)))
=C2=A0 =C2=A0 (define-key map "q"=C2=A0 =C2=A0 =C2=A0(lamb= da () (interactive) (setq hycontrol--exit-status 'quit-from-frames)))))=

(defvar hycontrol--quit-function nil
=C2=A0 "Stores functi= on auto-generated by a call to `set-transient-map' to remove the transi= ent-map later.")

(defconst hycontrol--frames-prompt "F= rames> ")

(defun hycontrol-frames-post-command-hook ()
= =C2=A0 "Added to `post-command-hook' while in HyControl frames mod= e."
=C2=A0 (condition-case ()
=C2=A0 =C2=A0 =C2=A0 (funcall #'= hycontrol-frames-post-command-hook-body)
=C2=A0 =C2=A0 (quit (funcall #&#= 39;hycontrol-frames-post-command-hook-body))))

(defun hycontrol-fr= ames-post-command-hook-body ()
=C2=A0 (if hycontrol--exit-status
=C2=A0= =C2=A0 =C2=A0 (progn (remove-hook 'post-command-hook 'hycontrol-fr= ames-post-command-hook)
=C2=A0 = =C2=A0 =C2=A0(funcall hycontrol--quit-function))
=C2=A0 =C2=A0 (message h= ycontrol--frames-prompt)))

= (defun hycontrol-exit-mode ()
=C2=A0 = "Run by the HyControl frame or window transient keymap after it is dis= abled."
=C2=A0 (setq inhibit-quit nil)
=C2=A0 (pcase hycontrol--ex= it-status
=C2=A0 =C2=A0 ('abort-from-frames=C2=A0 =C2=A0(progn (ding)=
=C2=A0 (setq hycontrol--exit-= status nil)
;; Use of (keyboa= rd-quit) here would
;; trigge= r an error in post-command-hook,
<= font face=3D"monospace, monospace"> ;; so don't use.
(top= -level)))
=C2=A0 =C2=A0 ('quit-from-frames=C2=A0 =C2=A0 (message &quo= t;Finished controlling frames"))))

(defun hycontrol-stay-in-m= ode ()
=C2=A0 "Return non-nil if HyControl mode should remain active= ."
=C2=A0 (null hycontrol--exit-status))

(defun hycontrol-f= rames ()
=C2=A0 "(Excerpt only) Control frames interactively."<= /font>
=C2=A0 (setq inhibit-quit t
hycontrol--exit-status nil)
<= div class=3D"gmail_default">=C2=A0 (fun= call #'hycontrol-frames-post-command-hook)
=C2=A0 (add-hook 'post= -command-hook 'hycontrol-frames-post-command-hook)
=C2=A0 ;; Use norm= al event loop with transient-map until {C-g} or {q} is
=C2=A0 ;; pressed,= then exit.
=C2=A0 (setq hycontrol--quit-function
(set-transient-map hycontrol-frames-mode-map #'hyco= ntrol-stay-in-mode
=C2=A0 =C2= =A0#'hycontrol-exit-mode)))


--001a1141bda23fde41055be82f4c--