vimrc サポート
Vim.handleEx() を使って @replit/codemirror-vim に vimrc スタイルの設定を適用する方法。
vimrc サポート
@replit/codemirror-vim は Vim.handleEx() メソッドを使って、実行時に vimrc スタイルのコマンドを実行できます。これにより、ユーザーが提供する Vim 設定テキストをアプリケーションで1行ずつパースして適用できます。
Vim.handleEx()
Vim.handleEx() は、ユーザーがコマンドラインモードで入力したかのように、単一の Ex コマンド文字列を実行します。
import { getCM, Vim } from "@replit/codemirror-vim";
const cm = getCM(view)!;
Vim.handleEx(cm, "set number");
Vim.handleEx(cm, "imap jk <Esc>");
第1引数は getCM(view) で取得できる CodeMirror アダプターオブジェクトです。第2引数は生の Ex コマンド文字列(先頭の : なし)です。
CodeMirror アダプターの取得
getCM() は EditorView を受け取り、Vim レイヤーが操作する内部の CodeMirror アダプターを返します。
import { getCM } from "@replit/codemirror-vim";
const cm = getCM(view);
⚠️ Warning
getCM() は、指定された view で Vim Extension がアクティブでない場合、undefined を返すことがあります。Vim.handleEx() に渡す前に戻り値を確認してください。
vimrc 文字列のパース
vimrc ファイルは1行に1コマンドで構成されます。空行と "(ダブルクォート)で始まる行はコメントです。
function parseVimrc(content: string): string[] {
return content
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith('"'));
}
vimrc コマンドの適用
パースされたコマンドを順に実行します。すべての vimrc コマンドがサポートされているわけではないため、try/catch でラップします。
import { getCM, Vim } from "@replit/codemirror-vim";
function applyVimrc(view: EditorView, vimrcContent: string) {
const cm = getCM(view);
if (!cm) return;
for (const cmd of parseVimrc(vimrcContent)) {
try {
Vim.handleEx(cm, cmd);
} catch (e) {
console.warn("vimrc command failed:", cmd, e);
}
}
}
vimrc の内容の例
ユーザーが提供する典型的な vimrc 文字列です。
" Basic settings
set number
" Key mappings
imap jk <Esc>
nmap H ^
nmap L $
nnoremap j gj
nnoremap k gk
" Leader mappings
map <Space> <Leader>
nmap <Leader>w :w<CR>
nmap <Leader>q :q<CR>
よく使われる vimrc 設定
行番号
set number
set nonumber
これらは @replit/codemirror-vim でそのまま動作します。
行の折り返し
set wrap
set nowrap
📝 Note
set wrap と set nowrap は Vim.defineOption() によるカスタムオプション定義が必要です。実装方法は Vim オプションのページを参照してください。
Insert モードのエスケープマッピング
imap jk <Esc>
表示行でのナビゲーション
j/k をバッファ行ではなく表示行(折り返された行)での移動にマッピングします。
nnoremap j gj
nnoremap k gk
リーダーキーの設定
map <Space> <Leader>
nmap <Leader>w :w<CR>
nmap <Leader>q :q<CR>
nmap <Leader>h :nohlsearch<CR>
タイミングに関する注意
vimrc コマンドは、EditorView が作成され Vim Extension が初期化された後に適用してください。早すぎるタイミングで適用すると、getCM(view) が undefined を返す場合があります。
const view = new EditorView({
extensions: [vim(), basicSetup],
parent: document.getElementById("editor")!,
});
// Apply vimrc after view creation
applyVimrc(view, userVimrcContent);
ユーザー入力からの vimrc 読み込み
実用的なパターンとして、ユーザーに vimrc をペーストまたはアップロードしてもらい、保存し、エディター初期化時に再適用する方法があります。
function loadVimrc(): string | null {
return localStorage.getItem("user-vimrc");
}
function saveVimrc(content: string) {
localStorage.setItem("user-vimrc", content);
}
// On editor init
const vimrc = loadVimrc();
if (vimrc) {
applyVimrc(view, vimrc);
}
ファイルベース + 設定ベースの vimrc レイヤリング
デスクトップアプリケーションでは、2つの vimrc ソースをサポートしたい場合があります。
- ファイルベースの vimrc — ディスク上の
.vimrcファイル。バックエンド(例: Tauri IPC)経由でロード - 設定ベースの vimrc — アプリケーション設定 UI に保存された vimrc 文字列
レイヤリング戦略は、ファイルベースの vimrc を最初に適用し、次に設定ベースの vimrc を適用します。これにより、設定ベースのオーバーライドが優先され、ユーザーはディスクファイルを編集せずに動作をカスタマイズできます。
import { getCM, Vim } from "@replit/codemirror-vim";
async function applyLayeredVimrc(
view: EditorView,
settingsVimrc: string,
readVimrcFromDisk: () => Promise<string | null>,
) {
const cm = getCM(view);
if (!cm) return;
const applyVimrc = (source: string, label: string) => {
for (const cmd of parseVimrc(source)) {
try {
Vim.handleEx(cm, cmd);
} catch (e) {
console.warn(`${label} vimrc command failed: ${cmd}`, e);
}
}
};
// レイヤー 1: ファイルベースの vimrc(ディスクからロード)
const fileVimrc = await readVimrcFromDisk();
if (fileVimrc) {
applyVimrc(fileVimrc, "file-based");
}
// レイヤー 2: 設定ベースの vimrc(ファイルベースをオーバーライド)
if (settingsVimrc) {
applyVimrc(settingsVimrc, "settings");
}
}
💡 Tip
このレイヤリングパターンはシェル設定の動作(/etc/profile の後に ~/.profile)を模倣しています。ファイルベースの vimrc はマシンレベルのデフォルトを提供し、設定ベースの vimrc は UI を通じたアプリケーションごとのカスタマイズを可能にします。
非同期ロードの処理
ファイルベースの vimrc はファイルシステム API 経由で非同期にロードされるため、vimrc の適用は EditorView の作成後に行われます。破棄されたエディターへの vimrc 適用を防ぐために、キャンセルガードを使用します。
let cancelled = false;
readVimrcFromDisk().then((vimrcContent) => {
if (cancelled) return;
const cm = getCM(view);
if (!cm) return;
if (vimrcContent) {
applyVimrc(vimrcContent, "file-based");
}
if (settingsVimrc) {
applyVimrc(settingsVimrc, "settings");
}
});
// クリーンアップ時:
cancelled = true;
これは、非同期の vimrc 読み取りが完了する前にエディターが破棄され再作成される可能性がある React アプリケーションで重要です。