■ emacs 全般で利用できる設定


【お知らせ】


<2019/06/21 追記>
os-open-command 関数、find-file のアドバイスの設定を一部見直しました。

<2019/06/18 追記>
マウスの左ボタン押下時に、マウスポインタ位置のファイルまたはディレクトリを OS で開く設定を追加しました。この対応に併せ、以下の見直しも行いました。

<2018/09/11 追記>
マウスの右ボタン押下時の処理に、選択したウインドウをカレントにする処理を最初に行うようにしました。この対応で、カレントでないウインドウでの右ボタン押下の場合も違和感なく動作するようになりました。

<2018/09/11 追記>
sglstart を使っている場合、内部から呼ばれている winactivate.exe コマンドを更新してください。emacs のタイトルによるウィンドウのアクティベートの誤動作が解消されます。

<2018/08/22 追記>
マウスの右ボタン押下時に、開いているディレクトリを explorer で開く設定を追加しました。

<2016/09/15 追記>
本設定と同様な機能をもつパッケージとして dired-open というものがあります。tramp との連携が必要なければ、こちらの利用の方がシンプルかと思います。dired-open-extensions に指定するアプリケーションソフトを cygstart や wslstart、sglstart などに置き換えることで、本設定と同様な利用が可能となります。但し、アプリケーションソフト(の suffix )毎に指定する必要があります。



<2017/08/23 追記>
dired のソート順で、アンダーバーで始まるファイル等の並びが一箇所にまとまらない場合は、LANG 環境変数が ja_JP.UTF-8 になっているか確認してみてください。

<2017/05/01 追記>
dired のソート順を制御する ls-lisp-UCA-like-collation の設定を追加しました。

<2017/03/28 追記>
os-open-command ファンクションの中の判定で、Cygwin の ln -s で作成したショートカットが、MinGW版 emacs ではレギュラーファイルと認識されません。但し、シンボリックファイルとしては認識されたので、その判定を追加しました。

<2016/11/06 追記>
本設定は emacs の以下の仕組みを利用しています。*scratch* バッファなどにコピペして評価してみてください。後者は tramp の接続環境が出来ていれば実行できると思います。この後者の実行は、一度実行すると二回目から高速に結果が返って来ることも確認できると思います。(一度接続したセッションが再利用されています。)
;; コマンドがローカルホストで実行される。
(shell-command-to-string "uname -a")

;; コマンドがリモートホスト(<username>@<hostname>)で実行される。
;; (リモートホストを ~/.ssh/config に登録すると、ユーザ名や接続ポートなどを指定したエリアス名が使用出来て便利です。
;;  その場合は、<username>@ の部分は不要となります。)
(let ((default-directory "/<username>@<hostname>:~"))
  (shell-command-to-string "uname -a"))

<2016/09/29 追記>
os-open-command 関数をファイルとディレクトリのみ(URLは対象外)に機能する関数として見直しました。この見直しにより、os-open-command 関数に指定したファイルとディレクトリを開くコマンドを起動するホストの決定は、第一引数に指定した内容からのみで(default-directory を使わずに)判断するようにしました。(「VirtualBox 上の Ubuntu から WSL を経由して Windows 環境にアクセスするための設定」を利用するための見直しです。)
(os-open-command "/home/foo.pdf") ← ローカルホストで実行
(os-open-command "/<username>@<hostname>:/home/foo.pdf") ← <hostname> に指定したホストで実行

<2016/09/24 追記>
os-open-command-name 関数でファイル、ディレクトリ、URL を直接開くためのコマンドを選択していますが、tramp による接続先に対してこのコマンドを実行した場合、.bashrc 等でローカルに設定した PATH が有効となっていないため、~/bin 等に格納することを想定している sglstart や wslstart が呼び出されない状態となっていました。この対策を行いました。

【本題】


OS のアプリケーションとシームレスに連携させるための設定です。

Cygwin、Linux、Mac で起動している Emacs から、OS にインストールされているアプリケーションを起動することができます。(Mac は持っていないので、未テストです。)

次の設定と合わせて利用することで、tramp で接続した先にあるドキュメントを Xクライアントソフトを使ってローカルマシンに表示させることができます。(接続先が UNIX系の OS で xdg-open等 のソフトウェアが利用できる場合)
逆に、Linux にリモートログインして emacs を使っている場合、次の設定を行うことでローカルマシンのドキュメントを Windowsアプリケーションで開くことができるようになります。


1) 以下の設定を行う。

