AutoHotkeyのコメントからヘルプを作成する

# 概要
たくさん登録しすぎて忘れられてしまったホットキーの魂を救済するために、AutoHotkeyのスクリプト内に記述したコメントからホットキー/ホットストリングの一覧を作成するスクリプトを作りました。
![AutoHotkeyHelp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189679/6912c040-cafb-13a3-3fd4-da08ffe534a5.png)

## コメント形式
以下の形式で記述したコメントを解析して、GUIウィンドウを生成します。
コメント内に`@hotkey`, `@hotstr`のいずれかのドキュメンテーションタグが存在しないとヘルプの対象にはならないので、自作関数などのコメントとも共存可能です。

```js
/**
 * ホットキー/ホットストリングの説明
 *
 * @hotkey [ホットキー]
 * @hotstr [ホットストリング]
 * @target [対象ウィンドウ]
 * @condition [発動条件]
 */
```
`@hotkey`、`@hotstr`タグは、一つのコメント内で複数記述が可能なので、同じ操作が複数のホットキーに割り当てられている時でも、コメントブロックは1つでOKです。
`@target`はホットキーの発動対象となるウィンドウを`#IfWinActive`等で限定している場合にその対象を記載します。同じ名前のウィンドウはヘルプ左側のツリーでまとめられるので、間違えると残念なことになります。
`@condition`はIMEがオンの場合などの特定の発動条件がある場合に記載します。

なお、上記以外のドキュメンテーションタグ(`@hogehoge`)は全てヘルプには反映しない安全設計なので、ヘルプには表示したくない`@todo`とか`@author`なんかを書いておいても大丈夫です。

## ソース
ヘルプの表示は[Win]+[F1]に割り当てています。

