zudo-codemirror-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

カスタムキーマッピング

@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): void

Vim.noremap()

非再帰的なキーマッピングを作成します。{rhs} は生のキーとして解釈され、他のマッピングによる展開は行われません。

Vim.noremap(lhs: string, rhs: string, mode?: string): void

Vim.unmap()

以前に定義したマッピングを削除します。

Vim.unmap(lhs: string, mode?: string): void

mode パラメータ

オプションの 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 pattern

Tip

.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 も調査候補になる。未割り当てのキーをすべて無効にせず、確認できた競合だけを止める。

参考: CodeMirror KeyBinding。

Revision History

作成更新