Neovim/tmuxのペイン配置について

Neovim,tmuxのペイン配置をキーボード操作する方法を、同じI/Fで扱えるようにする。 NOTE: Neovimでは window 、tmuxでは pane だが、紛らわしいのでともに「Paneペイン」と呼ぶことにする。

サポートしたい機能

ペイン構造の表現方法

分割されたペイン群は、全二分木として表現する。 本来はn分木で表現することも可能だが、検討の結果二分木のほうが扱いやすいためそうする。

以下に例を示す。 Pane の各番号は分割ごとに新しいペインに次の順序を割り当てたものとする。

例1: 分割されてない状態

-- pane

例2: ヨコに1回だけ分割した状態

-- H
    +- pane
    +- pane

例3: タテヨコに分割した状態

  1. タテに分割
  2. 上のペインをヨコに分割
-- V
    +- H
    |   +- pane
    |   +- pane
    |
    +- pane

例4: ヨコに2回分割した状態

  1. ヨコに分割
  2. 右のペインを更にヨコに分割
-- H
    +- H
    |   +- pane
    |   +- pane
    |
    +- pane

データ表現の形

各ペインはツリー構造で表現されるが、各ノードは以下のような構造で表現できるだろう。 (便宜上TypeScriptで表現する)

type Node = Horizontal | Vertical | Pane

type Pane {
    kind: "pane";
    ... // その他のペインに関する情報群を持ちうる。PaneIDやWindowIDなど
}

type Horizontal {
    kind: "horizontal";
    first: Node;
    second: Node;
    ... // サイズなどの情報を持ちうる。未検討
}

type Vertical {
    kind: "vertical";
    first: Node;
    second: Node;
    ... // サイズなどの情報を持ちうる。未検討
}

var root Node

全てのHorizontal,Verticalfirst/secondnullableではない点に要注意(冒頭に述べた通り、全二分木であるため、欠落はない)。

操作

ペインの操作は、特別なモードを設けてその中で完結させる。 以下の各操作での状態を、ペインの図(HTML)と入れ子リストで表現している。

凡例は以下の通り:

pane Aフォーカスされた
ノードの最初の子
pane Bフォーカスされた
ノードの2番目の子
pane C
-- V
    +- H           <<╮  // フォーカスされたノード
    |   +- pane A    │  // フォーカスされたノードの子孫 (1st child)
    |   +- pane B    │
    |               ─╯  // フォーカスされたノードの終端
    +- pane C
pane Aフォーカスされた
末端ノード
pane B
-- H
    +- pane A   <<   // フォーカスされた末端ノード
    +- pane B        // フォーカス外のノード

操作モードの開始

操作モードに入ると、操作を開始したペインにそのままフォーカスされた状態から始まる。

-- H
    +- pane
    +- pane    <<

フォーカスの変更: 親

親のあるノードがフォーカスされているとき、親ノードへフォーカスを変更できる。 また、さらにその親ノードへと繰り返し(親がなくなるまで)フォーカスを変更できる。

-- V
    +- H
    |   +- pane
    |   +- pane   <<
    |
    +- pane

親ノードへフォーカスを変更した場合:

-- V
    +- H         <<╮
    |   +- pane    │
    |   +- pane    │
    |             ─╯
    +- pane

さらに親ノードへフォーカスを変更した場合:

-- V             <<╮
    +- H           │
    |   +- pane    │
    |   +- pane    │
    |              │
    +- pane        │
                  ─╯

フォーカスの変更: 子

子を持つノードがフォーカスされているとき、1つ目の子ノード、2つ目の子ノードにそれぞれ直接フォーカスを変更することができる。

-- V             <<╮
    +- H           │
    |   +- pane    │
    |   +- pane    │
    |              │
    +- pane        │
                  ─╯

1つ目の子にフォーカスを変更した場合:

-- V
    +- H         <<╮
    |   +- pane    │
    |   +- pane    │
    |             ─╯
    +- pane

2つ目の子にフォーカスを変更した場合:

-- V
    +- H
    |   +- pane
    |   +- pane
    +- pane       <<

フォーカスの変更: 兄弟

