rcmdnk's blog

20130723_AutoHotKey_200_200

AuotHotkeyで何らかアクションを起こす時に カーソル下の文字を見てアクションを変える方法を覚えたのでそれについて。

やりたいこと

このIssueでもらったもの:

Normal mode ‘a’ behavior in the end of line · Issue #74 · rcmdnk/vim_ahk

vim_ahkでノーマルモード中、 Aを押すとカーソルの右側に移動してインサートモードに移る機能があります。 これは単にrightを入力してモード変換をしているだけです。

これが行末の場合、次の行に行ってしまいます。

一方、実際のVimでは行末でAを押すとその行にとどまってインサートモードに入ります。 正確にはVimのノーマルモードではカーソルは各文字の上にあるような形になっていて、これは通常時の文字の左側にカーソルがあるのと同じです。 そして行末、というのが最終文字、となっているので実際には最後から一文字手前のところにカーソルがあることになります。 なのでそこからAするとその行の最後から書き始め、になります。

これを実際のVimっぽくしたい、と。

方法

この問題は分かってましたが行末とかわからないしな、と思って取り組めてませんでしたが、 良い参考を教えてもらいました。

Identifying “End of Line” when typing with AHK? And implementing it into a hotstring. : AutoHotkey

ここにある例がまさに行末の判定をしてアクションを変えていて、ほぼそのまま使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  CheckChr(key){
    BlockInput, Send
    tempClip := clipboard
    clipboard := ""
    SendInput {Shift Down}{Right}{Shift up}{Ctrl down}c{Ctrl Up}{Left}
    Sleep 10
    ret := False
    If (clipboard ~= key){
      ret := True
    }
    sleep 10
    clipboard := tempClip
    BlockInput, off
    Return ret
  }

https://github.com/rcmdnk/vim_ahk/blob/98759b8a692896fa96c0b1e96d2a325847ce542d/lib/vim_ahk.ahk#L278

こんな感じの関数を用意して、カーソル下の文字が引数にいれる文字と一致するかどうかをチェックします。

AutoHotkeyではclipboardという変数がWindowsのクリップボードの値と同期しているのでそれを使います。

このアクションが起こった後にクリップボードを変更したくないので元の値を一旦退避して、

  • Shift-Right

で一文字選択した状態で

  • Ctrl-C

でコピーする、という作業をします。

コピーした後にその中身が何かをチェックしています。

あとは AutoHotkeyでの改行は`nなので、 こんな感じで改行コードでない場合だけ右にいくようにするだけ。

1
2
3
4
5
6
a::
  if(! Vim.CheckChr("`n")){
    Send, {Right}
  }
  Vim.State.SetMode("Insert")
Return

https://github.com/rcmdnk/vim_ahk/blob/98759b8a692896fa96c0b1e96d2a325847ce542d/lib/bind/vim_enter_insert.ahk#L9

以下、元の参考を見て新たに知ったこととかよく分かってないこととか。

Hotstrings

元の参考コードは括弧を自動で閉じる機能ですが、 キーバインド部を

1
2
:*:{::
    EOL(StrReplace(A_ThisHotkey, ":*:", ""))

こんな感じにしています。

この:*:{::ですが、:*:{が左辺で::を境にして設定される側とそれに対するアクション(キー)が書かれています。

左辺に:が2つあるのが Hotstrings と呼ばれるもので、これは複数の入力文字に対してアクションを指定したい場合に使います。

1
::btw::by the way

これだとbtwと打った後にSpace.Enterのいずれかが押されるとbtwby the wayに変換されます。

この最初の::の間にはオプションを入れることができ、 :*:は上の指定キーを入力した瞬間に即座にアクションを実行する、というもの。

btwの例だとスペースとかを押さなくてもすぐに変換されることになります。

スニペット的な感じ。

他のオプションとかは Hotstringsのドキュメント 参照してください。

で、ここでは{とかだけをキーバインドしたいわけですが、 それに対して即時オプションを設定すると単に

1
2
{::
    EOL(StrReplace(A_ThisHotkey, ":*:", ""))

とするのと変わらない気が。。。

実際ちょっと試した限りではまったく同じように動作しました。

上の参考ページは5年前の記事なので、その間に変わったこととかがあるのかもしれませんし、 何かしら特殊な状況で必要なのかもしれませんが、それが何か分かってません。

A_ThisHotkey

キーバインドする際に、出力側でA_ThisHotkeyを使うと入力の文字列になります。 (正確には最後に実行されたホットキーが格納されている。)

上の例だと

1
    EOL(StrReplace(A_ThisHotkey, ":*:", ""))

のようにして、:*:{とかなっているのをSetReplaceできれいにして{として渡しています。

これはもしEOLがこれにしか使わないならSetReplaceEOLの関数の中に入れてしまえば良いし、 もう一つラッパー関数を作っても良いかもしれません。

また、そもそもそれぞれのキーは決まっているので

1
    EOL("{")

そしてしまえば。

また、全部同じことをしたいなら,

1
2
3
4
5
    :*:{::
    :*:[::
    :*:(::
      EOL(StrReplace(A_ThisHotkey, ":*:", ""))
      Return

といった感じにまとめることもできます。

Sleep or Clipwait

上の例では Sleep を使っていますが、AutoHotkeyには ClipWait という機能もあります。

ClipWaitだとタイムアウト時間を設定してコピーが完了次第次に進めるのでそっちのが良さげ。

実はvim_ahkの中でClipWaitを使ってる箇所があります。

vim_ahk/vim_normal.ahk at 98759b8a692896fa96c0b1e96d2a325847ce542d · rcmdnk/vim_ahk

完全にどうやってるか、は忘れてましたが、~でカーソル下の文字を大文字小文字でトグルする機能で 文字の大小を見て変換する、という際にクリップボードを使っています。 ここではClipWaitを利用。

多分ClipWaitの方が良いんだと思いますが、ちょっとまたちゃんと調べてから変更しようかと思ってます。

その他で活用できそうな所

vim_ahkの中で実際のVimと動作が微妙に違う部分はたくさんあって、 特に今回の例のようにカーソル下の状態とか、周辺の状況を把握しないと無理、というものが結構あります。

その辺あまりまとめて無かったんですが、 ざっと思いつくところからリストアップしてそのうちやろうかと。

use CheckChr, BlockInput for some places · Issue #76 · rcmdnk/vim_ahk

クリップボード以外にも、ノーマルモードでh/j/k/lのような移動キーをキーリピートをすると文字が入力されてしまったりすることもあり、 この辺参考例の中にある BlockInput を使ったらうまく出来ないかな、とかも。

もし何か使ってて気になる点とかがあれば気軽にGitHubのIssueとか Twitterとかでも良いので教えていただけるとありがたいです。

Sponsored Links
Sponsored Links

« ちょっといい椅子買った(エルゴヒューマンプロオットマン) PTH-8: スマホで確認できる二酸化炭素濃度計 »