有些插件還是要傳播一下的
很多VI黨經(jīng)常吐槽Emacs按鍵難按她按, 特別是保存一個(gè)文件還要?jiǎng)e扭的按 Ctrl + x Ctrl + s, 其實(shí)這個(gè)世界上很多Emacs高手一般看到這些誤解都是不吭聲的火本, 本來(lái)我也是不想吭聲的, 直到我去年上海參加 Gopher 2015 大會(huì)夜談會(huì)斧吐, 看到一波Eclipse高手和編輯器高手介紹自己寫(xiě)了一個(gè)多么方便的保存插件, 怎么點(diǎn)一下就很方便可以保存文件悍抑, 我當(dāng)時(shí)深深的忍住了罩阵, 但是我居然看到會(huì)上一個(gè) Google 工程師, 說(shuō)自己寫(xiě)了一個(gè) VI 插件序臂, 怎么怎么按什么快捷鍵就可以快速保存文件了。
對(duì)于我這種資深Google粉來(lái)說(shuō), 實(shí)在看不慣 Google 工程師這么low奥秆, 當(dāng)時(shí)就在臺(tái)上跟他們演示了什么叫做自動(dòng)保存.....
Emacs 的插件哲學(xué)
因?yàn)镋macs發(fā)展30多年到今天逊彭, 其實(shí)已經(jīng)不是一個(gè)簡(jiǎn)簡(jiǎn)單單編輯器了, Emacs是一個(gè)自給自足的操作系統(tǒng)构订, 就像我在《Emacs是什么》里面講的一樣侮叮, Emacs強(qiáng)調(diào)的是工具協(xié)調(diào)的生態(tài)。 Emacs的插件強(qiáng)調(diào)的不是按鍵要多么簡(jiǎn)潔悼瘾, 它強(qiáng)調(diào)的時(shí)候研究開(kāi)發(fā)者心理囊榜, 開(kāi)發(fā)者在編程場(chǎng)景中遇到最大的痛點(diǎn)是什么? 它強(qiáng)調(diào)的是開(kāi)發(fā)者不需要做任何按鍵就能自動(dòng)在后臺(tái)做好亥宿,最大化減少插件和開(kāi)發(fā)者之間的交互過(guò)程卸勺, 讓開(kāi)發(fā)者把所有時(shí)間都放在實(shí)際代碼和深度思考上。
所以很多Emacs插件的優(yōu)秀標(biāo)準(zhǔn)就是“潤(rùn)物細(xì)無(wú)聲”烫扼, 插件的存在感越少越好曙求, 最好什么按鍵都不要按, 插件作者已經(jīng)總結(jié)出開(kāi)發(fā)者在編程中思考的必經(jīng)之路映企, 一切都是順其自然的完成的悟狱。
自動(dòng)保存文件
說(shuō)到編輯器保存這個(gè)功能, 我最開(kāi)始學(xué)Emacs的時(shí)候按了一個(gè)月的 Ctrl + x Ctrl + s 就受不了了堰氓, 最讓人受不了的時(shí)候有時(shí)候辛辛苦苦寫(xiě)的代碼挤渐, 忘記按保存了, 這時(shí)候突然停電了双絮, 除了 WTF 就沒(méi)有任何然后了浴麻。那時(shí)候我就想為什么一定要手動(dòng)按 Ctrl + x Ctrl + s按鍵來(lái)保存呢?能否自動(dòng)保存所編輯的文件掷邦? 什么時(shí)候最合適呢白胀?
其實(shí)每個(gè)程序員編寫(xiě)程序的時(shí)候, 寫(xiě)著寫(xiě)著思維不是那么流暢了抚岗, 需要停下來(lái)稍微思考一下或杠, 其實(shí)這時(shí)候就是自動(dòng)保存的最佳時(shí)刻: 利用程序員猶豫下一個(gè)字符應(yīng)該敲什么的空隙時(shí)間就足夠保存一個(gè)文件了。
所以那時(shí)候就寫(xiě)了一個(gè) auto-save.el 來(lái)做這件事情宣蔚, 每當(dāng)Emacs發(fā)現(xiàn)你停止敲鍵盤超過(guò)1s鐘以后向抢, 它就會(huì)把所有沒(méi)有保存的文件全部保存一遍。
所以自從那時(shí)候我基本十年沒(méi)有在手動(dòng)保存過(guò)任何文件了胚委, 也從來(lái)沒(méi)有丟失過(guò)任何一行寫(xiě)的代碼挟鸠。 配置很簡(jiǎn)單, 下載 auto-save.el亩冬, 然后在 ~/.emacs 里面加上下面的代碼:
(require 'auto-save) ;; 加載自動(dòng)保存模塊
(auto-save-enable) ;; 開(kāi)啟自動(dòng)保存功能
(setq auto-save-slient t) ;; 自動(dòng)保存的時(shí)候靜悄悄的艘希, 不要打擾我
懶人也可以從 init-auto-save.el 下載寫(xiě)好的配置文件硼身, 然后只在 ~/.emacs 寫(xiě)上下面配置文件就可以了:
(require 'init-auto-save)
到這里, 以后用Emacs就不用傻傻的按保存鍵了覆享, Emacs發(fā)現(xiàn)你手指頭沒(méi)有敲鍵盤的時(shí)候自動(dòng)保存的佳遂,沒(méi)事不會(huì)和你搶CPU的。 ;)
auto-save.el 源碼解析
對(duì)Elisp感興趣的同學(xué)可以繼續(xù)往下看 auto-save.el 的源碼解析:
;; defgroup 關(guān)鍵字的意思是定義一個(gè)工作組撒顿,執(zhí)行 Alt + x customize-group 命令的時(shí)候可以進(jìn)行圖形化的模塊配置
;; 第一個(gè)參數(shù)是模塊的名字丑罪, 比如 auto-save
;; 第二個(gè)參數(shù)是模塊默認(rèn)開(kāi)啟的狀態(tài), 在 elisp 中凤壁, t 表示 true, nil 表示 false
;; 第三個(gè)參數(shù)是對(duì)模塊的文本解釋
;; 第四個(gè)參數(shù)表示對(duì)外提供 auto-save 這個(gè) group
(defgroup auto-save nil
"Auto save file when emacs idle."
:group 'auto-save)
;; defcustom 關(guān)鍵字的意思是定義一個(gè)可以被用戶自定義的變量吩屹, 當(dāng)用戶執(zhí)行 Alt + x customize-variable 的時(shí)候就可以補(bǔ)全 auto-save-idle 這個(gè)變量, defcustom 和 defvar 的區(qū)別主要是 defcustom 用于提供一些參數(shù)讓用戶可以在 Emacs 中圖形化定制變量?jī)?nèi)容拧抖, defvar 這只有變量名和 List 內(nèi)容煤搜, 一般用于函數(shù)內(nèi)部變量值存儲(chǔ)用, 不對(duì)外拋出給用戶定制
;; 第一個(gè)參數(shù)是變量的名字 autos-ave-idle
;; 第二個(gè)參數(shù)是變量的值徙鱼, 這里我們定義為 1s, 表示自動(dòng)保存的延遲秒數(shù)
;; 第三個(gè)參數(shù)是變量的解釋宅楞, 一般在 Alt + x describe-variable 的時(shí)候就會(huì)顯示具體變量的文檔描述
;; 第四個(gè)參數(shù)用于定義變量的類型, 這里定義為整形袱吆, 這樣在 customize-group 的時(shí)候只有輸入整型才是正確保存
;; 第五個(gè)參數(shù)表示這個(gè)變量屬于 auto-save 這個(gè)組厌衙, 主要作用就是 customize-group 的時(shí)候能夠在一個(gè)界面中設(shè)置同一組的所有變量
(defcustom auto-save-idle 1
"The idle seconds to auto save file."
:type 'integer
:group 'auto-save)
;; autos-save-slient 的作用就是一個(gè)boolean值得變量, 設(shè)置為 nil 的時(shí)候绞绒, 表示每次自動(dòng)保存都會(huì)在 minibuffer 提示婶希, 設(shè)置成 t 的時(shí)候就會(huì) shutup, 讓我安安靜靜寫(xiě)會(huì)代碼, 別鬧...
(defcustom auto-save-slient nil
"Nothing to dirty minibuffer if this option is non-nil."
:type 'boolean
:group 'auto-save)
;; 這段代碼的作用就是避免 Emacs 在保存文件的時(shí)候生成一大堆垃圾的 #foo# 文件蓬衡, 這種文件最討厭了喻杈, 不但什么用都沒(méi)有, 反而污染代碼目錄狰晚, 刪除都刪的我手酸
;; 想當(dāng)年為了找到關(guān)閉這個(gè)腦殘功能的變量筒饰, 我把 emacs 幾百個(gè)帶有 save 的變量全部打出來(lái), 一個(gè)一個(gè)變量的試才找到你啊 (可惜當(dāng)年我英文不好壁晒, 不知道怎么描述我想要的效果)
(setq auto-save-default nil)
;; 前方高能核心代碼瓷们, 請(qǐng)集中注意力
(defun auto-save-buffers ()
;; 所有你在 Alt + x 以后可以調(diào)用的函數(shù)都要手動(dòng)加上 (interactive) , 否則這段代碼只能在 Elisp 解釋器中執(zhí)行秒咐, 但是不能直接被用戶從 Alt + x 調(diào)用谬晕, 就想 interactive 這個(gè)單詞的意思一樣
(interactive)
;; 創(chuàng)建 autosave-buffer-list 這個(gè)變量, 用于保存所有需要遍歷的 buffer 列表
(let ((autosave-buffer-list))
;; save-excursion 這個(gè)關(guān)鍵字的意思是携取, 所有在 save-excursion 里面的代碼不管怎么折騰都不會(huì)對(duì) save-excursion 之前的Emacs狀態(tài)進(jìn)行任何改變攒钳, 你可以理解為這個(gè)關(guān)鍵字的意思就是用于保護(hù)現(xiàn)場(chǎng)用的 ;)
(save-excursion
;; dolist 的作用就和很多語(yǔ)言的 foreach 一個(gè)意思, 把 buffer-list 這個(gè)函數(shù)返回的所有 buffer 在循環(huán)內(nèi)賦值給 buf 這個(gè)變量雷滋, 并在 dolist 的作用域中執(zhí)行對(duì) buf 影響的代碼
(dolist (buf (buffer-list))
;; 設(shè)置當(dāng)前代碼的 buffer 為 buf 變量值不撑, 如果沒(méi)有前面 save-excursion, 你會(huì)發(fā)現(xiàn)emacs會(huì)一直在快速的切換所有 buffer 的過(guò)程
(set-buffer buf)
;; 如果當(dāng)前 buffer 有一個(gè)相關(guān)聯(lián)文件 (buffer-file-name), 同時(shí)當(dāng)前 buffer 已經(jīng)被用戶修改了 (buffer-modified-p) 的情況下就執(zhí)行自動(dòng)保存
(if (and (buffer-file-name) (buffer-modified-p))
(progn
;; 把當(dāng)前 buffer 的名字壓進(jìn) autosave-buffer-list 列表文兢, 用于后面的保存提示
(push (buffer-name) autosave-buffer-list)
(if auto-save-slient
;; 如果 auto-save-slient 這個(gè)變量為 true, 就不顯示任何保存信息, 因?yàn)?Emacs 的保存函數(shù) (basic-save-buffer) 本身機(jī)會(huì) blabla 的告訴你文件已經(jīng)保存了燎孟, 所以我們用 with-temp-message 配合空字符串來(lái)禁止 with-temp-message 里面的代碼在 minibuffer 顯示任何內(nèi)容
(with-temp-message ""
(basic-save-buffer))
(basic-save-buffer))
)))
;; unless 的意思是除非 auto-save-slient 為 false 就執(zhí)行
(unless auto-save-slient
;; cond 就是 elisp 版的 switch禽作, 用于條件語(yǔ)句對(duì)比執(zhí)行
(cond
;; 如果 autosave-buffer-list 列表里面沒(méi)有任何一個(gè)文件需要保存, 我們就不要去煩用戶了揩页, 默默打醬油路過(guò)就好了
;; 如果有一個(gè)文件需要保存, 我們就說(shuō) Saved ...
((= (length autosave-buffer-list) 1)
(message "# Saved %s" (car autosave-buffer-list)))
;; 如果有多個(gè)文件需要保存烹俗, 就說(shuō) Saved ... files
((> (length autosave-buffer-list) 1)
(message "# Saved %d files: %s"
(length autosave-buffer-list)
(mapconcat 'identity autosave-buffer-list ", ")))))
)))
(defun auto-save-enable ()
(interactive)
;; run-with-idle-timer 函數(shù)的意思就是在 auto-save-idle 定義的描述以后自動(dòng)執(zhí)行 auto-save-buffers 函數(shù)
;; #' 的意思就是在 runtime 執(zhí)行的時(shí)候再展開(kāi) auto-save-buffers 函數(shù)
(run-with-idle-timer auto-save-idle t #'auto-save-buffers)
)
(provide 'auto-save)
最后
Enjoy, have fun! ;)