sasaryo.dev  |  blog   about

zk-nvimでNeovimをZettelkastenノート環境にする

#terminal

メモはとるのに、二度と見返さない問題

メモをとる習慣自体はある。技術検証の結果、読んだ本の要点、ふと思いついたアイデア。その場ではちゃんと書く。問題はその後で、書いたメモを見返すことがほとんどない。

原因のひとつは置き場所だと思っています。

この「分類に悩む→埋もれる」のループを抜けるために、Zettelkastenという方法論と、それをNeovimで実践するためのツールであるzk・zk-nvimを導入しました。この記事ではその環境構築と運用を紹介します。

Zettelkastenをざっくり言うと

Zettelkastenはドイツの社会学者ニクラス・ルーマンが実践していたノート術で、要点だけ抜き出すと次の2つになります。

ポイントはフォルダで分類しないこと。ノート同士の関係はリンクとタグで表現します。あらかじめ作った階層に押し込むトップダウンの整理ではなく、書いてからつなぐボトムアップの整理になる。「どのフォルダに入れるか」という悩みがそもそも発生しません。

方法論の話はこれ以上深入りしません。この記事の主役は、これをNeovimで快適に回すための環境のほうです。

zk:ZettelkastenのためのプレーンテキストCLI

zkは、Markdownのノート群(ノートブック)を管理するCLIツールです。

brew install zk
zk init ~/zk-notes                # ノートブックを作る
zk new --title "Neovimのfloating windowを調べる"  # 新規ノート
zk list                           # ノート一覧
zk edit --interactive             # 対話的に選んで編集

CLIとしても十分便利ですが、zkの最大の特徴はLSPサーバーを内蔵していることだと思います。zk lsp でLanguage Serverとして起動でき、エディタからは「Markdownノート用の言語サーバー」として振る舞う。リンクの補完、リンク先へのジャンプ、リンク切れの診断といった機能が、普段コードを書くときと同じLSPの仕組みに乗ってきます。これがエディタ統合の鍵になります。

設定はノートブック内の .zk/config.toml(またはグローバルの ~/.config/zk/config.toml)に書きます。私の設定から要点を抜粋します。

[note]
# ファイル名は「ランダムID-タイトルのスラッグ」
filename = "{{id}}-{{slug title}}"
extension = "md"

[format.markdown]
# [[wikiリンク]] 形式でリンクを張る
link-format = "wiki"
hashtags = true

[lsp.diagnostics]
# リンク切れをエラーとして報告する
dead-link = "error"

zk-nvim:NeovimをZettelkastenクライアントにする

zk-nvimはzkの公式Neovimプラグインで、実体は「zkのLSPクライアント+Telescopeなどのpicker統合」になっています。私のlazy.nvimでの設定がこれ(一部簡略化)。

return {
  "zk-org/zk-nvim",
  ft = { "markdown" },
  dependencies = { "nvim-telescope/telescope.nvim" },
  config = function()
    require("zk").setup({
      picker = "telescope",
      lsp = {
        config = {
          name = "zk",
          cmd = { "zk", "lsp" },
          filetypes = { "markdown" },
          on_attach = function(_, bufnr)
            local map = function(mode, lhs, rhs, desc)
              vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, desc = desc })
            end

            map("n", "<leader>zn", function()
              require("zk").new({ title = vim.fn.input("Title: ") })
            end, "ZK: new note")

            map("n", "<leader>zo", function()
              require("zk").edit({ sort = { "modified" } })
            end, "ZK: open notes")

            map("n", "<leader>zt", "<Cmd>ZkTags<CR>", "ZK: tags")
            map("n", "<leader>zi", "<Cmd>ZkInsertLink<CR>", "ZK: insert link")

            -- wikiリンクの上でEnter → リンク先へジャンプ
            map("n", "<CR>", vim.lsp.buf.definition, "ZK: follow link")
            -- リンク先を覗き見る
            map("n", "K", vim.lsp.buf.hover, "ZK: preview (hover)")
          end,
        },
        auto_attach = { enabled = true },
      },
    })
  end,
}

工夫したのは、キーマップを on_attach の中でバッファローカルに定義しているところ。これは2段構えで効いています。

  1. on_attach が呼ばれるのは、zkのLSPがattachしたバッファだけ。zk-nvimの auto_attach は「ノートブック配下のMarkdownか」を判定してからLSPを繋ぐので、プロジェクトのREADMEなどノートブック外のMarkdownでは、キーマップを定義するコード自体が実行されない
  2. vim.keymap.set{ buffer = bufnr } を渡すことで、マップの効力をそのバッファに閉じ込める。これがないと、一度ノートを開いた瞬間にグローバルなキーマップとして登録されてしまい、以降は他のすべてのバッファで <CR> がリンクジャンプに化けてしまう

おかげで <CR>K のような一等地のキーを、ノート専用に安心して使えます。

実際の運用フロー

キーマップの全体像はこうなります。

キー動作
<leader>znタイトルを入力して新規ノート
<leader>zo更新順のノート一覧(Telescope)
<leader>ztタグ一覧から絞り込み
<leader>ziカーソル位置にノートへのリンクを挿入
<CR>wikiリンクの上で押すとリンク先へジャンプ
Kリンク先ノートをホバーでプレビュー

具体例で見てみます。あるとき、こんな技術メモを書いたとします。

# zshの起動が遅い問題を調べる

zprofで計測したところ、compinitが支配的だった。
補完キャッシュの扱いは [[f3kx]] にまとめた。
miseのactivateも重い。これは [[8d2w]] のPATH構築の話とつながる。

執筆中に [[ と打つとLSPの補完が立ち上がり、既存ノートのタイトルで絞り込んでリンクを挿入できます。<leader>zi でTelescopeから選んで挿入する手もあります。

読み返すときは、[[f3kx]] の上で K を押せばリンク先の中身がフローティングウィンドウで覗けます。本格的に読みたければ <CR> でジャンプ。ジャンプ先からさらに別のリンクを辿っていくと、過去の自分のメモが芋づる式に出てくる。この「辿れる」体験こそZettelkastenの肝で、コードリーディングの定義ジャンプと同じ操作感でノートの間を歩き回れるのが気に入っています。

そして dead-link = "error" の効果がここで効いてきます。リンク先のノートを消したりIDを書き間違えたりすると、その行が即座にエラー表示になる。リンク切れがその場でわかるので、ノートの分割やリネームを恐れる必要がありません。コードのリファクタリングを型エラーが支えてくれるのと同じ構図だと思います。

使ってみてどうか

回り始めてよかったこと。

一方で、ツールでは解決しないこともわかってきました。ノートを見返して整理する習慣そのものは、環境を整えても自動では生まれない。リンクも、張ろうという意識がないと孤立したノートが溜まっていきます。このあたりは運用の問題で、inboxノートを作って定期的に処理するなどの工夫を試している最中です。

まとめ

ノートは書くだけなら何でもできる。でも「つないで辿る」体験は、ツールの支えがあって初めて成立する気がします。Neovimユーザーならzk + zk-nvimはかなり手に馴染むはず。私もまだ運用を試行錯誤している途中なので、まずはこの環境でノートを育てていきたいと思います。

参考リンク