超軽量。その名は hairline（statusline と tabline の設定プラグイン）

まずは記事掲載に遅れたことをお詫び申し上げます。
この記事は [Vim Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vim) その2 の 23日目の記事です。

statusline と tabline を設定できるプラグインを作りました
→ **[seroqn/vim\-hairline](https://github.com/seroqn/vim-hairline)**


参考：[itchyny/lightline\.vim](https://github.com/itchyny/lightline.vim)：偉大なる先達。

## 動機
初めはタブライン tabline だけ設定するプラグインを作っていました。タブページの数が増えてくるとタブラインの表示が圧迫され、まともに表示できないことがあったのが気になったからです。私の考えでは現在表示しているタブページと、その両隣のタブページさえ示せれば実用に足ると考えています（タブにめちゃくちゃ長い文字列を表示させるなんてことをしない限りは）。
そしてそういう表示をしてくれるプラグインがなかったので、自作しました。そして tabline についてだけ作ったら、結局設定方法が似ているステータスライン statusline も設定できるようにしたい魔力に襲われたので、結局ステータスラインについても実装してしまいました。

## コンセプト
- 起動時に低負荷。設定を全て `vimrc` に書けば起動時に読み込まれるのは `plugin/` 以下にある1ファイルだけにする[^noautoload]。
- 実行時もなるべく低負荷
- 高カスタマイズ性
- コンパクトな設定表記
  - プレフィクスによって特別な部品やハイライト指定を通常の部品に混ぜて記述できるようにした
- 自分が使う機能以外は削いだ。他の似た既存の plugin より低機能[^lowfeature]かもしれない。その分低負荷だと思われる[^lowload]。
    - 多分この低負荷さを大幅に崩すような機能は追加しないつもり。

[^noautoload]: ほぼ必ず Vim起動時に読み込まれるものを autoload 化するべきではないよねという思想。
[^lowfeature]: たとえば、リプレイス（R)モードに入ったことを認識できない。他のプラグインはどうやって認識しているのだろう。
[^lowload]: ウィンドウを移るたびに全てのウィンドウの statusline を再設定するようなことはしない。ステータスラインの再設定は抜けるウィンドウと入ったウィンドウのものだけに対して行われる。

## できないこと
自分の使う機能しか実装していないのと、技術的理由、軽量化的理由から、以下の機能はありません。

- パーツとパーツの間を区切るセパレーター文字 "|" の設定
- フォントにパッチを当てて powerline や airline や lightline のように三角形▶をセパレーターとして使うこと
- リプレイスモード（ノーマルモードで `R` 押打で入るモード）の認識

## こんなやつです
`g:hairline` という辞書変数を定義します。この変数に `statusline` という辞書を定義すればステータスラインが、`tabline` という辞書を定義すればタブラインが有効になります。
（どちらかを無効にしたければそれを定義しないか、`statusline.disable` `tabline.disable` を非0 に設定すればそれを無効にできます）

```vim:とりあえず空の辞書を.vimrcに定義すればデフォルト設定が読み込まれます
let g:hairline = {'statusline': {}, 'tabline': {}}
```
![vim-hairline1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17558/a4f94bd9-8cad-62c4-1458-b768d4643335.png)

powerline や lightline をパクった外見を初期状態に設定させていただきました。

![vim-hairline2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17558/ab91245c-f58e-2a2c-0615-0005441c2792.png)

タブページを複数開いても現在タブとその両隣のタブしか表示しません。デフォルトではタブリストの右にタブ総数と現在のタブ番号を数字で表示しています。
タブラベルの表示領域を潤沢に使える余裕があります。


## ステータスラインの設定方法
#### 部品の指定
`statusline.left` `statusline.right` `tabline.left` `tabline.right` に部品（パート）を並べていきます。これらは文字列のリストです。

```vim
let g:hairline.statusline.left = ["bufname", "filetype"]
let g:hairline.statusline.right = ["percent"]

```

その部品は `g:hairline.part` で設定します。これは辞書で、中身は式の文字列です。

```vim
let g:hairline.part = {
	\ "bufname": "%t",
	\ "filetype": "%{&ft !=# '' ? &ft : 'no ft'}",
	\ "persent": "%3p%%",
	\ }
```

部品に関数を使いたいときには `g:hairline.part_func` を使います。その部品を指定するときには名前の前にプレフィクス `f:` をつけます。

```vim:"f&#58;mode"&#32;で&#32;part_func&#32;の&#32;"mode"&#32;を見に行きます
let g:hairline.statusline.left = ["f:mode", "bufname", "filetype"]

let g:hairline.part_func = {}
function! g:hairline.part_func.mode() abort
  let mode = get(
  \ {'n': 'NORMAL', 'i': 'INSERT', 'v': 'VISUAL', 'V': 'V-LINE',
  \ "\<C-v>": 'V-BLOCK', 't': 'TERMINAL'}, mode(), 'NORMAL')
  return mode
endfunction
```

#### 文脈
hairline には文脈というものがあります。

```
"NC"      非カレントウィンドウ
"n"       カレントウィンドウのノーマルモード、または以下にないモード
"v"       カレントウィンドウのビジュアルモード・セレクトモード
"i"       カレントウィンドウのインサートモード
"t"       カレントウィンドウのターミナルモード
```

ステータスラインにおいて、`left` `right` の後ろにアンダースコアと文脈名をポストフィクスした名前があればその文脈ではそちらが優先されます。

```vim:"NC"&#32;コンテキストでは&#32;left_NC&#32;の方を参照します。
let g:hairline.statusline.left = ["f:mode", "bufname", "filetype"]
let g:hairline.statusline.left_NC = ["bufname", "filetype"]
```

上記例では非カレントウィンドウでは `"f:mode"` がない左辺が使われます。

#### ハイライト定義
文脈はハイライト定義にも使われます。上記文脈に加えて "TAB" と "COMMON" が使われます。"COMMON" は全ての文脈で共通して使われるハイライトです。ただし、"COMMON" ハイライトを定義する際には `g:hairline.common_hl_basenames` も設定しなければ他の文脈に適用されません。"TAB" はタブラインで有効です。
ハイライトは `g:hairline.highlight` 関数内で定義します。
ハイライト名は `Hairline_{文脈}_{任意の名前}` で設定します。

```vim
let g:hairline.common_hl_basenames = ['plain', 'bufname', 'ftype', 'percent']

function! g:hairline#default.highlight() abort "{{{
  hi Hairline_COMMON_plain    guifg=gray54 guibg=gray19 ctermfg=245 ctermbg=236
  hi Hairline_COMMON_bufname  guifg=white guibg=gray35 ctermfg=231 ctermbg=240
  hi Hairline_COMMON_ftype    guifg=gray38 guibg=gray82 ctermfg=241 ctermbg=252
  hi Hairline_COMMON_percent  guifg=gray74 guibg=gray34 ctermfg=250 ctermbg=240

  hi Hairline_NC_bufname    guifg=gray34 guibg=gray15 ctermfg=240 ctermbg=235
  hi Hairline_NC_ftype      guifg=gray15 guibg=gray38 ctermfg=235 ctermbg=241
  hi Hairline_NC_percent    guifg=gray34 guibg=gray15 ctermfg=240 ctermbg=235
endfunc
"}}}
```

これで非カレントウィンドウ以外の文脈の "bufname"、"ftype"、"percent" は共通のハイライトが使われ、別の定義がある非カレントウィンドウだけには別のハイライトが適用されます。"plain" は特別な名前で、`left` と `right` の間のスペースなどを埋めるときなどに使われます。

実は先の例、

```vim
let g:hairline.statusline.left = ["bufname", "filetype"]
let g:hairline.statusline.right = ["percent"]
```

だと、ステータスラインのハイライトには `Hairline_COMMON_plain` だけしか使われません。ハイライトをパーツに適用させるには、`H:` プレフィックスのパーツを使います。
`H:{ハイライト名}` でその文脈のハイライトが選択されて適用されます。ここでは非カレントウィンドウにだけ別のハイライトが適用されます。`H:`プレフィクスでハイライトを指定すると以後、別のハイライトを指定するまでの間はそのハイライトが使われます。ただし、left の終端から先は "plain" が使われます。right の先頭で別のハイライトを使う場合は再度設定する必要があります。

```vim
let g:hairline.statusline.left = ["H:bufname", "bufname", "H:ftype", "filetype"]
let g:hairline.statusline.right = ["H:percent", "percent"]
```

#### そのほかのプレフィクス
部品に使えるプレフィクスは他にも `r:`（式を直接記述する）、`I:`（このパーツを無視する）、`X:`（パーツの両側にスペースを挟まない）などがあります。

## とりあえずここまで
続きはヘルプをご参照ください。とりあえず言えることは、かなり軽量であろうということです。あと、部品指定の中にハイライト指定も含まれているので、もしかしたら後から設定を見返すとき理解しやすいかもしれません。
タブラインの設定も同じような感じです。

それではよいクリスマスを。