Skip to content

Latest commit

 

History

History
143 lines (101 loc) · 6.27 KB

08-第一次試著用Emacs Lisp寫出自己想要的功能.org

File metadata and controls

143 lines (101 loc) · 6.27 KB

本篇假設你已經讀過並理解前面所有內容,這裡不再囉唆解釋,直接切入正題。

好啦,現在來試試,要怎麼用 Emacs Lisp 寫出一點我們想要的功能吧?

需求

假設我現在想要 按一鍵(例如 F12 ),就可以直接開啟某個檔案 ,該從何下手呢?

找出需要的 API

當你不知道該用哪個 Emacs Lisp 的 API 時,當然可以直接 Google 查像是 “emacs lisp open file” 之類的關鍵字。但我現在要講的不是這方法,而是如何利用 Emacs 內建的 API 說明文件。

透過 key-binding 得知

那要怎麼辦?首先我們知道, C-x C-f 可以互動式地開啟某個檔案,那我想這可能有我們需要的線索吧?所以現在按下 C-h k 再按 C-x C-f ,以得知 C-x C-f 這個 key-binding 呼叫了哪個 function,你會看見跳出了一個如下的 buffer:

C-x C-f runs the command find-file, which is an interactive compiled
Lisp function in `files.el'.

It is bound to <open>, C-x C-f, <menu-bar> <file> <new-file>.

(find-file FILENAME &optional WILDCARDS)

Edit file FILENAME.
Switch to a buffer visiting file FILENAME,
creating one if none already exists.
Interactively, the default if you just type RET is the current directory,
but the visited file name is available through the minibuffer history:
type M-n to pull it into the minibuffer.

You can visit files on remote machines by specifying something
like /ssh:SOME_REMOTE_MACHINE:FILE for the file name.  You can
also visit local files as a different user by specifying
/sudo::FILE for the file name.
See the Info node `(tramp)File name Syntax' in the Tramp Info
manual, for more about this.

Interactively, or if WILDCARDS is non-nil in a call from Lisp,
expand wildcards (if any) and visit multiple files.  You can
suppress wildcard expansion by setting `find-file-wildcards' to nil.

To visit a file without any kind of conversion and without
automatically choosing a major mode, use M-x find-file-literally.

嗯嗯,我們得知了 C-x C-f 執行的是 find-file 這個指令,它的用途是 「Edit file FILENAME. Switch to a buffer visiting file FILENAME, creating one if none already exists.」這看起來就是我們要的東西。

eval 看看

文件中提到, find-file 的語法是 (find-file FILENAME &optional WILDCARDS) ,看起來我們只要餵給它第一個參數 FILENAME ,它應該就會幫我開啟這個檔案了吧?所以我們來試試看:

(find-file "~/hello.txt")

游標移動到括弧尾端、按下 C-x C-e 來 eval,Bingo!它真的幫我們開啟了 ~/hello.txt 這個檔案。

做成 function

跟 Common Lisp 一樣,要 define function 時都是用 defun 。來試試看:

(defun my-open-hello ()
  (find-file "~/hello.txt"))

eval 後,minibuffer 會顯示 my-open-hello ,那就是成功定義了這個 function 啦!來呼叫看看:

(my-open-hello)

嗯嗯,真的開啟了耶!

抓 key-binding 的真正名稱

來試試用前面設定檔教學時講過的 global-set-key 來綁定這個 function。

我們想用 F12 這個鍵來呼叫 my-open-hello ,但不知道 F12 該怎麼寫才能讓 Emacs 認得…所以現在按下 C-h k 再按 F12 ,你會看到 <f12> is undefined ,可以得知兩件事:

  1. F12 的寫法是 <f12>
  2. F12 還沒被使用!可以盡情自訂!

但如果跳出一個 buffer 顯示類似 <f12> runs the command balah-balah, which is a Lisp function. 的訊息,就要注意按鍵會被蓋過去的問題。

所以我們試試看:

(global-set-key (kbd "<f12>") 'my-open-hello)

呼叫吧!函數!

看起來都沒問題了,eval 後,現在按下 F12 ....這這這怎麼爆炸惹:

command-execute: Wrong type argument: commandp, my-open-hello

M-x 也會發現無法呼叫這個 function,怎麼回事呢?

這是初學時常犯的錯誤,因為我們忘記加上 (interactive) 來標記這個 function 可以被「互動式地使用」了,所謂 interactive function,就是可以透過 M-x ,或者綁定到按鍵上的 function。所以我們來加上:

(defun my-open-hello ()
  (interactive)
  (find-file "~/hello.txt"))

再 eval 一次…可以了耶!這就是我們要的功能啊!啊啊啊啊啊啊嘶~~~!

雖然這只是一個很簡單很簡單的例子,不過當你熟悉這個思路與流程後,再搭配 Google 搜尋,就可以很容易的堆砌出一點自己想要的小功能了。

再一個簡單範例:一鍵執行

上面的例子太沒用?那來一個應該很多人需要的範例功能:在 python-mode 中,一鍵 f5 執行目前的 Python 檔案!

就不再囉唆長篇大論,直接來 code:

(with-eval-after-load
    'python
  (define-key python-mode-map (kbd "<f5>")
    'run-buffer-with-python3-interpreter))
(defun run-buffer-with-python3-interpreter ()
  (interactive)
  (save-buffer)
  (shell-command (format "python3 %s" (file-name-nondirectory buffer-file-name)))
 )
  1. save-buffer 其實就是 C-x C-s 儲存目前 buffer 啦!
  2. buffer-file-name 是一個內建變數,它的值就是目前 buffer 檔案的絕對路徑。
  3. 餵給 file-name-nondirectory 一個絕對路徑的話,回傳值是該絕對路徑的 filename,也就是相對路徑。(其實可以直接讓 python3 執行絕對路徑啦,只是這裡當作例子讓你看更懂。)
  4. format 是有寫過程式的大家應該都很熟悉的東西,總之他在這的功能就只是造出 python3 FILENAME 這樣的字串而已。
  5. 最後 shell-command 呼叫外部 command 執行 python3 FILENAME 啦!執行的結果(stdout, stderr)會開一個新 buffer 顯示出來(如果內容只有兩三行則會只顯示在 minibuffer)。

你可能會問, shell-command 執行命令時的 pwd 是在哪裡?答案就是你目前的 buffer 路徑。所以這裡可以直接餵給 python3 相對路徑。

目前 buffer 的路徑則是由內建變數 default-directory 儲存。

別忘記,以上你都可以自行開個檔案 C-x C-e 試試。