🌠

Neovim起動時にロゴをヒュンヒュンヒュンヒュンヒュン!!フワワ~~ン!!する

に公開

この記事はVim駅伝の2025-04-25の記事です。
前回の記事はyuys13さんの忙しい人向け nvim-lspconfigのnvim v0.11対応です。
次回の記事はkyoh86さんの.zsh_historyファイルをイジるです。
Vim駅伝は常に参加者を募集しています。詳しくはこちらのページをご覧ください。


以下のGIFをご覧いただきたい。Neovimを起動したところです。


ヒュンヒュンヒュンヒュンヒュン!!フワワ~~ン!!

本記事はこれの作り方を解説します。最後まで読んでも開発生産性とかエディタの使いやすさとかは一切向上しません。素敵性能に興味がある方だけ読んでください。

準備

使うのはこちらです。お好きな手段でインストールしてください。筆者はnixで入れました。

https://github.com/ChrisBuilds/terminaltexteffects

もちろん、表示するテキストロゴも必要です。筆者は以下のサイトを使って作りました。

https://texteditor.com/multiline-text-art/

冒頭の作例で使っているのはこちらです。Zennのコード表示だと線のつながりが崩れてますがターミナルにコピペすれば良い感じになると思います(フォントによっては崩れるかもしれません)。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
━━━━━━━━ ▄▄    ▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄   ▄▄ ▄▄ ▄▄   ▄▄ ━━━━━━━━
━━━━━━━━█  █  █ █       █       █  █ █  █  █  █▄█  █━━━━━━━━
━━━━━━━━█   █▄█ █    ▄▄▄█   ▄   █  █▄█  █  █       █━━━━━━━━
━━━━━━━━█       █   █▄▄▄█  █ █  █       █  █       █━━━━━━━━
━━━━━━━━█  ▄    █    ▄▄▄█  █▄█  █       █  █       █━━━━━━━━
━━━━━━━━█ █ █   █   █▄▄▄█       ██     ██  █ ██▄██ █━━━━━━━━
━━━━━━━━█▄█  █▄▄█▄▄▄▄▄▄▄█▄▄▄▄▄▄▄█ █▄▄▄█ █▄▄█▄█   █▄█━━━━━━━━
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

作り方

やっていることは、枠と背景のないターミナルウィンドウを作ってその中で上記ロゴを表示してtteで加工しているだけです。

まず必要なシェルコマンドを作ります。Neovimのターミナルに渡すので、sh -c $(echo -e {logo文字列} | tte {アニメーションのオプション})という感じになります。
tteはいくつかサブコマンドがあるのでランダムで選ばれるようにしました。冒頭のGIFはbeamsの場合ですね。また、デフォルトだと動きが遅すぎるのでスピードを調整し、最終グラデーションはどのサブコマンドでも斜めになるよう設定しています。

