Neovimのカラーテーマ「sakurajima.nvim」を作成して学んだこと

Neovimのカラーテーマ「sakurajima.nvim」を作成して学んだこと

はじめに

2024年5月、私は自作のNeovimカラーテーマ「sakurajima.nvim」を作成した。

そして2年間、毎日このテーマを使い続けた。

「いつか直そう」と思いながら。

2年である。730日。17,520時間。その間、私は毎日「ここの色、ちょっと見づらいな…」と思いながらコードを書いていた。人間の適応能力は恐ろしい。

そしてついに2026年1月、重い腰を上げてリファクタリングを実施した。この記事では、カラーテーマ作成で学んだことと、2年越しの大改修について書いていく。

sakurajima.nvimとは

鹿児島県にある活火山「桜島」をモチーフにしたNeovim用のダークテーマである。

溶岩のような暖色系と、火山灰をイメージしたグレーがかった背景が特徴だ。名前の由来は単純で、「かっこいい日本語の名前をつけたかった」というだけである。富士山は既に使われていそうだったので桜島にした。

リポジトリ: Daiki48/sakurajima.nvim

ディレクトリ構成

sakurajima.nvim/
├── colors/sakurajima.lua           # :colorscheme で呼び出される入口
├── lua/
│   ├── lualine/themes/sakurajima.lua  # lualine用テーマ
│   └── sakurajima/
│       ├── init.lua                # メインの初期化処理
│       ├── colors.lua              # カラーパレット定義
│       ├── highlight.lua           # ハイライト設定のユーティリティ
│       ├── plugins/hop.lua         # hop.nvim対応
│       └── themes/dinner.lua       # 全ハイライトグループ定義
└── screenshots/

themes/dinner.luaという命名は、桜島の夕景をイメージしている。lunch.luaも用意してあるが、中身は空っぽだ。いつかライトテーマを作ろうと思って2年が経った。

まだ実装予定は無い

カラーパレットの設計思想

火山をイメージした配色

-- lua/sakurajima/colors.lua より抜粋
local colors = {
    -- 溶岩のような暖色
    orange = "#E38D2C",
    dark_orange = "#97812C",
    red = "#E34C36",
    yellow = "#E3D92C",

    -- 火山灰をイメージした背景
    black = "#22272e",
    winter_gray = "#2D333B",

    -- 冷たさを加えるアクセント
    cyan = "#2BB6BA",
    dark_cyan = "#3B7B7D",
}

暖色を中心にしつつ、シアン系の色でアクセントを加えている。全部が暖色だと目が疲れるし、何より「火山の近くには海がある」という理由をつけてシアンを入れた。後付けの理由である。

セマンティックな色の割り当て

用途
dark_yellow関数名
dark_cyanキーワード
dark_orange文字列
dark_green定数・数値
light_cyan

これらの割り当ては、他の有名テーマを参考にしつつ、自分が「この色はこの要素っぽい」と感じるものを選んだ。関数が黄色っぽいのは、なんとなく「動き」を感じるからだ。理論的根拠はない。

2年越しのリファクタリング

Before: 2024年の実装

-- 旧実装
function highlight.setup(group, guifg, guibg, gui, guisp)
    local cmd = "highlight " .. group
    if guifg then cmd = cmd .. " guifg=" .. guifg end
    if guibg then cmd = cmd .. " guibg=" .. guibg end
    -- ...
    vim.cmd(cmd)
end

文字列を連結してvim.cmd()に渡すという、いかにも「Vimscriptから移行したばかり」な実装だった。動くには動くが、エレガントとは言い難い。

After: 2026年の実装

-- 新実装
function highlight.setup(group, opts)
    local hl_opts = {}
    for k, v in pairs(opts) do
        if v ~= nil and v ~= "NONE" and v ~= "cleared" then
            hl_opts[k] = v
        end
    end
    vim.api.nvim_set_hl(0, group, hl_opts)
end

vim.api.nvim_set_hl()を使ったモダンな実装に変更した。Neovim 0.7で追加されたAPIだが、当時の私は知らなかったのか、知っていて使わなかったのか、もはや記憶にない。

呼び出し側も変わった。

-- Before
highlight.setup("Function", colors.dark_yellow, nil, colors.bold, nil)

