經(jīng)驗(yàn)總結(jié):實(shí)用的Emacs配置文件搜羅
以下的Emacs配置文件是我多年積累起來(lái)的,它們?cè)谖覛v次整理配置文件(.emacs)的過(guò)程中幸存了下來(lái),經(jīng)過(guò)了時(shí)間考驗(yàn),所以現(xiàn)在我決定發(fā)出 來(lái)和大家分享一下。雖然某些功能很有可能已經(jīng)有更好的實(shí)現(xiàn)方法了,但是這些例子對(duì)讀者學(xué)習(xí)emacs lisp還是會(huì)有幫助的。
在一些文本的末尾添加遞增的數(shù)字
inc-num-region把一段文本中重復(fù)出現(xiàn)的數(shù)字替換成遞增的數(shù)字
- (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選中如下的文本區(qū)域
- 1foo
- 1foo
- 1foo
- 1foo
執(zhí)行該函數(shù),那么上述文本在緩沖區(qū)中變成
- 1foo
- 2foo
- 3foo
- 4foo
再比如選中如下的文本區(qū)域
- foo3
- foo3
- foo3
- foo3
執(zhí)行給函數(shù),得到
- foo3
- foo4
- foo5
- foo6
給代碼做筆記
在我們公司使用reviewboard之前,代碼審查都是面對(duì)面進(jìn)行的。我曾經(jīng)使用下面這個(gè)函數(shù)來(lái)幫助記錄意見(jiàn)所對(duì)應(yīng)的源文件和行號(hào)。
- 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) ": ")))
使用方法是,光標(biāo)停在源代碼的需要做批注的位置,然后執(zhí)行該函數(shù),emacs會(huì)創(chuàng)建一個(gè)新的叫做NOTES的緩沖區(qū),其中記錄源代碼的路徑和光標(biāo)所在的行 號(hào),用戶在接下來(lái)的區(qū)域中輸入筆記。這個(gè)函數(shù)的好處是,該新建的buffer的工作模式是compilation-shell-minor-mode。所 以可以直接點(diǎn)擊其路徑和行號(hào),就可以直接打源文件跳到相應(yīng)的行上去。比如
- #include
- int main()
- {
- std::cout << "Hello Word!" << std::endl; //光標(biāo)停在這里
- return 0;
- }
執(zhí)行該函數(shù),在新buffer中得到如下內(nèi)容,在compilation-shell-minor-mode模式下,筆記前面的內(nèi)容將呈現(xiàn)出一個(gè)鏈接,可以點(diǎn)擊直接打開(kāi)main.cpp
- /home/iamxuxiao/main.cpp:5: miss spelling "word"
在我的.emacs中,我把這個(gè)函數(shù)和C-c、r做了綁定
自動(dòng)給C代碼頭文件的首位添加ifndef和endif
get-include-guard函數(shù)在我們要編輯一個(gè)新頭文件時(shí),自動(dòng)給文件添加上預(yù)處理指示符: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))))))
如果我們?cè)趀macs中要新建一個(gè)文件foo.h(C-x,C-f foo.h),emacs新創(chuàng)建的foo.h緩沖區(qū)中看上去將是這樣的
- #ifndef FOO_H_
- #define FOO_H_
- #endif
在foo.cpp和foo.h之間自動(dòng)的切換
如果一個(gè)文件夾中同時(shí)含有foo.h和foo.cpp兩個(gè)文件的話,下面的函數(shù)幫助你在這兩個(gè)文件之間切換
- (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中,我把這個(gè)函數(shù)和C-c,n做了綁定
注:Reddit網(wǎng)友提出ff-find-other-file實(shí)現(xiàn)了非常類似的功能
c-macro模板
我們?cè)趯?xiě)C++代碼的時(shí)候,經(jīng)常要鍵入一些重復(fù)的操作,比如歷遍容器,try catch等等。而這些代碼的特點(diǎn),可以歸結(jié)成一個(gè)不變的模板+幾個(gè)變化參數(shù),下面的emacs函數(shù)自動(dòng)幫你擴(kuò)展這個(gè)模板,打印代碼。
我們先描述該函數(shù)的效果,在C++代碼中插入如下待擴(kuò)展的句子
- (doit std::vector myContainer)
然后在該行的末尾執(zhí)行我們的函數(shù),該行被自動(dòng)替換成如下的C++代碼
- for (std::vector::iterator it = myContainer.begin();
- it != myContainer.end();
- ++it)
- {
- // 光標(biāo)將停在這里 等待具體的編輯
- }
該c-macro還可以接受變長(zhǎng)參數(shù),比如下面的模板接受兩個(gè)參數(shù)
- (doit std::vector myIt myContainer)
生成的代碼如下:
- for (std::vector::iterator myIt = myContainer.begin();
- myIt != myContainer.end();
- ++myIt)
- {
- // 光標(biāo)將停在這里 等待具體的編輯
- }
下面的macro將幫助用戶自己打印try catch block
- (api-fn)
擴(kuò)展之后將變成
- try
- {
- // 光標(biāo)將停在這里 等待具體的編輯
- }
- catch(const std::exception& e)
- {
- TRACE("Unhandled exception in function %s: %s\n",
- __func__, e.what());
- return -1;
- }
下面的j-newline-and-indent是以上功能的入口函數(shù),其將尋找光標(biāo)前是否出現(xiàn)已定義的c-macro.在上面的例子中就是doit和api-fn。
如果出現(xiàn)了macro就做擴(kuò)展,如果沒(méi)有出現(xiàn),j-newline-and-indent等于內(nèi)置的newline-and-indent函數(shù):加入新行,并且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))
- 下面是自定義的兩個(gè)模板c-macro,讀者可以根據(jù)需要定義自己的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"))