abo-abo / lispy

Short and sweet LISP editing
http://oremacs.com/lispy/
1.2k stars 132 forks source link

making lispy map transient? #577

Closed hai-nc closed 3 years ago

hai-nc commented 3 years ago

Lispy is an amazing package. However, it happens to me sometimes when my cursor is in front of an opening parenthesis and I want to insert a character I accidentally invoke a lispy command instead.

I think this issue can be solved by making a lispy map transient, which is temporarily active when the user press the 'lispy-forward' or 'lispy-backward' key and the map remains active only when the user keep pressing keys defined in this map. Additionally, the cursor color could be temporarily set to some color when the lispy map active and restored back to normal when the lispy map no longer active.

This interface has the following advantages:

Anyway, I am new to lispy and this is just my thought experiment.

For illustration, I have implemented the code below (tested on Emacs 28.0.50). After eval-ing them, pressing 'M-0' to move forward to an opening parenthesis or 'M-9' to move backward to a closing one, and the map remains active only as long as the user press the key 'f' (forward-list) or 'b' (backward-list) afterwards. (Note: in this demonstration, the key 'M-0' and 'M-9' are set globally to my-lispy-forward and my-lispy-backward, respectively.)

(defconst my-lispy-opening-chars '(?\( ?\[ ?\{))
(defconst my-lispy-closing-chars '(?\) ?\] ?\}))
(defconst my-lispy-parens-regexp "\\[\\|\\]\\|[(){}]"
  "Used to search for the next opening/closing char (either parenthese, bracket,
or curly brace).")

(defvar my-lispy-mode-map (make-sparse-keymap))

(cl-loop for (keyname fun) in
         '(("b" backward-list)
           ("f" forward-list))
           do (define-key my-lispy-mode-map (if (stringp keyname)
                                               (kbd keyname)
                                             keyname)
                fun))

(defvar my-lispy-saved-cursor-color nil)
(defconst my-lispy-cursor-color "red")

(defun my-lispy-cursor-on ()
  "Turn on the cursor to warn the user that lispy map is active."
  (setq my-lispy-saved-cursor-color
        (frame-parameter (selected-frame) 'cursor-color))
  (set-cursor-color my-lispy-cursor-color))

(defun my-lispy-cursor-off ()
  "Restore the color of the cursor before lispy map is active."
  (set-cursor-color my-lispy-saved-cursor-color))

(defun my-lispy-forward (&optional count)
  (interactive "p")
  ;; if cursor is right in front of an opening char, don't skip it unless the
  ;; user repeat this very command a second time. This is because the user may
  ;; just want to activate the lispy transient map.
  (when (and (member (char-after) my-lispy-opening-chars)
             (eq real-last-command 'my-lispy-forward))
    (forward-char))
  (re-search-forward my-lispy-parens-regexp)
  (when (member (char-before) my-lispy-opening-chars)
    (backward-char))
  (set-transient-map my-lispy-mode-map t 'my-lispy-cursor-off)
  (my-lispy-cursor-on))

(defun my-lispy-backward (&optional count)
  (interactive "p")
  ;; if cursor is right in front of an opening char, don't skip it unless the
  ;; user repeat this very command a second time. This is because the user may
  ;; just want to activate the lispy transient map.
  (when (and (member (char-before) my-lispy-closing-chars)
             (eq real-last-command 'my-lispy-backward))
    (backward-char))
  (re-search-backward my-lispy-parens-regexp)
  (when (member (char-after) my-lispy-closing-chars)
    (forward-char))
  (set-transient-map my-lispy-mode-map t 'my-lispy-cursor-off)
  (my-lispy-cursor-on))

(global-set-key (kbd "M-9") 'my-lispy-backward)
(global-set-key (kbd "M-0") 'my-lispy-forward)

(provide 'my-lispy)
hai-nc commented 3 years ago

After reconsideration I think this is just a minor fix that would break the normal lispy keybinding muscle memory so it may not be worthwhile to consider, so I just close this then.