一直以來都想寫一篇關於我當前 Emacs 配置的文章,來描述我是如何在 Mac 和 Linux 上使用 Emacs 的,即我的 Emacs 工作流。我使用這套配置一年多了,一直以來這套配置都工作的很好,幾乎不需要怎麼調整。我現在用的是 Emacs24,但是這套配置對更早些版本的Emacs也適用。
首先,我需要解釋一下我是怎麼工作的,以及我希望我的Emacs工作流能達到什麼效果. 我大多數時候用的都是命令列,隨機在終端或者Emacs的eshell中執行. 當然長時間的tail日誌或者ssh操作可能導致Emacs崩潰因此這類操作我一般不在eshell中進行. 我之前用過很長一段時間點額vim,我習慣於快速開啟一個檔案,做一些修改,然後再關閉這個檔案. 但是我也確實發現在Emacs中同時開啟多個檔案也很有用. Emacs daemon似乎能夠滿足這兩種模式,但是我不喜歡在我登入系統或啟動Emacs時自動啟動該功能.
Avdi Grimm 曾經寫過 一篇文章關於他是如何執行Emacs的,這篇文章給我以啟示。在這之前我不太常用emacsclient. 但隨後我發現它的 -a
引數能自動啟動daemon,正是我所需要的. Avdi就是使用該指令碼來啟動emacsclient並建立新frame的. 預設情況下,終端會掛起並等待你關閉Emacs,但是你可以給 ec
指令碼(指令碼內容在後面)傳遞 -n
引數,這會使得控制權立刻回到終端.
My ‘Emacs Workflow’
我的工作流有點與眾不同. 當在圖形化的Emacs中開啟一個檔案的時候,我會編輯這個檔案但不會去關閉它. 然後我經常會再回到終端去執行命令處理那個新編輯過的檔案(rake test或mvn packge). 這意味著終端執行emacsclient後不能一直在那等待emacsclient執行完畢. 若已經有一個圖形化的emacs在執行,我希望能在這個emacs中編輯檔案而不是重新開啟一個新frame. 若有一個圖形化的Emacs在執行但是處於最小化狀態,則我希望最大化這個emacs然後用它開啟檔案.
對於一下快速編輯的情況,我希望在當前終端用Emacs快速開啟檔案,做出修改,然後關閉該檔案. 這時終端應該等待直到我完成修改.
Sidebar
我在學會magit前都是使用這種快速編輯的方法來提交git commit的. 若你沒有用過magit,我強烈推薦你花點時間學習它. 參加 http://magit.github.io/magit/magit.html. 這也是為什麼我在
~/.bashrc
中新增了export editor=et
的緣故了.
Tools
最終,我寫了兩個指令碼: ec
和 et
. 前一個指令碼會在圖形化Emacs中開啟emacsclient然後將控制權立即交回shell. 後一個會在當前終端開啟emacs並等待我完成編輯. 由於兩個指令碼都是連線的同一個daemon,因此所有開啟的檔案對兩者都是可見的. 兩個指令碼都會在daemon未啟動的情況下自動啟動daemon. ec
指令碼還有一些額外的程式碼用來實現我工作流中所描述的那樣將焦點切換到emacs上. 下面是指令碼的內容,我已經加上了註釋了.
ec
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 41 42 43 44 45 46 47 |
#!/bin/bash # This script starts emacs daemon if it is not running, opens whatever file # you pass in and changes the focus to emacs. Without any arguments, it just # opens the current buffer or *scratch* if nothing else is open. The following # example will open ~/.bashrc # ec ~/.bashrc # You can also pass it multiple files, it will open them all. Unbury-buffer # will cycle through those files in order # The compliment to the script is et, which opens emacs in the terminal # attached to a daemon # If you want to execute elisp, pass in -e whatever. # You may also want to stop the output from returning to the terminal, like # ec -e "(message \"Hello\")" > /dev/null # emacsclient options for reference # -a "" starts emacs daemon and reattaches # -c creates a new frame # -n returns control back to the terminal # -e eval the script # Number of current visible frames, # Emacs daemon always has a visible frame called F1 visible_frames() { emacsclient -a "" -e '(length (visible-frame-list))' } change_focus() { emacsclient -n -e "(select-frame-set-input-focus (selected-frame))" > /dev/null } # try switching to the frame incase it is just minimized # will start a server if not running test "$(visible_frames)" -eq "1" && change_focus if [ "$(visible_frames)" -lt "2" ]; then # need to create a frame # -c $@ with no args just opens the scratch buffer emacsclient -n -c "$@" && change_focus else # there is already a visible frame besides the daemon, so change_focus # -n $@ errors if there are no args test "$#" -ne "0" && emacsclient -n "$@" fi |
et
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/bin/bash # Makes sure emacs daemon is running and opens the file in Emacs in # the terminal. # If you want to execute elisp, use -e whatever, like so # et -e "(message \"Word up\")" # You may want to redirect that to /dev/null if you don't want the # return to printed on the terminal. Also, just echoing a message # may not be visible if Emacs then gives you a message about what # to do when do with the frame # The compliment to this script is ec # Emacsclient option reference # -a "" starts emacs daemon and reattaches # -t starts in terminal, since I won't be using the gui # can also pass in -n if you want to have the shell return right away exec emacsclient -a "" -t "$@" |
Github repo
可以在https://github.com/mjwall/dotfiles 中找到這兩個指令碼以及在Mac和Linux下安裝Emacs的方法. 該倉庫存放的其實是我的 ~/.emac.d
配置. 我把所有的東西都放在一起,這樣便於在新機器上重建配置,也便於在多型機器之間同步配置.
Warning
若你用的是Mac,請務必保證新版本的emacs和emacsclient被放在了正確的路徑中. 可以去gist 上看看我是怎麼做的. 當然肯定還有其他更好的實現方式吧.
Bonus, executing elisp
我使用這兩個指令碼的另一種方式是使用 -e
選項來執行elisp程式碼. 例如,我就在我的bashrc中為magit設定了一個alias. 由於用到了這兩個指令碼因此它也能自動啟動daemon並自動捕獲焦點. 下面是定義alias的方法
1 |
alias magit='ec -e "(magit-status \"$(pwd)\")"' |
這樣在終端中執行magit就會呼叫Emacs並在當前目錄下執行magit-status了. This was inspired by a similiar tweet somewhere, but takes advantage of the rest of the ec script.
Stopping the Daemon
最後還有一個指令碼用於關閉daemon,這個指令碼常用於重新載入emacs配置. 有時我的Mac在關機時會掛起等待Emacs退出,因此我一般都是先手工執行該指令碼關閉Emacs. 指令碼內容如下:
es
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/bin/bash # simple script to shutdown the running Emacs daemon # emacsclient options for reference # -a Alternate editor, runs bin/false in this case # -e eval the script # If the server-process is bound and the server is in a good state, then kill # the server server_ok() { emacsclient -a "false" -e "(boundp 'server-process)" } if [ "t" == "$(server_ok)" ]; then echo "Shutting down Emacs server" # wasn't removing emacs from ALT-TAB on mac # emacsclient -e "(server-force-delete)" emacsclient -e '(kill-emacs)' else echo "Emacs server not running" fi |
似乎有很好的方法能解決Mac關機掛起的問題,但這個問題對我來說不是什麼大問題,因此我沒有深入這個問題.
Wrap up
若你讀到了這裡,你可能會想”在shell執行elisp程式碼真不錯啊”。如果你有這個想法,可以看看 https://github.com/mjwall/dotfiles/blob/master/bin/ed.el, 你會看到下面這樣的實現方式
1 2 3 4 |
#!/usr/bin/env emacs --script (print "Hi mike") (require 'server) (print (server-running-p)) |
想象一下吧. 藉助這種能力,你可以遍歷一個git倉庫,將其原始碼中的所有tab都替換成空格. 這樣做可能沒什麼意義,但是想一想也覺得蠻有意思的。
如果你沒有讀到這裡,可能是因為你覺得這樣搞太小題大做了。嗯,沒準你是對的。