经验总结:实用的Emacs配置文件搜罗
以下的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