カーソル移動の実装。無知を思い知る

TUI のファイラーまがい、というか単にディレクトリ間を移動してファイル一覧を閲覧できるようなものを C言語で作ってみようとしてカーソル移動の考え方に苦しむ。

カーソルの移動を実装しようとして四苦八苦する。

ただ単に上下に移動したらフォーカスが移動すればいいというような話ではないことに気づき愕然とする。

必要な変数が何かもよくわからずコードを書いては実行して試してみたが埒が明かない。

一度立ち止まってどういうことになっているのか考え直す。

仮に表示領域が10行分でファイルの数が20の場合。

pos:   ありのままの実際のポジション
head:  表示されるファイル群の1番上
tail:  表示されるファイル群の1番下
focus: フォーカスされる行 (> や 反転など)

カーソルが下がり続ける時
pos   0 -> 1  head  0 -> 0  tail 10 -> 10 fucus  0 ->  1
pos   1 -> 2  head  0 -> 0  tail 10 -> 10 fucus  1 ->  2
pos   2 -> 3  head  0 -> 0  tail 10 -> 10 fucus  2 ->  3
pos   3 -> 4  head  0 -> 0  tail 10 -> 10 fucus  3 ->  4
pos   4 -> 5  head  0 -> 0  tail 10 -> 10 fucus  4 ->  5
pos   5 -> 6  head  0 -> 0  tail 10 -> 10 fucus  5 ->  6
pos   6 -> 7  head  0 -> 0  tail 10 -> 10 fucus  6 ->  7
pos   7 -> 8  head  0 -> 0  tail 10 -> 10 fucus  7 ->  8
pos   8 -> 9  head  0 -> 0  tail 10 -> 10 fucus  8 ->  9
pos   9 -> 10 head  0 -> 0  tail 10 -> 10 fucus  9 -> 10
pos  10 -> 11 head  0 -> 1  tail 10 -> 11 fucus 10 -> 10
pos  11 -> 12 head  1 -> 2  tail 11 -> 12 fucus 10 -> 10
pos  12 -> 13 head  2 -> 3  tail 12 -> 13 fucus 10 -> 10
pos  13 -> 14 head  3 -> 4  tail 13 -> 14 fucus 10 -> 10
pos  14 -> 15 head  4 -> 5  tail 14 -> 15 fucus 10 -> 10
pos  15 -> 16 head  5 -> 6  tail 15 -> 16 fucus 10 -> 10
pos  16 -> 17 head  6 -> 7  tail 16 -> 17 fucus 10 -> 10
pos  17 -> 18 head  7 -> 8  tail 17 -> 18 fucus 10 -> 10
pos  18 -> 19 head  8 -> 9  tail 18 -> 19 fucus 10 -> 10
pos  19 -> 20 head  9 -> 10 tail 19 -> 20 fucus 10 -> 10

カーソルが上がり続ける時
pos  20 -> 19 head  10 -> 9  tail 20 -> 19 fucus 10 -> 9
pos  19 -> 18 head   9 -> 8  tail 19 -> 18 fucus  9 -> 8
pos  18 -> 17 head   8 -> 7  tail 18 -> 17 fucus  8 -> 7
pos  17 -> 16 head   7 -> 6  tail 17 -> 16 fucus  7 -> 6
pos  16 -> 15 head   6 -> 5  tail 16 -> 15 fucus  6 -> 5
pos  15 -> 14 head   5 -> 4  tail 15 -> 14 fucus  5 -> 4
pos  14 -> 13 head   4 -> 3  tail 14 -> 13 fucus  4 -> 3
pos  13 -> 12 head   3 -> 2  tail 13 -> 12 fucus  3 -> 2
pos  12 -> 11 head   2 -> 1  tail 12 -> 11 fucus  2 -> 1
pos  11 -> 10 head   1 -> 0  tail 11 -> 10 fucus  1 -> 0
pos  10 ->  9 head   0 -> 0  tail 10 -> 10 focus  0 -> 0                              
pos   9 ->  8 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   8 ->  7 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   7 ->  6 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   6 ->  5 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   5 ->  4 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   4 ->  3 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   3 ->  2 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   2 ->  1 head   0 -> 0  tail 10 -> 10 focus  0 -> 0
pos   1 ->  0 head   0 -> 0  tail 10 -> 10 focus  0 -> 0

さらに、一旦表示領域をはみ出す位置まで pos が進んだ後に、一旦上に戻して、また下に移動するときなどはまた話が変わってくる。

// 一部抜粋.
// j で下、 k で上に移動

    if (c == 'j' && pos < fcount - 1) {
      pos++;
      if (pos < max_visible) {
        focus++;
      } else if (pos >= max_visible - 1) {
        if (focus < max_visible - 1) {
          // pos が max_visibleを超えていて、focus が末端にないということは、一旦上に動いた後
          // head は変わらないまま、focus だけが変わる
          focus++;
        } else if (focus == max_visible - 1) {
          // pos が max_visibleを超えていて、focus が末端にあるということは、前回も末端にいたということ
          // focus をいじる必要はない
          head++;
        }
      }
    } else if (c == 'k' && pos > 0) {
      pos--;
      // 1周前の foucs が0でないということは focus は常に上に動いて良い
      if (focus != 0) {
        focus--;
      } else {
        focus = 0;
        head--;
      }
    }

ややこしい。