この記事では、Emacs 29.1で導入されたvtable.elについて紹介します。
vtable.el(以下vtable)は、動的で操作できる表のUIを簡単に作成できるライブラリです。
整列や列幅調整などの機能を標準装備しています。
似たパッケージとしては、同じく標準のtabulated-list-modeがあります。
基本的な使い方
……はEmacsのマニュアルに、端的にまとまっています(個人的に翻訳しかけたものもあるためよろしければ)。
ひとまず動かしてみましょう。
いろいろ試す上では専用のバッファがあるとよいと思うので、以下の雛形を用意します。
(let ((buf (get-buffer-create "*vtable-playground*")))
(with-current-buffer buf
(erase-buffer)
;; ここに書いていきます。
(insert "hello\n"))
(pop-to-buffer buf));^^)
次のようにして実行できます:
-
*scratch*バッファに貼り付け -
;^^)のセミコロンにポイント(カーソルみたいなもの)を持って行く -
C-x C-e(eval-last-sexp) で実行
実行すると、*vtable-playground*バッファに「hello」と表示されるはずです。
以降は ;; ここに書いていきます。 の部分のコードを示します。
最小限の例
(require 'vtable)
(make-vtable
:columns '("県名" "県庁所在地")
:objects '(("栃木県" "宇都宮市")
("山梨県" "甲府市")
("Loiret" "Orléans")))
以下のような表が表示されます。
| 県名 | 県庁所在地 |
|---|---|
| 栃木県 | 宇都宮市 |
| 山梨県 | 甲府市 |
| Loiret | Orléans |
:columnsには列の設定を書きます。
ここでは単に、列名のリストにしています。
:objectsには表示するデータを入れます。
ここでは単に、そのまま表示するデータを指定しています。
列の設定
県庁所在地の列を設定して、文字を逆にしてみましょう。
(make-vtable
:columns '((:name "県名")
(:name "県庁所在地"
:formatter reverse))
:objects '(("栃木県" "宇都宮市")
("山梨県" "甲府市")
("Loiret" "Orléans")))
「県名」は、同じ見た目ですが、'(:name "県名") のように冗長な書き方をしています。
「県庁所在地」はさらに複雑にして '(:name "県庁所在地" :formatter reverse) になっています。
このように、属性を足していくことで、柔軟に見た目や動きを調整できます。
主なAPI
なんとなくvtableのイメージが掴めたでしょうか。
よく使うAPIは、なにはともあれmake-vtable関数です。
主な引数について……
- 多くの場合、
:columnsを指定します。 - ここでの例はデータが固定だったため
:objectsでいいのですが、動的に取得して表示したいときは:objects-functionが便利です。 - データが複雑なときは、
:getterを使うときもあります。
実際にvtableを使うときは、マニュアルにあるインターフェース関数を使うことになります。
vtable-goto-table, vtable-beginning-of-table, vtable-end-of-tableなどです。
実装時のTips
ここでは実装時のコツといいますか、気付いたことをいくつか紹介します。
キーバインド
vtableの表にポイントがあるとき、:actionsや:keymapで指定されたキーバインドが使えます。
おそらく、:actionsが各行に対応するデータに対するもので、:keymapは表全体で使うものという棲み分けだと思います。
それはいいとして、問題はvtableを含むようなメジャーモードを作っているときです。
おそらくvtableを埋め込みたくなるメジャーモードは、special-modeを継承して読み専用にしたいはずです。
そしてそのバッファ全体に対してキーマップを追加したいときは、次のような感じにするとよさそうな気がしています。
(define-derived-mode your-awesome-mode special-mode "YourAwesomeMode"
"Your awesome mode (hopefully)."
(let ((inhibit-read-only t))
(erase-buffer)
(use-some-vtable-or-something))
(beginning-of-buffer)
(use-local-map (make-composed-keymap some-keymap special-mode-map)))
use-local-mapの行がミソです。
ちなみに、special-modeにそのままではvtableを書き込めないため、一時的にinhibit-read-onlyをtにする必要があります。
表の再描画
表を更新するとき、普通はvtable-revertやvtable-revert-commandを使うのだと思います。
しかしそれでも更新されないときは、腕力に訴えるよりなさそうです。
(defun some-awesome--revert-vtable (table inserter)
"現在ある表TABLEを、表を新たに挿入する関数INSERTERを使って再生成します.
`vtable-revert'の実装を参考にしています。"
(save-excursion
(vtable-goto-table table)
(let ((inhibit-read-only t))
(delete-region (vtable-beginning-of-table) (vtable-end-of-table))
(funcall inserter))))
この関数は既に存在するvtableの表と、新しく表を描く関数を取ります。
delete-regionのところで現在の表を消し去っているわけですね。
なお、save-excursionはポイントの位置を保存しておくためのマクロです。
間に入り込ませない
表のヘッダとデータ本体の間は、気を抜くと何かしら挟み込まれてしまうことがあります。
- 表の上に何かがあるとき
- 表の下に何かがあるとき
それぞれの場合を見てみましょう。
まず、表の上に何かがあるとき。
(insert "ほうとう\n")
(make-vtable
:columns '("県名" "県庁所在地")
:objects '(("栃木県" "宇都宮市")
("山梨県" "甲府市")
("Loiret" "Orléans")))
すると、以下みたいな感じになるはずです。
県名 県庁所在地
ほうとう
栃木県 宇都宮市
山梨県 甲府市
Loiret Orléans
こうなるのは、ヘッダがバッファの一番上に陣取ってこようとしているからです。
:use-header-line nilを使うと解消します。
(insert "ほうとう\n")
(make-vtable
:columns '("県名" "県庁所在地")
:objects '(("栃木県" "宇都宮市")
("山梨県" "甲府市")
("Loiret" "Orléans"))
:use-header-line nil)
次に、表の下に何かがあるとき。
(make-vtable
:columns '("県名" "県庁所在地")
:objects '(("栃木県" "宇都宮市")
("山梨県" "甲府市")
("Loiret" "Orléans")))
(insert "ほうとう\n")
これもやっぱり、ほうとうが注ぎ込まれてしまいます。
正解は(vtable-end-of-table)です。
要は、表を描き終えたら、ポイントを表の末尾に移動しておくわけです。
(make-vtable
:columns '("県名" "県庁所在地")
:objects '(("栃木県" "宇都宮市")
("山梨県" "甲府市")
("Loiret" "Orléans")))
(vtable-end-of-table)
(insert "ほうとう\n")
まとめ
vtableを使うことで、Emacs上で簡単に表のUIを実装できます。
パッケージ開発やカスタムコマンドの作成に活用してみてください。
Copyright (c) 2025 gemmaro.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
The license is located at <https://www.gnu.org/licenses/fdl-1.3.html>.