Subject: bug#18132: Time for a smarter dired-guess-shell-alist-default? (dired-x.el)

From: Juri Linkov <[email-masked]>
Date: Wed, 22 May 2024 09:16:18 +0300

>>> https://lists.gnu.org/archive/html/bug-gnu-emacs/2021-10/msg01952.html
>>
>> This says two things:
>>
>>   . some commands in the list are less capable then others
>>   . XDG can do the job better
>>
>> To the first point, my response would be: put the better ones first in
>> the list, but leave the worse ones in the list for those users who
>> don't have the better alternatives.
>
> I completely agree.  This is what I tried to do,
> but couldn't find a way to refactor this amalgamation
> in dired-guess-shell-alist-default.  We need to split it
> to two parts, so users without XDG will have:
>
> 1. better ones from dired-guess-shell-alist-default
> 2. worse ones from dired-guess-shell-alist-default
>
> And users with XDG should be able to have:
>
> 1. better ones from dired-guess-shell-alist-default
> 2. XDG commands
> 3. worse ones from dired-guess-shell-alist-default
>
> Probably I'll move the worse ones to a new variable
> 'dired-guess-shell-alist-default-2'.

Ok, here is a new list 'dired-guess-shell-alist-optional'
that contains rare commands that don't exist on many systems.

So now the default order of a list of commands to select with M-n
for M-!, ! in Dired and the Dired context menu with this patch
is the following:

1. commands from dired-guess-shell-alist-user
2. commands from dired-guess-shell-alist-default
3. commands found by XDG on user's system
4. commands found by mailcap on user's system
5. rare commands from dired-guess-shell-alist-optional

