読者です 読者をやめる 読者になる 読者になる

blechmusikの日記

キー・カスタマイズ・ソフトウェア "DvorakJ" の覚え書きをはじめとして様々なことを書いています。

gnupack の startup_config.ini と同様のものを Emacs 25 で使う

AutoHotkey Emacs

 これまで、バッチファイルを利用して環境変数の値を変更しながら Emacs 25 を起動するようにした。そして emacsclinet を利用しているかのような挙動を実現することも試みてきた。
 その後、これまで利用してきた gnupack の startup_config.ini を参考にし、その stratup_config.ini と同一の方法での環境変数を設定する AutoHotkeyスクリプトを作成した。[Local Variable]に emacs のパスを記載し、[Process Variable] に上書きしたい環境変数の値を記入する。これを使えば、以前利用したバッチファイルを用いる必要はない。
 なお、このスクリプトを流用すれば、 gnupack の startup_cygwin.exe, startup_gvim.exe などの機能も容易に実装できるだろう。

;; -*-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ファイルを作成する
このプログラムと同じ場所に startup_config.iniを作成する

そして、セクション名は "Local Variable" とし
emacs.exe キーに renemacs.exe のパスを記述する


[Local Variable]
    emacs.exe = <runemacs.exeのパス>

この<runemacs.exeのパス>は変数を利用した表記方法も可能だ。
たとえば emacs.exe をこのように設定すると

[Local Variable]
    msys64_directory = C:\msys64
    emacs.exe = %msys64_directory%\usr\local\bin\runemacs.exe


実際に展開されるのは
    emacs.exe = C:\msys64\usr\local\bin\runemacs.exe
である


また、HOME や PATH のような環境変数を上書きしたうえで
Emacsを起動することもできる。
"Process Variable" セクションに書く。

[Process Variable]
    msys64_directory = C:\msys64
    
    HOME = %msys64_directory%\home\%USERNAME%

    PATH = %msys64_directory%\usr\bin;%PATH%;
    PATH = %msys64_directory%\usr\local\bin;%PATH%;
    PATH = %msys64_directory%\opt\bin;%PATH%;
    PATH = %msys64_directory%\mingw64\bin;%PATH%;
    PATH = %msys64_directory%\usr\bin\site_perl;%PATH%;
    PATH = %msys64_directory%\usr\bin\vendor_perl;%PATH%;
    PATH = %msys64_directory%\usr\bin\core_perl;%PATH%;



[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 := "startup_config.ini"

; emacs のパス
ini_local_section := "Local Variable"
; 一時的に環境変数を設定するセクション名
ini_process_section := "Process Variable"


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

;; iniファイルをもとに、一時的に環境変数を設定する
updateEnv(storeVariables(readIniVariables(ini_file, ini_process_section)))

obj := storeVariables(readIniVariables(ini_file, ini_local_section))

;; emacsを起動する(既に起動していない場合)
;  既に起動しているならば、フォアグラウンドに移す
ret := startEmacs(find_item(obj, "emacs.exe"))


; 起動後すぐに drag and drop をすると
; ファイルの編集に失敗することがあったので
; 一定時間をあけておく
if (TRUE == ret )
{
    sleep, 500
}

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

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

ExitApp


; ========================================
; join(obj)
; {
;     s:= ""
;     For k, v in obj
;         s .= k "=" v "`n"
;     return s
; }

find_item(obj, key)
{
    if ({} == obj)
    {
        return ""
    }

    ret := get_next_index(obj, key)
    if (FALSE == ret)
    {
        return ""
    }

    return obj[ret]
}


get_next_index(obj, key)
{
    for i, v in obj
    {
        if (key == v)
        {
            return i + 1
        }
    }

    return False
}

storeVariables(lines)
{
    obj := {}

    Loop, Parse, lines, `n, `r
    {
        Try
        {
            ; "<left hand side (lhs)> = <right hand side (rhs)>"
            ret :=  RegExMatch(Trim(A_LoopField), "^(.+?)=(.*)$", $)

            ; 行頭に = があるときか
            ; 行中に = がないとき
            if (0 == ret)
            {
             Throw "不正な設定です`n"
                       . "設定値:" . Trim(A_LoopField)
            }
    
            lhs := Trim($1)
            rhs := expand_variables(Trim($2), obj)
    
            ; キーと値がまだ設定されていなければ
            ; 登録
            next_index := get_next_index(obj, lhs)
            if (FALSE == next_index)
            {
                obj.push(lhs)
                obj.push(rhs)

                continue
            }

            obj[next_index] := rhs
        }
        Catch e
        {
            MsgBox, % "環境変数の一時的な設定のエラー`n"
                     . e . "`n"
                     . "プログラムを続行します"
        }
    }


    return obj
}

expand_variables(string, obj)
{
    result := ""
    Loop, Parse, string, `%
    {
        ; A_Index が偶数の場合に、その文字列をもとに環境変数を取得
        ; 奇数の場合には処理中断
        if (1==Mod(A_Index, 2))
        {
            result .= A_LoopField
            continue
        }


        ; 変数の値を明示的に設定していない場合
        next_index := get_next_index(obj, A_LoopField)
        if (FALSE == next_index)
        {
			; 環境変数の値を利用する
            result .= getEnv(A_LoopField)
            continue
        }
        
        result .= find_item(obj, A_LoopField)
    }

    return result
}

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

    return Variables
}

updateEnv(obj)
{
    for i, v in obj
    {
        ; A_Index が偶数の場合に、その文字列をもとに環境変数を取得
        ; 偶数の場合には処理中断
        if (0==Mod(A_Index, 2))
        {
            continue
        }

        key := obj[i]
        value := obj[i + 1]
        EnvSet, %key%, %value%
    }
}

getEnv(string)
{
    EnvGet, OutputVar, %string%
    return OutputVar
}



GetArgs()
{
    global

    _ArgCount=%0%
    _Args := {}

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

    return _Args
}


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

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

    Try
    {
        if not (FileExist(path))
        {
             Throw "不正確なパスです`n"
                       . "設定値:" . path
        }

        Run, % path
        return True
    }
    Catch e
    {
        MsgBox, % "実行プログラムが見つかりません`n"
                 . e . "`n"
                 . "プログラムを終了します"
        ExitApp
    }

}

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
        }

        return
    }
    Catch e
    {
        MsgBox, % "ファイル指定のエラー`n"
                 . e . "`n"
                 . "プログラムを終了します"
        ExitApp
    }
}

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

; 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
}