Skip to content

Latest commit

 

History

History
232 lines (164 loc) · 15.6 KB

02-預備知識.org

File metadata and controls

232 lines (164 loc) · 15.6 KB

預備知識

這一篇會講解 Emacs 的必備預備知識, 請務必詳讀,後面不會再解釋。

以前你可能聽過 Emacs,但可能因為各種原因而沒用過或學不下去,例如網路上的教學又凌亂不連貫、有問題不知從何問起?下面整理了幾乎所有初學者都會遇到的最常見問題。

然而首先我想從這個問題開始:「到底 Emacs 是什麼東西?」在這之前,先打開你系統上的 Emacs:

  • Q: 「我的人生太無聊、我就是喜歡在終端機裡面瞎折騰;該怎麼強迫使用終端機版、而非 GUI 版本的 Emacs?」
  • A: 「加上 -nw 參數」:
$ emacs -nw
  

你會見到一個歡迎畫面,按 q 關掉它,會看到 *scratch* 這個空空如也的畫面配著三行字。這個 *scratch* 可以看作是個無特別意義的塗鴉紙,可以在上面亂打一些字。好啦,就這樣開始吧:

C-x, M-f 這些東西代表什麼意思?

  • Emacs 使用 簡寫 表示鍵盤組合鍵。如:
    • C-a 表示按住 Ctrl 再按 a
    • M-f 表示按住 Alt/Meta 再按 f ,或者按一下 Esc 再按 f
    • C-x C-f 表示按住 Ctrl 後,再按 xf
    • C-x k 表示按了 C-x 後,放開 ,再按一下 k
    • C-x RET 表示按了 C-x 後,放開,再按一下 Enter
  • C-x, C-c, C-u 這三者是前綴 (prefix,稍後詳述) 組合鍵,你不能單獨使用(例如你無法把某個功能綁到只按一個 C-x 就能達成,它會報錯)
    • 例如開檔 C-x C-f 、存檔 C-x C-s 、離開 Emacs C-x C-c
  • M-x 是直接呼叫「指令名稱」。或者精確的說,「函數」(function)
  • C-u 是給命令加上參數時在使用的,初學不需要知道這個,以後再說 XD

前面提到的,Emacs 是個 Lisp 環境,而你按的每一個按鍵,對 Emacs 來說實際上其實就是在 呼叫函數 。例如:

  • 方向鍵右 → 執行函數 (right-char) ,它的副作用是讓游標向右移動一個字元。
  • 方向鍵上 → 執行函數 (previous-line) ,它的副作用是讓游標向上移動一行。

因此,只要是 M-x 呼叫得出的函數,你都可以重新綁定成你自己喜歡的按鍵。(方法後述)

Emacs 是什麼?

pic/alliances_zh.png

這個問題聽起來很蠢,但其實我認為非常重要,因為當你領悟了這點,你在學習 Emacs 時腦中會少掉很多問號與 WTF,並越用越豁然開朗。

我的理解是這樣的: Emacs 是一個剛好具有文字編輯功能的 Lisp 環境

什麼意思呢?Emacs 本身是一個完整的 Lisp 環境/直譯器,除了 Lisp 直譯器自己本身與一些低階命令外,其餘部份全是使用 Emacs Lisp 所建構而成。

動手試試看,在上面提到的 *scratch* 畫面中輸入 (message "Hello World!") ,然後游標移動到括弧後方並按下 C-x C-e ,你會發現下方的 minibuffer 訊息列就會顯示出 Hello World!

發生了什麼事? C-x C-e 的動作代表「執行(eval)該 Lisp 表達式」, message 是一個 Emacs 內建的 Lisp function,用途就是在 minibuffer 中顯示字串。或更精確地說,「執行 message 這個 Lisp function 的 副作用 就是在 minibuffer 中顯示字串。」 (message "Hello World!") 是一個完整的 Lisp 表達式,所以你 eval 它,就能在 minibuffer 中顯示 Hello World! 這個字串。

Emacs 就是成千上萬個 Lisp function 跟一堆 variable 所構成, 並靠著執行這些 function 的副作用而構成一個文字編輯環境的。

因為 Lisp 表達式如果有副作用,執行的結果就會直接立刻影響整個 Emacs。所以我們常透過 setq 這個 function 來 assign variable 達成自訂 Emacs 的目的。

