Common Lisp製のテキストエディタ
Lemにフレーム多重化機能をつくった
t-sin (@sin_clav)
2020-08-27, lispmeetup #89
もくじ
●
Lemについて
●
フレーム多重化機能について
●
Lemの描画関連用語について
●
フレーム多重化機能対応でやったこと
Lemってなんだ?
●
Common Lisp製テキストエディタ
Lemってなんだ?
●
Common Lisp製テキストエディタ
●
https://github.com/cxxxr/lem
●
GNU Emacsのような操作感
●
vi-modeもある
●
Common Lispとの統合がよい
フレーム多重化機能って?
●
tmux
●
あるいはGNU Emacsのelscreen
●
画面中のバッファ配置を複数記憶しそれらを
切り替えられる機能
●
つまりtmux
フレーム多重化機能って?
frame-multiplexerの
ヘッダウィンドウ
#付きが現在のフレーム
Lemの描画関係用語
(ユーザが操作するもの)
ウィンドウ
モードライン
バッファ
ミニバッファ/エコーエリア
floatingウィンドウ
Lemの描画関係用語
(ユーザが操作するもの)
●
ウィンドウ
– 編集領域。C-x oで切り替えているアレ
– window-tree構造体により2分木で分割を表現
●
スクリーン
– 各バッファの表示を抽象的に担当する構造体
●
裏に具体的な描画を担当する「ビュー」がある(後述)
– バッファとは1対他の関係(同じバッファを複数表示)
●
バッファ
– 編集内容を保持している構造体
– キー入力するとバッファの中身が書き換えられる
Lemの描画関係用語
(実際に描画するためのもの)
●
ディスプレイ
– 描画している画面
– たとえば端末エミュレータなど
●
ビュー
– スクリーンを実際の画面に表示するためのもの
– スクリーンとは1対1の関係
●
フロントエンド
– ディスプレイやビューの定義と描画を担当する
– ncursesやelectronなど
Lemの描画関係用語
(実際に描画するためのもの)
Lem上のデータ構造 フロントエンド
ビュー
ビュー
ビュー
ビュー
ディスプレイ
ウィンドウ
とスクリーン
ウィンドウツリー
フレーム多重化機能対応でやったこと
●
「フレーム」の概念をLemに追加
– ウィンドウ(つまり画面構成)を保持するもの
●
Lemの描画処理をフレームに対して働くよう変更
– 起動時に1個のフレームを作成しておく
●
フレームを複数持てるように拡張
– 仮想フレームって呼ぶことにした
●
切り替えコマンドを追加
– 大事
フレーム多重化機能対応でやったこと
●
「フレーム」の概念をLemに追加
– ウィンドウ(つまり画面構成)を保持するもの
●
Lemの描画処理をフレームに対して働くよう変更
– 起動時に1個のフレームを作成しておく
●
フレームを複数持てるように拡張
– 仮想フレームって呼ぶことにした
●
切り替えコマンドを追加
– 大事
フレームの概念を追加
●
画面表示関係のグローバル変数を集約
●
frame構造体を作成
●
以下のものを持つ
– 現在操作しているウィンドウ
– ウィンドウ構成(分割状態)
– ミニバッファのウィンドウ
– ミニバッファのバッファ
– 補完時に出てくるウィンドウ
など…
フレームの概念を追加
Lem上のデータ構造 フロントエンド
ディスプレイ
フレーム
フレームの概念を追加
●
frame構造体
– 現在の「ウィンドウ」
– ウィンドウ構成(window-tree)
– ミニバッファ関連
–
補完時のfloatingウィンドウ
– ヘッダウィンドウ
– ウィンドウまわりのフラグ
(defstruct frame
;; window
current-window
(window-tree nil)
(floating-windows '())
(header-windows '())
(modified-floating-windows nil)
(modified-header-windows nil)
;; minibuffer
minibuffer-buffer
echoarea-buffer
(minibuffer-window nil)
(minibuffer-calls-window nil)
(minibuffer-start-charpos nil))
フレーム多重化機能対応でやったこと
●
「フレーム」の概念をLemに追加
– ウィンドウ(つまり画面構成)を保持するもの
●
Lemの描画処理をフレームに対して働くよう変更
– 起動時に1個のフレームを作成しておく
●
フレームを複数持てるように拡張
– 仮想フレームって呼ぶことにした
●
切り替えコマンドを追加
– 大事
フレームに対する描画処理
●
Lemの起動時に1個のフレームを作成
●
グローバル変数を使う処理が現在のフレームに
対して働くようにぜんぶ変更
●
動作確認
– 大事
フレームに対する描画処理(変更例)
●
floating-windowに対する削除処理
– 前: グローバルな変数に対してsetf
– 後: グローバルなフレームのスロットにsetf
(defmethod %delete-window ((window floating-window))
(when (eq window (current-window))
(editor-error "Can not delete this window"))
- (setf *modified-floating-windows* t)
- (setf *floating-windows*
- (delete window *floating-windows*)))
+ (setf (frame-modified-floating-windows (current-frame)) t)
+ (setf (frame-floating-windows (current-frame))
+ (delete window (frame-floating-windows (current-frame)))))
ここまでのPR
●
https://github.com/cxxxr/lem/pull/500
フレーム多重化機能対応でやったこと
●
「フレーム」の概念をLemに追加
– ウィンドウ(つまり画面構成)を保持するもの
●
Lemの描画処理をフレームに対して働くよう変更
– 起動時に1個のフレームを作成しておく
●
フレームを複数持てるように拡張
– 仮想フレームって呼ぶことにした
●
切り替えコマンドを追加
– 大事
フレームを複数持てるよう拡張
●
やること
– 「仮想フレーム」の用意
●
フレームを複数保持するスロット
●
現在表示中のフレームを置くスロット
– 仮想フレームの描画処理の実装
フレームを複数持てるよう拡張
●
仮想フレームの用意
– フレームの配列
– 現在のフレーム
– ディスプレイ幅・高さ
– 仮想フレームが変更されたかフラグ
– 仮想フレームのヘッダ用バッファ
●
(いまvfはvirtual-frameにリネームされてます)
●
ディスプレイ-仮想フレームの対応表も追加
– ただのハッシュテーブル
– キーはディスプレイ (現在は1つしかない)
– Lemが複数の表示画面を持つときのため
(defclass vf (header-window)
((frames)
(current)
(display-width)
(display-height)
(changed)
(buffer)))
フレームを複数持てるよう拡張
これが仮想フレーム
兼
ヘッダウィンドウ
フレームを複数持てるよう拡張
●
vfはLemのヘッダウィンドウなので
●
Lemのウィンドウ再描画メソッドを実装する必要あり
●
仮想フレームの描画処理
– ヘッダを描画
– 現在のフレームのウィンドウをぜんぶ描画
– 仮想フレームの描画フラグをnilに設定
(defmethod window-redraw ((window vf) force)
;; draw button for frames
...
;; redraw windows in current frame
...
;; clear all vf-changed to nil because of applying redraw
...
(call-next-method))
フレーム多重化機能対応でやったこと
●
「フレーム」の概念をLemに追加
– ウィンドウ(つまり画面構成)を保持するもの
●
Lemの描画処理をフレームに対して働くよう変更
– 起動時に1個のフレームを作成しておく
●
フレームを複数持てるように拡張
– 仮想フレームって呼ぶことにした
●
切り替えコマンドを追加
– 大事
操作の実装とコマンドの定義
●
基本的なフレーム操作
– フレーム多重化機能の起動・終了処理
– フレームの追加・削除
– フレームの切り替え
●
どの操作でも最後にvf-changedをtにする
– 現在のフレームを変更した後に再描画させるため
●
操作用コマンド
– toggle-frame-multiplexer
– frame-multiplexer-create (C-z c)
– frame-multiplexer-delete (C-z d)
– frame-multiplexer-prev (C-z p)
– frame-multiplexer-next (C-z n)
操作の実装とコマンドの定義 (例)
●
仮想フレーム上のフレーム切り替え(前へ)
– 仮想フレーム上の1つ前のフレームを探し、
– そのフレームを現在のLemのフレームに設定し、
– 再描画フラグをtに設定する。
(define-command frame-multiplexer-prev () ()
(check-frame-multiplexer-enabled)
(let* ((vf (gethash (implementation) *virtual-frame-map*))
(frame (search-previous-frame vf (virtual-frame-current vf))))
(when frame
(setf (virtual-frame-current vf) frame)
(lem:map-frame (implementation) frame))
(lem::change-display-size-hook)
(setf (virtual-frame-changed vf) t)))
ここまでのPR
●
https://github.com/cxxxr/lem/pull/501
このPRのときはfm-mode
という名前でした。
(いまはframe-multiplexer)
こぼれ話
●
実装にあたってLemのコードを調査した
– よくできててかなり勉強になった
– 描画の用語の整理結果はWikiに記載した
https://github.com/cxxxr/lem/wiki/Lem's-displaying-concepts
– 起動時の流れも調べた
https://gist.github.com/t-sin/cc0d036e40669395fd41cfd48bb9c997
●
最初はfm-modeという名前で作っていたが
Lemのdefine-modeを使っておらず実はモードではなかった
– frame-multiplexerに名称変更
– lem/modes/fm-mode/ から lem/lib/core/frame-multiplexer.lisp に
– 地味にcoreに取り込まれた🎊
こぼれ話
●
現在はバッファリスト(C-x bの対象)が共有されている
– elscreenの動き
– cxxxr氏より「作業を分けるためバッファリスト非共有がいい」
– ノリノリで雑ハックし、激しいバグを生む
●
PR: https://github.com/cxxxr/lem/pull/504
●
Lemの非公開関数でグローバル変数をもりもり変更するマナーの悪いコードだっ
たのでいったんクローズ
●
事前に設計をちゃんとしましょう
●
ちなみにGNU Emacsで仮想フレームに対応するものは
window-configurationというのだそう
– https://www.gnu.org/software/emacs/manual/html_node/elisp/Window-Configurations.html
– @conao_3 さんに教えてもらいました
●
https://twitter.com/conao_3/status/1291710740294901760
まとめ
●
Lemにフレーム多重化機能を実装した
●
Lemは読むとCommon Lispの勉強になる
– clrhash関数など
– http://www.lispworks.com/documentation/HyperSpec/Body/f_clrhas.htm
●
Lemは大きなソフトウェアなのでPRを投げると
Common Lispチョットデキル…という気持ちになれる
●
事前の設計は大事

Common Lisp製のテキストエディタLemにフレーム多重化機能をつくった