我的 EAF 里面有一个 eaf-monitor-key-event 函数, 用于监听 eaf buffer 的按键并转发给 python 进程。
实现方式是:
(add-hook 'pre-command-hook #'eaf-monitor-key-event)
(defun eaf-monitor-key-event ()
(ignore-errors
(with-current-buffer (buffer-name)
(when (eq major-mode 'eaf-mode)
(let* ((event last-command-event)
(key (make-vector 1 event))
(key-command (format "%s" (key-binding key)))
(key-desc (key-description key))
)
;; Uncomment for debug.
;; (message (format "!!!!! %s %s %s %s" event key key-command key-desc))
(cond
;; Just send event when user insert single character.
;; Don't send event 'M' if user press Ctrl + M.
((and
(or
(equal key-command "self-insert-command")
(equal key-command "completion-select-if-within-overlay"))
(equal 1 (string-width (this-command-keys))))
(eaf-call "send_key" buffer-id key-desc))
((string-match "^[CMSs]-.*" key-desc)
(eaf-call "send_keystroke" buffer-id key-desc))
((or
(equal key-command "nil")
(equal key-desc "RET")
(equal key-desc "DEL")
(equal key-desc "TAB")
(equal key-desc "<backtab>")
(equal key-desc "<home>")
(equal key-desc "<end>")
(equal key-desc "<left>")
(equal key-desc "<right>")
(equal key-desc "<up>")
(equal key-desc "<down>")
(equal key-desc "<prior>")
(equal key-desc "<next>")
)
(eaf-call "send_key" buffer-id key-desc)
)
(t
(unless (or
(equal key-command "keyboard-quit")
(equal key-command "kill-this-buffer")
(equal key-command "eaf-open"))
(ignore-errors (call-interactively (key-binding key))))
)))
;; Set `last-command-event' with nil, emacs won't notify me buffer is ready-only,
;; because i insert nothing in buffer.
(setq last-command-event nil)
))))
大部分情况都能很好的工作, 因为我转发完事件以后,会重置 last-command-event 为 nil, 这样 emacs 只会把事件转发给我, 但是在 eaf buffer 里面不论怎么按键都不会执行任何操作。
今天调试 Pdf Viewer 的时候, 当要跳转页面的时候, 需要在 Minibuffer 输入跳转的页数, 如果直接输入数字回车没有问题, 但是直接按 Ctrl + G 以后, 就会导致 Emacs 报下面的错误:
Error in pre-command-hook (eaf-monitor-key-event): (quit)
funcall-interactively: No recursive edit is in progress
这段错误的意思是, 在执行 pre-command-hook 一个钩子函数 eaf-monitor-key-event 的时候, eaf-monitor-key-event 内部发生了错误, 导致 emacs 就把 eaf-monitor-key-event 这个钩子函数从 pre-command-hook 的钩子列表中去掉, 导致 EAF 无法再通过 eaf-monitor-key-event 函数转发事件了。
我现在郁闷的是, 不知道 eaf-monitor-key-event 里面哪个函数触发了错误, toggle-debug-on-error 也无法调试 recursive edit 的错误。
发出来和大家讨论一下,说不定就找到解决问题的线索了。
我最开始也想在 read-string 那里防御一下, 怎么捕获 quit ?
我最开始是用了 unwind-protect
(defun eaf-input-message (buffer_id interactive_string callback_type)
(let ((input-message ""))
(unwind-protect
(setq input-message (read-string interactive_string))
(if (string= input-message "")
(message "Got error")
(eaf-call "handle_input_message" buffer_id callback_type input-message)))))
这个函数本身没有问题, 关键是 recursive-edit 的错误, 经过我的测试和 keboard-quit 中断 read-string 没有关系。
C-h f ignore-errors error -> quit
这个版本我也试过了:
(defun eaf-input-message (buffer_id interactive_string callback_type)
(let ((input-message))
(setq input-message (ignore-errors (read-string interactive_string)))
(if input-message
(eaf-call "handle_input_message" buffer_id callback_type input-message)
(message "Got error")
)))
问题的关键不在于 read-string 的防御, 没有用。 问题出在 pre-command-hook 的函数挂了。
想到一个非常贱的方法:
(defun eaf-monitor-key-event ()
(unless
(ignore-errors
(with-current-buffer (buffer-name)
(when (eq major-mode 'eaf-mode)
(let* ((event last-command-event)
(key (make-vector 1 event))
(key-command (format "%s" (key-binding key)))
(key-desc (key-description key))
)
;; Uncomment for debug.
;; (message (format "!!!!! %s %s %s %s" event key key-command key-desc))
(cond
;; Just send event when user insert single character.
;; Don't send event 'M' if user press Ctrl + M.
((and
(or
(equal key-command "self-insert-command")
(equal key-command "completion-select-if-within-overlay"))
(equal 1 (string-width (this-command-keys))))
(eaf-call "send_key" buffer-id key-desc))
((string-match "^[CMSs]-.*" key-desc)
(eaf-call "send_keystroke" buffer-id key-desc))
((or
(equal key-command "nil")
(equal key-desc "RET")
(equal key-desc "DEL")
(equal key-desc "TAB")
(equal key-desc "<backtab>")
(equal key-desc "<home>")
(equal key-desc "<end>")
(equal key-desc "<left>")
(equal key-desc "<right>")
(equal key-desc "<up>")
(equal key-desc "<down>")
(equal key-desc "<prior>")
(equal key-desc "<next>")
)
(eaf-call "send_key" buffer-id key-desc)
)
(t
(unless (or
(equal key-command "keyboard-quit")
(equal key-command "kill-this-buffer")
(equal key-command "eaf-open"))
(ignore-errors (call-interactively (key-binding key))))
)))
;; Set `last-command-event' with nil, emacs won't notify me buffer is ready-only,
;; because i insert nothing in buffer.
(setq last-command-event nil))
))
(run-with-timer
0.2
nil
(lambda ()
(progn
(add-hook 'pre-command-hook #'eaf-monitor-key-event))))))
先用 ignore-errors 报裹住 pre-command-hook 的函数内容, 一旦出了错误 ignore-errors 会返回 nil , 这个时候再在 ignore-errors 外面套上一个 unless , 一旦遇到错误, 就会被 unless 捕获, 然后再次用 run-with-timer 来执行 (add-hook 'pre-command-hook #'eaf-monitor-key-event)。
这样, 一旦在 pre-command-hook 中出现错误, 就再次添加函数到 pre-command-hook 中, 有一种逃不出我魔掌的感觉, 哈哈哈哈。
我解决方案还给我
(defun eaf-monitor-key-event ()
(ignore-errors
(with-current-buffer (buffer-name)
(let* ((event last-command-event)
(key (make-vector 1 event))
(key-command (format "%s" (key-binding key)))
(key-desc (key-description key))
)
(message (format "!!!!! %s %s %s %s" eveneo1t key key-command key-desc))
(cond
((and
(or
(equal key-command "self-insert-command")
(equal key-command "completion-select-if-within-overlay"))
(equal 1 (string-width (this-command-keys))))
(message "send_key %s" key-desc))
((string-match "^[CMSs]-.*" key-desc)
(message "send_keystroke %s" key-desc))
((equal "RET" key-desc) (condition-case nil (read-string "Hello:") (quit "")))
(
(or
(equal key-command "nil")
(equal key-desc "RET")
(equal key-desc "DEL")
(equal key-desc "TAB")
(equal key-desc "<backtab>")
(equal key-desc "<home>")
(equal key-desc "<end>")
(equal key-desc "<left>")
(equal key-desc "<right>")
(equal key-desc "<up>")
(equal key-desc "<down>")
(equal key-desc "<prior>")
(equal key-desc "<next>")
)
(message "send_key %s" key-desc)
)
(t
(unless (or
(equal key-command "keyboard-quit")
(equal key-command "kill-this-buffer")
(equal key-command "eaf-open"))
(ignore-errors (call-interactively (key-binding key))))
)))
)))
(add-hook 'pre-command-hook #'eaf-monitor-key-event)
unwind-protect 不能捕获 quit
写个小函数测试一下就知道
为什么不看看 keyboard-quit 的源码
为什么不看看 ignore-errors 的实现
其实你还可以看看 C-h k C-h k 里是如何读取按键输入的
pre-command-hook 应该不是一个好方法
问题不是出在 read-string , 而是在 eaf-monitor-key-event 里面
我当然知道这些, 在问问题之前, ignore-errors, unwind-protect 我都试过了, 我当然也知道按键是怎么读取的, 我十多年前就开发了 one-key.el
只是我发现问题不是出在 read-string 这里, 我最开始也是怀疑的这里.
小伙子不要激动, 有话慢慢说, 慢慢讨论, 这里是大家互相学习的地方, 不要着急.
我把回车键绑定到了read-string 已经重现了你的问题
运行了啊, 但是没有解决问题啊, 运行你的代码以后 eaf-monitor-key-event 完全就不工作了.
eaf-monitor-key-event 只是转发 eaf buffer 事件的, 而不是在 eaf-monitor-key-event 里面直接调用 read-string 读取用户输入的
所以, 你并没有理解清楚我描述的问题, 我在 minibuffer 里面 Ctrl + G 会影响 eaf-monitor-key-event , 但问题不在 read-string 这里.
;; This buffer is for text that is not saved, and for Lisp evaluation.
;; To create a file, visit it with C-x C-f and enter text in its buffer.
(defun eaf-monitor-key-event ()
(message "DDD")
(with-current-buffer (buffer-name)
(let* ((event last-command-event)
(key (make-vector 1 event))
(key-command (format "%s" (key-binding key)))
(key-desc (key-description key))
)
(message (format "!!!!! %s %s %s %s" event key key-command key-desc))
(cond
((and
(or
(equal key-command "self-insert-command")
(equal key-command "completion-select-if-within-overlay"))
(equal 1 (string-width (this-command-keys))))
(message "send_key %s" key-desc))
((string-match "^[CMSs]-.*" key-desc)
(message "send_keystroke %s" key-desc))
((equal "RET" key-desc) (condition-case nil (read-string "Hello:") (quit "")))
(
(or
(equal key-command "nil")
(equal key-desc "RET")
(equal key-desc "DEL")
(equal key-desc "TAB")
(equal key-desc "<backtab>")
(equal key-desc "<home>")
(equal key-desc "<end>")
(equal key-desc "<left>")
(equal key-desc "<right>")
(equal key-desc "<up>")
(equal key-desc "<down>")
(equal key-desc "<prior>")
(equal key-desc "<next>")
)
(message "send_key %s" key-desc)
)
(t
(unless (or
(equal key-command "keyboard-quit")
(equal key-command "kill-this-buffer")
(equal key-command "eaf-open"))
(ignore-errors (call-interactively (key-binding key))))
)))
))
(add-hook 'pre-command-hook #'eaf-monitor-key-event)