🐼

heirline.nvimでNeovimのStatusLineをカスタマイズしてみる

2023/03/10に公開1

この記事はVim駅伝2023-03-10枠への参加記事として書きました。

対象読者

  • StatusLineのカスタマイズを色々やってきた
  • heirline.nvimがどんなものか気になる

背景

これまで、StatusLineのカスタマイズにはfeline.nvimを使ってきましたが、どうやら作者の方がメンテナンスを維持できなくなってしまったようで、乗り換える必要が生じました。

  • 他のプラグインへの依存が少ない
  • カスタマイズ性が高い
  • デフォルトの機能が少ない

これらを条件に乗り換え先を探していたところ、Redditにてheirline.nvimが良い、との投稿を見かけたため、
試しに使ってみることにしました。

この記事では、他にも検討しているかたの一助になれば、と思い、

  • heirline.nvimの仕様
  • 設定してみた感想

についてまとめました。

はじめに:出典

何につけても、公式ドキュメントにまさるものはありません。
heirline.nvimは非常に細かいCOOKBOOKが用意されています。

THE FEATUREFUL COOKBOOK

私の拙い理解力では、誤解があるかもしれないので、もし妙だと思った場合は必ず公式ドキュメントをあたるようにしてください。

heirline.nvimの設定仕様

heirline.nvimの設定方法を理解するうえで、特に覚えておくと話が早くすすみそうな仕様をいくつか紹介します。

デフォルトでは何もしない

heirline.nvimは未設定の状態では何も表示されません。
ほぼゼロから自分で組み立てる必要があります。

ComponentTreeで表現する

heirline.nvimはHTMLのようなツリー構造を模している、と理解するのが手っとり早いように思います。
ツリー構造はluaのtableの入れ子で表現され、各ノードの属性はtableのFieldで指定します。

HTMLで例えれば次のような構造を

<StatusLine>
  <VimMode style="color: blue; background-color: bright-blue">
    <ModeValue>
      {mode()}
    </ModeValue>
    <Separator style="color: bright-blue; background-color: gray">
      &#xE0BC;
    </Separator>
  </VimMode>

  <FileName style="color: black; background-color: gray">
    {bufname()}
  </FileName>
</StatusLine>

lua tableで次のように表現します。

{
  { -- VimMode
    hl = { fg="blue", bg="bright-blue" },
    { -- ModeValue
      provider = function()
        return vim.fn.mode()
      end,
    },
    { -- Separator
      hl = { fg = "bright-blue", bg = "gray" }
      provider = "\u{E0BC}",
    },
  },
  { -- FileName
    hl = { fg="black", bg="gray" },
    provider = function()
      return vim.fn.bufname()
    end,
  },
}

入れ子の各TableはComponentと呼ばれています。

Componentの振る舞い

Componentはいわゆるクラスオブジェクト的な振る舞いをします。

各Fieldに指定した関数はいわゆるメソッドのように、 self.xxx でコンポーネント自体の状態にアクセスできます。
hlprovider で同じ値に依拠したい場合など、初期化処理としてinitを指定することもできます。
親で用意した値は、子に引き継がれます。

例:

{
  init = function(self)
    self.mode = vim.fn.mode(0) -- 親の`init`でVimの入力状態を取得しておく
  end,
  {
    provider = function(self)
      return self.mode -- 親で仕込んだ値を使用する
    end,
    hl = function(self)
      if self.mode == "n" then
        return { bg = "red", fg = "white" }
      else
        return { bg = "gray", fg = "white" }
      end
    end,
  },
}

Componentの予約されたField

Componentには、子要素とともにFieldでさまざまな属性を指定します。
全て列挙するのは本家のCOOKBOOKに任せるとして、ここでは私が使用した主要なものを紹介します。

provider

コンポーネントで表示する文字列を指定する時に使用するFieldです。

例:

