iT邦幫忙

DAY 7
2

EMACS 新思維系列 第 7

[EMACS新思惟 第十天] Comint-mode:自製自己的交互式介面

昨天介紹了三種在 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'")

上一篇
[EMACS新思維 第九天] 在 Emacs 內運行 shell
下一篇
[EMACS新思維 第十一天] Elisp 入門(一):執行 elisp 程式碼
系列文
EMACS 新思維27

尚未有邦友留言

立即登入留言