解决 Ctrl 和 Meta 按键的方案之一

今天下午看狗哥的 Meow ,发现其理念中有一条是极力避免 Ctrl Meta 等键。最近写代码的时候我的小拇指有一点痛,忽然就有了这个想法。

现在已经有了各种各样的模式编辑插件,解决了总是按 Ctrl Meta 等键 90% 的问题,但还有 9% 则是各种 minibuffer 导致的,一般的模式编辑里都默认在 minibuffer 中不启用。
我今天为我的 Sniem 加上了一个新模式 minibuffer-keypad-mode ,它可以让用户在 minibuffer 下依旧不需要使用 Ctrl 等按键。先放图:

使用方法就是,默认打开是输入状态,按空格后接内容也就是普通输入空格后再插入文字。按两次空格就进入或者退出 keypad 模式;可以用 , . / 进行切换快捷键前缀,,C-.M-/C-M- ,如果输入的那个字符所对应的前缀和当前前缀相同,就直接输入;否则切换当前前缀。

该模式实现很容易,就是把空格绑定一个函数,用来开关 minibuffer 下的 keypad 模式,代码如下:


(defun sniem-minibuffer-keypad-start-or-stop ()
  "Start or stop the minibuffer-keypad mode."
  (interactive)
  (self-insert-command 1 32)
  (let ((char (read-char))
        command)
    (if (= 32 char)
        (progn
          (setq-local sniem-minibuffer-keypad-on ;这个变量用于检测 keypad 模式是否打开,要自己加
                     (if sniem-minibuffer-keypad-on
                         nil
                       t))
          (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
      (if (and sniem-minibuffer-keypad-on
               (memq char '(44 46 47)))
          (progn
            (setq-local sniem-minibuffer-keypad-prefix
                        (sniem-keypad--convert-prefix char))
            (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
        (execute-kbd-macro (vector char))))))

其它所有输入按键都绑定的是另一个函数:


(defun sniem-minibuffer-keypad ()
  "The function to insert the input key or execute the function."
  (interactive)
  (if sniem-minibuffer-keypad-on
      (sniem-keypad last-input-event)
    (self-insert-command 1)))

这个函数用于正常输入或者调用 sniem-keypad 进行按键操作。

然后就是 sniem-keypad function


(defun sniem-keypad (&optional external-char)
  "Execute the keypad command.
EXTERNAL-CHAR is the entrance for minibuffer-keypad mode."
  (interactive)
  (let ((key (if external-char
                 (when (and (memq external-char '(44 46 47))
                            (/= (sniem-keypad--convert-prefix
                                 sniem-minibuffer-keypad-prefix)
                                external-char))
                   (setq-local sniem-minibuffer-keypad-prefix ;这个变量要自己加
                               (sniem-keypad--convert-prefix external-char))
                   (setq external-char t))
               (pcase last-input-event
                 (109 "M-") (98 "C-M-") (118 "C-"))))
        tmp command prefix-used-p)
    (unless (stringp key)
      (setq key (if external-char
                    (concat sniem-minibuffer-keypad-prefix
                            (when (numberp external-char)
                              (concat (char-to-string external-char) " ")))
                  (concat "C-" (char-to-string last-input-event) " "))))

    (message key)
    (catch 'stop
      (when (and (numberp external-char)
                 (commandp (setq command (key-binding (read-kbd-macro (substring key 0 -1))))))
        (throw 'stop nil))
      (while (setq tmp (read-char))
        (if (= tmp 127)
            (setq key (substring key 0 -2))
          (when (= tmp 59)
            (keyboard-quit))
          (setq key (concat key
                            (cond ((and (= tmp 44)
                                        (null prefix-used-p))
                                   (setq prefix-used-p t)
                                   "C-")
                                  ((and (= tmp 46)
                                        (null prefix-used-p))
                                   (setq prefix-used-p t)
                                   "M-")
                                  ((and (= tmp 47)
                                        (null prefix-used-p))
                                   (setq prefix-used-p t)
                                   "C-M-")
                                  (t
                                   (when prefix-used-p
                                     (setq prefix-used-p nil))
                                   (concat (char-to-string tmp) " "))))))
        (message key)
        (when (commandp (setq command (key-binding (read-kbd-macro (substring key 0 -1)))))
          (throw 'stop nil))))
    (call-interactively command)))

这个函数是我写 Sniem 之前看到 Meow 有这个功能后实现的,当时用的是函数写得,现在发现用函数给我省去了不少时间。不过目前还需要修改。

还有获取对应前缀的函数:


(defun sniem-keypad--convert-prefix (prefix)
  "Convert PREFIX from string to char or from char to string."
  (let* ((prefix-string '("C-" "M-" "C-M-"))
         (prefix-char '(44 46 47))
         (from (if (stringp prefix)
                   prefix-string
                 prefix-char))
         (to (if (stringp prefix)
                 prefix-char
               prefix-string))
         index)
    (setq index (sniem--index prefix from))
    (when index
      (nth index to))))

现在是解决了 99% 的问题了,剩下的就是 EAF 和输入法导致的了,目前我还没办法解决。:joy:

1 个赞

相当于说 minibuffer 中两下空格当 leader ?

可以把 ctrl 挪到右 alt 上,我这么干了以后就没再出现什么不舒服的感觉了。

也可以直接按右边的 Ctrl 。习惯了之后也还不错。

这个思路挺棒的,minibuffer输入两次空格的实际需求太少了,拿来启动keypadding刚好。如果能再多一个退出这个状态的方案,比如keypadding后用户发现想继续输入字符来进一步筛选选项,感觉更灵活了

退出状态也可能是伪需求,因为用户非常可能是看到了想选择的项才决定进入keypadding的

也不能说是 leader 吧,可以当做普通输入空格,也可以两下空格当开关。

我 Ctrl 绑定在 Caps_Lock 上,开始用还可以,时间长了之后还是觉得不是特别好。个人原因吧。
然后就是我觉得 Alt 搭配同一只手按的区域的时候就也不是特别舒适。
既然 ELisp 能做到基本舍去 Ctrl Meta ,何乐而不为呢 :joy:

有的,两次空格就是开关。没有打开 keypad 就打开,打开了两次空格就关闭。

我倒是蛮喜欢ctrl, meta,否就入vim阵营了。。。个人习惯吧,ctrl换到caps lock,感觉挺好的

1 个赞

把两个函数稍微改进了一下:


(defun sniem-minibuffer-keypad-start-or-stop ()
  "Start or stop the minibuffer-keypad mode."
  (interactive)
  (self-insert-command 1 32)
  (let ((char (read-char))
        command)
    (if (= 32 char)
        (progn
          (setq-local sniem-minibuffer-keypad-on
                      (if sniem-minibuffer-keypad-on
                          nil
                        t))
          (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
      (if (and sniem-minibuffer-keypad-on
               (memq char '(44 46 47)))
          (progn
            (setq-local sniem-minibuffer-keypad-prefix
                        (sniem-keypad--convert-prefix char))
            (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
        (sniem-minibuffer-keypad)))))

(defun sniem-minibuffer-keypad ()
  "The function to insert the input key or execute the function."
  (interactive)
  (if sniem-minibuffer-keypad-on
      (sniem-keypad last-input-event)
    (if (symbolp last-input-event)
        (progn
          (sniem-minibuffer-keypad-mode -1)
          (let (command)
            (when (commandp (setq command
                                  (key-binding
                                   (vector last-input-event))))
              (let ((last-command-event last-input-event))
                (ignore-errors
                  (call-interactively command)))))
          (sniem-minibuffer-keypad-mode t))
      (let ((last-command-event last-input-event))
        (call-interactively #'self-insert-command)))))

现在这个版本在 eval-expression 中可以正常补全括号等;其它的一些非输入的按键比如 C-backspace 也不会报错了。

强啊老哥,效率挺快

:joy: 只是改了几行代码

这个可以不依赖 sniem 单独成一个包吗.

有时间我写一个 :joy:
其实现在这个我给的代码差不多了,基本只需要改一下名字,然后加一个 define-minor-mode 就可以用了

1 个赞

(defun sniem-minibuffer-keypad ()
  "The function to insert the input key or execute the function."
  (interactive)
  (if sniem-minibuffer-keypad-on
      (sniem-keypad last-input-event)
    (if (or (symbolp last-input-event)
            (< last-input-event 33)
            (> last-input-event 126))
        (progn
          (sniem-minibuffer-keypad-mode -1)
          (let (command)
            (if (commandp (setq command
                                (key-binding
                                 (vector last-input-event))))
                (let ((last-command-event last-input-event))
                  (ignore-errors
                    (call-interactively command)))
              (execute-kbd-macro (vector last-input-event))))
          (sniem-minibuffer-keypad-mode t))
      (let ((last-command-event last-input-event))
        (call-interactively #'self-insert-command)))))

又修改了一下,由于一些按键有的转化成 last-input-event 后依旧是 ASCII 而不是像 backspace 这样的 symbol,就干脆直接用 ASCII 码的大小来判断了。


(defun sniem-minibuffer-keypad-start-or-stop ()
  "Start or stop the minibuffer-keypad mode."
  (interactive)
  (if current-input-method
      (if (and (= (char-before) 32)
               (not (= (point) (line-beginning-position))))
          (progn
            (setq-local sniem-minibuffer-keypad-on
                        (if sniem-minibuffer-keypad-on
                            nil
                          t))
            (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
        (self-insert-command 1 32))
    (self-insert-command 1 32)
    (let ((char (read-char)))
      (if (= 32 char)
          (progn
            (setq-local sniem-minibuffer-keypad-on
                        (if sniem-minibuffer-keypad-on
                            nil
                          t))
            (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
        (if (and sniem-minibuffer-keypad-on
                 (memq char '(44 46 47)))
            (progn
              (setq-local sniem-minibuffer-keypad-prefix
                          (sniem-keypad--convert-prefix char))
              (call-interactively (key-binding (read-kbd-macro (char-to-string 127)))))
          (sniem-minibuffer-keypad))))))

对这个函数修改了一下,这下输入法的问题也解决了。

(when (and sniem-minibuffer-keypad-first-start
                      (null sniem-minibuffer-keypad-open-timer))
             (setq sniem-minibuffer-keypad-open-timer
                   (run-with-timer
                    0 0.2 #'sniem-minibuffer-keypad-first-start-timer)))

(defun sniem-minibuffer-keypad-first-start-timer ()
  "If minibuffer-keypad mode is not started, start it."
  (when last-input-event
    (if (or (null sniem-minibuffer-keypad-first-start)
            (eq (key-binding (read-kbd-macro "SPC"))
                'sniem-minibuffer-keypad-start-or-stop))
        (progn
          (setq sniem-minibuffer-keypad-first-start nil)
          (when (timerp sniem-minibuffer-keypad-open-timer)
            (cancel-timer sniem-minibuffer-keypad-open-timer)
            (setq sniem-minibuffer-keypad-open-timer nil)))
      (sniem-minibuffer-keypad-mode -1)
      (sniem-minibuffer-keypad-mode t))))

这两个部分修复了首次打开 minibuffer minibuffer-keypad-mode 不生效的问题。

已经写了

https://emacs-china.org/t/minibuffer-keypad-mode/18099?u=springhan