例如執行 (setq read-file-name-completion-ignore-case t) 將會把 read-file-name-completion-ignore-case 這個 variable 的值設為 t ,以後在 C-x C-f 就能忽略路徑檔名的大小寫。

如果你不懂 Lisp, (setq VAR_NAME VALUE) 是 Lisp 中設定 variable 的意思,等同 Python 中的 VAR_NAME = VALUEt 是 Lisp 中的 Truenil 則是 False 之意。

當你清楚意識並理解到這點後,往後很多 Emacs 的行為你會豁然開朗,尤其是你在學 Emacs Lisp 時,這是很重要的概念。這也是一個蠻實用的特性,因為你可以極為容易地寫出一些臨時需要的 function 做出一些快速的編輯功能。

我剛剛不知動到什麼操作了,要怎麼取消?

操作遇到問題想取消,狂按幾下 C-g 就對了,就像你在 Bash/Zsh 下狂按 C-c 一樣。

如果你是不小心編輯到檔案內容了,按 C-/C-_ 可以復原(Undo)。

Emacs 內建的 Undo 跟一般你在其他應用程式中所知道的 Undo 不一樣,而且不太好理解。可以參考 https://www.gnu.org/software/emacs/manual/html_node/emacs/Undo.html ,詳情稍後會再解釋。

除了檔案內容可以 undo,window 的操作(就是 C-x 0 C-x 1 C-x 2 C-x 3 那些東西啦)也可以!啟用 Emacs 內建的 winner.el:

