■ MinGW版 Emacs 特有の設定


【お知らせ】


<2015/10/17 追記>
advice-add の depth の設定に 99 を設定しました。これで、fakecygpty のアドバイスより一つ外側で処理が行われます。
http://g000001.cddddr.org/3623238000

<2015/04/01 追記>
SJIS のダメ文字対策が行われていない NTEmacs(恐らくですが、gnupack 以外の ほとんどの NTEmacs)の対策を追加しました。

<2015/03/09 追記>
locale-coding-system の設定を追加しました。

【本題】


NTEmacs から外部プロセスへ引数として渡せる文字列はコードページ(日本語Windows の場合は cp932)に依存するのだそうです。しかし一方で、プログラム引数の coding-system は process-coding-system の「プロセスへの符号化設定」の値と同じものに設定されるため、LANG環境変数を ja_JP.UTF-8 にして process-coding-system の「プロセスへの符号化設定」の値を utf-8 に合わせると、漢字を引数に含むプログラムを正しく動かすことができません。

以下は、この問題を改善するための設定です。
(require 'cl-lib)

(setenv "LANG" "ja_JP.UTF-8")

;; IME の設定をした後には実行しないこと
;; (set-language-environment "Japanese")

(prefer-coding-system 'utf-8-unix)
(set-file-name-coding-system 'cp932)
(setq locale-coding-system 'utf-8-unix)

;; プロセスが出力する文字コードを判定して、process-coding-system の DECODING の設定値を決定する
(setq default-process-coding-system '(undecided-dos . utf-8-unix))

;; サブプロセスに渡すパラメータの文字コードを cp932 にする
(cl-loop for (func args-pos) in '((call-process        4)
                                  (call-process-region 6)
                                  (start-process       3))
         do (eval `(advice-add ',func
                               :around (lambda (orig-fun &rest args)
                                         (setf (nthcdr ,args-pos args)
                                               (mapcar (lambda (arg)
                                                         (if (multibyte-string-p arg)
                                                             (encode-coding-string arg 'cp932)
                                                           arg))
                                                       (nthcdr ,args-pos args)))
                                         (apply orig-fun args))
                               '((depth . 99)))))

さらに以下は、SJIS のダメ文字対策も含めた設定です。SJIS のダメ文字対策が必要な場合(恐らくですが、gnupack 以外の ほとんどの MinGW版 Emacs は未対策)は、上記の設定の代わりに以下の設定の利用を検討ください。
NTEmacsスレッド4 967、NTEmacsスレッド5 33 の投稿を基とした設定であり、次のページの設定を全面的に参考とさせていただきました。(というかほとんどそのままです..m_O_m)

なお、Emacs 本体のクオート処理関連プログラムは次のページにあります。
また、Windows プログラムの場合のクオートの仕様は次のページが参考となります。
(require 'cl-lib)

(setenv "LANG" "ja_JP.UTF-8")

;; IME の設定をした後には実行しないこと
;; (set-language-environment 'Japanese)

(prefer-coding-system 'utf-8-unix)
(set-file-name-coding-system 'cp932)
(setq locale-coding-system 'utf-8)

;; プロセスが出力する文字コードを判定して、process-coding-system の DECODING の設定値を決定する
(setq default-process-coding-system '(undecided-dos . utf-8-unix))

;; ldd の結果のキャッシュ
(defvar ldd-cache nil)

;; filename が Cygwin のプログラムかどうか判定する
(defun cygwin-program-p (filename)
  (let ((target (and filename (executable-find filename))))
    (when target
      (cdr (or (assoc target ldd-cache)
               (car (push (cons target
                                (with-temp-buffer
                                  (let ((w32-quote-process-args nil)) ; advice 中で再帰しないよう nil
                                    ;; Cygwin のライブラリをロードしているか判定
                                    (when (eq (call-process "ldd" nil t nil (concat "\"" target "\"")) 0)
                                      (goto-char (point-min))
                                      (number-or-marker-p
                                       (re-search-forward "cygwin[0-9]+\.dll" nil t))))))
                          ldd-cache)))))))

;; サブプロセスに渡すパラメータに SJIS のダメ文字対策を行い、さらに文字コードを cp932 にする
(defun convert-process-args (orig-fun prog-pos args-pos args)
  (let ((cygwin-quote (and w32-quote-process-args ; cygwin-program-p の再帰防止
                           (cygwin-program-p (nth prog-pos args)))))
    (setf (nthcdr args-pos args)
          (mapcar (lambda (arg)
                    (when w32-quote-process-args
                      (setq arg
                            (concat "\""
                                    (if cygwin-quote
                                        (replace-regexp-in-string "[\"\\\\]"
                                                                  "\\\\\\&"
                                                                  arg)
                                      (replace-regexp-in-string "\\(\\(\\\\\\)*\\)\\(\"\\)"
                                                                "\\1\\1\\\\\\3"
                                                                arg))
                                    "\"")))
                    (if (multibyte-string-p arg)
                        (encode-coding-string arg 'cp932)
                      arg))
                  (nthcdr args-pos args))))

  (let ((w32-quote-process-args nil))
    (apply orig-fun args)))

(cl-loop for (func prog-pos args-pos) in '((call-process        0 4)
                                           (call-process-region 2 6)
                                           (start-process       2 3))
         do (eval `(advice-add ',func
                               :around (lambda (orig-fun &rest args)
                                         (convert-process-args orig-fun
                                                               ,prog-pos ,args-pos
                                                               args))
                               '((depth . 99)))))



<変更履歴>
  • 2012/09/19 mapcar の Warning が出るのを対策した。(http://www.mew.org/~kazu/doc/elisp/function.html
  • 2012/12/27 lambda関数のクォートをとった。
  • 2013/01/08 set-process-args-coding-system から引数の存在チェックを削除した。
  • 2015/03/09 locale-coding-system の設定を追加した。
  • 2015/04/01 SJIS のダメ文字対策が行われていない NTEmacs(恐らくですが、gnupack 以外の ほとんどの NTEmacs)の対策を追加した。
  • 2015/09/09 advice を Emacs-24.4 以降の書式に見直した。
  • 2015/10/17 advice-add の depth の設定に 99 を設定した。