第 25 章 Emacs 入门

目录

基础知识

缓冲区

信息栏

按键描述

回显区

内部命令

Emacs 命令行

Emacs 终端

Emacs 文件管理器

区块选择

中止执行

基本配置

帮助系统

基本操作

数字参数

基础编辑

定位

删除

复制

粘贴

撤消

重做

区块编辑

搜索和替换

其它

窗格和缓冲区管理

多窗格

多缓冲区

寄存器管理

光标位置和窗口状态

文本和数字

书签管理

Shell 模式

定义与运行

宏队列

文件管理

定位、查看

标记

操作

服务器模式

大纲模式

定制结构标识

配置

操作列表

在 Emacs 中使用 sdcv

Windows 下字体设置

版本

基础知识

引起我们痛苦的东西我们就会去爱它,以使自己觉得这份痛苦是值得的

Emacs 是一个架构在编辑器上的集成环境,除了最基本的编辑功能,还可以完成文件管理、终端模拟、浏览网页、收发邮件、编译程序等工作。

Emacs 使用 Elisp 语言进行配置和扩展,它本身也可以作为 Elisp 解释器使用。

Emacs 的界面主要由三部分构成:信息栏、回显区(echo)、缓冲区(buffer)

缓冲区

缓冲区(buffer) 类似于常规编辑器的文字编辑区。Emacs 并不直接对文件进行修改,而是读取文件的内容并显示在缓冲区中,在收到保存的指令后才将修改写入文件。

缓冲区名称通常为它所读取文件的文件名。

信息栏

在缓冲区之下为状态栏,像这样的

简单说明一下:

-U(UNIX)**-  emacs.xml      15% (28,32)  Git:master (DocBook-XML Outl)----二  9月 30 11:57--------------------

1 点击拖动这里可以状态栏位置

2 文件编码 U 代表 UTF-8; c 代表 chinese-gbk

3 换行符类型 有 UNIX、DOS 和 MacOS 三种

4 文件状态 %代表只读 -代表可写 *代表未保存

5 当前工作路径

6 当前编辑的文件

7 光标在当前文件中的位置

8 光标所在的行、列

9 版本控制系统

10 主模式

11 辅模式

12 日期时间

标题栏也可以显示一部分信息,并且可以自由定义。

按键描述

Emacs 的功能键,通常为组合键。例如

表 25.1. Emacs 按键描述

Emacs 描述 实际热键 功能
C-f Ctrl+f 光标前进一格
C-b Ctrl+b 光标后退一格
C-d Ctrl+d 删除一个字符
C-a Ctrl+a 回到行首

Emacs 对按键的描述方式中, - 之前的一个字符为修饰键,表示按住该键,再按 - 后面的键。

例如: C-a 表示按住 Ctrl 再按 a 键。 Emacs 对其它一些特殊按键的描述

C- 按住 Ctrl键
M- 按住 Meta键。在 PC 上,Meta键 通常对应 Alt 键
C-M- 同时按住 Ctrl键 和 Meta键
S- Shift键
s- Linux 下对应 WIN 键
RET 回车键
TAB Tab键
ESC Esc键
SPC 空格键
DEL 退格键
Delete 删除键

注意:在 Emacs 中,ESC 等价于 M-,也就是说,按一次 ESC 键,与按住 Meta 键的作用是相同的。

类似 C-M-r 的按键,可以使用 ESC C-r(按一次 ESC,再按 C-r)这种更容易的方式

在后面的部分中,将统一使用 Emacs 对按键的描述方式。

Emacs 十分强大,上面的组合键,远不能涵盖 Emacs 的功能于万一。除基本的编辑功能键外,其它功能多使用 按键序列: 连续的按下多组快捷键

例如: C-x C-c 表示先按下 C-x ,再按下 C-c 。也就是 Ctrl+x 后,再 Ctrl+c (退出 Emacs)

接下来 C-h t ,进入 《Emacs 快捷指南》

回显区

C-x h (先按 Ctrl+x 再按 h) 后,您会发现状态栏和编辑器底部之间的区域出现 Mark set 字样。同时,整个缓冲区的内容都被选中。

它是一个迷你缓冲区(minibuffer),叫作回显区(echo area),提示您正在进行的操作,比如 Mark set(设定标记)

如果一个按键序列没有完成,却停止了输入。大约两秒后,回显区会显示已输入部分,以免您忘记。不要以为是 Emacs 反应迟钝

内部命令

C-h k 后,回显区提示

Describe key (or click or menu item):

接着 C-x h ,您会发现,缓冲区被水平分割为两个。另一个名为 help 缓冲区中显示的内容为

