請先閱讀Peter Norvig的一篇好文: http://norvig.com/21-days.html
(譯者注:中文版請見http://blog.youxu.info/21-days/)
之後安裝GNU Emacs 24.3:
- Debian:
apt-get install emacs
(視具體發行版而定) - MacOSX: http://emacsformacosx.com/emacs-builds/Emacs-24.3-universal-10.6.8.dmg
- Windows: http://ftp.gnu.org/gnu/windows/emacs/emacs-24.3-bin-i386.zip
更多資訊可以在這裡找到:
http://www.gnu.org/software/emacs/#Obtaining
很重要的警告:
- 按照這個教程來學習並不會對你的電腦有任何損壞
- 除非你自己在學習的過程中憤怒地把它砸了
- 如果出現了這種情況,我不會承擔任何責任
開啟emacs
按`q`消除歡迎介面
現在請注意視窗底部的那一個灰色長條
“scratch” 是你現在編輯介面的名字。這個編輯介面叫做一個”buffer”。 每當你開啟Emacs時,都會預設開啟這個scratch buffer。 此時你並沒有在編輯任何檔案,而是在編輯一個buffer。 之後你可以將這個buffer儲存到一個檔案中。
之後的”Lisp interaction” 則是表明我們可以用的某組命令。Emacs在每個buffer中都有一組內建的命令。 而當你啟用某種特定的模式時,就可以使用相應的命令。 這裡我們使用lisp-interaction-mode
, 這樣我們就可以使用內建的Emacs Lisp(以下簡稱Elisp)命令了。
;; 分號是註釋開始的標誌
Elisp 是由符號表示式構成的 (即”s-表示式”或”s式”):
(+ 2 2)
這個s式的意思是 “對2進行加2操作”.
s式周圍有括號,而且也可以巢狀:
(+ 2 (+ 1 1))
一個s式可以包含原子符號或者其他s式。在上面的例子中,1和2是原子符號。(+ 2 (+ 1 1))
和 (+ 1 1)
是s式.
在 lisp-interaction-mode
中你可以計算s式。把游標移到閉括號後,之後按下ctrl+j(以後簡寫為`C-j`)
(+ 3 (+ 1 2))
游標放到最後的括號後,按下C-j
就會輸出 6。
C-j
會在buffer中插入當前運算的結果
而C-xC-e
則會在emacs最底部顯示結果,也就是被稱作”minibuffer”的區域。為了避免把我們的buffer填滿無用的結果,我們以後會一直用C-xC-e
。
setq
可以將一個值賦給一個變數
(setq my-name "Bastien")
C-xC-e
輸出 “Bastien” (在 mini-buffer 中顯示)
insert
會在游標處插入字串:
(insert "Hello!")
C-xC-e
輸出 “Hello!”
在這裡我們只傳給了insert一個引數”Hello!”, 但是我們也可以傳給它更多的引數,比如2個:
(insert "Hello" " world!")
C-xC-e
輸出 “Hello world!”
你也可以用變數名來代替字串
(insert "Hello, I am " my-name)
C-xC-e
輸出 “Hello, I am Bastien”
你可以把s式嵌入函式中
(defun hello () (insert "Hello, I am " my-name))
C-xC-e
輸出 hello
現在執行這個函式
(hello)
C-xC-e
輸出 Hello, I am Bastien
函式中空括號的意思是我們不需要接受任何引數。但是我們不能一直總是用my-name這個變數。所以我們現在使我們的函式接受一個叫做”name”的引數。
(defun hello (name) (insert "Hello " name))
C-xC-e
輸出 hello。
現在我們呼叫這個函式,並且將”you”作為引數傳遞
(hello "you")
C-xC-e
輸出 “Hello you”
成功!
現在我們可以休息一下
下面我們在新的視窗中新建一個名為 *test*
的buffer:
(switch-to-buffer-other-window "*test*")
C-xC-e
這時螢幕上會顯示兩個視窗,而游標此時位於test buffer內。用滑鼠單擊上面的buffer就會使游標移回。或者你可以使用 C-xo
使得游標跳到另一個視窗中
你可以用 progn
命令將s式結合起來:
(progn
(switch-to-buffer-other-window "*test*")
(hello "you"))
C-xC-e
此時螢幕分為兩個視窗,並且在test buffer中顯示”Hello you”
現在為了簡潔,我們需要在每個s式後面都使用C-xC-e
來執行,後面就不再說明了。記得可以用過滑鼠或者C-xo
回到scratch這個buffer。
清除當前buffer也是常用操作之一:
(progn
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(hello "there"))
也可以回到其他的視窗中
(progn
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(hello "you")
(other-window 1))
你可以用 let
將一個值和一個區域性變數繫結:
(let ((local-name "you"))
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(hello local-name)
(other-window 1))
這裡我們就不需要使用 progn
了, 因為 let
也可以將很多s式組合起來。
格式化字串的方法:
(format "Hello %s!
" "visitor")
%s 是字串佔位符,這裡被”visitor”替代。
是換行符。
現在我們用格式化的方法再重寫一下我們的函式:
(defun hello (name)
(insert (format "Hello %s!
" name)))
(hello "you")
我們再用let
新建另一個函式:
(defun greeting (name)
(let ((your-name "Bastien"))
(insert (format "Hello %s!
I am %s."
name ; the argument of the function
your-name ; the let-bound variable "Bastien"
))))
之後執行:
(greeting "you")
有些函式可以和使用者互動:
(read-from-minibuffer "Enter your name: ")
這個函式會返回在執行時使用者輸入的資訊
現在我們讓greeting
函式顯示你的名字:
(defun greeting (from-name)
(let ((your-name (read-from-minibuffer "Enter your name: ")))
(insert (format "Hello!
I am %s and you are %s."
from-name ; the argument of the function
your-name ; the let-bound var, entered at prompt
))))
(greeting "Bastien")
我們讓結果在另一個視窗中顯示:
(defun greeting (from-name)
(let ((your-name (read-from-minibuffer "Enter your name: ")))
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(insert (format "Hello %s!
I am %s." your-name from-name))
(other-window 1)))
測試一下:
(greeting "Bastien")
第二節結束,休息一下吧。
我們將一些名字存到列表中:
(setq list-of-names `("Sarah" "Chloe" "Mathilde"))
用 car
來取得第一個名字:
(car list-of-names)
用 cdr
取得剩下的名字:
(cdr list-of-names)
用 push
把名字新增到列表的開頭:
(push "Stephanie" list-of-names)
注意: car
和 cdr
並不修改列表本身, 但是 push
卻會對列表本身進行操作。這個區別是很重要的: 有些函式沒有任何副作用(比如car
),但還有一些卻是有的 (比如 push
)。
我們來對list-of-names
列表中的每一個元素都使用hello函式:
(mapcar `hello list-of-names)
將 greeting
改進,使的我們能夠對list-of-names
中的所有名字執行:
(defun greeting ()
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(mapcar `hello list-of-names)
(other-window 1))
(greeting)
記得我們之前定義的 hello
函式嗎? 這個函式接受一個引數,名字。mapcar
呼叫 hello
, 並將list-of-names
作為引數先後傳給hello
。
現在我們對顯示的buffer中的內容進行一些更改:
(defun replace-hello-by-bonjour ()
(switch-to-buffer-other-window "*test*")
(goto-char (point-min))
(while (search-forward "Hello")
(replace-match "Bonjour"))
(other-window 1))
(goto-char (point-min))
將游標移到buffer的開始,(search-forward "Hello")
查詢字串”Hello”, (while x y)
當x返回某個值時執行y這個s式。 當x返回nil
(空), 退出迴圈。
(replace-hello-by-bonjour)
你會看到所有在test buffer中出現的”Hello”字樣都被換成了”Bonjour”
你也會得到以下錯誤提示: “Search failed: Hello”.
如果要避免這個錯誤, 你需要告訴 search-forward
這個命令是否在buffer的某個地方停止查詢, 並且在什麼都沒找到時是否應該不給出錯誤提示。
(search-forward "Hello" nil t)
可以達到這個要求:
-
nil
引數的意思是: 查詢並不限於某個範圍內 -
t
引數的意思是: 當什麼都沒找到時,不給出錯誤提示
在下面的函式中,我們用到了s式,並且不給出任何錯誤提示:
(defun hello-to-bonjour ()
(switch-to-buffer-other-window "*test*")
(erase-buffer)
;; 為`list-of-names`中的每個名字呼叫hello
(mapcar `hello list-of-names)
(goto-char (point-min))
;; 將"Hello" 替換為"Bonjour"
(while (search-forward "Hello" nil t)
(replace-match "Bonjour"))
(other-window 1))
(hello-to-bonjour)
給這些名字上個色:
(defun boldify-names ()
(switch-to-buffer-other-window "*test*")
(goto-char (point-min))
(while (re-search-forward "Bonjour \(.+\)!" nil t)
(add-text-properties (match-beginning 1)
(match-end 1)
(list `face `bold)))
(other-window 1))
這個函式使用了 re-search-forward
: 和查詢一個字串不同,你用這個命令可以查詢一個模式,即正規表示式
正規表示式 “Bonjour (.+)!” 的意思是:
字串 "Bonjour ", 之後跟著
一組 | \( ... \) 結構
任意字元 | . 的含義
有可能重複的 | + 的含義
之後跟著 "!" 這個字串
準備好了?試試看。
(boldify-names)
add-text-properties
可以新增文字屬性, 比如文字樣式
好的,我們成功了!
如果你想對一個變數或者函式有更多的瞭解:
- C-h v 變數 回車
- C-h f 函式 回車
閱讀Emacs Lisp官方文件:
C-h i m elisp 回車
線上閱讀Emacs Lisp文件:
https://www.gnu.org/software/emacs/manual/html_node/eintr/index.html
感謝以下同學的建議和反饋:
- Wes Hardaker
- notbob
- Kevin Montuori
- Arne Babenhauserheide
- Alan Schmitt