Emacs: 定义前缀键

本文介绍在Emacs中,如何定义前缀键、如何设置嵌套键映射。它将为你提供更多键的访问权限。

如果安装了 which-key 软件包,请打开 which-key-mode, 它会帮助你展示 有哪些前缀键。如果没有安装可以使用所绑定的键前缀后 C-h (<keymap> C-h) 来显示。

我们要实现如下键绑定:

C-t
   t <Command>
   f find-file
   d dired
   b "Buffer"
     m buffer-menu
     s switch-to-buffer

以下是在emacs 29之后的版本实现,29之前的版本可能有些函数名不一样。

定义一个前缀键

keymap-set 接收3个参数,

(keymap-set KEYMAP KEY DEFINITION)
;; Emacs 29  `keymap-set', `defvar-keymap'

;;;; 定义全局键映射 C-t, 动作为nil,不做任何事

(keymap-set global-map "C-t" nil)

;;;; 定义全局键映射C-t,执行test-command函数

;;; 定义交互式函数,回显一条消息This is a test!
(defun test-command ()
  (interactive) ; 这使用函数成为一个命令
  (message "This is a test!"))

(keymap-set global-map "C-t"  'test-command)

;;;; keymap-set的变体及在包管理(`use-package')中的使用

;;; 在旧版本中会使用 keymap-set 变体 define-key

(define-key global-map (kbd "C-t") 'test-command)

;;; use-package 中的键映射, 29版本后为内置包

(use-package xxx
  :bind ("C-t" . test-command))

嵌套键映射

当我们想要定义前缀键映射时,不想使用命令,而是绑定键映射。键映射是一个变量,像字符串一样保存键。

(defun test-command ()
  (interactive) ; 这使用函数成为一个命令
  (message "This is a test!"))

;;;; 使用defvar-keymap定义键映射

;;; C-t t 执行test-command函数
(defvar-keymap my-test-prefix-map
  :doc "My prefix map."
  "t" 'test-command)

(keymap-set global-map "C-t"  my-test-prefix-map)

;;; C-t 多个键映射 f d t
(defvar-keymap my-test-prefix-map
  :doc "My prefix map."
  "f" 'find-file
  "d" 'dired
  "t" 'test-command)

(keymap-set global-map "C-t"  my-test-prefix-map)

;;;; 使用defvar-keymap定义键映射,更友好写法
;;; 更友好的写法
;; #'井号加单引号写法和上面‘单引号的功能是相同的,只语义上有些不同。
;; #'告诉阅读此内容的人或编译器接下来是一个函数,不是任何形式的
;; 符号,而是一个特定的函数
(defvar-keymap my-test-prefix-buffer-map
  :doc "My prefix map for buffers."
  "s" #'switch-to-buffer
  "m" #'buffer-menu)

现在我希望能够从我的前缀映射内部访问 my-test-prefix-buffer-map

(defvar-keymap my-test-prefix-map
  :doc "My prefix map."
  "f" 'find-file
  "b" my-test-prefix-buffer-map
  "d" 'dired
  "h" help-map
  "t" 'test-command)

C-t 时which-key b 显示 +prefix , 而没展示b的作用。

img_20240731_224534.png
img_20240731_224649.png

解决嵌套键时,which-key展示问题

(which-key-add-keymap-based-replacements my-test-prefix-map
  "b" `("Buffer" .  ,my-test-prefix-buffer-map))
img_20240731_231128.png

`( ,<var/function>) 是动态展示来的用法

(setq test-hello "Hello!")

test-hello
"Hello!"

(message "The value is: %s" '(one two test-hello))
"The value is: (one two test-hello)"

(message "The value is: %s" `(one two ,test-hello))
"The value is: (one two Hello!)"

添加帮助信息

(defvar-keymap my-test-prefix-map
  :doc "My prefix map."
  "f" 'find-file
  "b" my-test-prefix-buffer-map
  "d" 'dired
  "h" help-map
  "t" 'test-command)

(keymap-set global-map "C-t"  my-test-prefix-map)

;;; which-key中显示嵌套键的注释
(which-key-add-keymap-based-replacements my-test-prefix-map
  "b" `("Buffer" .  ,my-test-prefix-buffer-map)
  "h" `("Help" .  ,help-map))

which-key 适合做演示。一般我们忘记快捷前缀有哪些嵌套键,可以在后面追加 C-h .

use-package 的绑定键

(use-package xxx
  :bind ("C-t" . test-command)) ; 相当于全局映射 global-map

(use-package xxx
  :bind
  ( :map global-map
    ("C-t" . test-command)
    ("C-c l" . org-store-link)
    :map org-mode-map
    ("C-c C-M-l" . org-toggle-link-display)
    ))

完整内容

;; 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.

;; Emacs 29  `keymap-set', `defvar-keymap'

;;;; 定义全局键映射 C-t, 动作为nil,不做任何事

(keymap-set global-map "C-t" nil)

;;;; 定义全局键映射C-t,执行test-command函数

;;; 定义交互式函数,回显一条消息This is a test!
(defun test-command ()
  (interactive) ; 这使用函数成为一个命令
  (message "This is a test!"))

(keymap-set global-map "C-t"  'test-command)

;;;; keymap-set的变体及在包管理(`use-package')中的使用

;;; 在旧版本中会使用 keymap-set 变体 define-key

(define-key global-map (kbd "C-t") 'test-command)

;;; use-package 中的键映射, 29版本后为内置包

(use-package xxx
  :bind ("C-t" . test-command)) ; 相当于全局映射 global-map

(use-package xxx
  :bind
  ( :map global-map
    ("C-t" . test-command)
    ("C-c l" . org-store-link)
    :map org-mode-map
    ("C-c C-M-l" . org-toggle-link-display)
    ))

(defun test-command ()
  (interactive) ; 这使用函数成为一个命令
  (message "This is a test!"))

;;;; 使用defvar-keymap定义键映射

;;; C-t t 执行test-command函数
(defvar-keymap my-test-prefix-map
  :doc "My prefix map."
  "t" 'test-command)

(keymap-set global-map "C-t"  my-test-prefix-map)


;;;; 使用defvar-keymap定义键映射,更友好写法
;;; 更友好的写法
;; #'井号加单引号写法和上面‘单引号的功能是相同的,只语义上有些不同。
;; #'告诉阅读此内容的人或编译器接下来是一个函数,不是任何形式的
;; 符号,而是一个特定的函数
(defvar-keymap my-test-prefix-buffer-map
  :doc "My prefix map for buffers."
  "s" #'switch-to-buffer
  "m" #'buffer-menu)

;;; C-t 多个键映射 f d t
(defvar-keymap my-test-prefix-map
  :doc "My prefix map."
  "f" 'find-file
  "b" my-test-prefix-buffer-map
  "d" 'dired
  "h" help-map
  "t" 'test-command)

(keymap-set global-map "C-t"  my-test-prefix-map)

;;; which-key中显示嵌套键的注释
(which-key-add-keymap-based-replacements my-test-prefix-map
  "b" `("Buffer" .  ,my-test-prefix-buffer-map)
  "h" `("Help" .  ,help-map))

(setq test-hello "Hello!")

test-hello
"Hello!"

(message "The value is: %s" '(one two test-hello))
"The value is: (one two test-hello)"

(message "The value is: %s" `(one two ,test-hello))
"The value is: (one two Hello!)"