AutoHotkey v2で実現するViエディタ風モーダル機能

IT

AutoHotkeyは、Windows環境でスクリプトを使ってタスクを自動化したり、カスタムツールを作成するための非常に便利なツールです。前回はAutoHotkey v2の基本的な使い方をご説明しましたが、今回はその続編として、Viエディタ のようなモーダル操作を AutoHotkey で実現する方法をご紹介します。これにより、キーボード操作の効率をさらに高め、より快適な Windows 環境を構築できるでしょう。

①このページでは、AutoHotkey v2でViエディタ風のモード切替機能を実装します。


先にAutohotkeyの基本を知りたい場合は、次の記事をご参照ください。


AutoHotkey で Viエディタ風モーダル操作を実現する

 出典:AutoHotkey.com

Viエディタ風モーダル操作とは?

Viエディタ(Vimエディタ) は、キーボードだけで効率的にテキスト編集を行うための高機能なテキストエディタです。その特徴の一つが「モーダル操作」です。これは、キーボードの状態によってキーの役割が変化する方式で、主に「ノーマルモード(コマンドモード)」と「挿入モード」の2つのモードがあります。

  • ノーマルモード(コマンドモード): 文字入力以外の操作(カーソル移動、コピー、ペースト、検索など)を行うモード。
  • 挿入モード: 文字を入力するモード。

モードを切り替えることで、キーの数を少なくしながら多くの操作を実現しています。

AutoHotkey で Viエディタ風モーダル操作を実現する

今回のスクリプトでは、Ctrl キーまたは無変換キーを使ってモードを切り替えることで、Vim のような操作感を実現します。具体的には、以下のようになります。

  • Ctrl のキーストローク(Down+Up): 挿入モードと FnMode(コマンドモードに相当)を切り替えます。
  • 無変換キーの押下中(Down 中): FnModeになります。離すと挿入モードに戻ります。

Viエディタの場合は、Escキーを押下する事でモードを切り替えますが、筆者は60%サイズのキーボードを使用している都合上、Escキーがそもそも存在しませんし、そもそもEscキーは左手にとっては少々遠い位置にあります。そこで上記の押しやすい位置のキーを、モード切替用に使用する事としました。

この FnModeでは、数字キーで F1〜F12 キー、h/j/k/l キーで矢印キー、n/m/コンマ/ピリオドキーで Home/End/PageUp/PageDown キーを操作できます。これにより、60% キーボードなどでも効率的に操作できます。

前の記事(参照項記事リンク)でもViエディタ風のカーソル移動ホットキーを説明しています。こちらは無変換キー+ [ H or J or K or I ] でカーソルを移動する方法を説明していますが、今回の記事ではCtrlキーの単独ストロークで明示的にモードを切り替える方法を説明しています。

Autohotkey v2のサンプルスクリプト

スクリプトの解説

以下のスクリプトの仕様は次の通りです。

サンプルスクリプトのGUIの仕様
  • FnMode(コマンドモード)の切り替え:
    • 左Ctrlキーのキーストローク(Down+Up)で、通常の挿入モードとFnMode(コマンドモード)が切り替わります。
    • 無変換キーを押している間はFnModeになり、離すと挿入モードに戻ります。
  • FnMode中のキー割り当て: FnMode中は、以下のキーが特別な機能に割り当てられます。
    • 数字キー(1〜0、-、^):それぞれF1〜F12キーとして機能します。
    • h、j、k、lキー:それぞれ左、下、上、右の矢印キーとして機能します(Vimの操作に準拠)。
    • n、mキー:それぞれHome、Endキーとして機能します。
    • コンマ(,)、ピリオド(.)キー:それぞれPageUp、PageDownキーとして機能します。
  • FnMode中の表示:
    • 左CtrlによりFnModeに入ると、画面上部中央に現在のモードと時刻を示す半透明のGUIが表示されます。これにより、現在のモードが視覚的に確認できます。
  • Delキーの割り当て:
    • Shift+BackSpaceでDelキーとして機能します。
    • Ctrl+Shift+BackSpaceでCtrl+Delとして機能します。

次のソースを.ahkファイルに張り付けてください。

;;;;;;;;;;; VIM風のモーダル機能の実装 ;;;;;;;;;;;;;;
;Ctrlキーをキーストローク(Down+Up)でVimのように挿入モードとコマンドモード(当スクリプトではFuncionModeと呼ぶ)を行き来する。
;また、無変換キーの押下中(Down中)でもVimのコマンドモード(当スクリプトではFuncionModeと呼ぶ)のようになる。無変換キーを離した状態だと通常の挿入モードになる。

; ctrlでFuncionModeにする場合、Excel等のオブジェクトの複数選択のctrl+マウスクリックや、ブラウザのリンククリックによるタブ追加のCtrl+マウスクリック時に意図せずFuncionModeになるので、これを防ぐ。
global mouse_count1 := 0
global mouse_count2 := 0

