💨

macOS Emacs で Dired の ! コマンドの初期値が xdg-open ? open では?

に公開

はじめに

Emacs の Dired で ! はご存知でしょうか?
Dired バッファで、カーソルを対象ファイルに移動し、
! のキーを押すと、dired-do-shell-command が呼び出されます。
dired-do-shell-command が表示するプロンプト入力で、Enter を押すと、
対応したアプリケーションでそのファイルを開くことができます。

背景

私、macOS Emacs の Dired で、! をほとんど使っていなかったので、
いままで気が付かなかったのですが、
下記の画像のように、
ミニバッファのプロンプト中に、
! on ファイル名 {2 guess} [xdg-open]:
のように、xdg-open と表示されている事に気づきました。

macOS なのに Linux の xdg-open が表示されていて、
「なぜ?」となりました。
もちろん、open と入力すれば、対応したアプリケーションでそのファイルを開くことはできますが...
xdg-open に相当する macOS のコマンドは、open です。
なので、
! on ファイル名 {2 guess} [open]:
と表示されないのはどうしてなのか?

その原因調査と対処方法について記述しようと思いました。

対処方法

先に対処方法を書いておきます。
下記を init.el などに記述することで対処できます。

use-package 使用時

(use-package dired-aux
  :config
    (add-to-list 'shell-command-guess-functions #'shell-command-guess-open t) ; The argument t of add-to-list is required
)

use-package 未使用時

(with-eval-after-load 'dired-aux
  (add-to-list 'shell-command-guess-functions #'shell-command-guess-open t)) ; the argument t of add-to-list is required

確認画像は、use-package 使用時と同様なので割愛します。

xdg-open と open コマンドの簡易説明

xdg-open は、Linux などの XDG Base Directory Specification
採用している Unix 系のコマンドでの、
open は、macOS での、
共に、ファイルに関連したアプリケーションで開くコマンドです。

以下に、それぞれのコマンドの man の抜粋を掲載しておきます。

man xdg-open
$ man xdg-open
       xdg-open {--help | --manual | --version}

DESCRIPTION
       xdg-open opens a file or URL in the user's preferred application. If a
       URL is provided the URL will be opened in the user's preferred web
       browser. If a file is provided the file will be opened in the preferred
       application for files of that type. xdg-open supports file, ftp, http
       and https URLs.
...
man open
$ man open
OPEN(1)                                                              General Commands Manual                                                             OPEN(1)

NAME
       openopen files and directories

SYNOPSIS
       open  [-e]  [-t]  [-f]  [-F]  [-W]  [-R]  [-n]  [-g]  [-j]  [-h]  [-u  URL]  [-s sdk] [-b bundle_identifier] [-a application] [--env VAR] [--stderr PATH]
            [--stdin PATH] [--stdout PATH] [--arch ARCH] [--args arg1 ...]

DESCRIPTION
       The open command opens a file (or a directory or URL), just as if you had double-clicked the file's icon. If no application name is  specified,  the  de‐
       fault application as determined via LaunchServices is used to open the specified files.

       If the file is in the form of a URL, the file will be opened as a URL.
...

Emacs Dired でのキーと関数バインディング

C-x C-f あるいは M-x dired でディレクトリを開き、
M-x describe-bindings を実行すると、
Key に対してどのような関数が Binding されているか確認できます。

! は、dired-do-shell-command に Binding されています。

原因

! キーにより、
dired-aux.el 中の
dired-do-shell-command
dired-read-shell-command
dired-guess-shell-command
shell-command-guess
が順に呼び出される。

(defun shell-command-guess (files)
  "Return a list of shell commands, appropriate for FILES.
The list is populated by calling functions from
`shell-command-guess-functions'.  Each function receives the list
of commands and the list of file names and returns the same list
after adding own commands to the composite list."
  (let ((commands nil))
    (run-hook-wrapped 'shell-command-guess-functions
                      (lambda (fun)
                        (setq commands (funcall fun commands files))
                        nil))
    commands))

shell-commands-guess 関数の中で、
shell-command-guess-functions という defcustom 変数に
設定された各関数
shell-command-guess-dired-optional
...
shell-command-guess-dired-user
を順に実行し、
dired-do-shell-command で使用する xdg-open 等の関数を設定します。

shell-command-guess-functions は、以下のような変数になっています。

(defcustom shell-command-guess-functions
  '(shell-command-guess-dired-optional
    shell-command-guess-mailcap
    shell-command-guess-xdg
    shell-command-guess-dired-default
    shell-command-guess-dired-user)
  "List of functions that guess shell commands for files.
Each function receives a list of commands and a list of file names
and should return the same list of commands with changes
such as new commands added to the beginning of the list.
In this case the commands from the last entry
will be at the top of the resulted list."
  :type '(repeat
          (choice (function-item shell-command-guess-dired-user)
                  (function-item shell-command-guess-dired-default)
                  (function-item shell-command-guess-dired-optional)
                  (function-item shell-command-guess-mailcap)
                  (function-item shell-command-guess-xdg)
                  (function-item shell-command-guess-open)
                  (function :tag "Custom function")))
  :group 'dired
  :version "30.1")

shell-command-guess-functions に設定されている関数には、
なぜか?...
shell-command-guess-open
が登録されていません。

これが、
macOS の Dired で ! 実行した際、
ミニバッファのプロンプトに xdg-open が表示される原因です!

なので、
対処方法は、shell-command-guess-functions に、
shell-command-gues-open を追加すれば良いのですが、

ディスクリプションに、

"List of functions that guess shell commands for files.
...
In this case the commands from the last entry
will be at the top of the resulted list.

とあるように、リストのエントリーの後ろの方が良いので、

リストの最後に、shell-command-guess-open 関数を追加するために、

(add-to-list 'shell-command-guess-functions #'shell-command-guess-open t) ; The argument t of add-to-list is required

と、add-to-list 関数の引数に t を指定し、
リストの 最後 に追加されるようにしました。

最後に追加するのが良いか?
それとも、
shell-command-guess-dired-user に追加するのが良いか?
今後の考慮が必要かと思います。

まとめ

macOS で Emacs Dired の ! dired-do-shell-command を使うなら、
shell-command-guess-open 関数を、
shell-command-guess-functions 変数のリストの後ろの方に追加しましょう!
この内容が、macOS の Emacs ユーザーの何らかのお役に立てれば、幸いです。

Discussion