diff --git a/lisp/dired.el b/lisp/dired.el
index 28ec187e666..b7c42318946 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -443,8 +443,9 @@ dired-guess-shell-case-fold-search
 
 (defcustom dired-guess-shell-alist-user nil
   "User-defined alist of rules for suggested commands.
-These rules take precedence over the predefined rules in the variable
-`dired-guess-shell-alist-default' (to which they are prepended).
+These rules take precedence over the predefined rules in the variables
+`dired-guess-shell-alist-default' and `dired-guess-shell-alist-optional'
+\(to which they are prepended).
 
 Each element of this list looks like
 
diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el
index 22c6881ae35..58d5a74f269 100644
--- a/lisp/dired-aux.el
+++ b/lisp/dired-aux.el
@@ -868,8 +868,8 @@ dired-do-shell-command
 the list of file names explicitly with the FILE-LIST argument, which
 can be produced by `dired-get-marked-files', for example.
 
-`dired-guess-shell-alist-default' and
-`dired-guess-shell-alist-user' are consulted when the user is
+`dired-guess-shell-alist-default', `dired-guess-shell-alist-optional'
+and `dired-guess-shell-alist-user' are consulted when the user is
 prompted for the shell command to use interactively.
 
 Also see the `dired-confirm-shell-command' variable."
@@ -1068,8 +1068,8 @@ dired-shell-command
 ;; * `dired-guess-shell-command' calls `dired-guess-default' with list of
 ;;    marked files.
 ;;
-;; * Parse `dired-guess-shell-alist-user' and
-;;   `dired-guess-shell-alist-default' (in that order) for the first REGEXP
+;; * Parse `dired-guess-shell-alist-user', `dired-guess-shell-alist-default',
+;;   `dired-guess-shell-alist-optional' (in that order) for the first REGEXP
 ;;   that matches the first file in the file list.
 ;;
 ;; * If the REGEXP matches all the entries of the file list then evaluate
@@ -1219,28 +1219,10 @@ dired-guess-shell-alist-default
                   " " dired-guess-shell-znew-switches))
    '("\\.pod\\'" "perldoc" "pod2man * | nroff -man")
 
-   '("\\.dvi\\'" "xdvi" "dvips")	; preview and printing
-   '("\\.au\\'" "play")			; play Sun audiofiles
-   '("\\.mpe?g\\'\\|\\.avi\\'" "xine -p")
-   '("\\.ogg\\'" "ogg123")
-   '("\\.mp3\\'" "mpg123")
-   '("\\.wav\\'" "play")
    '("\\.uu\\'" "uudecode")		; for uudecoded files
-   '("\\.hqx\\'" "mcvert")
    '("\\.sh\\'" "sh")			; execute shell scripts
-   '("\\.xbm\\'" "bitmap")		; view X11 bitmaps
-   '("\\.gp\\'" "gnuplot")
-   '("\\.p[bgpn]m\\'" "xloadimage")
-   '("\\.gif\\'" "xloadimage")		; view gif pictures
-   '("\\.tif\\'" "xloadimage")
-   '("\\.png\\'" "display")		; xloadimage 4.1 doesn't grok PNG
-   '("\\.jpe?g\\'" "xloadimage")
-   '("\\.fig\\'" "xfig")		; edit fig pictures
-   '("\\.out\\'" "xgraph")		; for plotting purposes.
    '("\\.tex\\'" "latex" "tex")
    '("\\.texi\\(nfo\\)?\\'" "makeinfo" "texi2dvi")
-   '("\\.pdf\\'" "xpdf")
-   '("\\.doc\\'" "antiword" "strings")
    '("\\.rpm\\'" "rpm -qilp" "rpm -ivh")
    '("\\.dia\\'" "dia")
    '("\\.mgp\\'" "mgp")
@@ -1269,7 +1251,39 @@ dired-guess-shell-alist-default
 
    '("\\.sign?\\'" "gpg --verify"))
   "Default alist used for shell command guessing.
-See `dired-guess-shell-alist-user'.")
+See also `dired-guess-shell-alist-optional' and
+`dired-guess-shell-alist-user'.")
+
+(defvar dired-guess-shell-alist-optional
+  (list
+   '("\\.dvi\\'" "xdvi" "dvips")	; preview and printing
+   '("\\.au\\'" "play")			; play Sun audiofiles
+   '("\\.mpe?g\\'\\|\\.avi\\'" "xine -p")
+   '("\\.ogg\\'" "ogg123")
+   '("\\.mp3\\'" "mpg123")
+   '("\\.wav\\'" "play")
+   '("\\.hqx\\'" "mcvert")
+   '("\\.xbm\\'" "bitmap")		; view X11 bitmaps
+   '("\\.gp\\'" "gnuplot")
+   '("\\.p[bgpn]m\\'" "xloadimage")
+   '("\\.gif\\'" "xloadimage")		; view gif pictures
+   '("\\.tif\\'" "xloadimage")
+   '("\\.png\\'" "display")		; xloadimage 4.1 doesn't grok PNG
+   '("\\.jpe?g\\'" "xloadimage")
+   '("\\.fig\\'" "xfig")		; edit fig pictures
+   '("\\.out\\'" "xgraph")		; for plotting purposes.
+   '("\\.pdf\\'" "xpdf")
+   '("\\.doc\\'" "antiword" "strings"))
+  "Optional alist used for shell command guessing.
+Unlike `dired-guess-shell-alist-default' that contains mostly the
+standard commands that handle the files with corresponding extensions
+such as the `tar' command handling the files with the `.tar' extension,
+this list contains the commands that don't exist on many systems
+where other alternatives are available.  So this needs to be in
+a separate list not to conflict with existing commands provided
+by mailcap and XDG.
+
+See also `dired-guess-shell-alist-user'.")
 
 (defun dired-guess-default (files)
   "Return a shell command, or a list of commands, appropriate for FILES.
@@ -1289,7 +1303,8 @@ dired-guess-default
                                       (string-match-p (car elem) file))
                                     files))
                                  (append dired-guess-shell-alist-user
-                                         dired-guess-shell-alist-default)))
+                                         dired-guess-shell-alist-default
+                                         dired-guess-shell-alist-optional)))
              nil)))))
     (if (length= programs 1)
         (car programs)
@@ -1323,13 +1338,21 @@ dired-guess-shell-command
       (if (equal val "") default val))))
 
 (defcustom shell-command-guess-functions
-  '(shell-command-guess-dired)
+  '(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 added new commands."
+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)
+          (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)
@@ -1350,9 +1373,29 @@ shell-command-guess
                         nil))
     commands))
 
-(defun shell-command-guess-dired (commands files)
-  "Populate COMMANDS using `dired-guess-default'."
-  (append (ensure-list (dired-guess-default files)) commands))
+(defun shell-command-guess-dired-user (commands files)
+  "Populate COMMANDS using `dired-guess-shell-alist-user'.
+This excludes `dired-guess-shell-alist-default' and
+`dired-guess-shell-alist-optional'."
+  (let ((dired-guess-shell-alist-default nil)
+        (dired-guess-shell-alist-optional nil))
+    (append (ensure-list (dired-guess-default files)) commands)))
+
+(defun shell-command-guess-dired-default (commands files)
+  "Populate COMMANDS using `dired-guess-shell-alist-default'.
+This excludes `dired-guess-shell-alist-user' and
+`dired-guess-shell-alist-optional'."
+  (let ((dired-guess-shell-alist-user nil)
+        (dired-guess-shell-alist-optional nil))
+    (append (ensure-list (dired-guess-default files)) commands)))
+
+(defun shell-command-guess-dired-optional (commands files)
+  "Populate COMMANDS using `dired-guess-shell-alist-optional'.
+This excludes `dired-guess-shell-alist-user' and
+`dired-guess-shell-alist-default'."
+  (let ((dired-guess-shell-alist-user nil)
+        (dired-guess-shell-alist-default nil))
+    (append (ensure-list (dired-guess-default files)) commands)))
 
 (declare-function mailcap-file-default-commands "mailcap" (files))