zk-nvimでNeovimをZettelkastenノート環境にする
メモはとるのに、二度と見返さない問題
メモをとる習慣自体はある。技術検証の結果、読んだ本の要点、ふと思いついたアイデア。その場ではちゃんと書く。問題はその後で、書いたメモを見返すことがほとんどない。
原因のひとつは置き場所だと思っています。
- 「このメモはどのフォルダに入れるべきか」で毎回悩む
- 悩んだ結果、とりあえず適当な場所に置いて、二度と見つからない
- 一念発起して階層を整理し直そうとして、挫折する
この「分類に悩む→埋もれる」のループを抜けるために、Zettelkastenという方法論と、それをNeovimで実践するためのツールであるzk・zk-nvimを導入しました。この記事ではその環境構築と運用を紹介します。
Zettelkastenをざっくり言うと
Zettelkastenはドイツの社会学者ニクラス・ルーマンが実践していたノート術で、要点だけ抜き出すと次の2つになります。
- 1つのノートには1つのことだけを書く(小さく保つ)
- ノート同士をリンクでつなぐ
ポイントはフォルダで分類しないこと。ノート同士の関係はリンクとタグで表現します。あらかじめ作った階層に押し込むトップダウンの整理ではなく、書いてからつなぐボトムアップの整理になる。「どのフォルダに入れるか」という悩みがそもそも発生しません。
方法論の話はこれ以上深入りしません。この記事の主役は、これを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"
filename = "{{id}}-{{slug title}}":ファイル名の先頭にランダムなIDが付く(f3kx-floating-window.mdのような形式)。タイトルが重複しても衝突しないし、IDでリンクしておけば後からタイトルを変えてもリンクが壊れないlink-format = "wiki":ノート間のリンクを[[f3kx]]のようなwikiリンクで書けるdead-link = "error":リンク先が存在しないリンクを、LSPの診断(エラー)として表示してくれる。個人的に一番好きな設定で、効果は後述
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段構えで効いています。
on_attachが呼ばれるのは、zkのLSPがattachしたバッファだけ。zk-nvimのauto_attachは「ノートブック配下のMarkdownか」を判定してからLSPを繋ぐので、プロジェクトのREADMEなどノートブック外のMarkdownでは、キーマップを定義するコード自体が実行されない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ノートを作って定期的に処理するなどの工夫を試している最中です。
まとめ
- Zettelkastenは「小さく書いてリンクでつなぐ」ノート術。フォルダ分類の悩みから解放される
- zkはノートブック管理のCLIで、LSPサーバーを内蔵しているのがエディタ統合の鍵
- zk-nvimを入れると、リンクの補完・ジャンプ・プレビュー・リンク切れ診断が、コードを書くときと同じ操作感で使える
- キーマップは
on_attachでバッファローカルにすると、ノートブックの外を汚さない
ノートは書くだけなら何でもできる。でも「つないで辿る」体験は、ツールの支えがあって初めて成立する気がします。Neovimユーザーならzk + zk-nvimはかなり手に馴染むはず。私もまだ運用を試行錯誤している途中なので、まずはこの環境でノートを育てていきたいと思います。
参考リンク