submode.vim/smartrep.el と同じことをする

submode.vim や smartrep.el は、VimEmacsで連続したキー入力を楽にできるプラグインです。詳しい説明は id:thinca さんの記事が参考になります。 submode.vim とその設定例なんかを紹介

このエントリではSublime Textで同じことを、1行もコードを書かずに、キーバインドの定義だけでやってみます。

たとえば、次のように ctrl+k, n で次のブックマークへ移動、ctrl+k, p で前のブックマークにカーソルを移動するキーバインドを定義します。

// Default.sublime-keymap
[
    { "keys": ["ctrl+k", "n"], "command": "next_bookmark" },
    { "keys": ["ctrl+k", "p"], "command": "prev_bookmark" }
]

これで連続して次のブックマークに移動しようとすると、 ctrl+k, n, ctrl+k, n, ctrl+k, n, ... というように、キーを交互に押さなければならないため面倒です。 これを、 ctrl+k, n, n, n, ... というように最初以外の ctrl+k を省いて入力できるようにする方法を説明します。

設定

キーバインドを追加して、次のようにします。

// Default.sublime-keymap
[
    { "keys": ["ctrl+k", "n"], "command": "next_bookmark" },
    { "keys": ["ctrl+k", "p"], "command": "prev_bookmark" },
    { "keys": ["n"], "command": "next_bookmark",
        "context": [
            {"key": "last_command", "operator": "equal", "operand": "next_bookmark"}
        ]
    },
    { "keys": ["p"], "command": "prev_bookmark",
        "context": [
            {"key": "last_command", "operator": "equal", "operand": "prev_bookmark"}
        ]
    }
]

この設定で、 ctrl+k, n, n, n, ...ctrl+k, p, p, p, ... でブックマーク間の移動ができるようになります。

解説

np に対してブックマークの移動コマンドを割り当ててしまっていますが、通常時にこれらのキーを押してもブックマークの移動にはなりません。 この設定の肝はキーバインド定義中の "context" の部分です。

{ "keys": ["n"], "command": "next_bookmark",
    "context": [
        {"key": "last_command", "operator": "equal", "operand": "next_bookmark"}
    ]
}

この "context" の内容は "last_command" が "operand" に指定されている "next_bookmark" と一致するかをチェックする、というものです。 つまり "next_bookmark" というコマンドが直前に実行されている時に限り、このキーバインドが有効になります。 その他の場合、通常通り n が入力されます。

サブモードっぽくする

上記の設定では ctrl+k, n を押したあとに p を押しても通常通り p が入力されるだけで前のブックマークに移動できません。 次のようにすると、 ctrl+k を押したあと、 np のどちらでも連続して移動できるようになります。

// Default.sublime-keymap
[
    { "keys": ["ctrl+k", "n"], "command": "next_bookmark" },
    { "keys": ["ctrl+k", "p"], "command": "prev_bookmark" },
    { "keys": ["n"], "command": "next_bookmark",
        "context": [
            {"key": "last_command", "operator": "equal", "operand": "next_bookmark"}
        ]
    },
    { "keys": ["n"], "command": "next_bookmark",
        "context": [
            {"key": "last_command", "operator": "equal", "operand": "prev_bookmark"}
        ]
    },
    { "keys": ["p"], "command": "prev_bookmark",
        "context": [
            {"key": "last_command", "operator": "equal", "operand": "next_bookmark"}
        ]
    },
    { "keys": ["p"], "command": "prev_bookmark",
        "context": [
            {"key": "last_command", "operator": "equal", "operand": "prev_bookmark"}
        ]
    }
]

キーバインドの内容は、単純に n を押したあとの p と、 p を押したあとの n を追加しているだけです。 next_bookmark と prev_bookmark が別のコマンドなのでこのようにする必要があります。

この冗長なキーバインドを何とかしたい場合は、ここでは説明しませんが、Pythonコードを書いてプラグインを作るしかないでしょう。

他に以下のような "operator" に "regex_match" を使う方法を思いつきましたが、動作しませんでした。context の key によっては "regex_match" は使えないようです。

// 動作しない
[
    { "keys": ["n"], "command": "next_bookmark",
        "context": [
            {"key": "last_command", "operator": "regex_match", "operand": "(next|prev)_bookmark"}
        ]
    },
    { "keys": ["p"], "command": "prev_bookmark",
        "context": [
            {"key": "last_command", "operator": "regex_match", "operand": "(next|prev)_bookmark"}
        ]
    }
]

連続入力状態から抜ける

np を押すのを止めて次の操作に移るときは何もせずに次の操作をすればいいです。しかし、 n や p を文字として挿入する場合はその前にキャンセル動作が必要となります。 キャンセルするには 何か別の動作を行って "last_command" を next_bookmark と prev_bookmark 以外にする必要があります。例えば、カーソルを動かせばキャンセルできます。

この方法が使えるコマンドについて

これまで解説した方法は、全てのコマンドで使えるわけではありません。しかも、どのコマンドが使えるのかの正確な情報は、探しても見つけられませんでした。 たとえば、左右のタブに切り替えるには next_view と prev_view が使えますが、上で解説した方法をこれらのコマンドで使おうとしても動作しませんでした。 私の予想ですが、おそらく使えるのは現在のタブに対して影響を与えるようなコマンドだけだと思います。カーソル位置の移動や、文字の挿入、削除などの動作がそれに当たります。 ユーザー定義のコマンドであれば、 sublime_plugin.TextCommad を継承しているコマンドです。

ちなみに、Sublime Text のマクロ機能にも同じような制限があり、マクロの記録中にタブを移動してもそれは記録されません。

参考リンク: http://sublimetext.userecho.com/topic/79139-recorded-macros-should-not-ignore-movement-commands/

まとめ

submode.vim/smartrep.el と似たような機能を、キーバインドの設定だけで実現できます。 Sublime Text のキーバインド設定は極めて柔軟であると言えます。 上に書いたような制限があるものの、活用してみてはいかがでしょうか。