昨天介紹了三種在 emacs 內使用 shell 的方式,今天要來介紹如何自己打造一個交互式界面。
(不好意思,因爲最近有點忙,本來是要先講 elisp 概論,但是時間關係,先拿之前寫的文章充場面。如果有朋友這篇看不太懂,請等下幾次的 elisp 概論介紹文出來看過之後,才來重讀!)
Emacs 的動態語言編輯有一個很有用的能力,就是他會提供交互式界面,供使用者程式打了一個段落之後,方便的送進去執行。不管是 python, perl, ruby,還是 sql 資料庫,都有提供!本 javascript 也是一個例子。實際展開每個語言的交互式界面,還要看該語言的 mode 怎麼設定哦!
假設我們現在要在 Emacs 中執行 node.js 的交互式介面。什麼!竟然沒有 run-node !(按:其實有 js-comint ,可以設定 node 當解釋器,但是讓我們假裝驚奇一下)
首先我們打開一個javascript檔案(懶的弄一個新的,用網路上不怎麼樣的質因數分解程式代替),並且 M-x run-node 把 *Node* 啟動。
開啟javascript檔案,並且也已經執行run-node
注意到我沒有寫這個功能「就算node沒啟動,只要send-buffer也會把它召喚出來」 (功力不夠),所以若沒先啟動 *Node* 會報錯。之後按下 C-c C-c (預設node-send-buffer)把整個 buffer 送到 *Node* 的指令。底下是執行效果:
成功的把程式碼送到 *Node* 並執行
底下慢慢講解,說明請看註釋,整份elisp請到此 GIST 下載。
首先設定環境變數,可以由使用者按照需求自訂,而不需要修改程式。
;; 宣告背景程式的路徑
(defvar node-file-path "/usr/bin/node"
"Path to the program used by `run-node'")
;; 宣告背景程式所需要的參數(arguments)
(defvar node-arguments '()
"Commandline arguments to pass to `node'")
;; 設定提示符號格式,因為node的比較簡單,就不需要複雜的regexp
(defvar node-prompt-regexp "^>\s"
"Prompt for `run-node'.")
撰寫啟動部份:包含 run-node 主程式,以及 node-mode 的細節定義。
;; 這坨代碼的目的是:重新啟動node程式 (假如正在該*Node*,且處於未活化狀態的話)
;; 或者創造它(假如*Node*)不存在的話。它的核心是apply那個語句。
;; Run-node
(defun run-node ()
"Run an inferior instance of `node.js' inside Emacs."
(interactive)
(setq node-buffer "*Node*")
(let* ((node-program node-file-path)
(buffer (comint-check-proc "Node")))
;; pop to the "*Node*" buffer if the process is dead, the
;; buffer is missing or it's got the wrong mode.
(pop-to-buffer-same-window
(if (or buffer (not (derived-mode-p 'node-mode))
(comint-check-proc (current-buffer)))
(get-buffer-create (or buffer "*Node*"))
(current-buffer)))
;; create the comint process if there is no buffer.
(unless buffer
(apply 'make-comint-in-buffer "Node" buffer
node-program node-arguments)
(node-mode))))
;; 關於 node-mode (由 run-node 呼叫)的細節設定
(define-derived-mode node-mode comint-mode "Node"
nil "Node"
(setq comint-process-echoes t)
(setq comint-use-prompt-regexp t)
(setq comint-prompt-regexp node-prompt-regexp)
(setq comint-prompt-read-only t) ; 設定提示符號「> 」為只讀
(set (make-local-variable 'paragraph-separate) "..'")
;; 底下可以讓使用者以 M-{ 及 M-} 在各段落間移動
(set (make-local-variable 'paragraph-start) node-prompt-regexp))
最後補強一般javascript文件與*Node*之間的關係,就是可以把編輯中文件的代碼送入到 *Node* 中並執行。礙於技術實力,有些可以把程式碼送過去 *Node* 之後並把 focus 轉移過去。另外簡單設定了Keymap。
;; 將內容送往解釋器
;; 送一個反白區域
(defun node-send-region (start end)
"Send the current region to the inferior Javascript process."
(interactive "r")
(comint-send-region node-buffer start end)
(comint-send-string node-buffer "\n"))
;; 重新送出上次段落?
(defun node-send-last-sexp ()
"Send the previous sexp to the inferior Javascript process."
(interactive)
(node-send-region (save-excursion (backward-sexp) (point)) (point)))
;; 將整個 buffer 送出
(defun node-send-buffer ()
"Send the buffer to the inferior Javascript process."
(interactive)
(node-send-region (point-min) (point-max)))
(defvar node-mode-map
(let ((map (nconc (make-sparse-keymap) comint-mode-map)))
(local-set-key (kbd "C-x C-e") 'node-send-region)
(local-set-key (kbd "C-x C-b") 'node-send-buffer)
map)
"Basic mode map for `run-node'")