Vim 駅伝 2025/06/11 の投稿です。前回の記事は、あべべさんの vim マクロで楽する!実践例あり でした。

背景

久しぶりに Neovim に入門しています。今回は設定ファイルを Org ファイルにしてみようと思います。

注意: lazy.nvim の設定に失敗しました。

Neovim の設定としては致命的な失敗があることをご了承お願いします。

Org とは

Org は主に Emacs で活躍するマークアップ言語です。 Org の処理系は文芸的プログラミングの機能を備えている場合があります。 Neovimnvim-orgmode/orgmode にも tangle が実装されており、これを使えば init.luainit.org の中に埋め込むことができます。

Tangle とは

具体的には、以下の init.org に対し org-babel-tangle コマンドを実行すると、全ての Lua ブロックを連結した init.lua が生成されます:

#+TITLE Neovim Configuration
#+STARTUP: nofold
#+PROPERTY: header-args:lua :tangle ./init.lua

* Bootstrapping
:PROPERTIES:
:VISIBILITY: folded
:END:

#+BEGIN_SRC lua
print("Hello, A!") -- 1
#+END_SRC

* Packages

#+BEGIN_SRC lua
print("Hello, B!") -- 2
#+END_SRC

生成結果は以下です:

print("Hello, A!")

print("Hello, B!")

このように init.lua 全体を init.org に埋め込むことで、構造化ドキュメントとしての利便性を享受して設定ファイルを編集できます。

Cons

org-mode のコードブロック中では、補間や言語サーバ等が動作しないかもしれません。この欠点は文芸的プログラミングに付き物だと思います。

どうしても言語サーバが必要な時は、 init.lua の方を見に行くことにします。

環境構築

init.org 事始め

始めから init.org を書きたいので、 Emacs で tangle する環境を作ります。 Emacs に不慣れな人も、コマンドラインツールとしての Emacs には抵抗が少ないのではないでしょうか。

Emacs の バッチモード (--batch) で org-bable-tangle-file を実行できます:

$ emacs --batch --eval "(require 'org)" --eval '(org-babel-tangle-file "init.org")'
Loading /nix/store/52b391v99j92cx9dka8py47cr8hx167b-emacs-unstable-30.1.90/share/emacs/site-lisp/site-start...
Tangled 1 code block from init.org
$ ls # init.lua が生成されました:
init.lua  init.org

これを tangle コマンドにしておけば、 emacs の使い方を忘れても安心です:

#!/usr/bin/env bash

cd "$(dirname "$0")"
emacs --batch --eval "(require 'org)" --eval '(org-babel-tangle-file "init.org")'

(Nix ユーザ向け) 上記の tangle コマンドを nix run .#tangle から実行できるようにします:

flake.nix

一応動きます:

{
  description = "A basic flake for generating `init.lua`";

  inputs = {
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    { nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs { inherit system; };
        tangleCommand = pkgs.writeShellApplication {
          name = "run-tangle";
          runtimeInputs = with pkgs; [emacs];
          text = ''
            ./tangle;
          '';
        };
      in
      {
        apps.tangle = flake-utils.lib.mkApp {
          drv = tangleCommand;
        };
      }
    );
}
$ nix run .#tangle
Tangled 2 code blocks from init.

これで init.org から init.lua を生成できるようになったので、 Neovim の設定が始められます。

Package Manager

packer.nvim が流行ったのも今は昔、現在は lazy.nvimdpp.vim の二強の時代のようです。今回は観光目的なので、簡単な lazy.nvim の方を使います。

Not Structuring Your Plugins

lazy.nvim のドキュメントでは、以下のファイル構成が 勧められています:

~/.config/nvim
├── lua
│   ├── config
│   │   └── lazy.lua
│   └── plugins
│       ├── spec1.lua
│       ├── **
│       └── spec2.lua
└── init.lua

これに沿って tangle の出力先ファイルを切り替えても良いのですが、今回はファイル分割しない簡単な構成とします。これはこれでハマり所がありそうですが……:

~/.config/nvim
├── init.lua
└── init.org

Neovim 入門 + Tangle の設定

mattn/vim-tanakh

まずは lazy.nvim の動作確認を兼ねて、 vim-tanakh をゲットします:

#+BEGIN_SRC lua
{
  "mattn/vim-tanakh"
},
#+END_SRC

tangle してから Neovim を起動すると、ダッシュボードが起動し、プラグインをインストールできました:

2025-06-11-lazy-nvim.png
Figure 1: lazy.nvim のダッシュボード (格好いい!)

config を設定すると、カーソル移動に応じてステータスバーが更新されるようになりました:

{
   "mattn/vim-tanakh",
   config = function()
      vim.opt.statusline = vim.fn["tanakh#face"]()
      vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, {
            callback = function()
               vim.opt.statusline = vim.fn["tanakh#face"]()
            end
      })
   end,
},
2025-06-11-tanakh.gif

