Skip to content

Instantly share code, notes, and snippets.

@t-sin
Last active July 27, 2020 06:14
Show Gist options
  • Save t-sin/cc0d036e40669395fd41cfd48bb9c997 to your computer and use it in GitHub Desktop.
Save t-sin/cc0d036e40669395fd41cfd48bb9c997 to your computer and use it in GitHub Desktop.
Lemのマルチフレーム対応とmux(tmux的な機能)までの道のり

プログラム中にコメントで残すようの英語版。

Lem's displaying concepts

Lem has some concept for displaying. This is a resuly of survey about them.

Display

Physical displaying area dealed by frontends; like ncurses, electron and so on. (For now) there is one display per one frontend. Lem doesn't touch displays directly but touch these via method like lem-if:display-width.

Source (for instance): /frontends/ncurses/ncurses.lisp

View

UI parts like minibuffers or popup windows or normal buffers, in the display(s?). They're defined in each frontend and Lem doesn't touch them directly.

Source (for instance): /frontends/ncurses/ncurses.lisp

Screen

Lem's low-level displaying area. Screens are correspond to views. Each screen has its position and size, and may have modeline.

Source: /lib/core/interface.lisp

Window

Lem's displaying area. Windows have its screen, buffer and view-point (maybe viewport).

Splitted area is implemented with windows and window-node structure. window-node has split type (:hsplit and :vsplit), car and cdr. Root area is stored in *window-tree* special variable. *window-tree* may be an window or an window-node, by which Lem manages nest of displaying area. If it's an window-node it means Lem's displaying area is splitted, when its child (car or cdr) is an window-node that child area is also splitted, and so forth...

Source: /lib/core/window.lisp

Frame

It does not exists as data structure for now, it is called frame that the visible area in running Lem. frame appears some place of /lib/core/window.lisp as arguments of adjust-windows. This function seems to do a process when frame size is changed, e.g. terminal resizing.

Lemの表示領域に関する用語整理

Lemは表示領域に関していくつかの用語がある。lemscreen(仮称)の実装にあたってまずそれの言葉を整理してみた。フロントエンド側から攻めたのでその順に並んでいる。

ディスプレイ

ncursesやelectronなど、フロントエンドにおいて実際に表示を担当するもの。 (現時点では)1フロントエンドにつき1ディスプレイっぽい。 Lem自身は直接こいつを叩かず、lem-if:display-widthとかを経由して操作する。

ソースコード(たとえばncurses): /frontends/ncurses/ncurses.lisp

ビュー

ミニバッファやポップアップウィンドウ、通常のバッファなど、ディスプレイにおけるUIの各部品。 Lemから直接操作されることはない(ディスプレイと同じ)。

ソースコード(たとえばncurses): /frontends/ncurses/ncurses.lisp

スクリーン

Lemの低レベルな表示領域。 各スクリーンはどれかのビューに対応している。 各スクリーンは画面中の位置(単位: 文字)やサイズなどの情報を持ち、モードラインがある場合もある。

ソースコード: /lib/core/interface.lisp

ウィンドウ

Lemの表示領域。いつも見ているのはこれ。 自身のスクリーンやバッファ、あとview-point (たぶんバッファ中の見えてる範囲)とかの情報を持つ。

縦や横に分割された表示領域はウィンドウとwindow-nodeという構造体で実現されている。 window-nodeは分割種別とcar、cdrのスロットを持ち、ルートのウィンドウ (もしくはwindow-node)は*window-tree*に格納されている。 *window-tree*window-nodeだったばあい画面は分割されているということになる。 その子がさらにwindow-nodeだったら、さらに分割されているというぐあい。

ソースコード: /lib/core/window.lisp

フレーム

現時点でデータ構造としては存在しないけど、Lem実行中に見えているLemの画面全体をこう呼ぶ。 名前としては<lem>/lib/core/window.lispadjust-windows関数の引数などに登場する。 この関数はだいたい、たとえばターミナルのリサイズなどによってフレーム(つまり画面全体)のサイズが変わったときの処理を担当してそう。

lemscreen(仮称)をやる前にまずフレームを定義してマルチフレーム対応をしたほうがよさそうな気がした。


このようなかんじ?

Lemのウィンドウが起動時にどんなかんじで生成されるのかわからなかったので調べた。

Lem起動の流れ

エントリポイント lem

roswell経由の場合、/lib/core/lem.lispmainが呼ばれる(roswellの形に合わせてる)。その中でlem/lib/core/lem.lisplemを呼んでる。このlem関数がほんとうのエントリポイント。

中では設定の読み込みをしたあとフロントエンドを起動する(invoke-frontend)。 このときエディタ用スレッドを起動する関数(run-editor-thread)をlambdaに包んでinvoke-frontendに渡してるので、エディタ用スレッドの作成やLem自体の初期化はその中で(適切なタイミングで)行われるもよう。