親のあるノードがフォーカスされているとき、兄弟要素へフォーカスを変更できる。 もう一度同じ操作を行えば、元のノードにフォーカスが戻る(全二分木なので、兄弟は常に2つである)

-- V
    +- H
    |   +- pane
    |   +- pane   <<
    +- pane

1回目の変更:

-- V
    +- H
    |   +- pane   <<
    |   +- pane
    +- pane

2回目の変更:

-- V
    +- H
    |   +- pane
    |   +- pane   <<
    +- pane

フォーカス中のノードが末端のペインでなく子を持つノードだったとしても、同じ親を持つ兄弟間でフォーカスを変更できる。 もう一度操作すれば元のフォーカスに戻るのも同様である。

-- V
    +- H         <<╮
    |   +- pane    │
    |   +- pane    │
    |             ─╯
    +- pane

1回目の変更:

-- V
    +- H
    |   +- pane
    |   +- pane
    |
    +- pane       <<

2回目の変更:

-- V
    +- H         <<╮
    |   +- pane    │
    |   +- pane    │
    |             ─╯
    +- pane

ペインの分割

末端のノード(=ペイン)がフォーカスされている時、タテまたはヨコに分割することができる。

-- pane   <<

ヨコに分割した場合:

-- H
    +- pane   <<
    +- pane

タテに分割した場合:

-- V
    +- pane   <<
    +- pane

子があるノードにフォーカスしているときは、この操作はできないものとする。

-- V
    +- H         <<╮
        +- pane    │
        +- pane    │
                  ─╯

→分割できない

ペインの入れ替え

子を持つノードがフォーカスされているとき、子ノード同士を入れ替えることができる。

pane A
pane B
-- V
    +- H           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

入れ替え後:

pane B
pane A
-- V
    +- H           <<╮
        +- pane B    │
        +- pane A    │
                    ─╯

直接の子が末端ノード(ペイン)でなくても、入れ替え可能である。

pane A
pane B
pane C
-- V               <<╮
    +- H             │
    |   +- pane A    │
    |   +- pane B    │
    |                │
    +- pane C        │
                    ─╯

入れ替え後:

pane C
pane A
pane B
-- V               <<╮
    +- pane C        │
    |                │
    +- H             │
        +- pane A    │
        +- pane B    │
                    ─╯

末端ノード(ペイン)を選択しているときは、入れ替え操作はできないものとする。

分割方向の変更

子を持つノードがフォーカスされているとき、分割の方向を変更することができる。

pane A
pane B
-- V
    +- H           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

入れ替え後:

pane A
pane B
-- V
    +- V           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

もう一度変更すれば、もとに戻る:

pane A
pane B
-- V
    +- H           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

末端ノード(ペイン)を選択しているときは、分割方向の変更操作はできないものとする。

分割サイズの変更

子を持つノードがフォーカスされているとき、子ノード同士のサイズを変えることができる。 1つ目のノードを大きくするか、2つ目のノードを大きくするかの2択となる。 (区切り線を上下左右に動かすイメージ)

pane A
pane B
-- V
    +- H           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

1つ目のノードを拡大した場合:

pane A
pane B
-- V
    +- H           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

2つ目のノードを拡大した場合:

pane A
pane B
-- V
    +- H           <<╮
        +- pane A    │
        +- pane B    │
                    ─╯

フォーカスしたノードの直接の子が末端ノード(ペイン)でなくても、リサイズ可能である。

pane A
pane B
pane C
-- V               <<╮
    +- H             │
    |   +- pane A    │
    |   +- pane B    │
    |                │
    +- pane C        │
                    ─╯

1つ目のノードを拡大した場合:

pane A
pane B
pane C
-- V               <<╮
    +- H             │
    |   +- pane A    │
    |   +- pane B    │
    |                │
    +- pane C        │
                    ─╯

2つ目のノードを拡大した場合:

pane A
pane B
pane C
-- V               <<╮
    +- H             │
    |   +- pane A    │
    |   +- pane B    │
    |                │
    +- pane C        │
                    ─╯

末端ノード(ペイン)を選択しているときは、分割サイズの変更操作はできないものとする。