emacsclint と同等の機能を AutoHotkey で実現する

はじめに

 前回までの設定によって、Windows 10Emacs をそれなりに使えるように整えることができた。

 Emacs を起動し、それから open-file 関数を使ってファイルを開くだけならば、これ以上とりわけ設定する必要はない、と思うかもしれない。だが、emacsclientのように、Emacs を起動すると同時に特定のファイルが開ければ使い勝手はかなりよいだろうし、起動中の Emacs で特定のファイルを外部から渡し、それをすぐに編集できるようにすることも同様だ。他には、他のウィンドウに隠れている Emacs のウィンドウを前面に動かすことや、最小化されている Emacs のウィンドウを表示することも出来ればよいだろう。
 ここでは、emacsclient と同機能を AutoHotkeyスクリプトを利用して実現する。

emacsclint の機能概要

ksugitaさんが gnupack の更新時に説明をしていたので、それらを参照してほしい。

 実現するものとして、"gnupack 12.03-2015.02.01 リリース"時点の "emacsclient" の挙動を念頭におく。この説明を整理するとこうなる。

  • emacs と emacsclient の二つのプログラムが存在する
  • emacsclient にファイルを渡すと既に起動している emacs 上でファイルを開く
  • かつては emacs 実行後に emacsclint を実行しなければならなかった
  • 今では emacs 未実行時に emacsclint を実行する場合、 emacs が実行される

 そして、emacsclient を利用するためには、 [Home] Emacs Client にあるとおり、server-start を使ってサーバーを起動させること。こうすれば、emacsclient にファイルを渡すことで、既に起動している Emacs で渡したファイルを編集することができるようになる。

実現する内容

 [Windows]gnupackのemacsclientw.exeを快適に使えるようにする。 - Qiitaで実現されている以下の機能を、emacsclient と server-start を使わずに実現する。

>emacsが起動してないときには起動
>起動してる時はアクティブ化
>ファイルを送ると読み込む

 ここでは emacs を起動する際の処理として、runemacs.exe を直接実行するだけでなく、msys2 を使用して Windows 10 に Emacs 25.0.94.2 (IMEパッチ適用) を導入した - blechmusikの日記 で設定したように、バッチファイル経由で runemacs.exe を実行することにも対応する。

AutoHotkeyスクリプトの使い方と内容

特定のフォルダに StartEmacs.ini を作成し、そこに

[path]
emacs="my_runemacs.bat"

のように、emacs起動のために実行する実行ファイル(やバッチファイル)のパスを=の右側に書き込む。

 それから同じフォルダの中に 後述のAutoHotkeyスクリプトを作成し、実行バイナリ化すれば、望みどおりの処理が可能となる。ファイルのドロップ処理は先人が作成しているものがあるので、それを利用する。ここでは複数ファイルを emacs に渡せるようにした。
 スクリプトの書き方については、StartEmacs.ahk · GitHub を参考にした。

;; -*-mode: ahk; coding:utf-8-with-signature-dos ; tab-width : 4 -*-"

/*
-----emacsを起動するためのスクリプト---

emacsclientw.exe とサーバー機能を使っているかのように runemacs.exe を使う
(emacsclientwとserverファイルのどちらも使用しない)
emacsが起動してないときには起動、起動してる時はアクティブ化、ファイルを送ると読み込む。

msys2版emacsで使うことを想定している。
AutoHotKey_Lw / Ahk2Exe_Lで動作。

[1] コンパイルして適当な場所に置く
emacs(を起動するバッチファイル)と同じ場所に置くとよい。

[2] iniファイルを作成する
このプログラムと同じ場所にStartEmacs.iniを作成する。
内容は以下のとおり。

[path]
emacs=<runemacs.exe(を起動するバッチファイル)のパス>

[3] 起動する
ファイルを送れば開く(複数ファイルの編集可)。
ショートカット作成や関連付けに使う。


以下のページを参考にしました。
[Windows]gnupackのemacsclientw.exeを快適に使えるようにする。 - Qiita
http://qiita.com/acple@github/items/4be7eb774a28d574281e
----------------------------------------------------------------
*/

#NoEnv
SendMode Input
SetWorkingDir %A_ScriptDir%
#SingleInstance force

DetectHiddenWindows,On


;; iniファイル名を指定
ini_file = StartEmacs.ini

;; =============================================================
;; メイン実行部

;; iniファイルからファイルのパスを取得する
path_emacs := read_ini_file_path(ini_file)