<for Cygwin> ※ singleton な動きが不要であれば、設定は不要
<for WSL> ※ singleton な動きが不要であれば、二つ目の設定は不要

2) dired でファイルやディレクトリの並び順等を調整するための設定を行う。
(require 'dired)
(require 'ls-lisp)

;; ls-lisp を使う
(setq ls-lisp-use-insert-directory-program nil)

;; dired の並び順を explorer と同じにする
(setq ls-lisp-ignore-case t)          ; ファイル名の大文字小文字無視でソート
(setq ls-lisp-dirs-first t)           ; ディレクトリとファイルを分けて表示
(setq dired-listing-switches "-alG")  ; グループ表示なし
(setq ls-lisp-UCA-like-collation nil) ; for 25.1 or later

3) dired の Wキーや Eキーで、カーソル位置のファイルや開いているディレクトリを OS で直接開くための設定を行う。
※ os-type 等を定数として設定していないのは、tramp 等での接続先での動作も想定しているためです。
(require 'cl-lib)
(require 'recentf)

;; OSタイプ を調べる
(defun os-type ()
  (let ((os-type (shell-command-to-string "uname")))
    (cond ((string-match "CYGWIN" os-type)
           'cygwin)
          ((string-match "Linux" os-type)
           'linux)
          ((string-match "Darwin" os-type)
           'darwin))))

;; OS でファイル、ディレクトリを直接開くためのコマンドを決定する
(defun os-open-command-name (os-type)
  (let ((command-name-list (cl-case os-type
                             ('cygwin
                              '("sglstart" "cygstart"))
                             ('linux
                              '("sglstart" "wslstart" "xdg-open" "gnome-open"))
                             ('darwin
                              '("open")))))
    (catch 'loop
      (dolist (command-name command-name-list)
        (let* ((command1 (concat "which " command-name " 2> /dev/null"))
               (command2 (if (file-remote-p default-directory)
                             ;; リモートではログインシェルでコマンドを実行する
                             (format "$0 -l -c '%s' 2> /dev/null" command1)
                           command1))
               (absolute-path-command-name (replace-regexp-in-string
                                            "\n" ""
                                            (shell-command-to-string command2))))
          (unless (string= absolute-path-command-name "")
            (throw 'loop absolute-path-command-name)))))))

;; os-open-command のキャッシュ
(defvar os-open-command-cache nil)

;; キャッシュを検索・登録する
(defun os-open-command-cache ()
  (let* ((hostname (if (file-remote-p default-directory)
                       (let* ((vec (tramp-dissect-file-name default-directory))
                              (host (tramp-file-name-host vec))
                              (user (tramp-file-name-user vec)))
                         (if user
                             (format "%s@%s" user host)
                           host))
                     "<localhost>")))
    (cdr (or (assoc hostname os-open-command-cache)
             (let* ((os-type (os-type))
                    (os-open-command-name (os-open-command-name os-type)))
               (car (push (cons hostname (list os-type os-open-command-name))
                          os-open-command-cache)))))))

;; OS で直接、ファイル、ディレクトリを開く
(defun os-open-command (filename)
  (interactive)
  (let* ((filename (expand-file-name filename))
         (default-directory (cond ((or (file-regular-p filename)
                                       ;; Cygwin の ln -s で作成したショートカットが、MinGW版 emacs では
                                       ;; レギュラーファイルと認識されない。但し、シンボリックファイルと
                                       ;; しては認識されたので、以下の設定を追加する。
                                       (file-symlink-p filename))
                                   (file-name-directory filename))
                                  ((file-directory-p filename)
                                   filename))))
    (if default-directory
        (let* ((cache (os-open-command-cache))
               (os-type (nth 0 cache))
               (os-open-command-name (nth 1 cache)))
          (if os-open-command-name
              (let ((localname (if (file-remote-p filename)
                                   (tramp-file-name-localname
                                    (tramp-dissect-file-name filename))
                                 filename)))
                (message "%s %s" (file-name-nondirectory os-open-command-name) localname)
                (cond ((and (eq os-type 'linux)
                            (not (file-remote-p default-directory)))
                       ;; 以下の URL の対策を行う
                       ;; http://d.hatena.ne.jp/mooz/20100915/p1
                       ;; http://i-yt.info/?date=20090829#p01
                       (let (process-connection-type)
                         (start-process "os-open-command" nil os-open-command-name localname)))
                      (t
                       ;; リモートでもコマンドを実行できるように、start-process ではなく shell-command系を使う
                       (shell-command-to-string (concat os-open-command-name " "
                                                        (shell-quote-argument localname) " &")))))
            (message "利用できるコマンドがありません。")))
      (message "オープンできるファイルではありません。"))))

;; OS で直接、ファイル、ディレクトリを開く(recentf に追加する機能有り版)
(defun os-open-command-2 (filename)
  (interactive)
  (recentf-push filename) ; recentf に追加する
  (os-open-command filename))

;; dired で W 押下時に、カーソル位置のファイルまたはディレクトリを OS で直接開く
(define-key dired-mode-map (kbd "W")
  (lambda ()
    (interactive)
    (os-open-command-2 (dired-get-file-for-visit))))

;; マウスクリック時に、マウスポインタ位置のファイルまたはディレクトリを OS で直接開く
(define-key dired-mode-map [mouse-2]
  (lambda (event)
    (interactive "e")
    (dired-mouse-find-file event 'os-open-command-2 'os-open-command-2)))

;; dired で E 押下時に、開いているディレクトリを OS で直接開く
(define-key dired-mode-map (kbd "E")
  (lambda ()
    (interactive)
    (os-open-command (dired-current-directory))))

;; マウスの右ボタンクリック時に、開いているディレクトリを OS で直接開く
(define-key dired-mode-map [mouse-3]
  (lambda (event)
    (interactive "e")
    (mouse-select-window event)
    (os-open-command (dired-current-directory))))

4)(オプション)dired の fキー で、指定した拡張子のファイルを OS で直接開くための設定を行う。
;; OS で起動したいファイルの拡張子一覧
(setq os-open-file-suffixes '("doc" "docx"
                              "xls" "xlsx"
                              "ppt" "pptx"
                              "mdb" "mdbx"
                              "vsd" "vdx" "vsdx"
                              "mpp"
                              "pdf"
                              "bmp" "jpg"
                              "odt" "ott"
                              "odg" "otg"
                              "odp" "otp"
                              "ods" "ots"
                              "odf"
                              ))

;; OS で直接開きたいファイルかどうかを判定する
(defun os-open-file-p (filename)
  (when (file-regular-p filename)
    (let ((ext (file-name-extension filename)))
      (when (and ext
                 (member (downcase ext) os-open-file-suffixes))
        t))))

;; dired でファイルを f で開く際に、os-open-file-suffixes リストに指定してあるサフィックスのファイルは OS で直接起動する
(advice-add 'find-file
            :around (lambda (orig-fun &rest args)
                      (let* ((file-name (nth 0 args))
                             (target-name (or (file-symlink-p file-name)
                                              file-name)))
                        (cond ((os-open-file-p target-name)
                               (os-open-command-2 target-name))
                              (t
                               (apply orig-fun args))))))

※ OS で直接ファイルを開くためには、以下を設定する方法もあります。(! や X の押下で起動します)
  Linux から xdg-open を利用する場合、上記の設定を使って起動した場合と異なり、起動するアプリケーションは同期プロセス(終了待ち)となります。
(setq dired-guess-shell-alist-user
      '(
        ("\\.doc$"  (nth 1 (os-open-command-cache)))
        ("\\.docx$" (nth 1 (os-open-command-cache)))
        ("\\.xls$"  (nth 1 (os-open-command-cache)))
        ("\\.xlsx$" (nth 1 (os-open-command-cache)))
        ("\\.ppt$"  (nth 1 (os-open-command-cache)))
        ("\\.pptx$" (nth 1 (os-open-command-cache)))
        ("\\.mdb$"  (nth 1 (os-open-command-cache)))
        ("\\.mdbx$" (nth 1 (os-open-command-cache)))
        ("\\.vsd$"  (nth 1 (os-open-command-cache)))
        ("\\.vdx$"  (nth 1 (os-open-command-cache)))
        ("\\.vsdx$" (nth 1 (os-open-command-cache)))
        ("\\.mpp$"  (nth 1 (os-open-command-cache)))
        ("\\.pdf$"  (nth 1 (os-open-command-cache)))
        ("\\.bmp$"  (nth 1 (os-open-command-cache)))
        ("\\.odt$"  (nth 1 (os-open-command-cache)))
        ("\\.ott$"  (nth 1 (os-open-command-cache)))
        ("\\.odg$"  (nth 1 (os-open-command-cache)))
        ("\\.otg$"  (nth 1 (os-open-command-cache)))
        ("\\.odp$"  (nth 1 (os-open-command-cache)))
        ("\\.otp$"  (nth 1 (os-open-command-cache)))
        ("\\.ods$"  (nth 1 (os-open-command-cache)))
        ("\\.ots$"  (nth 1 (os-open-command-cache)))
        ("\\.odf$"  (nth 1 (os-open-command-cache)))
        ))

5) さらに便利にするために、以下の設定を行う。


<変更履歴>
  • 2012/09/19 winstart の起動プログラムを cygstart に変更した。
  • 2012/11/12 winstart で cygstart を実行する function を shell-command に変更した。(偶にフォルダが開けなくなることの対策)
  • 2012/11/27 shell-command の引数の指定方法を変更した。
  • 2012/11/28 windows-exec-file-suffixes に登録していた vsdx というサフィックスはなかったようなので、vdx に変更した。
  • 2012/12/27 lambda関数のクォートをとった。
  • 2013/03/11 Xキーとの関連付け設定の記載を追加した。
  • 2013/05/09 全体の構成を見直した。また、おまけの設定を追加した。
  • 2013/07/03 winstart で起動したファイルも recentf に登録したいので、cygstart で起動する前に recentf-push を呼ぶようにした。
  • 2013/07/14 winstart から呼ばれる shell-command を shell-command-to-string に変更した。
  • 2013/11/09 winstart の実装方法を変更した。
  • 2013/11/09 recentf を require するようにした。
  • 2013/11/13 識別する拡張子に vsdx を追加した。
  • 2013/11/15 dired-guess-shell-alist-user のマッチング用正規表現の書き方にミスがあった($ がなかった)ので修正した。
  • 2013/11/15 Linux など他の OS にも対応するよう、全面的に見直した。
  • 2013/11/18 os-open-command-name function の内容を見直した。
  • 2013/11/18 os-open-command function の内部の判定が誤っていたので訂正した。
  • 2014/10/23 emacs-24.4 で os-open-command-name がエラーとなるようになったので対応した。
  • 2014/11/03 日本語パッチが当たっていない emacs で、起動するコマンドのパラメータに \ を含む漢字(構など)が含まれている場合でも動作するように対策した。
  • 2015/02/18 Wキー でファイルを開いた際も recentf に登録するようにした。
  • 2015/06/17 linux で os-open-command を起動する際の判定方法を見直した。
  • 2015/06/23 other window にシンボリックリンクを作成するコマンドを追加した。
  • 2015/09/13 advice を emacs-24.4 以降の書式に見直した。
  • 2016/09/15 「WSL で cygstart 的コマンドを使うための設定」 とも連携して利用できるようにするため、Linux 上で wslstart コマンドもサーチするように調整した。
  • 2016/09/21 「singleton な動きをする cygstart 的コマンドを使うための設定 (WSL版)」 とも連携して利用できるようにするため、Linux 上では、sglstart コマンドもサーチするように調整した。
  • 2016/09/24 os-open-command-name 関数でファイル、ディレクトリ、URL を直接開くためのコマンドの指定方法を一部変更した。
  • 2016/09/29 os-open-command 関数をファイルとディレクトリのみ(URLは対象外)に機能する関数として見直した。
  • 2016/10/05 os-open-command 関数が一度検索した接続先毎の利用コマンドをキャッシュするように改善した。
  • 2016/10/06 コマンドのキャッシュのキーを <ホスト名> から <ユーザ名>@<ホスト名> に変更した。
  • 2016/11/02 dired-do-shell-command(! や X の押下で起動)の際でも、os-open-command-cache の検索が働くように見直した。
  • 2016/11/08 ローカル接続(tramp 接続でない)の場合の os-open-command-cache のキーを "localhost" から "<localhost>" に変更した。
  • 2017/03/28 os-open-command ファンクションの中の判定で、Cygwin の ln -s で作成したショートカットが、MinGW版 emacs ではレギュラーファイルと認識されない。但し、シンボリックファイルとしては認識されたので、その判定を追加した。
  • 2017/04/09 (オプション)設定の find-file のアドバイスを一部見直した。(os-open-command コマンドの引数が絶対パスとなるように調整した。)
  • 2017/04/20 (オプション)設定の find-file のアドバイスを一部見直した。(リンク先のファイルの種別を判定できるように対策した。)
  • 2017/05/01 dired のソート順を制御する ls-lisp-UCA-like-collation の設定を追加した。
  • 2018/04/04 os-open-command-name 関数の一部見直しを行った。
  • 2018/09/09 3) の最後に、os-open-command として sglstart を使う場合に発生する不都合の対策方法を追記した。
  • 2018/09/11 マウスの右ボタン押下時の処理に、選択したウインドウをカレントにする処理を最初に行うようにした。