# 使用 Emacs 进行 Perl 编程

打开和新建文件
  .emacs 配置
  命令
编辑代码
  .emacs 配置2
  命令
语法检查和运行
  .emacs 配置
  命令
代码浏览
  .emacs 配置3
  命令
查看文档
  .emacs 配置
  命令
交互命令行
  .emacs 配置
Debugger
  .emacs 配置
  命令

Emacs 的配置和使用是一个很大的话题，这里我只介绍和 Perl 编程相关的配置 和使用，并结合 PDE 来说明它可能提供哪些功能。

注意：本文不是 PDE 的文档，有关 PDE 的安装和使用参见 pde.info。

下面的 .emacs 配置请按个人需要添加到自己的配置中。

## 打开和新建文件

在 emacs 打开和新建文件使用的是一个命令 find-file。如果文件不存在就创 建一个。默认的提示输入文件名的方式类似 shell 中的补全。事实上，这个补 全方式不是太好，起码是不直观。emacs 提供一个很好的补全方式──ido-mode。 这样打开文件时如下图所示：

Ido 1

在输入几个字符后，补全显示如下：

Ido 2

ido 补全不是从列表中字符串开头进行匹配，而是在任意位置。而且还可以使 用正则表达式进行匹配。匹配的方式是可以自定义的。

ido 不仅可以用于文件名的补全，还可以用于缓冲区列表的补全，imenu 的补 全等等。

当文件不存在或者是空文件时，会提示是否使用模板创建新文件：

New file with template

在打开文件后就可以进行编辑了。