;; Make window state undo-able
(require 'winner)
(winner-mode 1)
  

現在可以用 C-c C-right/left 來 undo / redo 你的 window 狀態。

有沒有什麼方法查詢function的key-binding啊?

只要是 M-x 呼叫得出的 function,你都可以透過各種方法來查詢他的key-binding:

  • 使用 C-h f 查詢某 function 的用途、文件與該 function 在目前的 buffer 中 所 bind 到的 key-binding 等資訊。
  • 使用 C-h m 查詢 在目前的 buffer 中 、啟動了哪一個 major-mode、哪些 minor-modes、以及所有可用的 key-bindings。
  • 按任意 prefix key 後再按下 C-h ,可以得知目前 buffer 下,以該prefix key為開頭的所有可用的key-bindings。例如按下 C-x C-h 你可以看到類似這樣的東西:
Global Bindings Starting With C-x:
key             binding
---             -------

C-x C-@         mc/edit-lines
C-x C-b         ibuffer
C-x C-c         save-buffers-kill-terminal
C-x C-d         list-directory
C-x C-e         eval-last-sexp
C-x C-f         ??
C-x TAB         indent-rigidly
C-x C-j         dired-jump
C-x C-k         kmacro-keymap
C-x C-l         downcase-region
C-x RET         Prefix Command
C-x C-n         set-goal-column
C-x C-o         delete-blank-lines
C-x C-p         mark-page
C-x C-q         read-only-mode
C-x C-r         helm-recentf
.....(以下省略)
  • 也可以倒過來查:使用 C-h k 、再按下任意 key-binding,可以查詢其 key-binding 在目前 buffer 下所綁定到的 function。

Buffer 與 Frame、Window 到底差在哪裡?(務必弄清)

注意,這幾個東西的概念完全不同;

  • Buffer 就是你用 Emacs 時, 開啟檔案後、拿來編輯檔案的地方 。編輯就編輯,為什麼會叫「Buffer(緩衝區)」這麼奇怪的名子呢?因為你在緩衝區裡面做的任何編輯都還不會被立刻實際寫入檔案,直到按 C-x C-s 存檔為止,這就是「緩衝區」的概念。
    • 順帶一題, Minibuffer 指的是 Emacs 視窗最下方、用來顯示訊息或者互動式操作中輸入資料的那一條。

Frame & Window

這兩個玩意要一起解釋:

  • 一啟動 Emacs 時,你會看到的畫面通常就是 「一個 Frame,裡面包著一個 Window」
  • Frame 裡面可以包含好幾個 Window,你可以把 Frame 切成好幾塊,例如上下各一個、或左右各一個等等各種不同的 layout, 每一塊就是一個 Window ,這樣你就可以同時看好幾個 buffer 的內容,有點像 tiling window manager 那樣。
  • 每個 Buffer 各自暫存著自己所存取的檔案的內容。
  • 每個 Window 各自顯示著 Buffer 的內容。每個 Window 總是、必定會顯示著一個 Buffer。
  • 每個 Frame 各自儲存著自己的 Window layout。
    • 也就是說你當然可以隨時新增並切換 Frame 來看到其各自不同的 Window layout。
    • 注意看 mode-line,檔案名稱左邊的 F1 意思就是你現在在第一個 Frame: pic/frame_identification.png

…唉呀拎老師靠北啦,還是拿兩張 screenshot 來講比較快:

我啟動 Emacs 後,開了兩個有著不同的 Window layout 的 Frame。

  • 這是第一個 Frame F1 ,包含了 2 個 Window ,左右各一個:
    • 左邊的 Window 正顯示著 02-預備知識.org 這個 Buffer (就是這篇文章的原稿檔案啦啊啊)
    • 右邊的 Window 正顯示著名為 *[萌典] 查詢結果*Buffer

pic/frame1.png

  • 這是第二個 Frame F2 ,包含了 3 個 Window:
    • 上方的 Window 正顯示著 rc-basic.el 這個 Buffer
    • 左下的 Window 正顯示著 emacs-101/ 這個目錄 (DiredBuffer)
    • 右下的 Window 正顯示著名為 testBuffer (Org-mode)。

pic/frame2.png

其實會有 Buffer 這麼奇怪的詞完全是歷史因素,因為 Vi 與 Emacs 發明時的 70 年代,大家都還在用 ed 之類的行編輯器在編輯檔案,還沒有這種「輸入什麼東西、即時就可以在螢幕上見到修正」的編輯器,所以才會把編輯區叫做 Buffer。在那時這種功能可是創新的呢。

– ono hiroko

操作方式

Buffer

  • 關掉 Buffer 是 C-x k將會真正地把檔案關掉 (kill-buffer)
    • 切換到下一個/前一個 buffer: C-x C-<right>/<left> (方向鍵)
    • 直接切到某個 buffer: C-x b (可以用 tab 鍵補全)
    • 開啟 buffer 管理員: C-x C-b (強力推薦改用 Ibuffer ,因為預設的非常難用)

Window

  • 關掉目前的 Window 是 C-x 0但這個動作並不會把 Buffer 也一起關掉! 即使關掉 window,buffer 其實還活在背景中、隨時可以叫出。這一點與現在一般常見的編輯器不同,並不是關掉視窗後、檔案也會一起關閉。因為 Window 本身並不存任何內容,只是拿來顯示 Buffer 用。
    • 切換到不同的 Window: C-x o
    • 將目前以外的所有 Window 關掉: C-x 1
    • 將目前的 Window 分成上下兩塊: C-x 2
    • 將目前的 Window 分成左右兩塊: C-x 3

為什麼還要設計 Buffer 這種東西搞得那麼複雜?其實這種特性非常好用也非常常用,由於 Window 與 Buffer 的概念是分開的,所以可以 開多個 Window,且每個 Window 都顯示同個 Buffer ,代表你可以同時開多個 Window,同時看「一個檔案的不同部份」,不管是寫文章或寫程式時都非常方便。

Frame

  • 關掉目前 Frame: C-x 5 0 ,跟 Window 一樣,關掉 Frame 並不會把 Buffer 一起關掉,因為 Frame 本身只是用來存 Window 的 layout 而已。關了 Frame 也只是把這個 Window layout 扔掉。
    • 切換到不同的 Frame: C-x 5 o
    • 將目前以外的 Frame 關掉: C-x 5 1
    • 新增 Frame: C-x 5 2

有一點要注意:因為我很少用 GUI 版,我後來才發現 GUI 和 console 版的 Frame 行為不同, GUI下 C-x 5 2 其實會新開一個 Emacs 視窗…我自己是不喜歡這樣,我是不知道有沒有辦法讓 GUI 下的frame 行為跟 console 下一樣啦。我問過這個問題不過好像無解, 除非用第三方package像是elscreen來達成類似的事情(我試用了一下, elscreen 做的事情跟 frame 有 87% 像,在GUI下也可以保持單一主視窗,而且還多一個可以開關的tab bar來切換frame)。

– ono hiroko

什麼是 Mode?Major-mode 跟 Minor-mode 又差在哪?

前面提到,Emacs 是一個 Lisp 環境,我們可以拿他來做各種任務。

我們會開很多不同的 buffer 來做不同的任務,例如我可能一個 buffer 在寫 Python 程式碼,另一個 buffer 在查 Python 的文件,另一個 buffer 拿來瀏覽專案目錄管理檔案,另一個 buffer 顯示 ag 的搜尋結果,最後一個 buffer 拿來偷偷分心上 IRC。因為每個 buffer 的任務不同,沒辦法用統一的 Lisp 環境設定直接拿來做這些任務,所以呢,mode 的用途就是「為各種不同的任務,創造適合該任務的環境」。

拿寫 Python 用的 python-mode 當例子好了,當你在 buffer 中使用 M-x python-mode 啟動該 mode 後, python-mode 就會做出像是下面這些行為

  • 把 buffer local 變數 tab-width 設定為 8 (一個 tab 有多少空格寬)
  • 把 buffer local 變數 comment-start 設定為 # (comment 的開頭字元)
  • 設定 indent-line-function 來指定當使用者按 [TAB] 鍵時,該怎麼縮排?
  • 設定好 Python 的 syntax highlight 等等規則。( font-lock
  • 讀取 python-mode-map ,看看有哪些 key-binding 可以按。(這部份稍後會詳細介紹)
  • 執行 python-mode-hook 內的 hooks(看不懂沒關係,這部份稍後也會詳細介紹)
  • ……etc

被這樣一設定,這個 buffer 就會變身成適合編輯 Python 的「環境」。這就是 mode 的用途。

Major-mode

Major mode 大致可以(非正式地)亂分成兩種類型:

  1. 程式語言編輯: python-mode, ruby-mode, haskell-mode, c++-mode, sql-mode
  2. 工具、應用程式類: dired-mode (檔案管理員), erc-mode (IRC client), term-mode (terminal emulator) …

一個 buffer 只能同時啟用一個 major mode,無法兩者同時處於啟用狀態,因為每個 major-mode 所需的環境通常都是互相衝突的。想像一下,你要在一個 buffer 中同時編輯 Python 與 Ruby 程式碼,這種事顯然是不合理的,例如 syntax highlight 到底該用 Python 還是 Ruby 的規則呢?

不過現實世界是很複雜的,像是 HTML code 裡面常常就會插入 JS,這種情況下有個非常知名的 Emacs 外掛叫做 mmm-mode 就是在解決這種事情,你可以在單一 buffer 中同時啟動好幾個 major-mode,這樣就可以同時顯示諸如 HTML/CSS/JS 的 syntax highlight 之類的,但我不會說明如何使用,等你讀完整本 Emacs 101 後再自己去看 mmm-mode 的文件自己安裝設定吧,讀完 Emacs 101 你自己就看得懂那些文件了。

Minor-mode

Major mode 沒有辦法同時啟動多個,但 minor mode 就可以同時啟動好幾個,你要幾個都可以。例如: pangu-mode (自動在中文跟英文之間插入空格)、 rainbow-delimiters-mode (即時把 buffer 中不同深度的括號上不同顏色)、 rainbow-mode (即時將 buffer 中所有包含 Hex/RGB color code 的顏色顯示出來)等等,這些功能顯然是不會互相衝突的。

minor-mode 基本上是不會衝突的,但有時有著相同功能的 minor mode 同時打開時行為可能就會很怪。例如你同時開兩個自動幫你補上右括號的 minor mode 之類的。這點就只能自行注意。

什麼是 Kill-ring?

其實 Kill-ring 就是現在大家常說的剪貼簿(clipboard)啦。

在 Emacs 中,刪除文字的指令(例如 C-k, M-d )其實通常不會把文字真的刪除掉,而是預設會丟入 Kill-ring。

  • C-y 可以把最近一次被 Kill 掉的文字從 Kill-ring 給「拉 (Yank) 」出來。
  • 再按 N 下 M-y 可以把前 N 次被 Kill 掉的文字從 Kill-ring 中「拉」出來。