; FnModeフラグ 0:通常モード  1:Fnモード
global FnMode_f := 0
; FnMode_fがFnモードである事を示すGUI
global FnModeGui := ""  ;

; ctrlでFuncionModeにする場合、Excel等のオブジェクトの複数選択のctrl+マウスクリックや、ブラウザのリンククリックによるタブ追加のCtrl+マウスクリック時に意図せずFuncionModeになるので、これを防ぐ。
~^LButton up::
~^RButton up::
{
  global
  mouse_count1 += 1
}

; 左CtrlでFnModeを切り替える
~LCtrl UP::
{
  global
  if ((A_PriorKey = "LControl" and  mouse_count1  = mouse_count2  )) { 
    if (FnMode_f>0) {
      sub_FnModeEnd()
    } else {
      sub_FnModeStartP()
    }
  }
  mouse_count2 := mouse_count1
}



; 無変換キーvk1D をFnキーとして使用する。
vk1D::
{ 
  sub_FnModeStart()

  ; KeyWaitはT秒待ち、タイムアウトしたら 0(偽)を、そうでないなら 1(真)を返す
  ; 無変換キーを0.2秒で離すならカナ変換を意図する
  if (KeyWait("vk1D", "T0.2"))  { 
    ; 無変換キーを0.2秒で離す間に別キーを押下している場合はカナ変換の意図なし
    if (A_ThisHotkey= "vk1D")  { 
      Send("{vk1D}")
    }
  } Else  {
    KeyWait("vk1D")
  }
  sub_FnModeEnd()
}



#HotIf (FnMode_f=1) 

;Functionキーを数値に割り当て
*1::Send("{Blind}{F1}")
*2::Send("{Blind}{F2}")
*3::Send("{Blind}{F3}")
*4::Send("{Blind}{F4}")
*5::Send("{Blind}{F5}")
*6::Send("{Blind}{F6}")
*7::Send("{Blind}{F7}")
*8::Send("{Blind}{F8}")
*9::Send("{Blind}{F9}")
*0::Send("{Blind}{F10}")
*-::Send("{Blind}{F11}") 
*^::Send("{Blind}{F12}")

;矢印キーをviエディタ風に割り当て
*h::Send("{Blind}{Left}")
*j::Send("{Blind}{Down}")
*k::Send("{Blind}{Up}")
*l::Send("{Blind}{Right}")

;その他の割り当て
*n::Send("{Blind}{Home}")
*m::Send("{Blind}{End}")
*,::Send("{Blind}{PgUp}")
*.::Send("{Blind}{PgDn}")

#HotIf


;Delの割り当て
 +BS::Send("{Del}")
^+BS::Send("^{Del}")

;半角全角キーのスキャンコード:SC029 
;60%キーボードのTabキーの上が半角全角キーなら、1回押下はIME切り替えで、2連打はEscの機能とする。
SC029::
{
  Send("{SC029}")
  If (A_PriorHotkey == A_ThisHotkey && A_TimeSincePriorHotkey < 400) { 
    Send("{Esc}")
    sub_FnModeEnd()
  }
}



;;;;;;;;;;; Function ;;;;;;;;;;;;;;

sub_FnModeStart()
{ global ;グローバル変数を使えるよう記載
  FnMode_f := 1
  ToolTip("FnMode_f:" FnMode_f , 5, 100, 1)
}

sub_FnModeStartP()
{
  global
  FnMode_f := 1
  A_NowFmt := FormatTime(A_Now, "HHmmss")
  ;画面全体のサイズを取得し、FnMode中である事を画面上部中央に表示する https://ahkscript.github.io/ja/docs/v2/lib/SysGet.htm
  w := SysGet(0)  ;SM_CXSCREEN
  h := SysGet(1)  ;SM_CYSCREEN
  bar_size := "800"
  x_cent := (w / 2)
  y_cent := (h / 2)
  x_loc  := x_cent  - bar_size/2
  y_loc  := 80
  ; FnModeGui の作成と設定  +ToolWindowはGUIをタスクバーに表示させない +AlwaysOnTopは最上位に表示
  FnModeGui := Gui("-Caption +ToolWindow +AlwaysOnTop")
  FnModeGui.Title := "fmtip"
  FnModeGui.MarginY := 5
  FnModeGui.MarginX := 5
  FnModeGui.SetFont("s14", "Segoe UI Black")
  FnModeGui.BackColor := "883322" 
  FnModeGui.AddText("x0 w" bar_size " Center cWhite", "FnMode_f" FnMode_f " " A_NowFmt "】") ; Textコントロールの追加(変数の埋め込みを修正)
  FnModeGui.Show("NoActivate X" x_loc " Y" y_loc " W" bar_size) ;表示位置とサイズを設定して表示 NoActivateはフォーカスが現在のアプリから移動しないようにする。
  WinSetTransparent(50, "fmtip") ;半透明
}