```ini
/**
 * AutoHotkey設定一覧を表示する。
 */
HotkeyHelp()
{
    ;GUIの値格納変数
    Static sTab
    Static sHotkeyTree
    Static sHotkeyList
    Static sHotstrTree
    Static sHotstrList

    ;GUIのウィンドウハンドル
    Static sHwndTab
    Static sHwndHotkeyTree
    Static sHwndHotkeyList
    Static sHwndHotstrTree
    Static sHwndHotstrList

    ;ホットキーヘルプのGUIを作成
    Gui, HotkeyHelp:New, , AutoHotkey設定一覧

    ;タブを追加
    Gui, HotkeyHelp:Add, Tab2, HwndsHwndTab VsTab W700 H400, ホットキー|ホットストリング

    ;1つ目のタブを選択
    Gui, Tab, 1

    ;1つ目のタブにツリービューを追加
    Gui, HotkeyHelp:Add, TreeView, HwndsHwndHotkeyTree VsHotkeyTree GSelectTree W120 H360,
    TV_Add("全てのホットキー", 0, "Expand")

    ;1つ目のタブにリストビューを追加
    Gui, HotkeyHelp:Add, ListView, HwndsHwndHotkeyList VsHotkeyList X+5 W550 H360 Grid ReadOnly, 対象|ホットキー|条件|説明|

    ;リストビューの項目幅を設定
    LV_ModifyCol(1, 100)
    LV_ModifyCol(2, 145)
    LV_ModifyCol(3, 90)
    LV_ModifyCol(4, 350)

    ;2つ目のタブを選択
    Gui, Tab, 2

    ;2つ目のタブにツリービューを追加
    Gui, HotkeyHelp:Add, TreeView, HwndsHwndHotstrTree VsHotstrTree GSelectTree W120 H360,
    TV_Add("全てのホットストリング", 0, "Expand")

    ;2つ目のタブにリストビューを追加
    Gui, HotkeyHelp:Add, ListView, HwndsHwndHotstrList VsHotstrList X+5 W550 H360 Grid ReadOnly, 対象|ホットストリング|条件|説明

    ;リストビューの項目幅を設定
    LV_ModifyCol(1, 100)
    LV_ModifyCol(2, 145)
    LV_ModifyCol(3, 90)
    LV_ModifyCol(4, 350)

    ;GUIの表示
    Gui, HotkeyHelp:Show

    /**
    * ツリーアイテム選択時の動作。
    */
    SelectTree:
        ;シングルクリックの場合のみリストビューを更新
        If (A_GuiEvent == "S")
        {
            ;選択中のタブを取得
            GuiControlGet, vCurrentTab, , %sHwndTab%,

            If (vCurrentTab == "ホットキー")
            {
                ;ホットキータブの場合、ホットキーのツリービューとリストビューを選択
                Gui, TreeView, %sHwndHotkeyTree%
                Gui, ListView, %sHwndHotkeyList%

                ;リスト項目を全削除
                Gui, ListView, %sHwndHotkeyList%
                LV_Delete()
            }

            If (vCurrentTab == "ホットストリング")
            {
                ;ホットストリングタブの場合、ホットストリングのツリービューとリストビューを選択
                Gui, TreeView, %sHwndHotstrTree%
                Gui, ListView, %sHwndHotstrList%

                ;リスト項目を全削除
                Gui, ListView, %sHwndHotstrList%
                LV_Delete()
            }

            ;先頭のツリーアイテムを取得
            vTreeTop := TV_GetNext()

            ;選択中のツリーアイテムを取得
            vCurrentTree := TV_GetSelection()

            ;ドキュメンテーションブロック判定フラグ
            vIsDoc := False

            ;スクリプトファイル読み込み
            Loop, Read, %A_ScriptFullPath%
            {
                If (ErrorLevel)
                {
                    ;エラーの場合はバルーンメッセージを表示して終了
                    TrayTip, AutoHotkey, スクリプトファイル読み込みでエラーが発生しました。`r`n%A_Index%行目 %ErrorLevel%, 1, 18
                    Break
                }

                If (A_LoopReadLine == "/**") {
                    ;ドキュメンテーション開始(/**)
                    vIsDoc := True

                    ;変数を初期化
                    vCommand := ""
                    vHotkey := ""
                    vHotstr := ""
                    vTarget := ""
                    vCondition := ""
                    vDesc := ""

                    Continue
                }

                If (vIsDoc) {
                    ;ドキュメンテーションブロックの場合のみ読み込み
                    If (RegExMatch(A_LoopReadLine, "^\s\*[\s|\t]*@hotkey[\s|\t]*"))
                    {
                        ;ホットキーコメント( * @hotkey)
                        RegExMatch(A_LoopReadLine, "(?<=@hotkey[\s|\t]).*$", vCommand)
                        vCommand := Trim(vCommand)

                        ;ホットキーコメントが複数あった場合、改行で区切って格納
                        vHotkey := vHotkey == "" ? vCommand : vHotkey . "`r`n" . vCommand
                    }
                    Else If (RegExMatch(A_LoopReadLine, "\s\*[\s|\t]*@hotstr[\s|\t]*"))
                    {
                        ;ホットストリングコメント( * @hotstr)
                        RegExMatch(A_LoopReadLine, "(?<=@hotstr[\s|\t]).*$", vCommand)
                        vCommand := Trim(vCommand)

                        ;ホットストリングコメントが複数あった場合、改行で区切って格納
                        vHotstr := vHotstr == "" ? vCommand : vHotstr . "`r`n" . vCommand
                    }
                    Else If (RegExMatch(A_LoopReadLine, "\s\*[\s|\t]*@target[\s|\t]*"))
                    {
                        ;対象コメント( * @target)
                        RegExMatch(A_LoopReadLine, "(?<=@target[\s|\t]).*$", vTarget)
                        vTarget := Trim(vTarget)
                    }
                    Else If (RegExMatch(A_LoopReadLine, "\s\*[\s|\t]*@condition[\s|\t]*"))
                    {
                        ;条件コメント( * @condition)
                        RegExMatch(A_LoopReadLine, "(?<=@condition[\s|\t]).*$", vCondition)
                        vCondition := Trim(vCondition)
                    }
                    Else If (RegExMatch(A_LoopReadLine, "\s\*[\s|\t]*@.+[\s|\t]*"))
                    {
                        ;上記以外のドキュメンテーションタグは読み飛ばす( * @hogehoge)
                        Continue
                    }
                    Else
                    {
                        ;それ以外はディスクリプションコメント( * )
                        RegExMatch(A_LoopReadLine, "(?<=\*[\s|\t]).*$", vText)
                        If (vText != "")
                        {
                            vDesc := vDesc . vText
                        }
                    }

                    If (A_LoopReadLine == " */") {
                        ;ドキュメンテーション終了( */)
                        vIsDoc := False

                        ;ツリーアイテムを追加
                        If (vHotkey != "")
                        {
                            ;@hotkey行が存在していた場合、ツリービューとリストビューを選択
                            Gui, TreeView, %sHwndHotkeyTree%
                            Gui, ListView, %sHwndHotkeyList%

                            ;ツリーの先頭項目を取得
                            vTreeItemId := TV_GetNext()

                            ;読み込み行のツリーアイテムID
                            vTargetTreeId := ""

                            ;ツリーの項目数分ループ
                            Loop, % TV_GetCount()
                            {
                                ;ツリーの項目名称を取得
                                TV_GetText(vTreeText, vTreeItemId)
                                If (vTreeText == vTarget)
                                {
                                    ;ツリーの項目名称に存在した場合、ループ処理を終了
                                    vTargetTreeId := vTreeItemId
                                    Break
                                }

                                ;ツリーの項目名称に存在しない場合、次のツリー項目を取得しループを継続
                                vTreeItemId := TV_GetNext(vTreeItemId, "Full")
                            }

                            If (!vTargetTreeId)
                            {
                                ;ツリーの項目名称に存在しない場合、ツリーに項目を追加
                                vTargetTreeId := TV_Add(vTarget, TV_GetNext())
                            }

                            ;ホットキーをリストビューに追加
                            Loop, Parse, vHotkey, `r, `n
                            {
                                ;ホットキーのツリービューとリストビューを選択
                                Gui, TreeView, %sHwndHotkeyTree%
                                Gui, ListView, %sHwndHotkeyList%

                                If (vCurrentTree == vTreeTop || vCurrentTree == vTargetTreeId)
                                {
                                    ;ツリートップ選択中または読み込み行が選択中のツリーアイテムIDと一致する場合、リストビューに追加
                                    vListItemId := LV_Add("Col1", vTarget, A_LoopField, vCondition, vDesc)
                                }
                            }
                        }

                        If (vHotstr != "")
                        {
                            ;@hotstr行が存在していた場合、ツリービューとリストビューを選択
                            Gui, TreeView, %sHwndHotstrTree%
                            Gui, ListView, %sHwndHotstrList%

                            ;ツリーの先頭項目を取得
                            vTreeItemId := TV_GetNext()

                            ;読み込み行のツリーアイテムID
                            vTargetTreeId := ""

                            ;ツリーの項目数分ループ
                            Loop, % TV_GetCount()
                            {
                                ;ツリーの項目名称を取得
                                TV_GetText(vTreeText, vTreeItemId)
                                If (vTreeText == vTarget)
                                {
                                    ;ツリーの項目名称に存在した場合、ループ処理を終了
                                    vTargetTreeId := vTreeItemId
                                    Break
                                }

                                ;ツリーの項目名称に存在しない場合、次のツリー項目を取得しループを継続
                                vTreeItemId := TV_GetNext(vTreeItemId, "Full")
                            }

                            If (!vTargetTreeId)
                            {
                                ;ツリーの項目名称に存在しない場合、ツリーに項目を追加
                                vTargetTreeId := TV_Add(vTarget, TV_GetNext())
                            }

                            ;ホットストリングをリストビューに追加
                            Loop, Parse, vHotstr, `r, `n
                            {
                                ;ホットストリングのツリービューとリストビューを選択
                                Gui, TreeView, %sHwndHotstrTree%
                                Gui, ListView, %sHwndHotstrList%

                                If (vCurrentTree == vTreeTop || vCurrentTree == vTargetTreeId)
                                {
                                    ;ツリートップ選択中・読み込み行が選択中のツリーアイテムIDと一致する場合、リストビューに追加
                                    vListItemId := LV_Add("Col1", vTarget, A_LoopField, vCondition, vDesc)
                                }
                            }
                        }
                    }
                }
            }
        }
    Return

    /**
    * [Esc][閉じる]ボタンクリック時の動作。
    */
    HotkeyHelpGuiEscape:
    HotkeyHelpGuiClose:
        ;GUIウィンドウを破棄
        Gui, HotkeyHelp:Destroy
    Return
}

/**
 * 設定されたホットキー/ホットストリングを一覧表示
 *
 * @hotkey [Win]+[F1]
 * @target デフォルト
 */
#F1::
    IfWinNotExist, AutoHotkey設定一覧
    {
        HotkeyHelp()
    }
    Else
    {
        WinActivate
    }
Return
```