C-x h runs the command mark-whole-buffer
   which is an interactive compiled Lisp function in `simple.el'.
It is bound to C-x h, <menu-bar> <edit> <mark-whole-buffer>.
(mark-whole-buffer)

Put point at beginning and mark at end of buffer.
You probably should not use this function in Lisp programs;
it is usually a mistake for a Lisp function to use any subroutine
that uses or sets the mark.

[back]

第一行说明了 C-x h 运行的命令为 mark-whole-buffer

第二行说明了该命令由 simple.el 这个扩展提供,绑定到 C-x h 、 菜单栏-编辑-标记全部缓冲区 、 和命令 mark-whole-buffer

第三行介绍了这个命令的行为: 在文档末尾设置一个标记,并把光标点[45]移动到文档起始。

注意:Emacs 使用命令进行处理,快捷键只是一种发送命令的方法!

一般情况下,我们用不到这么详细的说明,而且英文看起来也比较吃力。您可以使用 C-h c 以简洁模式查看说明。 只在回显区显示键位和它执行的命令:

C-x h runs the command mark-whole-buffer

通常这就足够了。

如果您知道一个命令,而不知道它绑定到什么键上,您可以使用 C-h w ,也就是命令 Where-is

Emacs 命令行

由于 Emacs 太过强大,内部命令恒河沙数,根本不可能有同样数量的快捷键位来绑定它们!

对于没有绑定的命令,可以使用命令执行!

M-x (Alt+x) 开启命令行,回显区显示为 M-x ,然后输入 newline

这个命令默认绑定在回车键,所以它和回车键的作用一样为 换行

C-h w newline 结果是: newline is on RET

提示:命令行中,可以使用 TAB 补全,使用 M-p 上翻, M-n 下翻

在后面的部分中,统一使用 M-x command 来表示 内部命令 command ;内部命令以 "(command)" 的形式写到配置文件中

Emacs 终端

M-x shell 激活 Emacs 终端。可以在 Emacs 终端中使用外部命令。

需要注意的是,Emacs 终端是哑终端,某些类型的输出不能够正确显示。

在 Emacs 终端中使用 exit 命令退出。

M-! (Alt+Shift+1)临时执行一条外部命令,并输出在名为 Shell Command Output 的缓冲区中 (M-x shell-command)

C-u M-! (Ctrl+u Alt+Shift+1)临时执行一条外部命令,并输出到光标位置。

Emacs 文件管理器

C-x d 进入 Dired 列表模式

C-x C-d 获取文件列表(简洁)

C-x C-f打开文件,输入路径为打开目录

详细介绍见 “文件管理”一节

区块选择

很多时候,我们需要选中缓冲区中的某一部分内容。和大多数程序一样,您可以在被选择区块的起始点按下左键,移动鼠标,在结束点释放左键,这部分区块便被选中。

这种方式效率并不高,而且一些场合并没有鼠标支持,例如控制台或者远程登录。

事实上,Emacs 进行区块选择的方法,是设置一个标记,标记到光标点[45]之间的部分将被选中。

标记的位置为 M-x set-mark-command 时,光标点[45]所处的位置。

M-x set-mark-command 是设置标记的内部命令,默认绑定在 C-SPC 键上。

如果使用输入法,这个键位多半是切换输入法的快捷键。键盘指令会先被输入法拦截下来,而无法发送到 Emacs。

当然也可以使用 M-@ 来设定标记。不过 M-@ 原绑定为 M-x mark-word ,虽然差不太多,但有时并不好用;况且对于一个常用的命令来讲, M-@ 键位的难度太高了

Emacs 的键位中,几乎没有默认绑定在 WIN 键上的命令,不妨利用一下

在 Emacs 的用户配置文件 ~/.emacs 中添加如下内容:

;; WIN+Space 设置标记
(global-set-key (kbd "s-SPC") 'set-mark-command)

在某些类型的终端中,WIN键 不起作用,建议使用命令。或者绑定到 C-t [46]

;; (在注释里说明原命令和绑定,是一个良好的习惯)
;; C-t 设置标记
(global-set-key (kbd "C-t") 'set-mark-command)

重要:重启 Emacs,或者在 ~/.emacs 文件的缓冲区中执行命令 M-x eval-buffer ,便可以使配置文件立即生效

中止执行

如果想放弃一个命令,可以使用 C-g (M-x keyboard-quit)打断。

建议您使用快捷键 C-g ,因为在需要中止执行的情况下, M-x 通常是无法使用的

ESC ESC ESC (M-x keyboard-escape-quit)可以从一些交互命令中退出。

例如从 "询问替换 M-x query-replace" 中退出。

当 C-g 不能搞定,您可以尝试连按三次 ESC


[45] 光标点假定光标为插入式(竖线),位置在覆盖式光标(方块)的左侧。

事实上,Emacs 中的相关判定以光标点为准!方块形光标只是为了减少视觉疲劳

[46] 这是一个让人头痛不已的地方。因为无论绑到哪,似乎都不太方便:

使用 WIN键 倒是挺好,但是在字符界面下,WIN键 通常不起作用;同样,C-; 这样使用标点的组合键在字符界面下也不行;

C-m、C-i 是两个不错的组合,但是 Emacs 认为 C-m 和 RET、C-i 和 TAB 是一个键,这样绑定,你的回车或者 TAB 就成了设置标记;

最后,C-t 这个键默认绑定的命令几乎没什么用,只是这个键位不是很好按,但这样也有好处──无论你用左手或者右手来按 t键,距离都差不多

基本配置

您已经知道了,Emacs 的配置文件为 ~/.emacs 。配置文件中,以 ; 起始到行末的部分为注释。

让我们些简单配置一下:

例 25.1. emacs 配置 ~/.emacs

;;========================================
;;添加 Emacs 搜索目录 可以将自定的扩展放该目录
;;========================================
;(add-to-list 'load-path "~/..emacs")
;如果有其它配置文件,使此命令读取
;(load "addon.el")

;;========================================
;; 外观设置
;;========================================

;;禁用工具栏
(tool-bar-mode nil)

;;禁用菜单栏,F10 开启关闭菜单
(menu-bar-mode nil)

;;禁用滚动栏,用鼠标滚轮代替
;;(scroll-bar-mode nil)

;;禁用启动画面
(setq inhibit-startup-message t)

;;========================================
;; 键绑定
;;========================================

;; C-t 设置标记 ;;
(global-set-key (kbd "C-t") 'set-mark-command)

;; C-x b => CRM bufer list
(global-set-key "\C-xb" 'electric-buffer-list)

;;---------- redo
(global-set-key ( kbd "C-.") 'redo)

;;========================================
;;关闭当前缓冲区 Alt+4  ;; C-x 0
(global-set-key (kbd "M-4") 'delete-window)
;;关闭其它缓冲区 Alt+1  ;; C-x 1
(global-set-key (kbd "M-1") 'delete-other-windows)
;;水平分割缓冲区 Alt+2  ;; C-x 2
(global-set-key (kbd "M-2") 'split-window-vertically)
;;垂直分割缓冲区 Alt+3  ;; C-x 3
(global-set-key (kbd "M-3") 'split-window-horizontally)
;;切换到其它缓冲区 Alt+0 ;; C-x o
(global-set-key (kbd "M-0") 'other-window)

;;F10 显示/隐藏菜单栏 ;; M-x menu-bar-open
;;(global-set-key (kbd "F10") 'menu-bar-mode)

;;WIN+s 进入 Shell ;; M-x shell
;(global-set-key (kbd "s-s") 'shell)
;;(define-key ctl-x-map "\M-s" 'shell)

;;========================================
;; 缓冲区
;;========================================

;;设定行距
(setq default-line-spacing 0)

;;页宽
(setq default-fill-column 90)

;;缺省模式 text-mode
(setq default-major-mode 'text-mode)

;;设置删除纪录
(setq kill-ring-max 200)

;;以空行结束
(setq require-final-newline t)

;;语法加亮
(global-font-lock-mode t)

;;高亮显示区域选择
(transient-mark-mode t)

;;页面平滑滚动, scroll-margin 5 靠近屏幕边沿3行时开始滚动,可以很好的看到上下文。
(setq scroll-margin 5
      scroll-conservatively 10000)

;高亮显示成对括号,但不来回弹跳
(show-paren-mode t)
(setq show-paren-style 'parentheses)

;;鼠标指针规避光标
;(mouse-avoidance-mode 'animate)

;;粘贴于光标处,而不是鼠标指针处
(setq mouse-yank-at-point t)

;;========================================
;; 回显区
;;========================================

;;闪屏报警
(setq visible-bell t)

;;使用 y or n 提问
(fset 'yes-or-no-p 'y-or-n-p)

;;锁定行高
(setq resize-mini-windows nil)

;;递归 minibuffer
(setq enable-recursive-minibuffers t)

;; 当使用 M-x COMMAND 后,过 1 秒钟显示该 COMMAND 绑定的键。
;;(setq suggest-key-bindings 1) ;;

;;========================================
;; 状态栏
;;========================================

;;显示时间
(display-time)
;;时间格式
(setq display-time-24hr-format t)
(setq display-time-day-and-date t)
(setq display-time-interval 10)

;;显示列号
(setq column-number-mode t)

;;标题栏显示 %f 缓冲区完整路径 %p 页面百分数 %l 行号
(setq frame-title-format "%f")

;;========================================
;; 编辑器设定
;;========================================

;;不生成临时文件
;;(setq-default make-backup-files nil)

;;只渲染当前屏幕语法高亮,加快显示速度
(setq font-lock-maximum-decoration t)

;;将错误信息显示在回显区
;(condition-case err
;    (progn
;    (require 'xxx) )
;  (error
;   (message "Can't load xxx-mode %s" (cdr err))))

;;使用X剪贴板
(setq x-select-enable-clipboard t)
;;;;;;;; 使用空格缩进 ;;;;;;;;
;; indent-tabs-mode  t 使用 TAB 作格式化字符  nil 使用空格作格式化字符
(setq indent-tabs-mode nil)
(setq tab-always-indent nil)
(setq tab-width 4)

;;========================================
;; 颜色设置
;;========================================

;; 指针颜色
(set-cursor-color "black")
;; 鼠标颜色
(set-mouse-color "black")
;; 背景和字体颜色
(set-foreground-color "gainsboro")
(set-background-color "grey30")
(set-border-color "black")
;; 语法高亮显示,区域选择,二次选择 ;;前景和背景色
(set-face-foreground 'highlight "white")
(set-face-background 'highlight "blue")
(set-face-foreground 'region "cyan")
(set-face-background 'region "blue")
(set-face-foreground 'secondary-selection "skyblue")
(set-face-background 'secondary-selection "darkblue")

;;日历配色
;(setq calendar-load-hook
;'(lambda ()
;(set-face-foreground 'diary-face "skyblue")
;(set-face-background 'holiday-face "slate blue")
;(set-face-foreground 'holiday-face "white")))

;;========================================
;; 字体设置
;;========================================
(set-default-font "Monospace-10")
(if window-system
   (set-fontset-font (frame-parameter nil 'font)
      'unicode '("Microsoft YaHei" . "unicode-bmp"))
)

;;========================================
;; 必备扩展
;;========================================

;;---------- color-theme
;(require 'color-theme)
;(color-theme-gray30)

;;========== ibuffer
(require 'ibuffer)
(global-set-key ( kbd "C-x C-b ")' ibuffer)

;;========== outline
(setq outline-minor-mode-prefix [(control o)])

;;---------- Docbook
;(require 'docbook-xml-mode)

;(add-hook 'docbook-xml-mode-hook
;      (function (lambda ()
;                  (setq outline-regexp "<!\\-\\-\\*+")
;              (outline-minor-mode)
;              (hide-body)
;                )))

;;开启服务器模式
;(server-start)

;;org-mode
(setq org-hide-leading-stars t)
 (define-key global-map "\C-ca" 'org-agenda)
 (setq org-log-done 'time)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;; F5 运行当前文件 ;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;(defun run-current-file ()
;  "Execute or compile the current file.
;For example, if the current buffer is the file x.pl,
;then it'll call “perl x.pl” in a shell.
;The file can be php, perl, python, bash, java.
;File suffix is used to determine what program to run."
;(interactive)
;  (let (ext-map file-name file-ext prog-name cmd-str)
;; get the file name
;; get the program name
;; run it
;    (setq ext-map
;          '(
;            ("php" . "php")
;            ("pl" . "perl")
;            ("py" . "python")
;            ("sh" . "bash")
;            ("java" . "javac")
;            )
;          )
;    (setq file-name (buffer-file-name))
;    (setq file-ext (file-name-extension file-name))
;    (setq prog-name (cdr (assoc file-ext ext-map)))
;    (setq cmd-str (concat prog-name " " file-name))
;;    (compile cmd-str)))
;    (shell-command cmd-str)))
;
;(global-set-key (kbd "<f5>") 'run-current-file)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;; 以下为实现 redo 的代码 ;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(provide 'redo)

(defvar redo-version "1.02"
  "Version number for the Redo package.")

(defvar last-buffer-undo-list nil
  "The head of buffer-undo-list at the last time an undo or redo was done.")
(make-variable-buffer-local 'last-buffer-undo-list)

(make-variable-buffer-local 'pending-undo-list)

;; Emacs 20 variable
(defvar undo-in-progress)

(defun redo (&optional count)
  "Redo the the most recent undo.
Prefix arg COUNT means redo the COUNT most recent undos.
If you have modified the buffer since the last redo or undo,
then you cannot redo any undos before then."
  (interactive "*p")
  (if (eq buffer-undo-list t)
      (error "No undo information in this buffer"))
  (if (eq last-buffer-undo-list nil)
      (error "No undos to redo"))
  (or (eq last-buffer-undo-list buffer-undo-list)
 ;; skip one undo boundary and all point setting commands up
 ;; until the next undo boundary and try again.
      (let ((p buffer-undo-list))
    (and (null (car-safe p)) (setq p (cdr-safe p)))
    (while (and p (integerp (car-safe p)))
      (setq p (cdr-safe p)))
    (eq last-buffer-undo-list p))
      (error "Buffer modified since last undo/redo, cannot redo"))
  (and (or (eq buffer-undo-list pending-undo-list)
       (eq (cdr buffer-undo-list) pending-undo-list))
       (error "No further undos to redo in this buffer"))
  (or (eq (selected-window) (minibuffer-window))
      (message "Redo..."))
  (let ((modified (buffer-modified-p))
    (undo-in-progress t)
    (recent-save (recent-auto-save-p))
    (old-undo-list buffer-undo-list)
    (p (cdr buffer-undo-list))
    (records-between 0))
 ;; count the number of undo records between the head of the
 ;; undo chain and the pointer to the next change.  Note that
 ;; by `record' we mean clumps of change records, not the
 ;; boundary records.  The number of records will always be a
 ;; multiple of 2, because an undo moves the pending pointer
 ;; forward one record and prepend a record to the head of the
 ;; chain.  Thus the separation always increases by two.  When
 ;; we decrease it we will decrease it by a multiple of 2
 ;; also.
    (while p
      (cond ((eq p pending-undo-list)
         (setq p nil))
        ((null (car p))
         (setq records-between (1+ records-between))
         (setq p (cdr p)))
        (t
         (setq p (cdr p)))))
 ;; we're off by one if pending pointer is nil, because there
 ;; was no boundary record in front of it to count.
    (and (null pending-undo-list)
     (setq records-between (1+ records-between)))
 ;; don't allow the user to redo more undos than exist.
 ;; only half the records between the list head and the pending
 ;; pointer are undos that are a part of this command chain.
    (setq count (min (/ records-between 2) count)
      p (primitive-undo (1+ count) buffer-undo-list))
    (if (eq p old-undo-list)
            nil ;; nothing happened
 ;; set buffer-undo-list to the new undo list.  if has been
 ;; shortened by `count' records.
      (setq buffer-undo-list p)
 ;; primitive-undo returns a list without a leading undo
 ;; boundary.  add one.
      (undo-boundary)
 ;; now move the pending pointer backward in the undo list
 ;; to reflect the redo.  sure would be nice if this list
 ;; were doubly linked, but no... so we have to run down the
 ;; list from the head and stop at the right place.
      (let ((n (- records-between count)))
    (setq p (cdr old-undo-list))
    (while (and p (> n 0))
      (if (null (car p))
          (setq n (1- n)))
      (setq p (cdr p)))
    (setq pending-undo-list p)))
    (and modified (not (buffer-modified-p))
     (delete-auto-save-file-if-necessary recent-save))
    (or (eq (selected-window) (minibuffer-window))
    (message "Redo!"))
    (setq last-buffer-undo-list buffer-undo-list)))

(defun undo (&optional arg)
  "Undo some previous changes.
Repeat this command to undo more changes.
A numeric argument serves as a repeat count."
  (interactive "*p")
  (let ((modified (buffer-modified-p))
    (recent-save (recent-auto-save-p)))
    (or (eq (selected-window) (minibuffer-window))
    (message "Undo..."))
    (or (eq last-buffer-undo-list buffer-undo-list)
 ;; skip one undo boundary and all point setting commands up
 ;; until the next undo boundary and try again.
    (let ((p buffer-undo-list))
      (and (null (car-safe p)) (setq p (cdr-safe p)))
      (while (and p (integerp (car-safe p)))
        (setq p (cdr-safe p)))
      (eq last-buffer-undo-list p))
    (progn (undo-start)
           (undo-more 1)))
    (undo-more (or arg 1))
 ;; Don't specify a position in the undo record for the undo command.
 ;; Instead, undoing this should move point to where the change is.
 ;;
 ;;;; The old code for this was mad!  It deleted all set-point
 ;;;; references to the position from the whole undo list,
 ;;;; instead of just the cells from the beginning to the next
 ;;;; undo boundary.  This does what I think the other code
 ;;;; meant to do.
    (let ((list buffer-undo-list)
          (prev nil))
      (while (and list (not (null (car list))))
        (if (integerp (car list))
            (if prev
            (setcdr prev (cdr list))
 ;; impossible now, but maybe not in the future
              (setq buffer-undo-list (cdr list))))
        (setq prev list
              list (cdr list))))
    (and modified (not (buffer-modified-p))
     (delete-auto-save-file-if-necessary recent-save)))
  (or (eq (selected-window) (minibuffer-window))
      (message "Undo!"))
  (setq last-buffer-undo-list buffer-undo-list))

帮助系统

使用 Emacs 的过程中,您随时可以获取帮助

M-x help-with-tutorial

C-h t Emacs快捷指南

M-x info-emacs-manual

C-h r Emacs使用手册

M-x info

C-h i 在线帮助

其它

表 25.2. Emacs帮助系统

C-h a M-x apropos-command 搜索命令
C-h f M-x describe-function 函数说明
C-h v M-x describe-variable 变量说明
C-h k M-x describe-key 键绑定说明
C-h c M-x describe-key-briefly 键绑定说明
C-h w M-x where-is 查找键绑定

基本操作

针对文件及编辑器的一些操作,绝大多数软件中,这类操作都安排在 文件 菜单里面。

表 25.3. Emacs 基本操作

C-x C-c M-x save-buffers-kill-emacs 保存退出
C-x C-z M-x iconify-or-deiconify-frame 挂起(最小化)
C-x C-f M-x find-file 打开文件、目录
C-x C-r M-x find-file-read-only 以只读模式打开
C-x i M-x insert-file 插入文件
C-x C-s M-x save-buffer 保存
C-x s M-x save-some-buffers 询问,保存所有未保存的缓冲区
C-x C-w M-x write-file 另存为文件
C-x RET r M-x revert-buffer-with-coding-system 以指定编码读取文件
C-x RET f M-x set-buffer-file-coding-system 以指定编码保存文件
M-x revert-buffer 恢复到原始状态

数字参数

Emacs 中可以使用 Ctrl+u 向命令传递参数。例如用数字作为参数,指定命令运行的次数

C-u (#) command

M-x universal-argument (通用参数)

例如:

C-u 10 C-f               向前10个字符
C-u 10 M-x forward-char

M-(#) (command)

negative-argument (负参数)

M-[1-9] 快速参数

digit-argument (数字参数)

基础编辑

几乎所有编辑器都具有的基础功能。

使用 Readline 控件的程序,例如 bash ;以及其它使用 Emacs风格 键绑定的程序,也使用基本相同的功能键。如果熟悉 bash 的快捷键,这些绑定您一定驾轻就熟

注意:这里只是一个列表,更详细的介绍,请参阅《Emacs 快捷指南》 C-h t

定位

表 25.4. Emacs 定位

向前 向后 向下 向上
卷屏 C-v M-v
字符 C-f C-b C-n C-p
单词 M-f M-b
C-a C-e 移动到行首或行尾,不能跨行
M-a M-e
段落 M-{ M-}
缓冲区 M-< M-> 移动到缓冲区起始或结束
行号 M-g g M-g M-g M-x goto-line 按行号跳转
字符位置 M-x goto-char 按字符跳转

其它:

C-M-l (M-x reposition-window)

将当前行卷至页面中部

C-l (M-x recenter)

刷新页面,将将当前行卷至页面中部 (使用数字参数指定行)

M-r M-x (move-to-window-line)

移动光标至页面中间的行 (使用数字参数指定行)

删除

表 25.5. Emacs 删除

向前 向后
字符 C-d M-x delete-char DEL M-x delete-backward-char
单词 M-d M-x kill-word C-Delete / M-DEL M-x backward-kill-word
光标至行末 C-k M-x kill-line
整行 C-S-backspace M-x kill-whole-line
按表达式删除 C-M-k M-x kill-sexp
区块 C-w M-x kill-region
空白 删除连续空格 M-x delete-horizontal-space

注意:上表中 DEL 实际按键为 Backspace ,PC 中只有 Delete 键,而没有 DEL 键,Emacs 把 Backspace 映射为 DELbackspace 实际按键也为 Backspace类似的,Emacs 把 PC 的 Enter 键映射为 RET ; 而 RET 实际为 C-m

可能您注意到了,Emacs 进行删除时有两种处理方法, delete 和 kill

kill

比较类似于 剪切 ,剪切掉的内容被依次放入 剪切队列 kill-ring ,可以召回。

delete

就是 删除 了,删除掉的内容并不能召回。但是可以通过 M-x undo 撤消删除。

复制

M-w (M-x kill-ring-save)

将内容放入 剪切队列 kill-ring

C-w

剪切

粘贴

C-y (M-x yank)

从 剪切队列 kill-ring 中召回最后一次放入的内容

M-y (M-x yank-pop)

从 剪切队列 kill-ring 中按后进先出的顺序,依次召回

  • 这个命令只能在 M-x yank 或者 M-x yank-pop 之后使用。也就是说,只能 C-y 后 M-y , M-y 可以连续多次。

撤消

C-/ (M-x undo)

撤消之前的修改

C-_ (M-x undo)

C-x u (M-x advertised-undo)

advertised-unde 是 undo 命令的一个别名

  • 为了减少 undo 的次数,每插入20个字符,视为一个 undo 的单位。

重做

安装 redo.el 扩展,并在配置文件中添加如下内容

;;---------- redo
;; 读取扩展
(require 'redo)
;; 设置 Redo 的键绑定
(global-set-key ( kbd "C-.") 'redo)

区块编辑

如何选中区块,可以参考“区块选择”一节

需要补充的是,完成区块选择时,实际定义了两种区块 :

---XXXXXxxx
xxxXXXXXxxx
xxxXXXXXxxx
xxxXXXXX---

---代表没有被选中的区域

连续区块为标记和光标点之间连续的区块;字符 X 和 x 均为连续区块

矩形区块为标记和光标点之间矩形的区块;大写字符 X 为矩形区块

表 25.6. Emacs 区块编辑

连续区块
C-SPC M-x set-mark-command 在光标点处设置标记
C-@ 同上 建议使用 C-t
M-@ M-x mark-word 在单词结尾处设置标记
M-h M-x mark-paragraph 选中段落
M-x mark-end-of-sentence 在句末设置标记
C-x h M-x mark-whole-buffer 整个缓冲区
C-x C-x M-x exchange-point-and-mark 交换标记和光标点
C-w M-x kill-region 剪切区块
M-w M-x kill-ring-save 复制区块
C-y M-x yank 粘贴区块
M-y M-x yank-pop 队列粘贴
矩形区块
C-x r k M-x kill-rectangle 删除矩形区块
C-x r y M-x yank-rectangle 粘贴上一次删除的矩形区块
C-x r t M-x string-rectangle 用指定字符填充
C-x r o M-x open-rectangle 用空格插入
C-x r c M-x clear-rectangle 用空格填充
C-x r r M-x copy-rectangle-to-register 拷贝到寄存器中

搜索和替换

Emacs中,默认使用 增量搜索 :在搜索对话模式中输入关键词的同时,Emacs 就开始进行搜索,随着关键字的输入,不断的缩小搜索范围

而传统的非增量搜索,则是关键词输入后,再进行搜索。

表 25.7. Emacs 搜索

增量搜索
C-s M-x isearch-forward 向前增量搜索
C-r M-x isearch-backward 向后增量搜索
C-M-s M-x isearch-forward-regexp 正则表达式向前增量搜索
C-M-r M-x isearch-backward-regexp 正则表达式向后增量搜索
询问替换
M-% M-x query-replace 询问替换
C-M-% M-x query-replace-regexp 正则表达式询问替换
搜索
M-x search-forward 向前搜索
M-x search-backward 向后搜索
M-x search-forward-regexp 正则表达式向前搜索
M-x search-backward-regexp 正则表达式向后搜索
替换
M-x replace-string 替换
M-x replace-regexp 正则表达式替换
  • 增量搜索时,关键词会被一直保留。可以直接进行下一次搜索
  • 下一次增量搜索,如果之前进行了其它操作,则需要连续两次命令(快捷键),才能召回关键词。
  • C-g 取消搜索,回到搜索前的位置
  • RET 结束搜索,停在当前位置
  • 可以选中区块后,在区块内进行替换

其它

插入控制字符

使用 C-q ,可以在缓冲区插入一个控制字符。例如:

C-q C-m = ^M

文本换位

表 25.8. Emacs 其它

字符 C-t M-x transpose-chars
单词 M-t M-x transpose-words
C-x C-t M-x transpose-lines

将 TAB 字符转换为空格

选中需要转换的区域, M-x untabify

对齐文本块

选中需要对齐的区域, M-x indent-region

窗格和缓冲区管理

多窗格

表 25.9. Emacs 窗格

C-x 2 M-x split-window-vertically 分隔出两个垂直窗格,水平分隔线
C-x 3 M-x split-window-horizontally 分隔出两个水平窗格,垂直分隔线
C-x 1 M-x delete-other-window 只保留当前窗格
ESC ESC ESC M-x keyboard-escape-quit 只保留当前窗格
C-x 0 M-x delete-window 关闭当前窗格
C-x o M-x other-window 在下一个窗格中激活光标
C-M-v M-x scroll-other-window 向下卷动下一个窗格,使用负参数可以向上卷动
  • 下一个窗格: 垂直分隔,则先左后右;水平分隔,则先上后下。如果窗格还有子窗格,则先遍历其子窗格后,再遍历其它窗格,以此递归。

多缓冲区

Emacs 中,打开新的缓冲区,原有缓冲区并不会关闭

表 25.10. Emacs 缓冲区

C-x C-b M-x list-buffers 查看缓冲区列表
C-x b M-x switch-to-buffer 切换到其它缓冲区
C-x k M-x kill-buffer 关闭当前缓冲区
  • 切换到其它缓冲区时,默认上一次使用的缓冲区(可以用 TAB 补全)
  • 使用多窗格时,缓冲区操作只对当前窗格有效
  • 建议使用 ibuffer.el 这个扩展。 Emacs 自带,在配置文件中添加如下语句

    ;;========== ibuffer
    (require 'ibuffer)
    (global-set-key ( kbd "C-x C-b ")' ibuffer)
    
  • 另一个缓冲区列表的扩展(Emacs 自带)

    ;;CRM bufer list
    (global-set-key "\C-x\C-b" 'electric-buffer-list)
    

寄存器管理

寄存器用于存贮内容,在需要时取出,插入缓冲区。

Emacs 的寄存器使用单个字符命名,可以存贮两种内容 :

光标位置和窗口状态

表 25.11. Emacs 寄存器

C-x r SPC (寄存器名) M-x point-to-register 存贮光标位置
C-x r w (寄存器名) M-x window-configuration-to-register 保存当前窗口状态
C-x r f (寄存器名) M-x frame-configuration-to-register 保存所有窗口状态
C-x r j (寄存器名) M-x jump-to-register 光标跳转
C-x j (寄存器名) 略…… 同上

文本和数字

表 25.12. Emacs 寄存器2

C-x r s (寄存器名) M-x copy-to-register 将连续区块拷贝到寄存器中
C-x r r (寄存器名) M-x copy-rectangle-to-register 将矩形区块拷贝到寄存器中
C-u (数字) C-x r n (寄存器名) M-x number-to-register 将数字拷贝到寄存器中
C-x r i (寄存器名) M-x insert-register 在缓冲区中插入寄存器内容
  • M-x view-register 查看寄存器内容
  • M-x list-registers 查看寄存器列表
  • 寄存器中的矩形区块,以矩形区块的方式插入到缓冲区中。 见 “区块编辑”一节
  • 也可以将文件插入到寄存器中 (set-register ?寄存器名称 '(file . 文件名)) ,示例

              M-x lisp-interaction-mode 进入交互模式,输入如下 Lisp 代码:
              (set-register ?e '(file . "~/.emacs"))(光标)移动此外, C-j 求值。
              M-x list-registers 查看寄存器列表,多了寄存器 e : Register e contains the file "~/.emacs".
    

书签管理

Emacs 可以在当前位置创建一个书签,以便能够快速的返回。

与存储光标位置的寄存器略有不同

  • 书签可以使用单词来命名,而不限于一个字符。起一个容易记住的名字
  • 退出 Emacs 后,书签不会消失,下次还可以使用

表 25.13. Emacs 书签

C-x r m (name) M-x bookmark-set 设置书签
C-x r b (name) M-x bookmark-jump 跳转到书签
C-x r l M-x bookmark-bmenu-list 书签列表
M-x bookmark-delete 删除书签
M-x bookmark-load 读取存储书签文件
  • 书签默认存储在 ~/.emacs.bmk 文件中
  • 在配置文件中,可以设置书签存储的文件

    ;; 书签文件的路径及文件名
    (setq bookmark-default-file "~/.emacs.d/.emacs.bmk")
    
    ;; 同步更新书签文件 ;; 或者退出时保存
    (setq bookmark-save-flag 1)
    

Shell 模式

M-x shell进入 Shell 模式,可以完成一些简单的工作。不过有些情况下,输出会有一些问题

事实上,这是 Emacs 自带的终端。它与 bash 和 sh 的兼容比较好,而 fish 之类比较现代的 Shell,在 Emacs 终端里的效果则很差

需要注意的是, readline-bash 的绑定 C-p C-n ,在 Emacs 终端需要使用 M-p M-n 。其它的键绑定,也以 Emacs 为准

记录一系列操作,在需要的时候运行

例如给一个单词加 " ,可以分解为以下操作:

    M-b 移动到词首
    "
    M-f 移动到词尾
    "

这种重复的操作往往需要经常执行,手动未免太没有效率。我们可以把这些操作制作成宏,然后运行这个宏

当然,这只是最简单的宏。结合正则表达式进行匹配,以宏进行操作,可以完成许多复杂的操作

定义与运行

表 25.14. Emacs 宏

开始录制 C-x ( (M-x kmacro-start-macro) F3 (M-x kmacro-start-macro-or-insert-counter)
结束录制 C-x ) (M-x kmacro-end-macro) F4 (M-x kmacro-end-or-call-macro)
播放 C-x e (M-x kmacro-end-and-call-macro)

宏队列

与 剪切队列 类似,Emacs 中也有 宏队列 的概念: 当一个新的宏被定义,原有的宏并不消失,只是在宏队列中的位置被挤到后面。

C-x C-k进入宏队列,以下的操作可以在宏队列中连续进行

表 25.15. Emacs 宏队列

基本操作
C-n M-x kmacro-cycle-ring-next 下翻
C-p M-x kmacro-cycle-ring-previous 上翻
C-d M-x kmacro-delete-ring-head 删除当前宏
C-k M-x kmacro-end-or-call-macro-repeat 运行当前宏
命名与保存
n (name) M-x kmacro-name-last-macro 命名
b M-x kmacro-bind-to-key 绑定
M-x insert-kbd-macro 在缓冲区中插入宏定义
宏编辑器
C-e M-x kmacro-edit-macro 编辑
e M-x edit-kbd-macro 编辑指定名称的宏
l M-x kmacro-edit-lossage
询问执行
q M-x kbd-macro-query 在播放宏时,将进行询问确认
计数器
C-i M-x kmacro-insert-counter 将宏计数器的数值插入缓冲区
C-c M-x kmacro-set-counter 为宏计数器设置一个数值
C-a M-x kmacro-add-counter 给宏计数器添加一个前缀参数
C-f M-x kmacro-set-format 给宏计数器指定一个将要插入的特殊值
  • 保存为文件,使用 M-x load-file 加载
  • 保存到配置文件中,启动时加载

文件管理

文件管理

C-x d (M-x dired)

进入 Dired 列表模式

C-x C-d (M-x list-directory)

获取文件列表(简洁)

C-x C-f (M-x find-file)

打开文件,没有文件名则打开目录

定位、查看

表 25.16. Emacs 文件管理

向下 向上
文件 n p
C-n C-p
SPC DEL 上一级
目录 > < ^
已标记 M-} M-{
g 刷新
s 切换名称/日期排序方式
i 在当前窗口插入一个子目录
v 查看当前文件
y 查看当前文件类型
= 比较

标记

普通标记 m 标记(显示为字符 * )
t 反向标记
u 取消标记
U 取消所有标记
* / 标记文件夹
标记所有可执行文件
* @ 标记所有符号连接
* c 改变标记的符号
% m 根据正则表达式标记文件
% g 根据正则表达式标记文件内容
删除标记 d 标记为删除(显示为字符 D )
~ 将备份文件标记为删除
# 将存盘文件标记为删除
& d 根据正则表达式标记删除
x 执行删除

操作

RET 在新缓冲区打开文件
o 在另一个窗格打开
C-o 在另一个窗格后台打开
C-x C-f 新建文件
+ 新建目录
C-x C-q 将文件列表设为只读
可以结合 * 标记批量进行 D 删除文件
C 拷贝
R 重命名/移动
O 改变用户
G 改变群组
M 改变权限
S 符号链接
H 硬链接
Z 压缩
T touch
w 复制文件名
k 删除行(刷新后恢复)

服务器模式

由于各种原因,Emacs 启动比较耗时。可以启动一个 Emacs 的守护进程

emacs --daemon

然后通过 emacsclient 来连接服务器

emacsclient  -t  --alternate-editor jed  file
  • -t 在当前控制台打开 emacs 窗口
  • --alternate-editor jed 如果不能连接到 emacs 服务器,则使用 jed 编辑器

也可以使用 Emacs 服务器模式,M-x server-start 或者在配置文件中添加 (server-start) 启用 Emacs 服务器,使用 emacs-client 连接。

大纲模式

Emacs 的大纲模式(Outline mode),是一个十分有用的模式。如果工程规模比较大,你应该用大纲来组织它。

大纲模式通常作为辅模式使用,M-x outline-minor-mode启用。

大纲模式可以根据代码的语法对结构进行识别,但是这种自动模式工作的不是很好,而且不够灵活

另一种工作方式是查找特定的字串,来组织文档的结构。这种工作方式是可控制的,不过需要手动加入这些作为结构标识的字串。似乎有点麻烦,但是对于严谨的构思来说,你是需要列一个提纲的

大纲模式默认以行首的 * 作为结构标识,每多一个 * 就是下一级分支。

大纲模式默认的键绑定太过复杂,在配置文件中添加以下语句,将所有大纲模式操作的前缀键改为 Ctrl+o

(setq outline-minor-mode-prefix [(control o)])

试着在文档中随便加入几个这样的标识

*主干一
**分支一
**分支二
*主干二

进行一些简单的操作

C-o C-a 全部显示
C-o C-t 显示主干
C-o C-q 全部隐藏
C-o C-i 显示下一级分支
C-o C-k 显示分支
C-o C-e 显示节点
C-o C-d 隐藏分支

定制结构标识

为了不影响代码的功能,通常要把结构标识放到注释中,这样作有可能会带来不便,例如在 XML 环境中就要像这样使用:

<!--
*结构标识
-->

这样会给你的工作制造出一些混乱,并且大纲模式是以行为单位进行识别的,也就是说&lt;!--属于上一个分支

好在大纲模式可以通过正则表达式来定义结构标识,你可以把结构标识定义为下面这种形式

<!--*结构标识-->

通过设置outline-regexp定制标识:

setq outline-regexp "<!\\-\\-\\*+

1 Elisp 语法中,\ 有特殊意义,所以脱字符要用\\表示

2 正则表达式中 - 有特殊意义,所以前面要加脱字符

3 *+ 表示一个或多个*

配置

完整的配置:

例 25.2. emacs 大纲模式

(setq outline-minor-mode-prefix [(control o)])

(require 'docbook-xml-mode)
(add-hook 'docbook-xml-mode-hook
      (function (lambda ()
                  (setq outline-regexp "<!\\-\\-\\*+")
              (outline-minor-mode)
              (hide-body)
                  )))

1 更改大纲模式前缀键

1 加载 docbook-xml-mode

3 添加 docbook-xml-mode 钩子,运行下面代码[47]

4&lt;!--*识别为标识

5 启动大纲模式作为辅模式

6 隐藏所有内容,只显示主干

操作列表

显示、隐藏

全部显示 显示分支 隐藏
全局 show-all C-o C-a hide-body C-o C-t hide-sublevels C-o C-q1
分支 show-subtree C-o C-s hide-leaves C-o C-l hide-subtree C-o C-d
show-branches C-o C-k
show-children C-o C-i
节点 show-entry C-o C-e hide-entry C-o C-c
其它分支 hide-other C-o C-o

1 可以带数字参数,如M-2 C-o C-q显示第2层子结构

移动

向前 向后
全局 outline-next-visible-heading C-o C-n outline-previous-visible-heading C-o C-p
同级 outline-forward-same-level C-o C-f outline-backward-same-level C-o C-b
返回上一级 outline-up-heading C-o C-u

[47] docbook-xml-mode.el中定义 docbook-xml-mode-hook,模式启动时运行钩子代码

在 Emacs 中使用 sdcv

在 Emacs 配置文件中加入以下代码

(global-set-key (kbd "C-c d") 'kid-sdcv-to-buffer)
(defun kid-sdcv-to-buffer ()
  (interactive)
  (let ((word (if mark-active
                  (buffer-substring-no-properties (region-beginning) (region-end))
                  (current-word nil t))))
    (setq word (read-string (format "Search the dictionary for (default %s): " word)
                            nil nil word))
    (set-buffer (get-buffer-create "*sdcv*"))
    (buffer-disable-undo)
    (erase-buffer)
    (let ((process (start-process-shell-command "sdcv" "*sdcv*" "sdcv" "-n" word)))
      (set-process-sentinel
       process
       (lambda (process signal)
         (when (memq (process-status process) '(exit signal))
           (unless (string= (buffer-name) "*sdcv*")
             (setq kid-sdcv-window-configuration (current-window-configuration))
             (switch-to-buffer-other-window "*sdcv*")
          (local-set-key (kbd "d") 'kid-sdcv-to-buffer)
             (local-set-key (kbd "q") (lambda ()
                                        (interactive)
                                        (bury-buffer)
                                        (unless (null (cdr (window-list))) ; only one window
                                          (delete-window)))))
           (goto-char (point-min))))))))

1 如果选中区域则查询区域内容,否则查询当前光标所在单词。查询结果显示在一个叫做 sdcv 的缓冲区

2sdcv 里面按 q,将它隐藏到缓冲区列表的结尾

3sdcv 里面按 d 查询当前单词

Windows 下字体设置

(set-default-font "Verdana-10")
(if window-system
   (set-fontset-font (frame-parameter nil 'font)
      'unicode '("simsun" . "unicode-bmp"))
)

1 英文字体

2 中文字体

版本

在 Linxu 系统中,Emacs 的最新版本通常为 emacs-snapshot、emacs-cvs

Emacs for Windows 请到这里下载,推荐“patched”版本