概要
AutoHotkey を用いて、vi っぽくカーソルを移動してみようという試みです。
前の記事のオマケの様なものです。
あちらの記事では、ahk ファイルをデデーンと上げただけだったので、今度は少し順を追って説明して行こうと思います。
こちらでは、スペースバー同時押し(SandS)というアプローチをとっているため、空白文字のキーリピートによる連続入力ができない、などの副作用があります。また、ロールオーバー打鍵時のスペースバー残りにより、所謂シフト残りのようなことが起こることがあります。(なるべく避けようとしていますが)
環境
- windows10 Home 1909
- AutHotkey v.1.1.33.02
日本語キーボード(106/109キー)環境を想定して作成されています。そのため、英語キーボード(101/104キー)を使用している場合などでは、予期せぬ動きをすることがあります。
そもそも作った本人が、常駐はさせていても、このスクリプトの機能をバリバリと呼び出すことがあまりないので、特に問題になっていません。
# vimode.ahk がある現状、もはやそれほどヘビーに使う物ではありませんし。。
それでも「今ほんのちょっとカーソルを動かしたいだけなのに!」という時、存外に役に立つのです。
前の記事で紹介した、拙作 vimode.ahk と同時に常駐させて使用していますが、これといった問題は起こっていません。
そもそも vi とは
モーダルエディタと呼ばれる、なんだかカッコイイテキストエディタです。
言葉としては、モーダルダイアログとかの方が馴染みがあるかもしれませんが、なんだか響きがモータル(定命の者)と似ていて、格好良くないでしょうか?
とても一言では説明できる物ではないのですが、乱暴に一口で言おうとすると、純然たる「文字の入力」と、それ以外の操作(カーソルを移動したり削除したりコピペしたり等々)をモードを分けて行う物、でしょうか。
一般的なモードレスエディターが、ストリートファイター系のコマンドが「レバー→ボタン」と例えるのならば、モーダルエディタである vi / Vim は、バーチャファイター系のコマンドのようにボタンの羅列でファイルと格闘することになります(全然違う)
その威力が十全に振るわれる様は「カーソルが蝶のよう画面を舞い飛び、ファイルが粘土のように姿を変える」とまで例えられたとか、例えられないとか。
親指シフターの言う「指が喋り出す」に通じるキャッチーさ、かつ、胡散臭さがあります。余談ですが、私は親指シフトは挫折しましたからね。指で軽やかにお喋りをするためにはキーボードの選択肢が限られてしまうという問題があり、常用を断念しました。そして今では中指シフトです。ええ、指で喋ってますとも。少なくともローマ字入力に比べれば断然、喋ってる気がする。
おそらく少数派であろうと思いますが、私は WZ Editor の「viモード(WZマクロ)」から vi道へ入門しました。
C風のソースだったので自分でも弄り回したものです。
その後、JVim3.0 をしばらく使い、その後は、Kaoriya版の GVim6.2 ~ GVim8.2 までを追いかけて使わせて貰っています。
今では、三度の飯とドッコイドッコイなくらいには Vim が好きです。
もしもこの世から Vim が無くなったら等と考え出したら、ご飯も喉を通らなくなってしまうでしょう。
是非、Vim にはイモータル(不滅)でいて欲しいものです。
ただし、その素晴らしきモーダルエディタたる vi / Vim 風にカーソルを移動しよう、というトピックであるにも関わらず、「モードレス版」とはこれ如何に?というのがここからのお話の筋道になります。
SandS とは
スペースバーにスペースバー以外の働きを持たせようという考え方です。
スペースバーを単体で押して離した場合は、単なるスペースバーとして作用するようにしておくため、スペースを入力することができなくなってしまうわけではありません。
スペースバーを押し下げている間に、他のキーを同時に押した場合、異なった作用をさせよう。という趣旨です。
元々は SandS (Space and Shift) の名の通り、スペースバーにシフトキー(小文字→大文字)の機能を持たせる物です。
具体的に説明をすると・・・
キーボードの「a」を打つと画面には「a」と出る。コレは当たり前です。。
シフトキーを押し下げても何も起こりません。
シフトキーを押したまま、キー「a」を打つと、画面には「A」と出る。
その後、シフトキーを離しても何も起きない。コレもまだ当たり前です。キーボードのスペースバーを押し下げると、画面には空白文字がキーリピートによりドバーと入り始めます。
スペースバーを押したまま、キー「a」を打つと、空白文字のリピートが止まり、画面には「a」と出ます。
その後、スペースバーを離しても何も起きません。コレもまだまだ当たり前です。
ここまでが、いつもの当たり前の世界です。
それが、SandS では以下のようになります。
- スペースバーを押し下げても何も起こりません。(普通じゃない!)
スペースバーを押したまま「a」を打つと、「A」と出る。(何それ便利!?)
ちなみに、何もせずにそのままスペースバーを離したときには1文字分だけの空白文字が画面に出力されます。
SKK、とか、新JISカナ入力、とか、英文タイプといった方面などでは、重宝する人もいるとかいないとか。
代償として、空白文字のキーリピートによる連続入力が行えなくなります。
日本語入力をしている上では、スペースバーは仮名漢字変換の機能に用いることが多いと思います。
スペースバーを押し下げて変換を開始、そのまま次の文章の入力を続けたつもりが、スペースバーから指が離れる前に文字の入力をしてしまっていて、変なところに大文字アルファベットが?とか、文字抜けが!とかいうこともあると聞き及んでいます。
これが発展して、スペースバーをシフトキーとして使うだけではもったいないのでは!? そうだ、Alt同時押しのようにまるで異なる機能を持たせてしまおう、というものも SandS と呼ばれているようです。
このような、装飾キーそのものにも機能を持たせてしまおうという考え方は、ワンショットモディファイヤとも呼ばれていました。(というかワンショットモディファイヤの一種が SandS ?)
SandSサンプル(AutoHotkey)
ごく簡単なSandSのサンプルコードを提示します。
実験的なコードなので、実際に使おうとしてはいけません。
サンプルの題材としては、タイトル通りに hjkl カーソルです。
Space::Send,{Space}
+Space::+Space
^Space::^Space
Space & h::Send,{Left}
Space & j::Send,{Down}
Space & k::Send,{Up}
Space & l::Send,{Right}
スペースバー単打の定義
最初のホットキー「Space::Send,{Space}」は、単打でスペースバーを打ったときにはスペースだよ、ということを指示しています。
コレがないと、スペースバーを離してもスペースが入力されなくなってしまうので必須です。
修飾キー+スペースバーの定義
次はリマップが2つあります。
(ホットキーとリマップの違いはAutohotKeyマニュアルなどを参照。「::」の後ろにコマンドではなくキー名が直接書いてあればリマップと思っていれば、たぶん問題ないです。)
+Space::+Space
^Space::^Space
コレがないと、Shift+Space や Ctrl+Space が効かなくなってしまいます。
Excel で行選択や列選択ができなくなってしまうので必須です。(Excelを使っていないなら要らないのかも?)
必要に応じて「#Space::#Space」等も書いた方が良いかもしれないですね。
スペースバーが押し下げられている最中、というのが SandS のキモなのですが、他の修飾キーとスペースバーの組み合わせは適宜、定義してあげないといけません。
hjkl カーソルの定義
下の4行がメイン部分です。
Space & h::Send,{Left}
Space & j::Send,{Down}
Space & k::Send,{Up}
Space & l::Send,{Right}
今更ですが、AutoHotkeyではホットキーの「::」の直後にコマンドを続けることで、1行で記述を済ますことができます。
とっても簡単ですね!
でもやっぱりコレではいろいろと足りていないのです。
たとえば、Shiftと同時押しで範囲選択しながらカーソル移動したいなー、と思ったらその定義もしなければなりません。
Altと同時押しで、進んだり戻ったりしたいなーと思った場合にも、やはり定義が必要です。
その辺りを、実際に使いながら育てていった結果として、出来上がった物が今回の成果物になります。
機能説明
常駐させると、hjkl 等のおなじみの vi でカーソルを移動させる系のコマンドが使えるようになります。
一部の編集系も入っています。
大まかな機能を以下に列挙します。
キー | Space同時押し | Space、Shift同時押し | Space、Ctrl同時押し | 備考 |
---|---|---|---|---|
h | ← | Shift + ← | Shit + Ctrl + ← | カーソル移動、選択しつつ移動、選択しつつ単語単位で移動 |
j | ↓ | Shift + ↓ | Shit + Ctrl + ↓ | 同上 |
k | ↑ | Shift + ↑ | Shit + Ctrl + ↑ | 同上 |
l | → | Shift + → | Shit + Ctrl + → | 同上 |
f | PgDn | Shift + PgDn | Shit + Ctrl + PgDn | 同上 |
b | PgUp | Shift + PgUp | Shit + Ctrl + PgUp | 同上 |
0 | Home | Shift + Home | 定義無し | 同上。行内の移動なので、Ctrl同時押し無し |
4 | End | Shift + End | 定義無し | 同上。$キーのつもりですが、Shift & で記述するに当たっては「4」と書かざるを得ませんでした |
o | End,Enter | Home,Enter,Up,End | 定義無し | 行末へ行って改行、行頭へ行って改行して全行に戻る |
m | Enter | 定義無し | 定義無し | 改行 |
x | Delete | BackSpace | 定義無し | 削除、後退 |
d | Ctrl + x | 定義無し | 定義無し | 切り取り |
i | Tab | 定義無し | 定義無し | Tab挿入 |
/ | Ctrl + f | 定義無し | 定義無し | 検索ダイアログ |
y | Ctrl + c | 定義無し | 定義無し | コピー |
p | Ctrl + v | Excelで値貼り付け | 定義無し | 貼り付け |
u | Ctrl + z | 定義無し | 定義無し | アンドゥ |
r | Ctrl + y | 定義無し | 定義無し | リドゥ |
[ | ESC | 定義無し | 定義無し | キャンセル |
他にも、Excelに特化したキーバインドがいくつか定義してあります。
ソースを見ればわかりますが、説明にも追々追記します。
ある意味、スペースバーを押している間だけ、vi の通常モードにいる。モーダルである、とも言えるのかもしれません。・・・いや、やはり私はこれをモーダルとは認めない!(即時前言撤回)
スクリプト本体
ファイルとしては、BOM付きの UTF-8 で保存してください。
拡張子 ahk が AutoHotkey に関連づけられている環境であれば、ファイルを起動するだけで常駐を開始します。
#InstallKeybdHook
;←
Space & h::
GetKeyState,state,Shift,P
if state=U
{
GetKeyState,state,Ctrl
if state=U
Send,{Left}
else
Send,^{Left}
}
else
{
GetKeyState,state,Ctrl
if state=U
Send,+{Left}
else
Send,^+{Left}
}
return
;↓
Space & j::
GetKeyState,state,Shift,P
if state=U
{
GetKeyState,state,Ctrl
if state=U
Send,{Down}
else
Send,^{Down}
}
else
{
GetKeyState,state,Ctrl
if state=U
Send,+{Down}
else
Send,^+{Down}
}
return
;↑
Space & k::
GetKeyState,state,Shift,P
if state=U
{
GetKeyState,state,Ctrl
if state=U
Send,{Up}
else
Send,^{Up}
}
else
{
GetKeyState,state,Ctrl
if state=U
Send,+{Up}
else
Send,^+{Up}
}
return
;→
Space & l::
GetKeyState,state,Shift,P
if state=U
{
GetKeyState,state,Ctrl
if state=U
Send,{Right}
else
Send,^{Right}
}
else
{
GetKeyState,state,Ctrl
if state=U
Send,+{Right}
else
Send,^+{Right}
}
return
;PageDn
Space & f::
GetKeyState,state,Shift,P
if state=U
{
GetKeyState,state,Ctrl
if state=U
Send,{PgDn}
else
Send,^{PgDn}
}
else
{
GetKeyState,state,Ctrl
if state=U
Send,+{PgDn}
else
Send,^+{PgDn}
}
return
;PageUp
space & b::
GetKeyState,state,Shift,P
if state=U
{
GetKeyState,state,Ctrl
if state=U
Send,{PgUp}
else
Send,^{PgUp}
}
else
{
GetKeyState,state,Ctrl
if state=U
Send,+{PgUp}
else
Send,^+{PgUp}
}
return
;Home
Space & 0::
GetKeyState,state,Shift,P
if state=U
Send,{Home}
else
Send,+{Home}
return
;End
Space & 4::
GetKeyState,state,Shift,P
if state=U
Send,{End}
else
Send,+{End}
return
;Space & v::return
Space & o::
GetKeyState,state,Shift
if state=U
{
Send,{End}
Send,{Enter}
}
else
{
Send,{Home}
Send,{Enter}
Send,{Up}
Send,{End}
}
return
Space & m::Send,{Enter}
Space & x::
GetKeyState,state,Shift
if state=U
Send,{Delete}
else
Send,{BS}
return
Space & d::Send,^x
Space & i::Send,{Tab}
Space & /::Send,^f
Space & y::Send,^c
Space & p::
GetKeyState,state,Shift
if state=U
Send,^v
else
{
Send,!e
Send,s
Send,v
Send,{Enter}
}
return
;save(危険なのでコメント)
;Space & s::Send,^s
Space & u::Send,^z
Space & r::Send,^y
ctrl & [::Send,{Esc}
;Excel
#ifWinActive ahk_class XLMAIN
Shift & Space::
GetKeyState,state,Ctrl
if state=U
Send,+{Space}
else
Send,+^{SPace}
return
Space & 1::Send,^{1}
Space & a::Send,{F2}
Space & i::
Send,{F2}
Send,^{Home}
return
Space & o::
Send,{Enter}
Send,{F2}
Send,{Home}
return
Space & c::
Send,{F2}
Send,{Home}
Send,+{End}
return
Space & ,::Send,^{PgUp}
Space & .::Send,^{PgDn}
#ifWinActive
;
;#ifWinActive ahk_class bosa_sdm_XL9
#ifWinActive セルの書式設定
Space & ,::Send,^{PgUp}
Space & .::Send,^{PgDn}
;Space & a::Send,!a
;Space & b::Send,!b
;Space & c::Send,!c
;Space & d::Send,!d
;Space & e::Send,!e
;Space & f::Send,!f
;Space & g::Send,!g
;Space & h::Send,!h
;Space & i::Send,!i
;Space & j::Send,!j
;Space & k::Send,!k
;Space & l::Send,!l
;Space & m::Send,!m
;Space & n::Send,!n
;Space & o::Send,!o
;Space & p::Send,!p
;Space & q::Send,!q
;Space & r::Send,!r
Space & s::Send,!s
;Space & t::Send,!t
;Space & u::Send,!u
;Space & v::Send,!v
;Space & w::Send,!w
;Space & x::Send,!x
;Space & y::Send,!y
;Space & z::Send,!z
#ifWinActive
Space::Send,{Space}
#Space::#Space
^Space::^Space
;シフト残りで変換し損ねて確定してしまうのを防止。
Space & Enter::
Gosub, IMEGetstate
If (vimestate=1) ;ime=on
{
Send {Space} ;変換
Send {Enter} ;確定
}else
{
Send {Enter}
}
return
Space & ,::
Gosub, IMEGetstate
If (vimestate=1) ;ime=on
{
Send {Space} ;変換
Send {,}
}else
{
Send {,}
}
return
Space & .::
Gosub, IMEGetstate
If (vimestate=1) ;ime=on
{
Send {Space} ;変換
Send {.}
}else
{
Send {.}
}
return
;変換
;vk1Csc079::Gosub, imeon
;無変換
;vk1Dsc07B::Gosub, imeoff
;改行
space & n::Send {Enter}
imeon:
Gosub, IMEGetstate
If (vimestate=1) ;imeがonなら
{
;なにもしない
}
else
{
Send, {vkf3} ;全角/半角
}
return
imeoff:
Gosub, IMEGetstate
If (vimestate=0) ;imeがoffなら
{
;何もしない
}
else
{
Send, {vkf3}
}
return
IMEGetstate:
DetectHiddenWindows, ON
WinGet, vcurrentwindow, ID, A
vgetdefault := DllCall("imm32.dll\ImmGetDefaultIMEWnd", "Uint", vcurrentwindow)
vimestate := DllCall("user32.dll\SendMessageA", "UInt", vgetdefault, "UInt", 0x0283, "Int", 0x0005, "Int", 0)
DetectHiddenWindows, Off
return
最後に
実は、こちらの vilike.ahk の方が、vimode.ahk よりも前にできあがっていた物になります。
vimode.ahk の方は、当時はまだ vimode.mayu という名前でした。
AutoHotkey_L が出てきたり、窓使いの憂鬱の開発が終了したりといろいろありましたが、
何はともあれ、今でも AutoHotkey という素晴らしいソフトウェアと共にあれることに感謝します。
もちろん Vim ともね!