-- ロゴを文字列で定義
local logo = [[
略
]]
-- 表示に使う`tte`のサブコマンド(アニメーション指定)
local subcommands = {
  'middleout --center-movement-speed 0.8 --full-movement-speed 0.2',
  'slide --merge --movement-speed 0.8',
  'beams --beam-delay 5 --beam-row-speed-range 20-60 --beam-column-speed-range 8-12',
}
-- 候補のサブコマンドからランダムに1個を使用
math.randomseed(os.time())
local subcommand = subcommands[math.random(#subcommands)]
-- Neovimのjobで実行するコマンド
local cmd = {
  'sh',
  '-c',
  'echo -e '
  .. vim.fn.shellescape(vim.trim(logo))
  .. ' | tte --anchor-canvas s ' .. subcommand
  .. ' --final-gradient-direction diagonal'
}
表示例


slide --merge


middleout


beams --beam-row-symbols ☆ ・ ・ --beam-column-symbols ☆ ・ ・

このコマンドを適当なスタート画面系プラグインと組み合わせます。

dashboard-nvimだと、コマンドをpreview.commandに指定するだけで表示できるんじゃないかな〜と思います。試してないので詳細はわかりません。

https://github.com/nvimdev/dashboard-nvim

snacks.dashboardでもコマンドを受け付けるようです。試してないので詳細はわかりません。

https://github.com/folke/snacks.nvim

筆者はmini.starterを使っています。このプラグインにはシェルコマンドを受け付ける機能はないので、自分で調整…というか描画処理の実装が必要になります。

https://github.com/echasnovski/mini.starter

とりあえずheader部分に改行をいくつか入れてロゴを表示する場所を確保します。

require('mini.starter').setup({
  header = string.rep('\n', 7),
})

ウィンドウを作ってロゴを表示する関数は以下のとおりです。dashboard-nvimのpreviewの描画を参考にしました。

local function display_logo(logo)
  -- ロゴ文字列を行ごとに分解
  local logo_lines = vim.split(vim.trim(logo), '\n')

  -- 描画用の空バッファ作成
  local buf = vim.api.nvim_create_buf(false, true)

  -- サイズと位置の指定
  local line_widths = vim.tbl_map(function(line)
    return vim.fn.strdisplaywidth(line)
  end, logo_lines) -- 各行の幅
  local width = math.max(unpack(line_widths)) -- ロゴの行で一番広い幅
  local height = #logo_lines + 1 -- ロゴの行数+1
  local row = 2 -- 縦位置は出力を見ていい感じに調整
  local col = math.floor((vim.o.columns - width) / 2) -- 左右中央

  local winopts = {
    style = "minimal",
    relative = "editor",
    width = width,
    height = height,
    row = row,
    col = col,
    border = "none",
    focusable = false,
    noautocmd = true,
  }

  -- フロートウィンドウ作成
  local win = vim.api.nvim_open_win(buf, false, winopts)
  -- 背景は透明
  vim.api.nvim_set_option_value('winblend', 100, { scope = "local", win = win })
  -- 非表示になったらwipeout
  vim.api.nvim_set_option_value('bufhidden', 'wipe', { scope = "local", buf = buf })

  -- 前述の「Neovimのjobで実行するコマンド」の結果をここに代入
  local cmd = {
    -- 省略
  }
  -- 作成したバッファをターミナルとしてコマンドを実行
  vim.api.nvim_buf_call(buf, function()
    vim.fn.jobstart(cmd, {
      term = true,
    })
  end)

  -- 作成したバッファとウィンドウの情報を返す
  return { buf = buf, win = win }
end

これをスタート画面の起動時にフックします。mini.starterの場合はUser MiniStarterOpenedイベントが呼ばれるのでこれを使います。

local augroup = vim.api.nvim_create_augroup('start-logo', {})

vim.api.nvim_create_autocmd('User', {
  group = augroup,
  pattern = 'MiniStarterOpened',
  callback = function()
    -- ロゴを描画
    local logo_info = display_logo(logo)
    -- 現在のバッファ(スタート画面)を抜けたときにロゴのウィンドウも閉じる
    -- bufhidden=wipeを設定しているのでバッファも自動で破棄される
    local buf = vim.api.nvim_get_current_buf()
    vim.api.nvim_create_autocmd('BufLeave', {
      group = augroup,
      once = true,
      buffer = buf,
      callback = function()
        vim.api.nvim_win_close(logo_info.win, true)
      end,
      desc = 'Close logo'
    })
  end,
  desc = 'Display logo when starter opened'
})

他のスタート画面系プラグインにも同様にシェルコマンドを実行する機能かスタート時に発火するautocmdがあると思います。使用しているプラグインに合わせて調整してみてください。
スタート画面系プラグインを入れていない場合はset shortmess+=Iを設定すれば起動時のメッセージを消せるので、そこにフローティングウィンドウを表示すれば同じようなことをできるはずです。ロゴの消去はCursorMovedとかModeChangedとかにフックすれば良いかと思います。試してないので詳細はわかりません。

制限

縦位置は筆者が使用しているノートPCで表示がいい感じになるように決めたのですが、画面が狭いと表示が重なってしまいます。まあ画面が小さいのですから使える範囲が狭くなるのは仕方ありません。


ロゴがメニュー項目に重なってしまっている

また、フローティングウィンドウはターミナル自体の背景透過に対応していないようです。set winblend=100しても背景画像やデスクトップは透過できません。


ロゴ部分の背景が黒くなっている

https://github.com/neovim/neovim/issues/10685

先行研究

以下の記事では画像を表示させています。かっこいい。

https://minerva.mamansoft.net/📘Articles/📘Neovimで最強のダッシュボードをつくってみた

スタート画面ではありませんが、筆者は過去にも同じようなこと(シェルコマンドをNeovim内で表示)をやっていました。

https://zenn.dev/kawarimidoll/articles/0f3fdfcd881f5c

Discussion