フロントエンドの起動 invoke-frontend

invoke-frontend/lib/core/interface.lispで定義された関数でlem-if:invokeメソッドを呼ぶだけのもの。このメソッドはimplementationクラスのインスタンスを引数に取りこれはフロントエンドの種類のこと。つまりlem-if:invokeで実際に行われる関数は各フロントエンドの初期化プロセスということになる。

画面の初期化等が終わったときにウィンドウとかを生成して設定しているのだろうなー。

フロントエンドの初期化処理 lem-if:invoke

ここではncursesフロントエンドを例に見てみる。ncursesフロントエンドにおいてlem-if:invokeのメソッドは/lem/frontends/ncurses/ncurses.lispで定義されている。

やっていることはざっくりとこんな感じ:

  1. ncursesのターミナルを初期化する
  • ちなみにncurses関連のコードは同ディレクトリのterm.lispにある
  1. 標準出力用のストリームを別途つくって*standard-output*などの名前で保持(実質上書き)
  2. lem関数でlambda式に包まれたrun-editor-threadを呼んで、エディタスレッドを走らせる
  • たぶん、ターミナル初期化がないとlemの画面がある前提の処理(幅の取得など?)が動かないのでここまで遅らせているのではないかなあ
  • 次の説でこの中身は追う
  1. 起動したエディタスレッドオブジェクトを持ったままinput-loopを回す
  • もしなんかエラーとか:abortイベント(たぶんC-c C-xみたいな)があったらエディタスレッドを止めて抜ける
  1. もしここまででエラーがでても、unwind-proectでncursesのターミナルの終了処理が走るのでターミナルエミュレータが壊れない

エディタスレッドの起動 run-editor-thread

run-editor-thread (/lib/core/lem.lisp)では、bordeaux-thread (いつも書けないボルドースレッヅ)を利用してスレッドを立ち上げる。ざっくりと以下のような感じ:

  1. フロントエンドから渡された初期化用関数(引数initializeで渡される)があったらそれを呼ぶ
  2. /lib/core/lem.lispsetupを呼ぶ
  • ウィンドウなんかの初期化はこのへんでやってそう
  1. /lib/core/interp.lisptoplevel-command-loopを呼び、キー入力受けつけループに入る
  • この奥にcommand-loopという関数があって、こいつがキーコマンドを処理していそうな
  1. ルーブを抜けたら(たぶんC-c C-x)、フロントエンドから終了時関数(引数finalize)があればそれを呼ぶ
  2. /lib/core/lem.lispteardownを呼ぶ
  • そのなかにteardown-windowsとかあるので見ておくといいかもしれない

エディタのセットアップ処理 setup

/lib/core/lem.lispsetup関数。ミニバッファを用意したりウィンドウをいろいろ用意したり、あとはデフォルトのさまざまなフック (*window-scroll-functions*はスクロール動作 C-vとかの動き)が設定されたりする。

ここではウィンドウのことが知りたいのでsetup-minibuffersetup-windowsを詳しく見ていく。

ミニバッファの初期化 setup-minibuffer

ミニバッファに表示する用の2つのバッファ*minibuffer-buffer**echoarea-buffer*を初期化し、ミニバッファウィンドウを*minibuf-window*に格納する。 現状ミニバッファはグローバルに一個しかないということ。

ウィンドウの初期化 setup-windows

いっこのウィンドウ(画面のサイズの)を作成しlem-if:set-first-viewして、グローバルのwindow-treeに設定する。 window-treeもグローバルにいっこしかない。

lem-if:set-first-viewがわからない。setup-windows内で呼ばれてるんだけど、実装しているフロントエンドがCAPIしかない…。どうして落ちないの…。 作者より「set-first-viewはcapi固有の初期化をこのタイミングでしたい為だけに用意した」という回答あり。

Lem終了の流れ

C-c C-xを押したときの流れ。エディタスレッドの起動 'run-editor-thread'

エディタの終了処理 teardown

teardown-windowsを呼ぶだけだった。

ウィンドウの終了処理 teardown-windows

*window-tree*の各ウィンドウについて%free-windowmapcしている。

ウィンドウの開放 %free-window

やっていることは3つ。

  1. ウィンドウのview-pointを消す (delete-point (window-view-point window))
  2. ウィンドウのポイントを消す (delete-point (%window-point window))
  3. スクリーンを消す

疑問点:

  • view-pointって「見える範囲」ではなくポイント(カーソル)なの?

mux実装時注意点:

  • スクリーン消しは、別のフレームでは表示されてるかもしれないので、そのような箇所ではscreen-deleteしてはいけない
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment