はじめに
この記事は Emacs Advent Calendar 2017 - Qiita の 17 日目の記事です。
想定する読者
- 簡単な Emacs Lisp プログラミングができる
- 特定のデータを一覧できるメジャーモードをシュッっと作りたい
tabulated-list-mode のご紹介
Emacs には tabulated-list-mode というメジャーモードがあります。
tabulated-list-mode は簡単にいうと表形式の表示を行い、ソートなどの基本的な操作を提供するメジャーモードです。また、直接呼び出して使うものではなく、特定のメジャーモードのベースとして使うものになります。
例えば M-x list-processes
と打つとプロセス一覧のバッファが、 M-x list-packages
とするとパッケージ一覧が表示されますが、これらは tabulated-list-mode を親モードとしたメジャーモードです。
- list-processes
- list-packages
他にも、Gist を扱う defunkt/gist.el というパッケージなども tabulated-list-mode をベースにしています。
tabulated-list-mode のカラム表示で、 [v]
と表示されているカラムが現在ソートされているカラムになります(この場合は昇順)。
ソートを行いたいカラムまでカーソル移動して S
を押すとそのカラムでソートされます。また、ソート済みカラムで S
を押すと昇順・降順がトグル表示されます(降順の場合は [^]
)
詳細はマニュアルの Tabulated List Mode - GNU Emacs Lisp Reference Manual に書いてありますので、必要に応じてご一読ください。
tabulated-list-mode をベースとしたメジャーモードの作成方法
ここでは例として process-menu-mode (Emacs 25.3 版) での設定内容を説明してみます。ソースを抜粋すると、
(define-derived-mode process-menu-mode tabulated-list-mode "Process Menu"
"Major mode for listing the processes called by Emacs."
(setq tabulated-list-format [("Process" 15 t)
("Status" 7 t)
("Buffer" 15 t)
("TTY" 12 t)
("Command" 0 t)])
(make-local-variable 'process-menu-query-only)
(setq tabulated-list-sort-key (cons "Process" nil))
(add-hook 'tabulated-list-revert-hook 'list-processes--refresh nil t)
(tabulated-list-init-header))
define-derived-mode は次のような引数を取る関数なので、
(define-derived-mode CHILD PARENT NAME &optional DOCSTRING &rest BODY)
(define-derived-mode メジャーモード名 tabulated-list-mode 表示名
説明
:
)
のように記述します。
tabulated-list 関連の変数・関数
マニュアル により詳しく書いてありますが、ざっと説明すると以下のような変数・関数を利用します。
Variable: tabulated-list-format
カラム表示の設定になります。 (カラム名 幅 ソート可否)
のベクターをセットします。
Variable: tabulated-list-sort-key
初期ソートを行うカラム(のリスト)をセットします。
Variable: tabulated-list-revert-hook
再描画を行う際の hook です。ここでは list-processes--refresh
が登録されています。
list-processes--refresh
は次のような関数です。
ちょっと長いのですが、ここで tabulated-list-entries
をセットしているのにご注目ください。
(defun list-processes--refresh ()
"Recompute the list of processes for the Process List buffer.
Also, delete any process that is exited or signaled."
(setq tabulated-list-entries nil)
(dolist (p (process-list))
(cond ((memq (process-status p) '(exit signal closed))
(delete-process p))
((or (not process-menu-query-only)
(process-query-on-exit-flag p))
(let* ((buf (process-buffer p))
(type (process-type p))
(name (process-name p))
(status (symbol-name (process-status p)))
(buf-label (if (buffer-live-p buf)
`(,(buffer-name buf)
face link
help-echo ,(format-message
"Visit buffer `%s'"
(buffer-name buf))
follow-link t
process-buffer ,buf
action process-menu-visit-buffer)
"--"))
(tty (or (process-tty-name p) "--"))
(cmd
(if (memq type '(network serial))
(let ((contact (process-contact p t)))
(if (eq type 'network)
(format "(%s %s)"
(if (plist-get contact :type)
"datagram"
"network")
(if (plist-get contact :server)
(format "server on %s"
(or
(plist-get contact :host)
(plist-get contact :local)))
(format "connection to %s"
(plist-get contact :host))))
(format "(serial port %s%s)"
(or (plist-get contact :port) "?")
(let ((speed (plist-get contact :speed)))
(if speed
(format " at %s b/s" speed)
"")))))
(mapconcat 'identity (process-command p) " "))))
(push (list p (vector name status buf-label tty cmd))
tabulated-list-entries))))))
Variable: tabulated-list-entries
これが表示対象のメインデータです。
(id [tabulated-list-format に設定したカラム順のベクター])
という形式のリストになります。
また、マニュアル には出てきませんが、カラム表示画面で (tabulated-list-get-id)
を呼び出すと現在行の id
を取得することができます。
Function: tabulated-list-init-header
カラムのヘッダを初期化します。 tabulated-list-format
のセットより後に呼び出される必要があります。
作成したメジャーモードの呼び出し
list-processes
本体は次のような関数です。同様の関数を定義して provide
すればオリジナルメジャーモードの完成です。
(defun list-processes (&optional query-only buffer)
"Display a list of all processes that are Emacs sub-processes.
If optional argument QUERY-ONLY is non-nil, only processes with
the query-on-exit flag set are listed.
Any process listed as exited or signaled is actually eliminated
after the listing is made.
Optional argument BUFFER specifies a buffer to use, instead of
\"*Process List*\".
The return value is always nil.
This function lists only processes that were launched by Emacs. To
see other processes running on the system, use `list-system-processes'."
(interactive)
(or (fboundp 'process-list)
(error "Asynchronous subprocesses are not supported on this system"))
(unless (bufferp buffer)
(setq buffer (get-buffer-create "*Process List*")))
(with-current-buffer buffer
(process-menu-mode)
(setq process-menu-query-only query-only)
(list-processes--refresh)
(tabulated-list-print))
(display-buffer buffer)
nil)
関数 list-processes
では、バッファ "*Process List*"
を作成して、メジャーモード (process-menu-mode)
の呼び出しを行い、 (list-processes--refresh)
で tabulated-list-entries
にデータをセットしたのち、 (tabulated-list-print)
で描画を行い、最後にそのバッファへ切り替えを行っています。
Function: tabulated-list-print
事前に設定された tabulated-list-entries
を tabulated-list-sort-key
によってソートして出力します。引数については マニュアル をご参照ください。
おわりに
以上が tabulated-list-mode のご紹介でした。
表形式データを扱うことはよくあると思われるので、お手軽にメジャーモードを作る時に参考になれば幸いです。
余談
実は、本エントリは tabulated-list-mode
をベースにした自作パッケージの紹介に繋げるつもりだったのですが、本エントリ公開までに細かい修正&公開が間に合わなかったので、そのご紹介は次回に延期することにしました…