以下的Emacs配置文件是我多年积累起来的,它们在我历次整理配置文件(.emacs)的过程中幸存了下来,经过了时间考验,所以现在我决定发出 来和大家分享一下。虽然某些功能很有可能已经有更好的实现方法了,但是这些例子对读者学习emacs lisp还是会有帮助的。
在一些文本的末尾添加递增的数字
inc-num-region把一段文本中重复出现的数字替换成递增的数字
- (defun inc-num-region (p m)
- "Increments the numbers in a given region"
- (interactive "r")
- (save-restriction
- (save-excursion
- (narrow-to-region p m)
- (goto-char (point-min))
- (forward-line)
- (let ((counter 1))
- (while (not (eq (point)
- (point-max)))
- (goto-char (point-at-eol))
- (search-backward-regexp "[0-9]+" (point-at-bol) t)
- (let* ((this-num (string-to-number (match-string 0)))
- (new-num-str (number-to-string (+ this-num
- counter))))
- (replace-match new-num-str)
- (incf counter)
- (forward-line)))))))
比如在emacs选中如下的文本区域
- 1foo
- 1foo
- 1foo
- 1foo
执行该函数,那么上述文本在缓冲区中变成
- 1foo
- 2foo
- 3foo
- 4foo
再比如选中如下的文本区域
- foo3
- foo3
- foo3
- foo3
执行给函数,得到
- foo3
- foo4
- foo5
- foo6
给代码做笔记
在我们公司使用reviewboard之前,代码审查都是面对面进行的。我曾经使用下面这个函数来帮助记录意见所对应的源文件和行号。
- defun add-code-review-note ()
- "Add note for current file and line number"
- (interactive)
- (let ((file-name (buffer-file-name))
- (file-line (line-number-at-pos)))
- (switch-to-buffer-other-window (get-buffer-create "NOTES"))
- (goto-char (point-min))
- (when (not (search-forward "-*- mode:compilation-shell-minor"
- nil t))
- (compilation-shell-minor-mode 1)
- (insert "-*- mode:compilation-shell-minor -*-\n\n"))
- (goto-char (point-max))
- (if (/= (current-column) 0)
- (newline))
- (insert file-name ":" (number-to-string file-line) ": ")))
使用方法是,光标停在源代码的需要做批注的位置,然后执行该函数,emacs会创建一个新的叫做NOTES的缓冲区,其中记录源代码的路径和光标所在的行 号,用户在接下来的区域中输入笔记。这个函数的好处是,该新建的buffer的工作模式是compilation-shell-minor-mode。所 以可以直接点击其路径和行号,就可以直接打源文件跳到相应的行上去。比如
- #include
- int main()
- {
- std::cout << "Hello Word!" << std::endl; //光标停在这里
- return 0;
- }
执行该函数,在新buffer中得到如下内容,在compilation-shell-minor-mode模式下,笔记前面的内容将呈现出一个链接,可以点击直接打开main.cpp
- /home/iamxuxiao/main.cpp:5: miss spelling "word"
在我的.emacs中,我把这个函数和C-c、r做了绑定
自动给C代码头文件的首位添加ifndef和endif
get-include-guard函数在我们要编辑一个新头文件时,自动给文件添加上预处理指示符:ifndef和endif
- defun get-include-guard ()
- "Return a string suitable for use in a C/C++ include guard"
- (let* ((fname (buffer-file-name (current-buffer)))
- (fbasename (replace-regexp-in-string ".*/" "" fname))
- (inc-guard-base (replace-regexp-in-string "[.-]"
- "_"
- fbasename)))
- (concat (upcase inc-guard-base) "_")))
- (add-hook 'find-file-not-found-hooks
- '(lambda ()
- (let ((file-name (buffer-file-name (current-buffer))))
- (when (string= ".h" (substring file-name -2))
- (let ((include-guard (get-include-guard)))
- (insert "#ifndef " include-guard)
- (newline)
- (insert "#define " include-guard)
- (newline 4)
- (insert "#endif")
- (newline)
- (previous-line 3)
- (set-buffer-modified-p nil))))))
如果我们在emacs中要新建一个文件foo.h(C-x,C-f foo.h),emacs新创建的foo.h缓冲区中看上去将是这样的
- #ifndef FOO_H_
- #define FOO_H_
- #endif
在foo.cpp和foo.h之间自动的切换
如果一个文件夹中同时含有foo.h和foo.cpp两个文件的话,下面的函数帮助你在这两个文件之间切换
- (defun next-file-with-basename ()
- "Cycles between files with the same basename as the given file.
- Usefull for cycling between header .h/.cpp/.hpp files etc."
- (interactive)
- (let* ((buf-file-name (replace-regexp-in-string
- "^.*/" ""
- (buffer-file-name)))
- (current-dir (replace-regexp-in-string
- "[a-zA-Z0-9._-]+$" ""
- (buffer-file-name)))
- (no-basename (equal ?. (aref buf-file-name 0)))
- (has-extension (find ?. buf-file-name)))
- ;; If the file is a .dot-file or it doesn't have an
- ;; extension, then there's nothing to do here.
- (unless (or no-basename (not has-extension))
- (let* ((basename (replace-regexp-in-string
- "\\..*" ""
- buf-file-name))
- (files-with-basename (directory-files
- current-dir f
- (concat "^" basename "\\."))))
- ;; If there's only 1 file with this basename, nothing to
- ;; do
- (unless (= (length files-with-basename) 1)
- ;; By making the list circular, we're guaranteed that
- ;; there will always be a next list element (ie. no
- ;; need for special case when file is at the end of
- ;; the list).
- (setf (cdr (last files-with-basename))
- files-with-basename)
- (find-file (cadr (member (buffer-file-name)
- files-with-basename))))))))
在我的.emacs中,我把这个函数和C-c,n做了绑定
注:Reddit网友提出ff-find-other-file实现了非常类似的功能
c-macro模板
我们在写C++代码的时候,经常要键入一些重复的操作,比如历遍容器,try catch等等。而这些代码的特点,可以归结成一个不变的模板+几个变化参数,下面的emacs函数自动帮你扩展这个模板,打印代码。
我们先描述该函数的效果,在C++代码中插入如下待扩展的句子
- (doit std::vector myContainer)
然后在该行的末尾执行我们的函数,该行被自动替换成如下的C++代码
- for (std::vector::iterator it = myContainer.begin();
- it != myContainer.end();
- ++it)
- {
- // 光标将停在这里 等待具体的编辑
- }
该c-macro还可以接受变长参数,比如下面的模板接受两个参数
- (doit std::vector myIt myContainer)
生成的代码如下:
- for (std::vector::iterator myIt = myContainer.begin();
- myIt != myContainer.end();
- ++myIt)
- {
- // 光标将停在这里 等待具体的编辑
- }
下面的macro将帮助用户自己打印try catch block
- (api-fn)
扩展之后将变成
- try
- {
- // 光标将停在这里 等待具体的编辑
- }
- catch(const std::exception& e)
- {
- TRACE("Unhandled exception in function %s: %s\n",
- __func__, e.what());
- return -1;
- }
下面的j-newline-and-indent是以上功能的入口函数,其将寻找光标前是否出现已定义的c-macro.在上面的例子中就是doit和api-fn。
如果出现了macro就做扩展,如果没有出现,j-newline-and-indent等于内置的newline-and-indent函数:加入新行,并且indent
- (defun j-newline-and-indent ()
- "Same as \"newline-and-indent\" except it also expands
- c-macros if it sees one."
- (interactive)
- (if (and (equal (char-before) ?\))
- (macro-function (car (preceding-sexp))))
- ;; This is a c-macro
- (expand-c-macro-in-place)
- (newline-and-indent)))
- (defun macro-function (name)
- "Given a name, returns the c-macro-name symbol if it
- exists as a function"
- (let ((macro-sym (intern (concat "c-macro-"
- (symbol-name name)))))
- (if (fboundp macro-sym)
- macro-sym
- nil)))
- (defun expand-c-macro-in-place ()
- "Given that point is at the end of a c-macro, expands
- it in-place"
- (let* ((sexp (preceding-sexp))
- (macro-name (car sexp))
- (replacement-text (apply (macro-function macro-name)
- (cdr sexp)))
- (jump-to (string-match "!!!BODY!!!;" replacement-text)))
- ;; Delete macro invocation
- (backward-list)
- (let ((start-del (point)))
- (forward-list)
- (kill-region start-del (point))
- ;; Insert macro expansion and indent appropriately
- (insert replacement-text)
- (indent-region start-del (point))
- (when jump-to
- (search-backward "!!!BODY!!!;")
- (kill-line))))
- (c-indent-command))
- 下面是自定义的两个模板c-macro,读者可以根据需要定义自己的macro
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- (defun c-macro-doit (container-type arg1 &optional arg2)
- "Emits code for iterating over an stl (or stl-like) structure"
- (let ((iterator-name (if arg2 arg1 "it"))
- (container-name (if arg2 arg2 arg1)))
- (format (concat "for (%s::iterator %s = %s.begin();\n"
- " %s != %s.end();\n"
- " ++%s)\n"
- "{\n"
- " !!!BODY!!!;\n"
- "}\n")
- container-type
- iterator-name
- container-name
- iterator-name
- container-name
- iterator-name)))
- (defun c-macro-api-fn ()
- "Emits code for wrapping an api function in a try/catch block"
- (concat "try\n"
- "{\n"
- " !!!BODY!!!;\n"
- "}\n"
- "catch(const std::exception& e)\n"
- "{\n"
- " TRACE(\"Unhandled exception in function %s: %s\\n\",\n"
- " __func__, e.what());\n"
- " return -1;\n"
- "}\n"))