【VSCode】カーソル移動を効率化するための拡張機能 Cursor State Checker を作りました


[CursorStateChecker]: https://marketplace.visualstudio.com/items?itemName=tokoro-it.cursor-state-checker

## はじめに

コーディングをする際は、VSCodeを使用しています。
基本的なカーソル移動は、以下の2つをよく使います。（コマンドは、VSCode上のコマンド名です）
* `→`
  * 1文字移動
  * `cursorRight`コマンド
* `ctrl + →`
  * 単語単位で移動
  * `cursorWordEndRight`コマンド

最近、仕事でTypeScriptを使い始めましたが、他の言語と比べてアロー関数を多用するため、括弧(`()`や `{}`)を書く頻度が増えました。  
特に括弧の外の移動するために `→`キーに手を伸ばすことが増え、以下の点が気になってきました。

* 頻繁にホームポジションと矢印キーをいったりきたりするのが煩わしい
  * また手がずれることで、タイプミスする
* 単語単位の移動は、連続した記号を1つの単語として扱うため、カーソルが行き過ぎることがある
  * `←`キーで戻ることが増えた

次の括弧（の外）にジャンプする標準の機能／設定や拡張機能はないか調べてみましたが、なさそうでした。  
近そうな機能として、対応する括弧にジャンプするショートカット(`ctrl + shift + \`)がありますが、括弧の手前にジャンプするものです。
`)`や`}`の外（右側）には移動できないので、今回の目的とは一致しません。

そこで、自作した拡張機能 **Cursor State Checker** で、カーソル位置を状態を監視し、キーボードショートカットからその状態によってコマンドを切り替えることで、カーソル移動を効率化してみました。

https://marketplace.visualstudio.com/items?itemName=tokoro-it.cursor-state-checker


## 実現した結果

`shift + space`に後述するキーボードショートカットを割り当てて、**Cursor State Checker** の状態によって、
* 1文字移動(`cursorRight`)
* 単語移動(`cursorWordEndRight`)

を切り替えて、単語単位で移動しつつ、`)`などがきたら、1文字移動するようにしました。
また行末にいるときは、カーソル移動せず、スペースが挿入されるようにしています。

言葉だけではイメージしにくいかもしれないので、動いているところをご覧ください。

<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="ja" dir="ltr">ショートカットの動作<br>shift+spaceに割り当てたcursorWordEndRightとcursorRightを切り替えてカーソル移動しています <a href="https://t.co/TYbDy8KSE3">pic.twitter.com/TYbDy8KSE3</a></p>&mdash; tokoro (@tokoroitit) <a href="https://twitter.com/tokoroitit/status/1840082436069409152?ref_src=twsrc%5Etfw">September 28, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### 設定手順

1. [**Cursor State Checker**][CursorStateChecker] をインストール
2. キーボードショートカットに以下を追加
```json:keybindings.json
  {
    "key": "shift+space",
    "command": "cursorWordEndRight",
    "when": "textInputFocus && !accessibilityModeEnabled && CSChecker.normal"
  },
  {
    "key": "shift+space",
    "command": "cursorRight",
    "when": "textInputFocus && !accessibilityModeEnabled && CSChecker.rightIsSpecifiedChar"
  },
```

※行末に達しても`cursorWordEndRight`で次の行へ単語移動したい場合は、1つ目の設定に、以下を使ってください
`"when": "textInputFocus && !accessibilityModeEnabled && !CSChecker.rightIsSpecifiedChar"`

`ctrl + right`でも、上記のように`when`を追記すれば動作しますが、システムが用意しているデフォルトの設定を無効化する必要があります。コマンドの先頭に`-`をつけると、無効化できます。

```json:keybindings.json
  {
    "key": "ctrl+right",
    "command": "-cursorWordEndRight",
    "when": "textInputFocus && !accessibilityModeEnabled"
  },
```

## 拡張機能：Cursor State Checker について

この拡張機能は、以下の2つの状態を判定し、`when`で参照できるコンテキストに値をセットします。

* カーソルが行末にいるとき
  * `CSChecker.eol`に`true`をセットする
* カーソルの次の文字が、特定の文字／スペース＋特定の文字であるとき
  * `CSChecker.rightIsSpecifiedChar`に`true`をセットする
* 上記のどちらでもないとき
  * `CSChecker.normal`に`true`をセットする

特定の文字とは、デフォルトで`"'``(){}[]<>` となっており、設定から変更できます。

詳細が知りたい方は、拡張機能の説明に記載していますので、そちらを参照してください。

## キーボードショートカットを実行したときの挙動

先ほどのキーボードショートカットのように設定した場合の挙動は以下のようになります。

`shift + space`を打ったとき、通常(`CSChecker.normal`=`true`)は、単語単位で移動`cursorWordEndRight`します。
カーソルの次の文字が、`"'``(){}[]<>`になった場合(`CSChecker.rightIsSpecifiedChar`=`true`)、1文字移動`cursorRight`になります。例えば`|)`→`)|`とカーソルが移動します。
カーソルが行末(`CSChecker.eol`=`true`)にくると、デフォルトの`shift+space (=space)`になり、スペースが入力されます。

また細かい注意点として、カーソルの次の文字がスペース＋特定の文字の場合も、移動は`cursorRight`なので、2回打たないと特定の文字の次まで移動できません。拡張機能は、カーソル位置を監視するだけで、カーソルの移動は行っていません。
`shift + space`を打つたびに、`| )`→` |)`→` )|`とカーソルが移動します。


行末にいるときに、`cursorWordEndRight`をさせない理由は、` = `のようにスペースとイコール(`shift + -`)を打っていると、`shift + space`と認識され、`cursorWordEndRight`で次の行へ移動してしまうためです。

## まとめ

**Cursor State Checker**とキーボードショートカットによって、ホームポジションを維持しつつ、1つのキーパターンで効率よくカーソル移動ができるようになりました。
1つのキーパターンにまとめているため、記号が連続していると、スペース連打しないといけないですが・・・

簡単な動作確認しかしていないため、意図しない挙動がありそうですが、気づいたら修正しようと思います。
もしよければ使ってみてください。

https://marketplace.visualstudio.com/items?itemName=tokoro-it.cursor-state-checker