カスタムキーマッピング
@replit/codemirror-vim でカスタム Vim キーマッピング、リーダーキー、モード別バインドを定義する方法。
@replit/codemirror-vim は、Vim スタイルのキーマッピングを定義するためのプログラマティック API を提供します。これらは :map や :noremap の Ex コマンドと同様に動作しますが、JavaScript/TypeScript から呼び出します。
コアマッピング API
すべてのマッピング関数は Vim オブジェクトのメソッドです。
import { Vim } from "@replit/codemirror-vim";Vim.map()
再帰的なキーマッピングを作成します。{rhs} 自体がマッピングされたシーケンスである場合、そのマッピングが展開されます。
Vim.map(lhs: string, rhs: string, mode?: string): voidVim.noremap()
非再帰的なキーマッピングを作成します。{rhs} は生のキーとして解釈され、他のマッピングによる展開は行われません。
Vim.noremap(lhs: string, rhs: string, mode?: string): voidVim.unmap()
以前に定義したマッピングを削除します。
Vim.unmap(lhs: string, mode?: string): voidmode パラメータ
オプションの mode パラメータを使って、マッピングを特定の Vim モードに制限できます。
| 値 | モード |
|---|---|
"normal" | Normal モード |
"insert" | Insert モード |
"visual" | Visual モード |
| (省略) | 全モード |
キー表記
キーシーケンスでは、特殊キーに Vim スタイルの表記を使用します。
| 表記 | キー |
|---|---|
<Esc> | Escape |
<CR> | Enter / Return |
<Tab> | Tab |
<Space> | Space |
<BS> | Backspace |
<C-w> | Ctrl+w |
<C-d> | Ctrl+d |
<A-x> | Alt+x |
<S-Tab> | Shift+Tab |
<Leader> | リーダーキー(設定可能、デフォルトは \) |
実用的な例
jk で Insert モードから抜ける
Escape キーに手を伸ばさずに済む定番のマッピングです。
Vim.map("jk", "<Esc>", "insert");リーダーキーの組み合わせ
リーダーキーを設定し、リーダーベースのショートカットを定義します。
// Set leader to space
Vim.map("<Space>", "<Leader>");
// Leader+w to write
Vim.map("<Leader>w", ":w<CR>");
// Leader+q to quit
Vim.map("<Leader>q", ":q<CR>");Insert モードでのウィンドウ風ショートカット
// Ctrl+s to save from insert mode
Vim.noremap("<C-s>", "<Esc>:w<CR>a", "insert");ナビゲーションキーの再マッピング
// Make H and L go to start/end of line
Vim.noremap("H", "^", "normal");
Vim.noremap("L", "$", "normal");Ex コマンドへのマッピング
: を含むマッピングはコマンドラインモードに入り、Ex コマンドを実行できます。
// Clear search highlighting with leader+h
Vim.map("<Leader>h", ":nohlsearch<CR>");
// Sort selection with leader+s
Vim.map("<Leader>s", ":sort<CR>", "visual");行の上下移動
Vim.noremap("<A-j>", ":m .+1<CR>", "normal");
Vim.noremap("<A-k>", ":m .-2<CR>", "normal");リーダーキーの設定
リーダーキーは、カスタムマッピングで使用する設定可能なプレフィックスです。デフォルトのリーダーは \(バックスラッシュ)です。
リーダーキーを変更するには:
Vim.map("<Space>", "<Leader>");これ以降、<Leader> を使うすべてのマッピングは、Space に続くキーで応答するようになります。
Warning
Vim.map によるリーダーキーの設定は、<Leader> を使うマッピングを定義する前に行う必要があります。そうしないと、先に定義されたマッピングは古いリーダーキーのままになります。
マッピングの削除
不要になったマッピングを削除します。
// Remove the jk mapping in insert mode
Vim.unmap("jk", "insert");
// Remove a normal mode mapping
Vim.unmap("H", "normal");再帰マッピングと非再帰マッピング
ほとんどの場合は Vim.noremap() を使用してください。
Vim.map()-- 右辺が既存のマッピングを通じて展開されるため、注意しないと無限ループを引き起こす可能性がありますVim.noremap()-- 右辺はリテラルなキーストロークとして扱われ、意図しない連鎖を防ぎます
// Safe: noremap prevents recursive expansion
Vim.noremap("j", "gj", "normal");
Vim.noremap("k", "gk", "normal");
// Dangerous with map: if gj were also mapped, this could loop
// Vim.map("j", "gj", "normal"); // avoid this patternTip
.vimrc でのベストプラクティスと同様に、再帰的な展開が明確に必要な場合を除き、map / nmap / imap / vmap よりも noremap / nnoremap / inoremap / vnoremap を優先してください。
Vim と CodeMirror のキーマップ優先順位
Vim は自身に割り当てられたキーを処理し、未処理のイベントを他の CodeMirror キーマップへ渡す。共有エディター拡張より前に vim() を置いても、後続の割り当ては無効にならない。未割り当てのキーは searchKeymap や補完コマンドを実行する場合がある。
macOS では Vim は通常 Cmd+D を割り当てていない。CodeMirror の searchKeymap は Mod-d を selectNextOccurrence に割り当てるため、ビジュアル選択中に別の一致箇所を選択して画面をスクロールし、前方の一致箇所へ折り返す場合もある。issue #874 に対応した zudo-text PR #910 の修正では、Vim が有効な場合だけこのキーを消費する。
import { keymap } from "@codemirror/view";
import { searchKeymap } from "@codemirror/search";
import { vim } from "@replit/codemirror-vim";
const vimEnabled = true;
const extensions = [
...(vimEnabled
? [vim(), keymap.of([{ key: "Mod-d", run: () => true }])]
: []),
keymap.of(searchKeymap),
];何もしないハンドラーでも true を返すことで処理済みとなり、後続の CodeMirror の割り当てを止める。preventDefault: true はブラウザーの既定動作を抑制する指定であり、成功を返すコマンドハンドラーの代わりにはならない。ブロッカーは共有検索キーマップと同じ優先度で、その前に置く。共有キーマップを Prec.high で包むと、そちらが先に実行される場合がある。
Mod-d は macOS では Cmd+D、Linux/Windows では Ctrl+D を意味する。この順序なら Mac 以外では Vim が本来の Ctrl+D(半ページ下へのスクロール)を先に処理する。Vim 無効時にはブロッカーがなく、CodeMirror の次の一致箇所を選択する機能が残る。導入時には macOS のノーマル・ビジュアル・矩形選択モード、Mac 以外の Ctrl+D、Vim 無効時の動作を確認する。
他のショートカットに違和感があれば、有効なキーマップ全体を確認する。Mod-Alt-g、Mod-Shift-l、補完キーマップの Escape も調査候補になる。未割り当てのキーをすべて無効にせず、確認できた競合だけを止める。