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? ​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 {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)))