[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Problem quitting properly from transient keymap with one keystroke

From: Robert Weiner
Subject: Re: Problem quitting properly from transient keymap with one keystroke
Date: Thu, 19 Oct 2017 11:40:36 -0400

On Wed, Oct 18, 2017 at 11:13 PM, Bob Weiner <address@hidden> 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?

​I have figured it out though, the resultant code cries out for a simplification
in handling ​exiting 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-exit
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

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.



(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.
    ('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

reply via email to

[Prev in Thread] Current Thread [Next in Thread]