-- After
highlight.setup("Function", { fg = colors.dark_yellow, bold = true })

テーブルベースになったことで、何を設定しているのかが一目瞭然になった。nilの羅列を見て「これは何番目の引数だっけ」と考える必要がなくなった。

ハイライトリンクという知恵

カラーテーマを作る上で最も重要な学びは「ハイライトリンク」の活用だった。

-- 基本グループを定義
highlight.setup("Constant", { fg = colors.dark_green })

-- 関連グループをリンク
highlight.link("Character", "Constant")
highlight.link("Number", "Constant")
highlight.link("Float", "Constant")
highlight.link("Boolean", "Constant")

「定数」という概念に対して色を決めたら、その下位概念(文字、数値、浮動小数点、真偽値)は全てリンクで済ませる。これにより:

  1. 色の変更が一箇所で済む
  2. テーマ全体の統一感が保たれる
  3. 新しいハイライトグループが増えても対応しやすい

最初は全部個別に定義していたが、途中で「これは無理がある」と気づいてリンク方式に切り替えた。

Tree-sitter対応の苦労

Neovimの構文ハイライトは、従来のVim方式からTree-sitter方式に移行しつつある。つまり、両方に対応する必要がある。

-- 従来のVim構文グループ
highlight.setup("Function", { fg = colors.dark_yellow })

-- Tree-sitterのキャプチャグループ
highlight.setup("@function", { fg = colors.dark_yellow })
highlight.setup("@function.builtin", { fg = colors.dark_yellow })
highlight.setup("@function.call", { fg = colors.dark_yellow })
highlight.setup("@function.method", { fg = colors.dark_yellow })

Tree-sitterのキャプチャグループは細かく分かれているので、定義数が膨れ上がる。しかしこの細かさのおかげで、「組み込み関数だけ色を変える」といった細やかな調整ができる。

今回は全部同じ色にしたが。

プラグイン対応

lualine.nvim

ステータスラインプラグインのlualine.nvimには専用テーマを用意した。

-- lua/lualine/themes/sakurajima.lua
local sakurajima = {
    normal = {
        a = { bg = colors.dark_gray, fg = colors.black, gui = "bold" },
        b = { bg = colors.winter_gray, fg = colors.light_gray },
        c = { bg = colors.winter_gray, fg = colors.light_gray },
    },
    insert = {
        a = { bg = colors.dark_blue, fg = colors.black, gui = "bold" },
    },
    -- ...
}

モードごとに色を変えることで、今どのモードにいるかが一目でわかる。Vimmerにとってモードの把握は死活問題だ。

hop.nvim

画面内をジャンプするhop.nvimにも対応した。

highlight.setup("HopNextKey", { fg = colors.cyan, bold = true })
highlight.setup("HopNextKey1", { fg = colors.green, bold = true })
highlight.setup("HopNextKey2", { fg = colors.red, bold = true })

ジャンプ先のマーカーが見やすくないと、hop.nvimの意味がない。派手な色を割り当てている。

学んだこと

1. 完璧を目指さない

最初から完璧なテーマを作ろうとすると、永遠に公開できない。v0.1.0で公開して、使いながら改善するのが正解だった。

2. 自分が使うものを作る

毎日使うからこそ、不満点が見えてくる。「誰かが使うかもしれない」ではなく「自分が使いたい」で作ると、モチベーションが続く。2年放置したけど。

3. 先人の知恵を借りる

他のカラーテーマのソースコードを読むと、「こうやって実装するのか」という発見がある。特にtokyonight.nvimは参考になった。

4. 2年放置しても動く

Neovimの後方互換性は素晴らしい。2年前のコードが、そのまま動いた。もちろんリファクタリングはしたが、「動かない」ということはなかった。

おわりに

カラーテーマ作成は、Neovimの内部構造を理解する良い機会だった。ハイライトグループの仕組み、Lua APIの使い方、プラグインとの連携方法など、普段は意識しない部分に触れることができた。

そして何より、自分だけのエディタ環境を作る楽しさがある。

「このテーマ、俺が作ったんだよ」と言えるのは、なかなか気分がいい。たとえ使っているのが自分だけでも。

興味があれば、ぜひ使ってみてほしい。バグ報告も歓迎する。次の改修は2028年になるかもしれないが。