面向产品经理的Emacs教程:10. Emacs编辑进阶

· 1920字 · 4分钟

1 课程回顾 🔗

通过上节课,我们对Emacs Lisp有了基础的认识,也了解了如何通过 C-h 系列按键,来查看变量或函数的定义。从这一节课后,我们就掌握了知其所以然的方法!遇到不明白的配置,直接 C-h 看一下文档,你会发现,随着时间的推移,你对Emacs的掌控力将越来越强!

那么,本节课,将进一步介绍通过Emacs来编辑文本时,有哪些高效的手段。

2 Emacs的矩形操作 🔗

Emacs无须安装任何插件就支持矩形操作,所谓矩形操作,就是选择一块矩形区域,对这块矩形区域进行编辑,如删除、替换、复制、粘贴等。

Emacs矩形操作的几个快捷键和命令如下:

表 1: 矩形操作快捷键
快捷键 命令 含义
C-x SPC rectangle-mark-mode 开始激活对一个矩形区域的标记
C-x r M-w copy-rectangle-as-kill 将标记的矩形区域拷贝
C-x r y yank-rectangle 将拷贝的矩形区域粘贴
C-x r t string-rectangle 将标记的矩形区域通过字符串来按行替换
C-x r k kill-rectangle 将标记的矩形区域删除
C-x r c clear-rectangle 将标记的矩形区域以空格清除

通过上面这些命令,我们就可以很方便的对矩形区域进行操作,在很多时候非常方便:

3 Avy快速移动光标 🔗

avy 是一个光标移动插件,能快速将光标移动到屏幕上的任意位置,非常强大!

我们先来安装配置 avy:

