前一篇《Emacs 是一臺計算機》
理解了 Emacs 身為計算機的本質之后,在 Emacs 里編程就順理成章了。不過,在此之前,還需要略微介紹一下 Emacs 最基本的操作。
系統的不一致,令人有點煩躁
現在,也可以坦然地說,Emacs 是一臺虛擬的計算機,目前,它只能在宿主操作系統中運行,而這個宿主操作系統可以是 Windows、Linux 或 Mac OS X。由于這個世界上絕大多數計算機所運行的操作系統不外乎這三者之一,因此無需擔心無 Emacs 可用。
現在假設你對自己所用的操作系統足夠熟悉,至少達到能夠從 https://www.gnu.org/software/... 頁面找到與自己所用的操作系統匹配的 Emacs 版本的安裝包并成功安裝的程度,那么我就假設你的操作系統中已經存在了一個可用的 Emacs。此外,我還得假設你知道怎么打開你所用的操作系統的命令行窗口。
命令行窗口,在 Windows 里,叫控制臺或命令行提示符,而在 Linux 與 Mac OS X 里,則稱為終端(Terminal)。
實際上,Windows 里所謂的命令提示符,這真的是一個很奇葩的名字,它實際上是以命令行窗口中里 >
符號的名字。在 Linux 與 Mac OS X 的終端里,命令提示符是 $
。所謂命令提示符,意思是在這個符號的后面可以輸入命令,這個符號本身不需要你輸入。下文以 $
作為命令提示符。
啟動 Emacs 的命令如下:
$ emacs
執行這條命令之后,可以得到如圖 1 所示的窗口,這或許是你有生以來第一次看到 Emacs,記住這一天。
接下來,在 Emacs 里執行第一個命令 C-x C-f
。這是 Emacs 特色的組合按鍵序列的表現形式,其含義:摁住 Ctrl 鍵,單擊 x
鍵;摁住 Ctrl 鍵,單擊 f
鍵。執行這一命令后,觀察 Emacs 窗口,它的底欄應該會出現 Find file: ...
字樣,其中 ...
是 Emacs 的當前工作目錄的路徑。在此,我不得不繼續假設你知道在一個操作系統中一個工作目錄的路徑指的是什么。或許 Windows 用戶對此不甚了解,下面略作解釋。
當你打開 Windows 命令提示符窗口的時候,窗口中應該會出現像下面這樣的字樣:
C:\Users\用戶名 >
這個 C:\Users\用戶名
就是當前的工作目錄的路徑。
再回到 C-x C-f
按鍵在 Emacs 底欄召喚出來的 Find file: ...
,現在,在 ...
之后輸入 foo.txt
,然后擊回車(Enter)鍵,這樣便在工作目錄中創建了一份名稱為 foo.txt 的文本文件。這份文件沒有什么特殊意義,它只不過是為了讓你體驗 Emacs 而隨便創建的一份文本文件。
從現在開始,將 Emacs 用于接受我們輸入 foo.txt
文件名的底欄稱為微緩沖區(Minibuffer)。微緩沖區是我們向 Emacs 傳遞各種按鍵序列以及命令的通道。
當 Emacs 在工作目錄中創建一份新的文件時,它會在微緩沖區上方的窗口中為這份文件創建一個緩沖區。這個緩沖區就叫緩沖區,它對應一塊內存區域,可以與硬盤上的文件相關,也可以無關。以后,當我談及「緩沖區」的時候,指的就是微緩沖區上方的緩沖區。
我們在緩沖區中編輯文本,在需要將所編輯的內容保存到與緩沖區相關聯的文件里之時,只需向 Emacs 發出 C-x C-s
指令——摁住 Ctrl 鍵,擊 x
鍵;摁住 Ctrl 鍵,擊 s
鍵。
像 C-x C-f
與 C-x C-s
這樣的組合鍵,由于每組按鍵都需要摁住 Ctrl 鍵,因此可以采用類似「英文連讀」的方式減少按鍵次數,即摁住 Ctrl 鍵,依次單擊 x
與 f
,或依次單擊 x
與 s
。
我們對 Emacs 的操作暫時只需了解這么多。倘若你學有余力,可繼續閱讀我寫的另一份文檔 [1]。
hello, world
這個世界上,被重復寫過最多的一個程序,它的名字叫 hello world。始作俑者是《The C Programming Language》的作者 Brain Kernighan。無數初學編程的人,用這個程序向計算機軟件世界發出了第一聲問候。
在 Emacs 中,我們不妨也貫徹一下這個儀式。在上一節所建立的 foo.txt 文件緩沖區里輸入 hello, world
。
對此,請不要吝惜發出~噫~的聲音!
若真的是這樣的 hello world,不論是誰,都會感到失望。不過,由于我此前將 Emacs 的窗口(還記得俄羅斯方塊嗎)稱為顯示器。這種直接在緩沖區里鍵入 hello, world
的方式其實很黑客,這是直接修改 Emacs 的顯存內容啊!
發出~噫~的聲音的人說,散了,散了,他就這兩下子!
當然不是。要通過一段程序,在 Emacs 窗口中顯現 hello, world
,這需要了解一下 Emacs 的啟動配置文件 init.el。要了解這個文件,需要繼續為三大操作系統的不一致而煩躁。
Emacs 的 HOME 目錄
init.el 文件是 Emacs 的啟動配置文件。它應該位于 HOME 目錄的 .emacs.d 子目錄中。
對于 Windows 用戶,倘若你的操作系統安裝在 C 盤。對于 Windows 7 或更新的版本,Emacs 會將 C:\Users\<用戶名>\AppData\Roaming
作為默認的 HOME 目錄。對于 Windowx 2000 或 XP,Emacs 會將 C:\Documents and Settings\<用戶名>\Application Data
作為 HOME 目錄。對于……Windows 就是這樣麻煩啊,倘若對此很煩躁,建議使用 Linux,或閱讀 Emacs 文檔中對 Windows 的 HOME 目錄的說明 [1]。
對于 Linux 與 Mac OS X 而言,由于它們有點血緣關系,因此它們的 Emacs HOME 目錄就是 $HOME
或 ~
,亦即操作系統的 HOME 目錄。
函數
init.el 文件是 Emacs 的配置文件。現在,假定你已經能夠在自己的系統中找到 Emacs 的 HOME 目錄。接下來,就在這個目錄中創建 .emacs.d 目錄,然后使用 Emacs 在這個目錄創建 init.el 文件。使用其他文本編輯器并非不可,但是不要浪費使用 Emacs 編輯文件的實踐機會。
每當 Emacs 啟動時,它都會讀取 init.el,認真貫徹這個文件中的一些設定。因此,我們可以在這個文件中寫一個 hello world 程序,讓 Emacs 能夠在緩沖區中顯現 hello, world
。以后,這個文件就是我們在 Emacs 環境里的編程實驗場地。
在開始寫這個 hello world 程序之前,先體驗一下如何通過 init.el 調整 Emacs 的外觀。
在 init.el 文件中寫入以下內容:
; 關閉菜單、工具欄、滾動條
(tool-bar-mode 0)
(menu-bar-mode 0)
(scroll-bar-mode 0)
然后重新啟動 Emacs,發現它的菜單、工具欄以及滾動條都不見了。
上述內容,以 ;
開頭的語句是 Emacs Lisp 的注釋語句,會被 Emacs 忽略。之后的三行語句,對于 Emacs 而言,是三個程序,因為之前很認真地說過,Emacs 是計算機。init.el 中的內容,對于 Emacs 而言就是一組指令,而每條指令都可以視為一個程序。
由于我們通常所遇到的程序往往是由很復雜的代碼構成。將這簡短的三行代碼視為三個程序,有煞有介事之嫌。那么,我們就將它們稱為表達式。對于 Emacs 而言,init.el 中的每對小括號包含的文本,只要它不屬于注釋語句,對于 Emacs 而言都是表達式,要牢記這一點。
以 (tool-bar-mode 0)
為例,這個表達式可以讓 Emacs 在啟動時關閉工具條。倘若將這個表達式改為 (tool-bar-mode 1)
,那么下次開啟 Emacs 的時候,工具條又會被打開。顯然,是語句中的 0
或 1
決定著 Emacs 是關閉還是打開工具條。
假設你曾經認真學過中學數學,想必還記得函數吧?y = f(x),以映射的記法可寫為 f: x -> y。倘若將 tool-bar-mode
視為 f,將 0
與 1
視為 x,將工具條的關閉與打開這兩種狀態視為 y,那么 (tool-bar-mode 0)
與 (tool-bar-mode 1)
在 Emacs 環境中所產生的作用,像不像 f: x -> y?
因此,像 tool-bar-mode
這樣的事物,在 Emacs 里,是函數。上面在 init.el 中寫入的三行語句,實際上是對三個函數進行求值。是誰在對函數進行求值,是 Emacs Lisp 解釋器!
定義一個函數
接下來,我們可以在 init.el 中定義一個函數,即在 init.el 文件的尾部新開一行,然后寫入以下內容:
(defun hello-world ()(interactive)(insert "hello, world"))
倘若你的確是按照我說的,使用 Emacs 編輯 init.el 文件,那么此刻只需使用 C-x C-s
便可將上述內容保存到 init.el 文件中,
現在關閉 Emacs,然后重新啟動它,再重新打開剛才創建的 foo.txt 文件。這樣 Emacs 便會重新讀取 init.el 文件,但是這次它會讀取上述語句。先不考慮 Emacs 對它們作何處置,現在,先在 Emacs 中嘗試輸入 M-x hello-world <RET>
命令,看看會緩沖區里會出現什么。這條命令里的 M-x
表示摁住 Alt
鍵,然后單擊 x
,然后松開 Alt
鍵,接下來繼續輸入 hello-world
,然后單擊回車鍵。由于在 Emacs 中,每當使用 C-x
或 M-x
時,Emacs 都會認為你要向它發出命令,所以它會使用微緩沖區接受你后續的輸入。我的表述雖然有些冗長,但是倘若你真的動手嘗試兩三次,就自會明白這一切。
向 Emacs 發送 M-x hello-world <RET>
命令之后,結果會怎樣呢?結果就是在 Emacs 當前的緩沖區里出現了 hello, world
。
這是我們向 Emacs 世界發出的第一聲真正的問候。
函數的定義是表達式
對于 Emacs 而言,像
(defun hello-world ()(interactive)(insert "hello, world"))
這樣的語句,雖然它似乎有些特殊——可以形成一條指令,但它依然是一個表達式。上文曾說過,在 init.el 中,每對小括號包含的文本,只要它不屬于注釋語句,對于 Emacs 而言,都是表達式。上述文本雖然比 (tool-bar-mode 0)
這樣的表達式更復雜了一些,但由于它是以 (
開始又以 )
結尾,所以它是一個表達式。前文也說過,Emacs Lisp 解釋器會對表達式進行求值。那么對于上述表達式,求值結果是什么呢?現在若不過于探究細節的話,不妨將這個求值結果視為在 Emacs 環境里定義了一個名為 hello-world
的函數,并且這個函數不接受任何參數。
不接受任何參數的函數,相當于一個常量函數,類似于 y = 1 這樣的函數。因此,上述的 hello-world
函數是一個常量函數,函數值永遠為 (insert "hello, world")
,這對于 Emacs 而言,又是一個表達式,而這個表達式是對 insert
這個函數進行求值。求值結果是什么?就是在緩沖區中出現 hello, world
,這就是 Emacs 對在響應 M-x hello-world <RET>
命令后,對 hello-world
函數的最終求值結果。
為什么 hello-world
的求值結果不是 (interactive)
呢?能在不清楚 (interactive)
這個表達式的含義的前提下提出這個問題的人,都是好同學。
對 hello-world
函數進行求值,本質上是對 hello-world
所包含的一組表達式進行求值。hello-world
包含了兩個表達式:
(interactive)
(insert "hello, world")
它們構成了 hello-world
函數的實體。函數的實體與函數的名稱通過 (defun ...)
表達式綁定到一起,這就是所謂的函數定義。
Emacs Lisp 解釋器對 (defun ...)
這種表達式的求值邏輯較為特殊,它會順序地對嵌入這一表達式中的子表達式按照先后進行排序,將最后一個表達式的求值結果作為函數的求值結果。
(interactive)
也是一個表達式,暫時不需要關心它對 Emacs 起到了什么作用—— Emacs Lisp 解釋器對它的求值結果,由于它位于 (insert "hello, world")
之前,所以它的求值結果沒有資格作為 hello-world
的求值結果。
公仆與民眾
那么,(interactive)
的求值結果是什么?讓 (defun ...)
定義的函數成為 Emacs 命令——可在微緩沖區執行的命令。
可以試著從 hello-world
函數中去掉 (interactive)
,即 hello-world
的定義變為:
(defun hello-world ()(insert "hello, world"))
然后保存 init.el 文件,再重新啟動 Emacs,再重新打開剛才創建的 foo.txt 文件,然后再次執行 M-x hello-world <RET>
命令,就會發現,Emacs 根本不知道你在做什么。
因此,在 Emacs 的世界里,有兩種函數,一種是可以作為命令執行的函數,它們就像通常所謂的程序一樣,另一種則是普通的函數,它們默默無聞、甘于奉獻,為那些可以作為命令使用的函數貢獻了光和熱。
重啟不是必要的
在上文中,每次修改了 init.el 文件的內容之后,為了驗證修改的結果,需要關閉 Emacs 再重新啟動它,然后再次打開 foo.txt。這種操作過于繁瑣,造成了人民日益增長的美好生活需要和不平衡不充分的發展之間的矛盾。
事實上,我們可以打開兩個 Emacs 窗口,一個用于編輯 init.el,另一個用于體驗 init.el 中的改動的效果,即用于編輯 foo.txt 文件。不妨前者稱為「哼」窗口,將后者稱為「哈」窗口。我們在「哼」窗口中所作的任何改動,保存到 init.el 文件中之后,在「哈」窗口中,只需執行 M-x load-file <RET> ~/.emacs.d/init.el <RET>
,便可讓修改后的 init.el 在「哈」窗口中生效。這條命令中的 ~
,Emacs 會將其視為 HOME 目錄路徑的簡寫。
以后,隨著對 Emacs 熟悉程度的增進,或者在閱讀文檔 [1] 之后,會發現打開兩個 Emacs 窗口也是不必要的。
下一篇:勤勞,還是懶惰?
[1] 走近 Emacs
[2] Emacs 文檔對 Windows 系統中的 HOME 目錄的說明