Zed vim modeの指をそのままNeovimに持ってきた
前に「すみませんZedが最強でした」という記事を書きましたが、今はNeovimも使っています。Zedを見限ったわけではなく、SSH先でも同じ指で作業したいからです。
Zedのvimモードは素のvimに独自バインド(g /、] c、d oなど)を足しています。出典は公式のassets/keymaps/vim.json。この記事はそれらをNeovimに移植した私の設定集です。
Gitハンク操作(一番の目玉)
ZedはdオペレータにGit操作をぶら下げています。doでハンク展開、dOでステージ、duでステージして次へ、dpで変更を捨てる。レビューしながらdu du duと叩けばコミットが組めます。
gitsigns.nvimのon_attachで同じキーに割り当てました。
-- Navigation (Zed: ]c / [c)
map("n", "]c", gs.next_hunk, "Next Change")
map("n", "[c", gs.prev_hunk, "Prev Change")
-- Expand diff hunk (Zed: do)
map("n", "do", gs.preview_hunk, "Expand Diff Hunk")
-- Toggle staged (Zed: dO)
map("n", "dO", gs.stage_hunk, "Toggle Staged")
-- Stage and next (Zed: du)
map("n", "du", function()
gs.stage_hunk()
gs.next_hunk()
end, "Stage and Next")
-- Unstage and next (Zed: dU)
map("n", "dU", function()
gs.undo_stage_hunk()
gs.next_hunk()
end, "Unstage and Next")
-- Restore change (Zed: dp)
map("n", "dp", gs.reset_hunk, "Restore Change")
-- Visual mode: line range for stage / restore
map("v", "dO", function() gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, "Toggle Staged")
map("v", "dp", function() gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, "Restore Change")
Zedのgit::StageAndNextは1コマンドですが、gitsignsには無いのでstage_hunk()とnext_hunk()を並べています。
移動・検索
| Neovim | Zed |
|---|---|
H / L → ^ / $
|
shift-h / shift-l(自分のkeymap.json) |
<C-p> → find files |
ctrl-p |
g/ → live grep |
g / |
gd → definition |
g d |
<F2> → rename |
f2 |
<C-s> → save |
ctrl-s |
vim.keymap.set({ "n", "v" }, "H", "^", { desc = "Go to line start" })
vim.keymap.set({ "n", "v" }, "L", "$", { desc = "Go to line end" })
vim.keymap.set("n", "g/", builtin.live_grep, { desc = "Project Search" })
vim.keymap.set({ "n", "i", "v" }, "<C-s>", "<cmd>w<CR><Esc>", { desc = "Save" })
g/はZedのデフォルトなので、Zedユーザーの指は無意識に叩きます。Neovimでは何も起きないので自分で生やす必要があります。
leader系
Zed時代にkeymap.jsonへ書いていたspace系。
// ~/.config/zed/keymap.json
"space r n": "editor::Rename",
"space c a": "editor::ToggleCodeActions",
"space f": "editor::Format",
"space e": "project_panel::ToggleFocus",
"space g d": "git_panel::ToggleFocus",
"space space": "vim::HelixJumpToWord"
もともとNeovim界の流儀(LazyVim等)から拾ってきたものなので、そのまま<leader>に戻すだけでした。
map("<leader>rn", vim.lsp.buf.rename, "Rename")
map("<leader>ca", vim.lsp.buf.code_action, "Code Action")
map("<leader>f", function()
vim.lsp.buf.format({ async = true })
end, "Format")
which-keyも両方で有効にしています。Zedのwhich_key.delay_ms: 1000は遅すぎたので、Neovim側はdelay = 500。
Ctrl+Shift系は諦めた
Zedではctrl-shift-cにコピーを割り当てていましたが、ターミナルのNeovimでは移植できません。
- 古典的な端末では
Ctrl+CとCtrl+Shift+Cが同じバイト(0x03)になりShiftが消える - kitty keyboard protocolで区別できる端末もあるが、tmuxやGNOME Terminal系で崩れる
- そもそも
Ctrl+Shift+Cは端末エミュレータ自身のコピーに先に食われる
なので**Ctrl+Shift+*前提の操作は捨てて、leader系と単発Ctrl系に寄せる**方針にしました。コピーは遊んでいた<C-c>に行ヤンクを割り当てて代替。
vim.keymap.set({ "n" }, "<C-c>", "yy", { desc = "Yank line" })
space spaceジャンプはhop.nvimで日本語対応
Zedのvim::HelixJumpToWord(画面内の単語先頭にラベルを振って飛ぶ)はhop.nvimで再現。ただしhopの標準は\k\+区切りなので、日本語だと
私は特別強いエンジニアではありません
が丸ごと1候補になります。文字種の切り替わりを単語境界にする正規表現を書きました。
{
"<leader><leader>",
mode = { "n", "x" },
function()
local hop = require("hop")
-- 漢字 / カタカナ / 英数字それぞれの連なりの先頭を候補にする。
-- ひらがなは候補から外す: 助詞や送り仮名まで拾うと候補が倍増し、
-- ラベルが 2 桁化して本文を読めなくする割に足場としての価値が低い。
-- カタカナ類には長音符「ー」を含める(含めないと「アップロード」が 2 候補に割れる)。
local re = vim.regex("\\v<\\w+|[一-龥]+|[ァ-ヶー]+")
hop.hint_with_regex({
-- oneshot=false: 1 行につき複数の候補を拾う
oneshot = false,
match = function(s)
return re:match_str(s)
end,
}, hop.opts)
end,
desc = "Hop: Jump to word (JP aware)",
},
ポイントは2つ。
- ひらがなを候補から外す:助詞や送り仮名まで拾うとラベルが2桁になって本文が読めない。「は」に飛びたい人間はいない
- 長音符「ー」をカタカナに含める:忘れると「アップロード」が「アップロ」+「ド」に割れる
日本語の文章編集に関しては、正直Zedより快適になりました。
(vim::HelixJumpToWordはジャンプ先がスカスカだが、hop.nvimは設定次第で文字境界を増やせる)

キー以外に揃えた挙動
Zedのdefault.jsonを根拠に合わせた設定。
| Neovim | Zedの設定 | 意味 |
|---|---|---|
clipboard = "unnamedplus" |
vim.use_system_clipboard: "always" |
ヤンクがそのままOSクリップボードへ |
updatetime = 250 + CursorHoldで診断float |
hover_popover_delay: 300 |
カーソルを止めると情報が出る |
ignorecase + smartcase
|
search.case_sensitive: false / use_smartcase_search
|
検索の当たり方 |
relativenumber |
vim.toggle_relative_line_numbers: true |
10jの数えやすさ |
BufWritePreでフォーマット |
format_on_save |
保存の副作用 |
vim.api.nvim_create_autocmd("CursorHold", {
callback = function()
vim.diagnostic.open_float(nil, { focusable = false, scope = "cursor" })
end,
})

