[AutoHotKey]TextExpanderっぽいことをやる方法と問題点まとめ


## 環境
Windows7
AutoHotKey 1.1.25.01

日本語環境で使うなら、絶対にAutoHotKey本体をUnicode版にすべき。そうでないと面倒が多すぎるので、以下はその前提で記述する。

## 概要
AutoHotKeyをTextExpanderのようなスニペット展開アプリとして利用しています。

実装としては、ホットストリングで文字列をペーストする関数をコールするという感じで。

```txt:sample.ahk
::ahk::
    PasteString("AutoHotKey")
    Return
```

んで、この`PasteString()`関数の中身の話。

## 方法
AutoHotKeyでTextExpanderっぽい動きをするには、以下のような方法がある。

- [自動置換型ホットストリング](http://ahkwiki.net/Hotstrings#.E8.87.AA.E5.8B.95.E7.BD.AE.E6.8F.9B.E5.9E.8B)
- スクリプト実行型ホットストリング
    - `Send`系コマンド
    - クリップボードに格納してペーストする
        - `Send`/`ControlSend`で`Ctrl+V`を送る
        - `PostMessage`/`SendMessage`で`WM_PASTE`メッセージを送る

入力はホットストリングで、出力をどうするかという話になる。これがまあ、見事にそれぞれ一長一短。

## 自動置換型
この場合、**IMEオンで実行すると、英文字が日本語入力になってしまう**のが最大の問題。
例えば、`:0:btw::by the way`とすると、`byてぇわｙ`という入力になってしまう。
また、変換未確定状態になるので、いちいち確定するのも面倒。

IMEオフで実行するなら、上記の問題はないし日本語も送れちゃうが、後述のスクリプト実行型と違い、これそのものにIMEオフにする処理を仕込むことはできない。

`*`オプションを指定するホットストリングでなければ、**終了文字のホットキーにIMEオフにする処理を仕込む**という対策はいちおう考えられる。

[IME\.ahk](https://github.com/karakaram/alt-ime-ahk/blob/master/IME.ahk)を使えば、`IME_SET(0)`でIMEをオフにできるので、例えば`;`を終了文字とするならこんな感じ。

```sample.ahk
~`;:: ; `;`キーの押し下げ時、そのままキー入力を通しつつIMEの状態を取得、かつオフにする
    ime_status := IME_GET()
    IME_SET(0)
    Return
`; up:: `;`キーを離した時、IMEの状態をもとに戻す
    ; Sleep, 100 ; 戻るタイミングが早すぎるようなら
    IME_SET(ime_status)
    Return
```

もちろん、`#If !IME_Get()`として、IMEがオフの場合のみ有効、といった形にもできる。

## スクリプト実行型
### [共通]ホットストリングの削除が間に合わない場合がある
<!-- [ホットストリングのオプション](http://ahkwiki.net/Hotstrings#.E3.82.AA.E3.83.97.E3.82.B7.E3.83.A7.E3.83.B3)で`K-1`を指定すると、 -->
どうも**ホットストリングの削除を待たずに**スクリプトの実行を開始するようで、削除が間に合わない場合がある。

ホットストリングを主に文字入力以外に用いるならこのほうがよかろうが、他の用途がメインの人はめったにいないと思う。

### `Send`系コマンド
`Send`系コマンドの場合、IMEオン時の問題は自動置換型と同様だが、こちらはホットストリング単独で回避可能。

例えば以下のようにすれば問題ない。

```sample.ahk
::btw::
    ime_status := IME_GET()
    IME_SET(0)
    Send, by the way
    Sleep, 100 ; 送信中に戻らないように待機
    IME_SET(ime_status)
```

送る文字列が長いと、送信中にIMEオンに戻ってしまうことがあるので、`Sleep`を入れるなどの対策が必要。

あと、Google日本語入力だとオンに戻った時通知が出るのがちょっとうっとうしかった。これはGoogle日本語入力側でモード表示をオフにすることで対応。

![スクリーンショット](https://i.imgur.com/MfGacOt.jpg)

修飾キーを示す記号（`^!+#`）や特殊キーのキー名とみなされる文字列（`{Enter}`等）を送信する場合、文字列そのものをエスケープするか、Rawで送信する必要がある。（

実行速度は、PCのスペック次第とはいえ遅い。概ね、見えない誰かがズバズバ入力しているのが見える程度だと考えるべき。

<!-- input/play -->

### クリップボードに格納してペーストする
#### [共通]OnClipboardChangeとの干渉
OnClipboardChangeラベル（関数）を利用してクリップボードの監視を行っている場合、当然これもトリガーになる。

<!-- `Sleep`さえ入れなければ、処理が速すぎて引っかからない場合もあるのだが、そういうわけにもいかない。 -->

関数版であれば、`OnClipboardChange("<関数名>",0)`とすることで、監視を停止することで対策は可能。（[\[AutoHotKey\]OnClipboardChange\(\)関数でクリップボード監視 \- Qiita](https://qiita.com/catfist/items/cdb8300221b48667b155)）
ラベル版でも、グローバル変数をフラグに使えば同じことはできるが、もうこの際みんな関数版使ったほうが幸せだと思う。

ただし、それも`OnClipboardChange`と`PasteString()`が同じスクリプト（`#Include`関係を含む）でなければ成立し得ない。

Windows環境変数とか、[OnMessage\(\)](http://ahkwiki.net/OnMessage)関数による[プロセス間通信](https://autohotkey.com/board/topic/71523-ahk-lahk-helpfile-onmessage/)を使えば、`OnClipboardChange`を含むスクリプトに監視停止させることは可能だが、めんどいし実行速度的にもよろしくなさそう。

#### `Send`/`ControlSend`で`Ctrl+V`を送る
これがまあ一番無難で、[AutoHotKey Wiki](http://ahkwiki.net/Hotstrings#.E3.82.B9.E3.82.AF.E3.83.AA.E3.83.97.E3.83.88.E5.AE.9F.E8.A1.8C.E5.9E.8B)のサンプルでも採用されている。

ただし、ターミナル作業中など、**ペーストのキー操作がCtrl+Vではない**とか、パスワード入力などで、文字入力だがペースト不可といったケースでは当然動作しない。
このような場合、`#IfWinActive`等で判定して`SendRaw`に切り替えるといった対策は可能。

#### `PostMessage`/`SendMessage`で`WM_PASTE`メッセージを送る
この場合、キー操作ではなく「ペーストしろ」という命令自体を送るので、どのようなキー操作にペーストが割り当てられていようが対応可能。

ただし、そもそも動作しないウィンドウが少なくない。
例えば、コマンドプロンプトは右クリックから貼り付けは可能だが、`WM_PASTE`命令は受け付けないようだ。
また、名前が割り当てられていないコントロールの場合も動作しないし、使える状況はかなり限られる。

どちらかといえば、ショートカットはCtrl+Vではないがペーストは可能な、一部のアプリで限定して利用すべきだろう。

```sample.ahk
PasteString(String)
{
    OnClipboardChange("ClipChanged",0)
    Backup := ClipboardAll
    Clipboard := String
    ControlGetFocus, focusedControl, A
    SendMessage, 0x0302, , , %focusedControl%, A
    Clipboard := Backup
    OnClipboardChange("ClipChanged",1)
}
```


## 割り切りか、分岐か
使えるところでだけ使うと割り切れば、それこそWikiのサンプルスクリプト程度の処理でも問題はない。
どこでも`Ctrl+V`、どこでも`SendRaw`でも、まあ便利ではある。

`Ctrl+V`でペーストできない奴がバカなんだバーカ！！！っつって、コマンドプロンプトに[Clink](https://mridgers.github.io/clink/)入れるとか、そういう工夫の方向性だってある。

分岐いっぱいつけてスクリプトをゴージャスにしても実行速度がアレだし、自分のユースケースに合わせて必要な条件を整理して、あとは割り切って使うのが重要な気がする。