### .emacs 配置

    (ido-mode 1)
    (require 'template-simple)

### 命令

- C-x C-f ido-find-file
- C-x C-b ido-switch-buffer

## 编辑代码

cperl-mode 提供了很好的 perl 编辑环境。大多数情况下，在设置好缩进风格 后，cperl-mode 的缩进方式已经很好了。这里推荐使用 PBP 中的设置。 如果想重新缩进代码，可以使用 indent-region 命令。推荐使用 pde-indent-dwim，可以不用选中区域。

代码注释使用 comment-dwim。当选中区域时，如果区域内有未注释的行，则注 释整个区域，如果区域内都是注释，则取消注释。如果没有选中区域，当前行如 果有非空白字符则在行尾添加注释，如果为空行或只有空白字符，则直接添加注 释。如果已经有注释，则跳到注释开始的位置，如果以 C-u 使用命令，则删除 注释。可见，这个命令几乎把所有有关注释的功能都包括了。

大部分编辑器都提供 snippet 的功能。在 emacs 中称为 abbrev。通常需要打开 abbrev-mode。cperl-mode 提供一些常用的 abbrev，比如在关键字 if else elsif while for foreach unless until，以及 =head11 =over =head2 =pod 之后都有一些很好的扩展。

emacs 的代码补全可能在用户界面上没有一些编辑器花哨，但是补全功能是一般 的编辑器所不能比的。最常用的补全命令是 hippie-expand 和 dabbrev-expand。 这两个命令几乎可以完成所有的补全。一般如果想补全整行，使用 hippie-expand，而补全单词使用 dabbrev-expand。由于 emacs 不可能提供对某 种语言进行解析的功能，所以更智能的代码提示功能对于 perl 还没有实现。虽 然 hippie-expand 包括文件名的补全，但是一般优先级太低，几乎不可能试到补 全文件名，所以需要使用别的命令。我推荐使用 comint-dynamic-complete，大 部分情况下是很好用的。

### .emacs 配置2

    ;; M-SPC not available, window manager take it away
    (global-set-key (kbd "M-'") 'just-one-space)
    (global-set-key (kbd "C-M-=") 'pde-indent-dwim)
    ;; nearest key to dabbrev-expand
    (global-set-key (kbd "M-;") 'hippie-expand)
    (global-set-key (kbd "C-;") 'comment-dwim)
    (global-set-key (kbd "C-c f") 'comint-dynamic-complete)

    (setq hippie-expand-try-functions-list
              '(try-expand-line
                try-expand-dabbrev
                try-expand-line-all-buffers
                try-expand-list
                try-expand-list-all-buffers
                try-expand-dabbrev-visible
                try-expand-dabbrev-all-buffers
                try-expand-dabbrev-from-kill
                try-complete-file-name
                try-complete-file-name-partially
                try-complete-lisp-symbol
                try-complete-lisp-symbol-partially
                try-expand-whole-kill))
    (autoload 'comint-dynamic-complete "comint" "Complete for file name" t)
    (setq comint-completion-addsuffix '("/" . ""))
    (setq-default indent-tabs-mode nil)

    (defalias 'perl-mode 'cperl-mode)
    (defun pde-perl-mode-hook ()
      (abbrev-mode t)
      (add-to-list 'cperl-style-alist
                   '("PDE"
                     (cperl-auto-newline                         . t)
                     (cperl-brace-offset                         . 0)
                     (cperl-close-paren-offset                   . -4)
                     (cperl-continued-brace-offset               . 0)
                     (cperl-continued-statement-offset           . 4)
                     (cperl-extra-newline-before-brace           . nil)
                     (cperl-extra-newline-before-brace-multiline . nil)
                     (cperl-indent-level                         . 4)
                     (cperl-indent-parens-as-block               . t)
                     (cperl-label-offset                         . -4)
                     (cperl-merge-trailing-else                  . t)
                     (cperl-tab-always-indent                    . t)))
      (cperl-set-style "PDE"))

### 命令

- C-M-\ indent-region
- C-M-= pde-indent-dwim
- C-; comment-dwim
- M-/ dabbrev-expand
- M-; hippie-expand
- C-c f comint-dynamic-complete
- M-' just-one-space
- M-\ delete-horizontal-space
- C-M-a beginning-of-defun
- C-M-e end-of-defun
- C-M-f forward-sexp
- C-M-b backward-sexp
- C-M-u backward-up-list
- M-{ backward-paragraph
- M-} forward-paragraph
- M-h mark-paragraph
- C-M-h mark-defun
- C-x h mark-whole-buffer

## 语法检查和运行

cperl-mode 没有提供语法检查和运行的命令，菜单上有这个选项，但是没有激 活，如果想用的话要下载 mode-compile。但是我不喜欢 mode-compile，因为它 写得太长了，把简单的问题复杂化了，我喜欢用 smart-compile+。由于它的一 些小缺点，我按 smart-compile 的思路重写了一个 compile-dwim。你可以用 compile-dwim-compile 和 compile-dwim-run 来进行语法检查和运行。 它们都是使用 compile 命令来运行程序。当进行 GUI 编程时一个经常遇到的问 题是要同时运行多个程序，这时候 compile 会提示你是否要先杀死前一个运行 的程序，这时候会觉得很不方便。如果你也有这个需求的话，可以设置 compilation-buffer-name-function 为 pde-compilation-buffer-name。 如果要实时显示语法错误，emacs 提供一个 flymake 这个工具。个人认为这个 功能有点鸡肋。不过有总比没有强。

对于新手，运行脚本时一个经常遇到的问题是忘了修改文件的可执行权限。 executable 这个扩展提供一个方法可以自动修改文件的权限。

### .emacs 配置

    (global-set-key (kbd "C-c s") 'compile-dwim-compile)
    (global-set-key (kbd "C-c r") 'compile-dwim-run)
    (setq compilation-buffer-name-function 'pde-compilation-buffer-name)
    (autoload 'compile-dwim-run "compile-dwim" "Build and run" t)
    (autoload 'compile-dwim-compile "compile-dwim" "Compile or check syntax" t)
    (autoload 'executable-chmod "executable"
              "Make sure the file is executable.")

    (defun pde-perl-mode-hook ()
       ;; chmod when saving
      (when (and buffer-file-name
            (not (string-match "\\.\\(pm\\|pod\\)$" (buffer-file-name))))
          (add-hook 'after-save-hook 'executable-chmod nil t))
      (set (make-local-variable 'compile-dwim-check-tools) nil))

### 命令

- C-c s compile-dwim-compile
- C-c r compile-dwim-run
- compile
- flymake-mode
- executable-set-magic

## 代码浏览

emacs 提供一些基本的代码浏览的工具和扩展。最常用的代码浏览工具是 etags。 cperl-mode 提供对创建 TAGS 文件的支持。直接使用 cperl-etags 可以对本文 件创建 TAGS。菜单中提供更多创建 TAGS 文件的选择。菜单路径是 <Perl> <Tools> <Tags>。这里还有一个命令 <Class Hierarchy from TAGS>（M-x cperl-tags-hier-init），效果如下图。但是在我这运行时会产生错误。错误的 原因作者没有考虑到有人会设置 tags-table-list。如果你没有设置这个变量， 这个命令是可以用的。如果你没有设置这个变量就用吧。在改变 TAGS 文件后， 要重新产生这个菜单，可以先用 M-x visit-tags-table 后再选择菜单上的 "++UPDATE++"，否则是无法更新的。

Class Hierarchy from TAGS

如果觉得这样的效果不错的话，可以试试 tags-tree，一个用于树形显示 TAGS 文件的扩展。

TAGS Tree

但是一般来说花哨的功能都不太实用，如果浏览代码时还要一会点鼠标，一会用 键盘转来转去的话，效率反而更低。还是老老实实用 find-tag 这样的命令比较 好。

如果仅限于本文件跳转，imenu 应该是最好的选择。效果如下图所示：

Ido Imenu

如果还要更直观一些，可以试试使用 imenu-tree。

Imenu Tree

所有这些都不是建立在语义分析基础上的，所以在使用别人的库时，想要查找库 函数的定义基本上除了创建 TAGS 文件之外，就只能打开这个库文件使用 imenu 自己查找函数定义了。

这里提供一些快速打开文件的方法。对于库文件，推荐使用 find-file 结合 complete 提供的方法打开。你需要使用一个 "<" 前缀来告诉 emacs 我想从 include path 中补全文件而不是当前目录。如果使用 ido-mode，需要使用C-x C-f C-f，最后一 个 C-f 是转换 ido-mode 成一般的文件补全模式。这不是专门针对打开 perl模 块的命令，所以补全之类是有一点不足。但是满足大部分情况的要求，不需要额 外的缓存（也就是始终是最新的），而且可以打开多种文件，比如 c 的库头文件 之类，所以我很喜欢。如果想专门针对 perl 模块可以使用 perldoc-find-module 这样的命令。在 cperl-mode 中，如果想打开光标下的模 块，可以使用 ffap。这也是一个很有用的命令，可以打开几乎所有能想到的 URL，所以值得做一个全局绑定。

PC include file complete

关于代码浏览另一个话题是代码折叠功能。emacs 一个重要的文字折叠扩展 是 outline。cperl-mode 支持使用 outline-minor-mode 来折叠代码。

Fold subroutines in outline-minor-mode

hideshow 是另一个用于代码折叠的扩展。hideshow 的特色在于它是基于块和注 释进行折叠，所以不会像 outline 那样把空白会收到折叠区域里。缺点是它只能 用于折叠，不能你 outline-mode 那样有层次，也没有在折叠块中移动的方法。 所以可能还是使用 outline-minor-mode 方便一些。

### .emacs 配置3

    (global-set-key (kbd "C-c i") 'imenu)
    (global-set-key (kbd "C-c v") 'imenu-tree)
    (global-set-key (kbd "C-c j") 'ffap)
    (setq tags-table-list '("./TAGS" "../TAGS" "../../TAGS"))
    (autoload 'imenu-tree "imenu-tree" "Show imenu tree" t)
    (setq imenu-tree-auto-update t)
    (eval-after-load "imenu"
     '(defalias 'imenu--completion-buffer 'pde-ido-imenu-completion))
    (autoload 'tags-tree "tags-tree" "Show TAGS tree" t)
    ;; A wonderful minibuffer completion mode
    (partial-completion-mode 1)
    (setq PC-include-file-path
          (delete-dups (append PC-include-file-path pde-perl-inc)))
    (setq ffap-url-regexp
          (concat
           "\\`\\("
           "news\\(post\\)?:\\|mailto:\\|file:" ; no host ok
           "\\|"
           "\\(ftp\\|https?\\|telnet\\|gopher\\|www\\|wais\\)://" ; needs host
           "\\)[^:]" ; fix for perl module, require one more character that not ":"
           ))
    (add-to-list 'ffap-alist  '(cperl-mode . pde-ffap-locate))

    ;; Rebinding keys for hideshow
    (require 'hideshow)
    (define-key hs-minor-mode-map "\C-c\C-o"
      (let ((map (lookup-key hs-minor-mode-map "\C-c@")))
        ;; C-h is help to remind me key binding
        (define-key map "\C-h" 'describe-prefix-bindings)
        (define-key map "\C-q" 'hs-toggle-hiding)
        ;; compatible with outline
        (define-key map "\C-c" 'hs-hide-block)
        (define-key map "\C-e" 'hs-show-block)
        map))

### 命令

- C-c f ffap
- C-c i imenu
- C-c v imenu-tree
- cperl-tags-hier-init
- M-. find-tag
- M-* pop-tag-mark

## 查看文档

在 unix 中查看文档的最方便，最全的方式应该是 man 了。emacs 用 Emacs Lisp 实现解析 man 文件，emacs 戏称为 woman (WithOut MAN)。有了 woman 可 以在 emacs 中很方便的查看文档。直接使用 woman 查看 perl 模块的文档已经 很好了，问题是 Windows 中没有这样的 manpage，而且 woman 的缓存更新太慢 了。cperl-pod-to-manpage 调用的外部的 man 来产生格式化的文档，但是没有 woman 显示的那样好看。所以我提供新的一个命令 pde-pod-to-manpage，使用 pod2man 产生 manpage 后再用 woman 来格式化，效果应该还是可以。同样的思 路，我写了 perldoc 这个扩展，先用 perldoc 提取 pod，再用 pod2man 转 换，woman 格式化。这时说明一下，perldoc 虽然可以单独使用，但是推荐结合 help-dwim 一起用。help-dwim 实现一种可以把多种文档帮助系统结合在一个命 令中。你可以用一个命令来查询 Emacs Lisp 中的函数或变量，或者查找 manpage 中的某个条目，查询 perldoc 或者 perlapi。我推荐使用。perldoc 扩 展还提供树形显示模块的功能。在这个模式中可以显示模块文档或者直接打开对 应的文件。

Perldoc Tree

再说说函数参数的提示，emacs 中函数参数提示是由 eldoc 这个扩展实现的。 但是 cperl-mode 自己也提供内建函数的提示。使用 cperl-lazy-install 启动。 但是这些函数说明仅限于 cperl-short-docs 提供的这些。

### .emacs 配置

    (global-set-key (kbd "C-c h") 'help-dwim)
    (setq cperl-lazy-help-time 2)
    (defun pde-perl-mode-hook ()
       (cperl-lazy-install))

### 命令

- C-c h help-dwim
- pde-pod-to-manpage
- perldoc-tree

## 交互命令行

动态语言的最强大之处在于它的 eval 功能。正是 eval 函数可以让程序可以像 堆积木一样慢慢壮大起来。理论上 perl -de 1 就是一个很好的交互命令行程序。 但是我还是写了一个新的程序，功能上没有前者强大，但是简单，容易扩展，而 且可以回显运行的结果。

Interactive perl shell

你可以在边写 perl 代码时，用 inf-perl-send 发送当前行到 shell 中运行， 也可以在写完一段代码时，用 inf-perl-send 发送选中区域到 shell 中运行。 如果你只是想验证一小段代码，用这个方式是很方便的，不需要新建文件，保存 文件，再运行这样折腾。当然还是推荐先写到文件中，需要时一点一点用 send 命令运行，差不多时再运行整个文件。perl interactive shell 的最大问题是 my 这样的词法变量无法在 eval 后保持，所以所有变量都需要是全局变量。如 果是单独一行一行 send，这个命令会先除去行首的 my 限制。如果发送整个区 域，就没有这样的功能了，使用时遇到这个问题时不要认为这是一个 bug。

目前我写了一个用于 Gtk2 的交互 shell 程序，需要进行 Gtk2-Perl 开发可以 考虑使用。

### .emacs 配置

    (autoload 'run-perl "inf-perl" "Start perl interactive shell" t)

## Debugger

emacs 的 gud 库提供对 perl5db 的支持，但是和 gdb-ui 还相差很大。我已经 开发了一个 perldb-ui，想提供类似的界面，目前还不稳定，但是基本功能已经 实现。开发难度比较大，主要是调试不方便，还有对 perl5db.pl 不太熟悉。如 果能实现我想要的功能，这个 Debugger 会很有用。

Perl Debugger

perl5db 提供的命令行界面已经相当简洁，可能比使用 emacs 按键还要快捷。除 了 n, s, c, b, B, w, W, L, p, x 这些常用的控制和显示命令，这些命令也很 好用：

- f 打开导入的文件
- l 跳到指定行或者函数
- . 跳到当前执行位置
- r 从当前函数返回
- V 查找变量
- S 查找函数
- y 显示所有词法变量

### .emacs 配置

    (autoload 'perldb-ui "perldb-ui" "perl debugger" t)

### 命令

- perldb-ui
- perldb-many-windows
- perldb-restore-windows

1. 扩展 head1 时，光标不能处于缓冲区末尾。原因不明。

2. pde-perl-mode-hook 在下面的配置中也有出现，如果需要，可以把这几处 写成一个函数用 add-hook 加到 cperl-mode-hook 中。

3. pde 开头的函数和变量如果想单独使用，请到 pde 开头的 elisp 文件中 找到对应的定义写到 .emacs 里。