普段は lazy = true を設定して封印します。

nvim-orgmode/orgmode

lazy.nvim の使い方が分かったところで、 Neovim から tangle できるように nvim-orgmode/orgmode をインストールしてみました。起動時にドキュメントが折り畳まれています:

2025-06-11-nvim-orgmode-headers.png
Figure 2: Neovim から見た init.org

Emacs から見るとこうで、 #+STARTUP: nofold が反映されています。この動きが正しいです:

2025-06-11-org-mode-headers.png
Figure 3: Emacs から見た init.org

いきなり未実装機能 (nvim-orgmode/orgmode#394) を引いてしまいました。どうしても欲しい機能にはコントリビュートする必要がありそうです。

畳み込みの初期値

README に沿って最低限の設定をしてみます:

{
  'nvim-orgmode/orgmode',
  event = 'VeryLazy',
  ft = { 'org' },
  config = function()
    require('orgmode').setup({
      org_startup_folded = 'showeverything', -- 1
      -- org_startup_indented = true,
      org_agenda_files = '~/org/**/*',
      org_default_notes_file = '~/org/refile.org',
    })
  end,
}

org-babel-tangle

nvim-orgmode/orgmode を使って init.lua を生成できるのか試してみます。一直線に DeepWiki に聞いてみると <Leader>obtorg-babel-tangle がマップされています。これを実行すると:

[orgmode] Tangled 0 blocks from init.org

何も起こらない! ファイル全体の #+PROPERTY が実装されていません。

現状の対策としては、見出しの header-args を設定するか:

* 見出し
:PROPERTIES:
:header-args:lua :tangle ./init.lua
:END:

コードブロック毎に :tangle を書く必要があります:

#+BEGIN_SRC lua :tangle ./init.lua
print('hello')
#+END_SRC

僕は #+PROPERTY:tangle の設定がしたかったので、 Emacs 依存の tangle コマンドを使い続けることにしました。

Neovim から tangle コマンドを実行する

SourceConfigtangle を実行できるようにします:

vim.api.nvim_create_user_command("SourceConfig", function()
  -- Run the `tangle` command
  local config = vim.fn.stdpath("config")
  local tangle = vim.fs.joinpath(config, "tangle")
  vim.fn.system(tangle, config)
  -- Reload the buffer in case it's `init.lua`
  vim.cmd("edit!")
end, {})

また :s:SourceConfig に展開されるようにしました。 Vimscript で恐縮ですが:

-- Abbreviate function:
-- https://stackoverflow.com/a/3879737
vim.cmd([[
function! Abbreviate(from, to)
  exec 'cnoreabbrev <expr> '.a:from
        \ .' ((getcmdtype() ==# ":" && getcmdline() ==# "'.a:from.'")'
        \ .'? ("'.a:to.'") : ("'.a:from.'"))'
endfunction

call Abbreviate('s', 'SourceConfig')
call Abbreviate('ed', 'edit ~/.config/nvim/init.org')

call Abbreviate('h', 'tab help')
call Abbreviate('hs', 'split')
]])

本来は :SourceConfig 実行時に init.lua を読み込む予定でしたが、 lazy.nvim では init.lua の再読み込みが禁止されていました:

Re-sourcing your config is not supported with lazy.nvim

派手に失敗していて申し訳ないですが、設定ファイルの変更内容をエディタに反映する方法は未検討です。

まとめ

久しぶりに Neovim に入門しました。 init.org で Neovim の設定を書くのは結構アリだと感じています。 Org は Emacs 以外のエディタでガンガン使っても良いですし、 emacs を CLI ツールとして使うのもポピュラーになったら良いなと思います。

Neovim は Telescope を始め各種プラグインの完成度が素晴らしく、また Lua が設定ファイルの記述に驚くほど適していると感じました。もっと変な Lua を読み書きしたいとすら思いました。 5 年遅れぐらいで熱が伝わってきたのかもしれません。イケています。

そんなわけで、 Neovim の入門記事にお付き合い頂きありがとうございました!

                             .|          /
                               !        /
                             .l    __/_
                               !   /  / \
                               ! /.   / _ノ  \
                             .l │.   /(● )(●)    ありがとう!
                             .| │  /  (__人__)
                               ! │. /    ` ⌒´ノ
                               ! │ /         }
                               | ノ./ヾ.ヘ     }
                         ..=ィ゙ニ| /、;i;i;ヾヘ  _ノ
               .       : :イ/{ / ̄ヾ}l!;i;i;iLc、>
               .       / '/,ム{ ∧  }ー-,-、《;i〈
               .       !:.,'〃´ハ{/  ハ::〃,=ヾミ;i
               .       :.:{/' 〃゙ヽ__ノヽi/´   }\
               .       :.:|!、/  ヽ::Y::/{  r、/ム .\
               .       !:.!ム    ヽj::ノ{   | ,';i;iム   ヽ.
               .       Ⅵマ\  _ ヽ';i乂__.ソ;i;i;i;i|     丶
               .       トj0l|Y´\{ }  Y;i;i;i;i;i;i;i;i;i;iト,     \
               .       `!0j;iト、  ヾ__.人;i;i;i;i;i;i;i;i;i;i;{         \
               .       〈ソ,∧ \  「 ! Y;i;i;i;i;i;i;i;i;iム
               .        j、;i;i;、  \___丿;i;i;i;i;i;i;i;i;i;iム
               .       /.:::∨;i;i`i.、___ノ;i\;i;i;i;i;i;i;i;i;i;i;iム
               .       ::::::::.∨;i;i|:;i;i;i;i;i;i;i;i;\;i;i;i;i;i;i;i;i;i;ム
               .       、_:::::::∨;i|:;i;i;i;i;i;i;i;i;i;i;i;丶:;i;i;i;i;i;i;i;ム
               .       ::ーニ=イ};i:!:;i;i;i;i;i;i;i;i;i;i;i;i;i;i\:;i;i;i;i;i;i;i〉
               .       ヽ:::::::::ノ;i:!:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i\:;i;i;/
               .        ヽ/;i;i:|:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i\:〉
               .       ../;i;i;i;i;i:|:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;\
               .       ,ゝ;i;i;i;i;i;i:|:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i/  丶
               .       i;i;i;i;i;i;i;i;i:|:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;/    \
               .       i;i;i;i;i;i;i;i;i:!:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i∧
               .       i;i;i;i;i;i;i;i;i:!:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i/. ム
               .       i;i;i;i;i;i;i;i;i:l:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i//マ___
               .       、i;i;i;i;i;i;i;i:|:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;}/イ;;;;;;;;;`!
               .       ';i;i;i;i;i;i;i;i:l:;i;i;i;i;i;i;i;i;i;i;i;i;i;i;i;iム.;;;;;;;;;;;;;;;;;〉