定制 spacemacs 中的 YASnippet

YASnippet 是 Emacs 上代码片段生成扩展。由于其灵活的定制能力,让其成为应对“重复”编辑工作的效率神器。

Example

c++ 编程中,经常会在头文件的开始添加 Include Header Guard 宏声明如下所示:

#ifndef XXX_YY_H
#define XXX_YY_H

#endif /* XXX_YY_H */

在 spacemacs 中只要键入once ,便会看到下拉选项列表,选择正确的宏定义,便生成了上述的“一串”代码。或者按住 Ctrl + p 也会弹出一个 mini buffer 供你选择。不使用 spacemacs 的 emacs 用户,是按 TAB 来生成。

原理

生成上述代码片段的代码如下:

#name : #ifndef XXX; #define XXX; #endif
# key: once
# --
#ifndef ${1:`(upcase (file-name-nondirectory (file-name-sans-extension (or (buffer-file-name) ""))))`_H}
#define $1

$0

#endif /* $1 */

其中once 便是调用生成代码的 key,-- 以下的部分为被生成的代码,$1 表示所有引用位置1 处的代码。`` 包围的部分是 YASnippet 最有威力的地方,其可以直接调用 EmacsLisp 代码做任何能做的事情,比如这里截取当前 buffer 的文件名,改成大写后组合成一个宏。

上述代码的文件放置在 Emacs plugin 目录中的yasnippet/snippets/cc-mode 下,该目录下每个文件都是一个 Snippet,在 major mode 为 cpp mode 时,均可通过 key 来生成对应的部分。

定制

在实际的使用中,绝大多数的 Snippet 无需修改,但是也有一些常用而不完全匹配的 Snippet 就需要我们自己动手定制。

还是以上述的 cpp 文件保护宏为例,假如需要遵循 Google 的 C++ coding style 编程规范,这个宏的格式变成了SOURCE_PATH_FILENAME_H ,并且最后的注释变为了 //

#ifndef SOURCE_PATH_FILENAME_H
#define SOURCE_PATH_FILENAME_H

#endif  // SOURCE_PATH_FILENAME_H

为了获取源文件路径,这时上面的 $1 处就要编写更复杂的代码。而 Snippet 中的 EmacsLisp 代码基本都是简单的函数调用,如果想要更复杂功能,可以在外面定制函数,在 Snippet 中直接调用。此时 .yas-setup.el 文件便开始登场。

.yas-setup.el

在 YASnippet 每个 mode 的文件目录下都可以放置一个名为.yas-setup.el 的 EmacsLisp 源文件。Emacs 在启动后便会自动编译加载该文件中的函数。

为了生成复合 Google C++ 规范的文件保护宏,定制了几个(丑陋)小函数:

(defun yee-format-dir (path)
  (replace-regexp-in-string "[-/]" "_" path))

(defun yee-prefix-file (prefix)
  (concat prefix (file-name-nondirectory (file-name-sans-extension (buffer-file-name)))))

(defun yee-del-substr (str sub)
  (substring str (string-width sub) nil))

(defun yee-del-src (dir)
  (let ((src "src_") (source "source_"))
    (cond ((string-match src dir) (yee-del-substr dir src))
          ((string-match source dir) (yee-del-substr dir source))
          (t dir))))

(defun google-cpp-header-guard ()
  (let ((proj-root (yee-format-dir (projectile-project-root)))
        (file-dir (yee-format-dir (file-name-directory (buffer-file-name)))))
    (upcase (yee-prefix-file (yee-del-src (yee-del-substr file-dir proj-root))))))

这样在为 once 定制生成的新代码变得简洁如下:

# -*- mode: snippet -*-
# name: #ifndef-google
# key: once
# --
#ifndef ${1:`(google-cpp-header-guard)`_H}
#define $1

$0

#endif  // $1

End

如果想查看 YASnippet 事先定制了哪些 key,在 spacemacs 上可以通过 helm-yas-visit-snippet-file 函数检索。

因为 YASnippet 可以使用 EmacsLisp 来拓展功能,应对代码生成的任务变得轻而易举。其并不局限于代码生成,你可以为不同的文件格式定制不同的 Snippet,比如 MarkDown 等等。

告别重复,节约时间。

13 December 2017

blog comments powered by Disqus