【感謝 iamxuxiao 的熱心翻譯。如果其他朋友也有不錯的原創或譯文,可以嘗試提交到伯樂線上。】
以下的Emacs配置檔案是我多年積累起來的,它們在我歷次整理配置檔案(.emacs)的過程中倖存了下來,經過了時間考驗,所以現在我決定發出來和大家分享一下。雖然某些功能很有可能已經有更好的實現方法了,但是這些例子對讀者學習emacs lisp還是會有幫助的。
在一些文字的末尾新增遞增的數字
inc-num-region把一段文字中重複出現的數字替換成遞增的數字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(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選中如下的文字區域
1 2 3 4 |
1foo 1foo 1foo 1foo |
執行該函式,那麼上述文字在緩衝區中變成
1 2 3 4 |
1foo 2foo 3foo 4foo |
再比如選中如下的文字區域
1 2 3 4 |
foo3 foo3 foo3 foo3 |
執行給函式,得到
1 2 3 4 |
foo3 foo4 foo5 foo6 |
給程式碼做筆記
在我們公司使用reviewboard之前,程式碼審查都是面對面進行的。我曾經使用下面這個函式來幫助記錄意見所對應的原始檔和行號。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(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。所以可以直接點選其路徑和行號,就可以直接打原始檔跳到相應的行上去。比如
1 2 3 4 5 6 7 |
#include int main() { std::cout << "Hello Word!" << std::endl; //游標停在這裡 return 0; } |
執行該函式,在新buffer中得到如下內容,在compilation-shell-minor-mode模式下,筆記前面的內容將呈現出一個連結,可以點選直接開啟main.cpp
1 |
/home/iamxuxiao/main.cpp:5: miss spelling "word" |
在我的.emacs中,我把這個函式和C-c、r做了繫結
自動給C程式碼標頭檔案的首位新增ifndef和endif
get-include-guard函式在我們要編輯一個新標頭檔案時,自動給檔案新增上預處理指示符:ifndef和endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
(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緩衝區中看上去將是這樣的
1 2 3 4 |
#ifndef FOO_H_ #define FOO_H_ #endif |
在foo.cpp和foo.h之間自動的切換
如果一個資料夾中同時含有foo.h和foo.cpp兩個檔案的話,下面的函式幫助你在這兩個檔案之間切換
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 30 31 32 |
(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++程式碼中插入如下待擴充套件的句子
1 |
(doit std::vector myContainer) |
然後在該行的末尾執行我們的函式,該行被自動替換成如下的C++程式碼
1 2 3 4 5 6 |
for (std::vector::iterator it = myContainer.begin(); it != myContainer.end(); ++it) { // 游標將停在這裡 等待具體的編輯 } |
該c-macro還可以接受變長引數,比如下面的模板接受兩個引數
1 |
(doit std::vector myIt myContainer) |
生成的程式碼如下:
1 2 3 4 5 6 |
for (std::vector::iterator myIt = myContainer.begin(); myIt != myContainer.end(); ++myIt) { // 游標將停在這裡 等待具體的編輯 } |
下面的macro將幫助使用者自己列印try catch block
1 |
(api-fn) |
擴充套件之後將變成
1 2 3 4 5 6 7 8 9 10 |
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
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 30 31 32 33 34 35 36 37 38 39 40 |
(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")) |