sub_FnModeEnd()
{ global ;グローバル変数を使えるよう記載
  FnMode_f := 0
  ToolTip()     ;ToolTipを消す
  if IsObject(FnModeGui) {
    FnModeGui.Destroy
  }
}

スクリプトの解説

1. 全体概要

このスクリプトは、主に「挿入モード」(キーボードの一般的な状態)と「FnMode」(コマンドモードに相当)の2つのモードを切り替えて使用します。FunctionModeでは、特定のキーが別の機能に割り当てられ、キーボード操作の効率化を図ります。特に、キーの少ない60%キーボードなどで不足しがちなキー(Fキー、矢印キー、Home/End/PageUp/PageDownキーなど)の操作を補完するのに役立ちます。

2. モード切り替え(2種類用意)

  • 左Ctrlキーによる切り替え: 左Ctrlキーを押して離す(Down+Up)ことで、挿入モードとFnModeが切り替わります。
    • ~LCtrl UP:: は、左Ctrlキーが離された瞬間に実行されるホットキーです。
    • A_PriorKey = "LControl" で直前に押されたキーがLControlであることを確認します。
    • mouse_count1mouse_count2 を用いて、Ctrlキーを押しながらのマウス操作(例:Excelでの図形の複数選択、ブラウザでリンククリックによるタブ追加、等)でFnModeの切り替えが動作しないよう制御しています。これは、マウスボタンが上げられた回数をカウントすることで実現しています。
  • 無変換キーによる切り替え: 無変換キー(仮想キーコード vk1D)を押している間はFnModeになり、離すと挿入モード(一般的な状態)に戻ります。
    • vk1D:: は、無変換キーが押されたときに実行されるホットキーです。
    • KeyWait("vk1D", "T0.2") で、無変換キーが0.2秒以内に離されたかどうかを判定しています。短時間で離された場合は、通常のカナ変換として処理します。
    • KeyWait("vk1D") は、キーが離されるまで待機します。

左Ctrlキーについては、本来は押下だけでモードを切り替えたい所でした。しかし押下中のCtrlキーはショートカットを実行する機能を優先する必要がある為、左Ctrlキー以外のキーが押されずにCtrlキーが押されて離された時だけモードを切り替える仕様としています。

3. FnMode中のキー割り当て

#HotIf (FnMode_f=1) ディレクティブは、FnMode_f変数が1(真)の場合、つまりFnModeが有効な場合にのみ、以下のホットキーが有効になるようにします。

  • *1::Send("{Blind}{F1}") のように、* はどの修飾キー(Shift、Ctrl、Altなど)と同時押しでもホットキーが発動するようにするワイルドカードです。これにより、単独の数字キーでF1〜F12を送信できます。
  • *h::Send("{Blind}{Left}") のように、h、j、k、lキーをそれぞれ左、下、上、右の矢印キーに割り当てています(Vimの操作に準拠)。
  • *n::Send("{Blind}{Home}") のように、n、m、コンマ(,)、ピリオド(.)キーをそれぞれHome、End、PageUp、PageDownキーに割り当てています。

Send("{Blind}{キー}") は、キー送信時に修飾キーの状態を変更しないようにするための記述です。

4. Delキーと半角/全角キーの割り当て

  • +BS::Send("{Del}") は、Shift+BackSpaceでDelキーとして機能します。
  • ^+BS::Send("^{Del}") は、Ctrl+Shift+BackSpaceでCtrl+Delとして機能します。
  • 半角/全角キー(スキャンコード SC029)は、1回押すと通常のIME切り替えとして機能し、短時間(400ms以内)に2回押すとEscキーとして機能し、さらにFnModeを終了します。

5. FnModeの表示

  • sub_FnModeStart() は、FnModeに入った際にToolTipを表示します。
  • sub_FnModeStartP() は、FnModeに入った際に画面上部中央に現在のモードと時刻を示す半透明のGUIを表示します。
    • SysGet(0)SysGet(1) で画面の幅と高さを取得し、GUIの表示位置を計算しています。
    • Gui("-Caption +ToolWindow +AlwaysOnTop") で、キャプション(タイトルバー)なし、タスクバーに表示しない、常に最前面に表示するGUIを作成しています。
    • WinSetTransparent(50, "fmtip") でGUIを半透明にしています。
  • sub_FnModeEnd() は、FnModeから抜けた際にToolTipとGUIを消去します。


まとめ

今回の記事では、AutoHotkey を使って Viエディタ 風のモーダル操作を実現する方法を紹介しました。このスクリプトを活用することで、キーボード操作の効率を大幅に向上させ、より快適な Windows 環境を構築できるでしょう。ぜひ、ご自身の環境に合わせてカスタマイズし、更なる効率化を目指してみてください。

タイトルとURLをコピーしました