{ provider = "foo" }
{
  provider = function(self)
    if self.state == "done" then
      return "foo"
    else
      return "bar"
    end
  end,
}

hl

Highlightを指定する際に使用するFieldです。
vim.api.nvim_set_hl で指定するのと同じようにfg, bg, boldなどを指定します。

例:

{ hl = { fg = "black", bg = "blue", bold = true } }
{
  hl = function(self)
    return { fg = self.warning_color, bg = "blue", bold = true }
  end,
}

condition

Componentを表示するかどうかを示すBooleanを表すFieldです。
表示しない場合はFalsyの値を指定します。

例:

{
  condition = function(self)
    if self.mode == "Normal" then
      return false
    end
    return true
  end,
}

update

Componentの更新契機を指定するFieldです。

例:

{ update = { "CursorHold", "CursorHoldI", "DirChanged" } }
{
  update = function(self)
    return self.prev_update + 3000 < self.now
  end,
}

Componentのライフサイクル

コンポーネントのライフサイクルには生成と評価の2つのフェーズがあります。

  • 作成:tableをインスタンス化する
  • 評価
    • 実際に表示される内容を決める
    • 表示が更新される度に呼び出される
    • condition, update, init, hl, on_click, provider, pick_child の順に評価する

Theme管理ができる

hl に指定する色は、各Componentで即値を指定することもできますが、
全体でStyleの一貫性を持たせるために、テーマとしての色セットを用意して間接的に指定できます。

例:

require("heirline").load_colors({ -- 色の名前と即値の組み合わせを用意する
  white = "#f0f0f0",
  dark_powered = "#9f6f3f",
})
{ hl = { fg = "white", bg = "dark_powered" } } -- 用意した名前を使用する

実際に書いた設定

heirline.nvimで実際にStatusLineを設定した内容は私のDotfilesリポジトリに置いてあります。

https://github.com/kyoh86/dotfiles/tree/8c834eb1de6e8f1cd2e021c506cba7de42eb971a/nvim/lua/kyoh86/plug/heirline

  • 'laststatus' = 3 で使用している
  • 見ているgitのstatusは、bufferの親ディレクトリではなく、NeovimのCWD

という前提があるため、そのまま使用できるとは限りませんが、一つの参考になれば幸いです。

感想

最後に、実際に仕様を理解し、設定を行った結果として気に入った部分を語ってみます。

邪魔な挙動がない

デフォルトでは何も設定しませんが、逆に言えば、余計なことをしないため、不必要な挙動に悩まされることが少なく済みます。
典型的な設定についてはCOOKBOOKに紹介されているので、コピペしつつ自分なりにカスタマイズして使用できます。

ツリーにより設定するという構造は分かりやすい

HTMLがそうであるように、見た目の構造は入れ子で表現するのが直観的で分かりやすいです。
リスト構造を使ったり、固定の変数(xxx_a, xxx_b, xxx_left ...)で指定させたりするよりも、素直に表現できました。

モジュール化しやすい

パーツを一つのComponentとして完結できるため、モジュールとして再利用可能な形で分離しやすくなっています。
設定が1ファイルの中で肥大化することが防げる他、WinBarとStatusLineで同じ情報を表示する際には便利ですし、
或いはモジュールとして配布したりすることもできるかもしれません。

ちょっと別の話ですが、 nvim-navicというpluginが便利でした。
heirline.nvimでも利用しやすく提供されているので、小さい設定コードで取り込めています。

懸念:既に結構複雑

luaのtableとmetatableをかなり良く活かしたプラグインですが、大局については直観で理解しやすい一方、

  • 作者のクセが強め
  • 世界観の説明が難しい
  • 細かいことをしようと思うと若干複雑さに向き合わされる

といった部分が少し目立ち、作者の熱量が多いうちは良いものの、孤立して力尽きてしまわないか心配になる部分はありますね。

Discussion

korallekoralle

私もfeline.nvim使っていたので参考になります...!!
ありがとうございます!!