;; 引数からファイルの情報を取得する
files := GetArgs()

;; emacsを起動する(既に起動していない場合)
;  既に起動しているならば、フォアグラウンドに移す
start_emacs(path_emacs)

; 起動後すぐに drag and drop をすると
; ファイルの編集に失敗することがあったので
; 一定時間をあけておく
sleep, 300

;; ファイルを個別にドラッグ・アンド・ドロップ
drag_and_drop_files_to_emacs(files)

ExitApp

; ========================================

read_ini_file_path(ini_file)
{
    Try
    {
        If not (FileExist(ini_file))
        {
             Throw "ini_file のパスが見つかりません`n"
                       . "設定値:" . ini_file
        }
    
        IniRead, path_emacs, %ini_file%, path, emacs
    
        If not (FileExist(path_emacs))
        {
             Throw "emacs起動用プログラムのパスが見つかりません`n"
                       . "設定値:" . path_emacs
        }
    }
    Catch e
    {
        MsgBox, ファイル指定のエラー`n%e%`nプログラムを終了します
        ExitApp
    }

    return path_emacs
}

GetArgs()
{
    global

    _ArgCount=%0%
    _Args := Object()

    Loop, %_ArgCount%
    {
      _Args.push(%A_Index%)
    }

    return _Args
}


; ========================================

start_emacs(path_emacs)
{
    ; WinGet, hwnd, ID, ahk_class Emacs
    hwnd := WinExist("ahk_class Emacs")
    ; hwnd := WinExist("ahk_exe Emacs")
    
    ; Emacs が既に起動している場合
    if (hwnd)
    {
        ; emacs のアクティブ化
        WinActivate, ahk_id %hwnd%
        return
    }

    Run, % path_emacs
    return
}

drag_and_drop_files_to_emacs(files)
{
    Try
    {
        Loop, % files.length()
        {
            the_file := files[A_Index]

            ; 複数ファイルの存在を確認する
            if not (FileExist(the_file))
            {
                 Throw "編集対象のファイルが見つかりません`n"
                        . "ファイル名:" . the_file
            }

            ; ファイルを Emacs に一つだけドロップする
            PostMessage, 0x233, HDrop(the_file), 0,, ahk_class Emacs
            sleep, 100
        }
    }
    Catch e
    {
        MsgBox, ファイル指定のエラー`n%e%`nプログラムを終了します
        ExitApp
    }

    return
}

; ========================================

; My Scripts
; http://lukewarm.s101.xrea.com/myscripts/
; ファイルドロップ関数
;     指定ウィンドウに改行区切りで指定されたファイルをドロップする 
; #17 [Make AHK drop files into other applications: post #17] - Posted 19 March 2014 - 07:54 AM
; http://autohotkey.com/board/topic/41467-make-ahk-drop-files-into-other-applications/?p=638376

; #Include DragDrop.ahk

HDrop(fnames,x=0,y=0) 
{
    characterSize := 2
    fns:=RegExReplace(fnames,"\n$")
    fns:=RegExReplace(fns,"^\n")
    hDrop:=DllCall("GlobalAlloc"
			,"UInt",0x42
			,"UInt",20+(StrLen(fns)*characterSize)+characterSize*2)
    p:=DllCall("GlobalLock","UInt",hDrop)
    NumPut(20, p+0)  ;offset
    NumPut(x,  p+4)  ;pt.x
    NumPut(y,  p+8)  ;pt.y
    NumPut(0,  p+12) ;fNC
    NumPut(1,  p+16) ;fWide
   
    p2:=p+20
    Loop,Parse,fns,`n,`r
    {
       DllCall("RtlMoveMemory"
			   ,"UInt",p2
			   ,"Str",A_LoopField
			   ,"UInt",StrLen(A_LoopField)*characterSize)
       p2+=StrLen(A_LoopField)*characterSize + characterSize
    }
    DllCall("GlobalUnlock","UInt",hDrop)
    Return hDrop
}

おわりに

 これで、 Windows 10 において GNU Emacs 25.0.94.2 (x86_64-w64-mingw32) (IMEパッチ適用済み) を快適に利用できる環境が一通り整った。
 インターネット上の書き込みをざっと見る限り、IMEパッチ適用済みの Emacs のうち、よく使用されているバージョンは24.5 のように思われる。WindowsEmacs 25 を本格的に使おうとする人に一連の記事が参考になればうれしい。