(use-package avy
  :ensure t
  :bind (("C-." . my/avy-goto-char-timer)
         ("C-。" . my/avy-goto-char-timer)
         :map isearch-mode-map
         ("C-." . avy-isearch))
  :config
  ;; Make `avy-goto-char-timer' support pinyin, refer to:
  ;; https://emacs-china.org/t/avy-avy-goto-char-timer/20900/2
  (defun my/avy-goto-char-timer (&optional arg)
    "Make avy-goto-char-timer support pinyin"
    (interactive "P")
    (let ((avy-all-windows (if arg
                               (not avy-all-windows)
                             avy-all-windows)))
      (avy-with avy-goto-char-timer
        (setq avy--old-cands (avy--read-candidates
                              'pinyinlib-build-regexp-string))
        (avy-process avy--old-cands))))

  (defun avy-action-kill-whole-line (pt)
    "avy action: kill the whole line where avy selection is"
    (save-excursion
      (goto-char pt)
      (kill-whole-line))
    (select-window
     (cdr
      (ring-ref avy-ring 0)))
    t)

  (defun avy-action-copy-whole-line (pt)
    "avy action: copy the whole line where avy selection is"
    (save-excursion
      (goto-char pt)
      (cl-destructuring-bind (start . end)
          (bounds-of-thing-at-point 'line)
        (copy-region-as-kill start end)))
    (select-window
     (cdr
      (ring-ref avy-ring 0)))
    t)

  (defun avy-action-yank-whole-line (pt)
    "avy action: copy the line where avy selection is and paste to current point"
    (avy-action-copy-whole-line pt)
    (save-excursion (yank))
    t)

  (defun avy-action-teleport-whole-line (pt)
    "avy action: kill the line where avy selection is and paste to current point"
    (avy-action-kill-whole-line pt)
    (save-excursion (yank)) t)

  (defun avy-action-helpful (pt)
    "avy action: get helpful information at point"
    (save-excursion
      (goto-char pt)
      (helpful-at-point))
    t)

  (defun avy-action-mark-to-char (pt)
    "avy action: mark from current point to avy selection"
    (activate-mark)
    (goto-char pt))

  (defun avy-action-flyspell (pt)
    "avy action: flyspell the word where avy selection is"
    (save-excursion
      (goto-char pt)
      (when (require 'flyspell nil t)
        (flyspell-correct-wrapper))))

  (defun avy-action-define (pt)
    "avy action: define the word in dictionary where avy selection is"
    (save-excursion
      (goto-char pt)
      (fanyi-dwim2)))

  (defun avy-action-embark (pt)
    "avy action: embark where avy selection is"
    (unwind-protect
        (save-excursion
          (goto-char pt)
          (embark-act))
      (select-window
       (cdr (ring-ref avy-ring 0))))
    t)

  (defun avy-action-google (pt)
    "avy action: google the avy selection when it is a word or browse it when it is a link"
    (save-excursion
      (goto-char pt)
      (my/search-or-browse)))

  (setf (alist-get ?k avy-dispatch-alist) 'avy-action-kill-stay
        (alist-get ?K avy-dispatch-alist) 'avy-action-kill-whole-line
        (alist-get ?w avy-dispatch-alist) 'avy-action-copy
        (alist-get ?W avy-dispatch-alist) 'avy-action-copy-whole-line
        (alist-get ?y avy-dispatch-alist) 'avy-action-yank
        (alist-get ?Y avy-dispatch-alist) 'avy-action-yank-whole-line
        (alist-get ?t avy-dispatch-alist) 'avy-action-teleport
        (alist-get ?T avy-dispatch-alist) 'avy-action-teleport-whole-line
        (alist-get ?H avy-dispatch-alist) 'avy-action-helpful
        (alist-get ?  avy-dispatch-alist) 'avy-action-mark-to-char
        (alist-get ?\; avy-dispatch-alist) 'avy-action-flyspell
        (alist-get ?= avy-dispatch-alist) 'avy-action-define
        (alist-get ?o avy-dispatch-alist) 'avy-action-embark
        (alist-get ?G avy-dispatch-alist) 'avy-action-google
        )

  :custom
  (avy-timeout-seconds 1.0)
  (avy-all-windows t)
  (avy-background t)
  (avy-keys '(?a ?s ?d ?f ?g ?h ?j ?l ?q ?e ?r ?u ?i ?p ?n))
  )

安装完 avy 插件后,我们就可以通过 C-. 快捷键来激活 avy,激活后,输入你想要跳转到的字符串,然后 avy 会自动将所有候选的位置高亮,并以字母提示,按照字母的提示选择,就会将光标移动到对应的位置,非常强大:

3.1 avy的高阶用法 🔗

avy 不仅仅能将光标快速跳转到屏幕的任何一个字符的位置,它还有更高阶的用法,详情可以参照这篇博客

简而言之,avy 在快速跳转光标位置之前,可以设定一个 action,在设定完 action 后,在光标跳转到指定位置的同时,会执行这个 action 对应的指令。

下表是一个 avy action 的示例表,所有这些表格里的 action 都已经在上面的配置文件里配置好了,可以直接使用。

表 2: Avy action key table
action指令 Avy action 按键 Emacs 相对应的按键 含义
Kill k (thing), K (line) C-k 删除目的光标处的字符
Copy w (thing), W (line) M-w 拷贝目的光标处的字符
Yank y (thing), Y (line) C-y 将目的光标处的字符粘贴到当前的光标位置
Transpose t, T (line) C-t, M-t etc. 将目的光标处的字符剪切到当前的光标位置
Mark m m in special buffers 选择目的光标处的字符
Activate region SPC C-SPC 选择从当前光标到目的光标处的区域
Helpful H C-h 展示目的光标处字符的定义

下面以 helpful 这个 action 为例:

  1. C-. 激活 avy
  2. 输入待选择的光标位置字符串 avy-action
  3. 输入 avy action,这里是 H ,即 helpful
  4. 输入候选位置 j
  5. avy 会自动执行action,展示候选位置 j 处的字符定义,即 avy-action-yank 的字符定义

4 multiple-cursors多光标编辑 🔗

multiple-cursors 插件能让Emacs实现多光标编辑和移动,非常强大!

我们先来安装和配置这个插件:

(use-package multiple-cursors
  :ensure t
  :bind-keymap ("C-c o" . multiple-cursors-map)
  :bind (("C-`"   . mc/mark-next-like-this)
         ("C-\\"  . mc/unmark-next-like-this)
         :map multiple-cursors-map
              ("SPC" . mc/edit-lines)
              (">"   . mc/mark-next-like-this)
              ("<"   . mc/mark-previous-like-this)
              ("a"   . mc/mark-all-like-this)
              ("n"   . mc/mark-next-like-this-word)
              ("p"   . mc/mark-previous-like-this-word)
              ("r"   . set-rectangular-region-anchor)
              )
  :config
  (defvar multiple-cursors-map nil "keymap for `multiple-cursors")
  (setq multiple-cursors-map (make-sparse-keymap))
  (setq mc/list-file (concat user-emacs-directory "/etc/mc-lists.el"))
  (setq mc/always-run-for-all t)
  )

当我们安装完这个插件后,我们可以通过 C-` 来激活多光标,激活后就可以直接编辑(输入字符、删除字符等),下面是三个典型的例子:

  1. 将指定的一些行前面加一些字符
  2. 将指定的一些行末尾加一些字符
  3. 将指定的一些字符替换成另外一些字符
图5 将每一行前面加上-加空格

图5  将每一行前面加上-加空格

图6 给每一行的行尾加上分号

图6  给每一行的行尾加上分号

图7 将所有的hello都替换成HellO

图7  将所有的hello都替换成HellO

当我们熟悉了多光标编辑后,我们就可以把Emacs自带的矩形操作给扔掉啦,所有的矩形操作,都可以通过多光标编辑来实现。我们真的应该感谢Emacs的自由,感谢有社区那么多厉害的大神,给我们开发出了如此高效的插件!

5 结语 🔗

经过今天的课程,我们在Emacs里编辑文本的效率进一步得到了提升,用魔兽里地精的一句名言作为本节课的结尾语“Time is money, my friend!”

配置文件的快照见:emacs-config-l10.org

你也可以在 这里 查看最新的配置文件。