From a6412b96e72c32ee981f469a564c8d2155d575aa Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Tue, 16 Aug 2022 17:14:33 +0200 Subject: [PATCH] Move dired-guess commands from dired-x to dired * lisp/dired-x.el (dired-shell-command-history) (dired-guess-shell-alist-default, dired-guess-default) (dired-guess-shell-command): Move from here... * lisp/dired-aux.el (dired-shell-command-history) (dired-guess-shell-alist-default, dired-guess-default) (dired-guess-shell-command): ...to here. (Bug#21981) * lisp/dired-x.el (dired-guess-shell-gnutar) (dired-guess-shell-gzip-quiet, dired-guess-shell-znew-switches) (dired-guess-shell-case-fold-search, dired-guess-shell-alist-user): Move from here... * lisp/dired.el (dired-guess-shell-gnutar) (dired-guess-shell-gzip-quiet, dired-guess-shell-znew-switches) (dired-guess-shell-case-fold-search, dired-guess-shell-alist-user): ...to here. Change :group to dired-guess. (dired-guess): New defgroup. * test/lisp/dired-x-tests.el (dired-guess-default): Move from here... * test/lisp/dired-aux-tests.el (dired-guess-default): ...to here. * doc/misc/dired-x.texi (Features, Technical Details, Installation): Delete any mention of shell command guessing. (Shell Command Guessing): Move from here... * doc/emacs/dired.texi (Shell Command Guessing): ...to here. Adapt to better fit the Emacs Manual conventions. * lisp/dired-aux.el (dired-do-shell-command): Doc fix to adjust for above changes. * etc/NEWS: Announce the above change. --- doc/emacs/dired.texi | 104 +++++++++++ doc/misc/dired-x.texi | 112 ------------ etc/NEWS | 6 + lisp/dired-aux.el | 259 ++++++++++++++++++++++++++++ lisp/dired-x.el | 325 ----------------------------------- lisp/dired.el | 71 ++++++++ test/lisp/dired-aux-tests.el | 13 ++ test/lisp/dired-x-tests.el | 13 -- 8 files changed, 453 insertions(+), 450 deletions(-) diff --git a/doc/emacs/dired.texi b/doc/emacs/dired.texi index 292c986c1c6..00028cac0fa 100644 --- a/doc/emacs/dired.texi +++ b/doc/emacs/dired.texi @@ -41,6 +41,7 @@ you to operate on the listed files. @xref{Directories}. * Operating on Files:: How to copy, rename, print, compress, etc. either one file or several files. * Shell Commands in Dired:: Running a shell command on the marked files. +* Shell Command Guessing:: Guessing shell commands for files. * Transforming File Names:: Using patterns to rename multiple files. * Comparison in Dired:: Running @code{diff} by way of Dired. * Subdirectories in Dired:: Adding subdirectories to the Dired buffer. @@ -1121,6 +1122,109 @@ buffer (@pxref{Dired Updating}). @xref{Single Shell}, for information about running shell commands outside Dired. +@node Shell Command Guessing +@section Shell Command Guessing +@cindex guessing shell commands for files (in Dired) + +Based upon the name of a file, Dired tries to guess what shell command +you might want to apply to it. For example, if you have point on a +file named @file{foo.tar} and you press @kbd{!}, Dired will guess that +you want to run @samp{tar xvf}, and suggest that as the default shell +command. + +The default is mentioned in brackets and you can type @kbd{M-n} to get +the default into the minibuffer for editing. If there are several +commands for a given file, e.g., @samp{xtex} and @samp{dvips} for a +@file{.dvi} file, you can type @kbd{M-n} several times to see each of +the matching commands. + +Dired only tries to guess a command for a single file, never for a +list of marked files. + +The following variables control guessing of shell commands: + +@defvar dired-guess-shell-alist-default +This variable specifies the predefined rules for guessing shell +commands suitable for certain files. Set this to @code{nil} to turn +guessing off. The elements of @code{dired-guess-shell-alist-user} +(defined by the user) will override these rules. +@end defvar + +@defvar dired-guess-shell-alist-user +If non-@code{nil}, this variables specifies the user-defined alist of +file regexps and their suggested commands. These rules take +precedence over the predefined rules in the variable +@code{dired-guess-shell-alist-default} (to which they are prepended) +when @code{dired-do-shell-command} is run). The default is +@code{nil}. + +Each element of the alist looks like + +@example +(@var{regexp} @var{command}@dots{}) +@end example + +@noindent +where each @var{command} can either be a string or a Lisp expression +that evaluates to a string. If several commands are given, all of +them will temporarily be pushed onto the history. + +A @samp{*} in the shell command stands for the file name that matched +@var{regexp}. When Emacs invokes the @var{command}, it replaces each +instance of @samp{*} with the matched file name. + +You can set this variable in your Init file. For example, to add +rules for @samp{.foo} and @samp{.bar} file extensions: + +@example +(setq dired-guess-shell-alist-user + (list + (list "\\.foo$" "@var{foo-command}") ; fixed rule + ;; possibly more rules... + (list "\\.bar$" ; rule with condition test + '(if @var{condition} + "@var{bar-command-1}" + "@var{bar-command-2}")))) +@end example + +@noindent +This will override any predefined rules for the same extensions. +@end defvar + +@defvar dired-guess-shell-case-fold-search +If this variable is non-@code{nil}, +@code{dired-guess-shell-alist-default} and +@code{dired-guess-shell-alist-user} are matched case-insensitively. +The default is @code{t}. +@end defvar + +@defvar dired-guess-shell-gnutar +If this variable is non-@code{nil}, it specifies the name of the GNU +Tar executable (e.g., @file{tar} or @file{gtar}). GNU Tar's @samp{z} +switch is used for compressed archives. If you don't have GNU Tar, +set this to @code{nil}: a pipe using @command{zcat} is then used +instead. +@end defvar + +@defvar dired-guess-shell-gzip-quiet +A non-@code{nil} value of this variable means that @samp{-q} is passed +to @command{gzip}, possibly overriding a verbose option in the +@env{GZIP} environment variable. The default is @code{t}. +@end defvar + +@defvar dired-guess-shell-znew-switches nil +This variable specifies a string of switches passed to @command{znew}. +An example is @samp{-K} which will make @command{znew} keep a +@file{.Z} file when it is smaller than the @file{.gz} file. The +default is @code{nil}: no additional switches are passed to +@command{znew}. +@end defvar + +@defvar dired-shell-command-history nil +This variable holds the history list for commands that read +dired-shell commands. +@end defvar + @node Transforming File Names @section Transforming File Names in Dired diff --git a/doc/misc/dired-x.texi b/doc/misc/dired-x.texi index 50d9914081c..002164ed91f 100644 --- a/doc/misc/dired-x.texi +++ b/doc/misc/dired-x.texi @@ -92,7 +92,6 @@ For @file{dired-x.el} as distributed with GNU Emacs @value{EMACSVER}. * Introduction:: * Installation:: * Omitting Files in Dired:: -* Shell Command Guessing:: * Virtual Dired:: * Advanced Mark Commands:: * Multiple Dired Directories:: @@ -135,9 +134,6 @@ Some features provided by Dired Extra: Omitting uninteresting files from Dired listing (@pxref{Omitting Files in Dired}). @item -Guessing shell commands in Dired buffers -(@pxref{Shell Command Guessing}). -@item Running Dired command in non-Dired buffers (@pxref{Virtual Dired}). @item @@ -165,8 +161,6 @@ When @file{dired-x.el} is loaded, some standard Dired functions from Dired}), if it is active. @code{dired-find-buffer-nocreate} and @code{dired-initial-position} respect the value of @code{dired-find-subdir} (@pxref{Miscellaneous Commands}). -@code{dired-read-shell-command} uses @code{dired-guess-shell-command} -(@pxref{Shell Command Guessing}) to offer a smarter default command. @node Installation @chapter Installation @@ -184,7 +178,6 @@ In your @file{~/.emacs} file, or in the system-wide initialization file (with-eval-after-load 'dired (require 'dired-x) ;; Set dired-x global variables here. For example: - ;; (setq dired-guess-shell-gnutar "gtar") ;; (setq dired-x-hands-off-my-keys nil) )) (add-hook 'dired-mode-hook @@ -436,111 +429,6 @@ Loading @file{dired-x.el} will install Dired Omit by putting call @code{dired-extra-startup}, which in turn calls @code{dired-omit-startup} in your @code{dired-mode-hook}. -@node Shell Command Guessing -@chapter Shell Command Guessing -@cindex guessing shell commands for files. - -Based upon the name of a file, Dired tries to guess what shell -command you might want to apply to it. For example, if you have point -on a file named @file{foo.tar} and you press @kbd{!}, Dired will guess -you want to @samp{tar xvf} it and suggest that as the default shell -command. - -The default is mentioned in brackets and you can type @kbd{M-n} to get -the default into the minibuffer and then edit it, e.g., to change -@samp{tar xvf} to @samp{tar tvf}. If there are several commands for a given -file, e.g., @samp{xtex} and @samp{dvips} for a @file{.dvi} file, you can type -@kbd{M-n} several times to see each of the matching commands. - -Dired only tries to guess a command for a single file, never for a list -of marked files. - -The following variables control guessing of shell commands: - -@defvar dired-guess-shell-alist-default -This variable specifies the predefined rules for guessing shell -commands suitable for certain files. Set this to @code{nil} to turn -guessing off. The elements of @code{dired-guess-shell-alist-user} -(defined by the user) will override these rules. -@end defvar - -@defvar dired-guess-shell-alist-user -If non-@code{nil}, this variables specifies the user-defined alist of -file regexps and their suggested commands. These rules take -precedence over the predefined rules in the variable -@code{dired-guess-shell-alist-default} (to which they are prepended) -when @code{dired-do-shell-command} is run). The default is -@code{nil}. - -Each element of the alist looks like - -@example -(@var{regexp} @var{command}@dots{}) -@end example - -@noindent -where each @var{command} can either be a string or a Lisp expression -that evaluates to a string. If several commands are given, all of -them will temporarily be pushed onto the history. - -A @samp{*} in the shell command stands for the file name that matched -@var{regexp}. When Emacs invokes the @var{command}, it replaces each -instance of @samp{*} with the matched file name. - -You can set this variable in your @file{~/.emacs}. For example, -to add rules for @samp{.foo} and @samp{.bar} file extensions, write - -@example -(setq dired-guess-shell-alist-user - (list - (list "\\.foo$" "@var{foo-command}");; fixed rule - ;; possibly more rules... - (list "\\.bar$";; rule with condition test - '(if @var{condition} - "@var{bar-command-1}" - "@var{bar-command-2}")))) -@end example - -@noindent -This will override any predefined rules for the same extensions. -@end defvar - -@defvar dired-guess-shell-case-fold-search -If this variable is non-@code{nil}, -@code{dired-guess-shell-alist-default} and -@code{dired-guess-shell-alist-user} are matched case-insensitively. -The default is @code{t}. -@end defvar - -@cindex passing GNU Tar its @samp{z} switch. -@defvar dired-guess-shell-gnutar -If this variable is non-@code{nil}, it specifies the name of the GNU -Tar executable (e.g., @file{tar} or @file{gnutar}). GNU Tar's -@samp{z} switch is used for compressed archives. If you don't have -GNU Tar, set this to @code{nil}: a pipe using @command{zcat} is then -used instead. The default is @code{nil}. -@end defvar - -@cindex @code{gzip} -@defvar dired-guess-shell-gzip-quiet -A non-@code{nil} value of this variable means that @samp{-q} is passed -to @command{gzip}, possibly overriding a verbose option in the @env{GZIP} -environment variable. The default is @code{t}. -@end defvar - -@cindex @code{znew} -@defvar dired-guess-shell-znew-switches nil -This variable specifies a string of switches passed to @command{znew}. -An example is @samp{-K} which will make @command{znew} keep a @file{.Z} -file when it is smaller than the @file{.gz} file. The default is -@code{nil}: no additional switches are passed to @command{znew}. -@end defvar - -@defvar dired-shell-command-history nil -This variable holds the history list for commands that read -dired-shell commands. -@end defvar - @node Virtual Dired @chapter Virtual Dired diff --git a/etc/NEWS b/etc/NEWS index 5d87bc9e2eb..4b3a48a8206 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1159,6 +1159,12 @@ change the input method's translation rules, customize the user option ** Dired ++++ +*** 'dired-guess-shell-command' moved from dired-x to dired. +This means that 'dired-do-shell-command' will now provide smarter +defaults without first having to require 'dired-x'. See the node +"(emacs) Shell Command Guessing" in the Emacs manual for more details. + --- *** 'dired-clean-up-buffers-too' moved from dired-x to dired. This means that Dired now offers to kill buffers visiting files and diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el index 7ff3e333515..426273f65e8 100644 --- a/lisp/dired-aux.el +++ b/lisp/dired-aux.el @@ -1070,6 +1070,265 @@ Return the result of `process-file' - zero for success." (pop-to-buffer out-buffer)) res))))) + +;;; Guess shell command + +;; * `dired-guess-shell-command' provides smarter defaults for +;; `dired-read-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 +;; that matches the first file in the file list. +;; +;; * If the REGEXP matches all the entries of the file list then evaluate +;; COMMAND, which is either a string or a Lisp expression returning a +;; string. COMMAND may be a list of commands. +;; +;; * Return this command to `dired-guess-shell-command' which prompts user +;; with it. The list of commands is put into the list of default values. +;; If a command is used successfully then it is stored permanently in +;; `dired-shell-command-history'. + +;; Guess what shell command to apply to a file. +(defvar dired-shell-command-history nil + "History list for commands that read dired-shell commands.") + +;; Default list of shell commands. + +;; NOTE: Use `gunzip -c' instead of `zcat' on `.gz' files. Some do not +;; install GNU zip's version of zcat. + +(autoload 'Man-support-local-filenames "man") +(autoload 'vc-responsible-backend "vc") + +(defvar dired-guess-shell-alist-default + (list + (list "\\.tar\\'" + '(if dired-guess-shell-gnutar + (concat dired-guess-shell-gnutar " xvf") + "tar xvf") + ;; Extract files into a separate subdirectory + '(if dired-guess-shell-gnutar + (concat "mkdir " (file-name-sans-extension file) + "; " dired-guess-shell-gnutar " -C " + (file-name-sans-extension file) " -xvf") + (concat "mkdir " (file-name-sans-extension file) + "; tar -C " (file-name-sans-extension file) " -xvf")) + ;; List archive contents. + '(if dired-guess-shell-gnutar + (concat dired-guess-shell-gnutar " tvf") + "tar tvf")) + + ;; REGEXPS for compressed archives must come before the .Z rule to + ;; be recognized: + (list "\\.tar\\.Z\\'" + ;; Untar it. + '(if dired-guess-shell-gnutar + (concat dired-guess-shell-gnutar " zxvf") + (concat "zcat * | tar xvf -")) + ;; Optional conversion to gzip format. + '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") + " " dired-guess-shell-znew-switches)) + + ;; gzip'ed archives + (list "\\.t\\(ar\\.\\)?gz\\'" + '(if dired-guess-shell-gnutar + (concat dired-guess-shell-gnutar " zxvf") + (concat "gunzip -qc * | tar xvf -")) + ;; Extract files into a separate subdirectory + '(if dired-guess-shell-gnutar + (concat "mkdir " (file-name-sans-extension file) + "; " dired-guess-shell-gnutar " -C " + (file-name-sans-extension file) " -zxvf") + (concat "mkdir " (file-name-sans-extension file) + "; gunzip -qc * | tar -C " + (file-name-sans-extension file) " -xvf -")) + ;; Optional decompression. + '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q" "")) + ;; List archive contents. + '(if dired-guess-shell-gnutar + (concat dired-guess-shell-gnutar " ztvf") + (concat "gunzip -qc * | tar tvf -"))) + + ;; bzip2'ed archives + (list "\\.t\\(ar\\.bz2\\|bz\\)\\'" + "bunzip2 -c * | tar xvf -" + ;; Extract files into a separate subdirectory + '(concat "mkdir " (file-name-sans-extension file) + "; bunzip2 -c * | tar -C " + (file-name-sans-extension file) " -xvf -") + ;; Optional decompression. + "bunzip2") + + ;; xz'ed archives + (list "\\.t\\(ar\\.\\)?xz\\'" + "unxz -c * | tar xvf -" + ;; Extract files into a separate subdirectory + '(concat "mkdir " (file-name-sans-extension file) + "; unxz -c * | tar -C " + (file-name-sans-extension file) " -xvf -") + ;; Optional decompression. + "unxz") + + '("\\.shar\\.Z\\'" "zcat * | unshar") + '("\\.shar\\.g?z\\'" "gunzip -qc * | unshar") + + '("\\.e?ps\\'" "ghostview" "xloadimage" "lpr") + (list "\\.e?ps\\.g?z\\'" "gunzip -qc * | ghostview -" + ;; Optional decompression. + '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) + (list "\\.e?ps\\.Z\\'" "zcat * | ghostview -" + ;; Optional conversion to gzip format. + '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") + " " dired-guess-shell-znew-switches)) + + (list "\\.patch\\'" + '(if (eq (ignore-errors (vc-responsible-backend default-directory)) 'Git) + "cat * | git apply" + "cat * | patch")) + (list "\\.patch\\.g?z\\'" "gunzip -qc * | patch" + ;; Optional decompression. + '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) + (list "\\.patch\\.Z\\'" "zcat * | patch" + ;; Optional conversion to gzip format. + '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") + " " dired-guess-shell-znew-switches)) + + ;; The following four extensions are useful with dired-man ("N" key) + ;; FIXME "man ./" does not work with dired-do-shell-command, + ;; because there seems to be no way for us to modify the filename, + ;; only the command. Hmph. `dired-man' works though. + (list "\\.\\(?:[0-9]\\|man\\)\\'" + '(let ((loc (Man-support-local-filenames))) + (cond ((eq loc 'man-db) "man -l") + ((eq loc 'man) "man ./") + (t + "cat * | tbl | nroff -man -h | col -b")))) + (list "\\.\\(?:[0-9]\\|man\\)\\.g?z\\'" + '(let ((loc (Man-support-local-filenames))) + (cond ((eq loc 'man-db) + "man -l") + ((eq loc 'man) + "man ./") + (t "gunzip -qc * | tbl | nroff -man -h | col -b"))) + ;; Optional decompression. + '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) + (list "\\.[0-9]\\.Z\\'" + '(let ((loc (Man-support-local-filenames))) + (cond ((eq loc 'man-db) "man -l") + ((eq loc 'man) "man ./") + (t "zcat * | tbl | nroff -man -h | col -b"))) + ;; Optional conversion to gzip format. + '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") + " " 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") + + ;; Some other popular archivers. + (list "\\.zip\\'" "unzip" "unzip -l" + ;; Extract files into a separate subdirectory + '(concat "unzip" (if dired-guess-shell-gzip-quiet " -q") + " -d " (file-name-sans-extension file))) + '("\\.zoo\\'" "zoo x//") + '("\\.lzh\\'" "lharc x") + '("\\.arc\\'" "arc x") + '("\\.shar\\'" "unshar") + '("\\.rar\\'" "unrar x") + '("\\.7z\\'" "7z x") + + ;; Compression. + (list "\\.g?z\\'" '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) + (list "\\.dz\\'" "dictunzip") + (list "\\.bz2\\'" "bunzip2") + (list "\\.xz\\'" "unxz") + (list "\\.Z\\'" "uncompress" + ;; Optional conversion to gzip format. + '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") + " " dired-guess-shell-znew-switches)) + + '("\\.sign?\\'" "gpg --verify")) + "Default alist used for shell command guessing. +See `dired-guess-shell-alist-user'.") + +(defun dired-guess-default (files) + "Return a shell command, or a list of commands, appropriate for FILES. +See `dired-guess-shell-alist-user'." + (let* ((case-fold-search dired-guess-shell-case-fold-search) + (programs + (delete-dups + (mapcar + (lambda (command) + (eval command `((file . ,(car files))))) + (seq-reduce + #'append + (mapcar #'cdr + (seq-filter (lambda (elem) + (seq-every-p + (lambda (file) + (string-match-p (car elem) file)) + files)) + (append dired-guess-shell-alist-user + dired-guess-shell-alist-default))) + nil))))) + (if (length= programs 1) + (car programs) + programs))) + +;;;###autoload +(defun dired-guess-shell-command (prompt files) + "Ask user with PROMPT for a shell command, guessing a default from FILES." + (let ((default (dired-guess-default files)) + default-list val) + (if (null default) + ;; Nothing to guess + (read-shell-command prompt nil 'dired-shell-command-history) + (setq prompt (replace-regexp-in-string ": $" " " prompt)) + (if (listp default) + ;; More than one guess + (setq default-list default + default (car default) + prompt (concat + prompt + (format "{%d guesses} " (length default-list)))) + ;; Just one guess + (setq default-list (list default))) + ;; Put the first guess in the prompt but not in the initial value. + (setq prompt (concat prompt (format "[%s]: " default))) + ;; All guesses can be retrieved with M-n + (setq val (read-shell-command prompt nil + 'dired-shell-command-history + default-list)) + ;; If we got a return, then return default. + (if (equal val "") default val)))) + ;;; Commands that delete or redisplay part of the dired buffer diff --git a/lisp/dired-x.el b/lisp/dired-x.el index 9edf8374815..cf1ef37694f 100644 --- a/lisp/dired-x.el +++ b/lisp/dired-x.el @@ -196,35 +196,6 @@ toggle between those two." :type 'boolean :group 'dired-x) -(defcustom dired-guess-shell-gnutar - (catch 'found - (dolist (exe '("tar" "gtar")) - (if (with-temp-buffer - (ignore-errors (call-process exe nil t nil "--version")) - (and (re-search-backward "GNU tar" nil t) t)) - (throw 'found exe)))) - "If non-nil, name of GNU tar executable. -\(E.g., \"tar\" or \"gtar\"). The `z' switch will be used with it for -compressed or gzip'ed tar files. If you don't have GNU tar, set this -to nil: a pipe using `zcat' or `gunzip -c' will be used." - ;; Changed from system-type test to testing --version output. - ;; Maybe test --help for -z instead? - :version "24.1" - :type '(choice (const :tag "Not GNU tar" nil) - (string :tag "Command name")) - :group 'dired-x) - -(defcustom dired-guess-shell-gzip-quiet t - "Non-nil says pass -q to gzip overriding verbose GZIP environment." - :type 'boolean - :group 'dired-x) - -(defcustom dired-guess-shell-znew-switches nil - "If non-nil, then string of switches passed to `znew', example: \"-K\"." - :type '(choice (const :tag "None" nil) - (string :tag "Switches")) - :group 'dired-x) - ;;; Key bindings @@ -726,302 +697,6 @@ Also useful for `auto-mode-alist' like this: default-directory))) (shell-command command output-buffer error-buffer))) - -;;; Guess shell command - -;; Brief Description: -;; -;; * `dired-do-shell-command' is bound to `!' by dired.el. -;; -;; * `dired-guess-shell-command' provides smarter defaults for -;; dired-aux.el's `dired-read-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 -;; that matches the first file in the file list. -;; -;; * If the REGEXP matches all the entries of the file list then evaluate -;; COMMAND, which is either a string or a Lisp expression returning a -;; string. COMMAND may be a list of commands. -;; -;; * Return this command to `dired-guess-shell-command' which prompts user -;; with it. The list of commands is put into the list of default values. -;; If a command is used successfully then it is stored permanently in -;; `dired-shell-command-history'. - -;; Guess what shell command to apply to a file. -(defvar dired-shell-command-history nil - "History list for commands that read dired-shell commands.") - -;; Default list of shell commands. - -;; NOTE: Use `gunzip -c' instead of `zcat' on `.gz' files. Some do not -;; install GNU zip's version of zcat. - -(autoload 'Man-support-local-filenames "man") -(autoload 'vc-responsible-backend "vc") - -(defvar dired-guess-shell-alist-default - (list - (list "\\.tar\\'" - '(if dired-guess-shell-gnutar - (concat dired-guess-shell-gnutar " xvf") - "tar xvf") - ;; Extract files into a separate subdirectory - '(if dired-guess-shell-gnutar - (concat "mkdir " (file-name-sans-extension file) - "; " dired-guess-shell-gnutar " -C " - (file-name-sans-extension file) " -xvf") - (concat "mkdir " (file-name-sans-extension file) - "; tar -C " (file-name-sans-extension file) " -xvf")) - ;; List archive contents. - '(if dired-guess-shell-gnutar - (concat dired-guess-shell-gnutar " tvf") - "tar tvf")) - - ;; REGEXPS for compressed archives must come before the .Z rule to - ;; be recognized: - (list "\\.tar\\.Z\\'" - ;; Untar it. - '(if dired-guess-shell-gnutar - (concat dired-guess-shell-gnutar " zxvf") - (concat "zcat * | tar xvf -")) - ;; Optional conversion to gzip format. - '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") - " " dired-guess-shell-znew-switches)) - - ;; gzip'ed archives - (list "\\.t\\(ar\\.\\)?gz\\'" - '(if dired-guess-shell-gnutar - (concat dired-guess-shell-gnutar " zxvf") - (concat "gunzip -qc * | tar xvf -")) - ;; Extract files into a separate subdirectory - '(if dired-guess-shell-gnutar - (concat "mkdir " (file-name-sans-extension file) - "; " dired-guess-shell-gnutar " -C " - (file-name-sans-extension file) " -zxvf") - (concat "mkdir " (file-name-sans-extension file) - "; gunzip -qc * | tar -C " - (file-name-sans-extension file) " -xvf -")) - ;; Optional decompression. - '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q" "")) - ;; List archive contents. - '(if dired-guess-shell-gnutar - (concat dired-guess-shell-gnutar " ztvf") - (concat "gunzip -qc * | tar tvf -"))) - - ;; bzip2'ed archives - (list "\\.t\\(ar\\.bz2\\|bz\\)\\'" - "bunzip2 -c * | tar xvf -" - ;; Extract files into a separate subdirectory - '(concat "mkdir " (file-name-sans-extension file) - "; bunzip2 -c * | tar -C " - (file-name-sans-extension file) " -xvf -") - ;; Optional decompression. - "bunzip2") - - ;; xz'ed archives - (list "\\.t\\(ar\\.\\)?xz\\'" - "unxz -c * | tar xvf -" - ;; Extract files into a separate subdirectory - '(concat "mkdir " (file-name-sans-extension file) - "; unxz -c * | tar -C " - (file-name-sans-extension file) " -xvf -") - ;; Optional decompression. - "unxz") - - '("\\.shar\\.Z\\'" "zcat * | unshar") - '("\\.shar\\.g?z\\'" "gunzip -qc * | unshar") - - '("\\.e?ps\\'" "ghostview" "xloadimage" "lpr") - (list "\\.e?ps\\.g?z\\'" "gunzip -qc * | ghostview -" - ;; Optional decompression. - '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) - (list "\\.e?ps\\.Z\\'" "zcat * | ghostview -" - ;; Optional conversion to gzip format. - '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") - " " dired-guess-shell-znew-switches)) - - (list "\\.patch\\'" - '(if (eq (ignore-errors (vc-responsible-backend default-directory)) 'Git) - "cat * | git apply" - "cat * | patch")) - (list "\\.patch\\.g?z\\'" "gunzip -qc * | patch" - ;; Optional decompression. - '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) - (list "\\.patch\\.Z\\'" "zcat * | patch" - ;; Optional conversion to gzip format. - '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") - " " dired-guess-shell-znew-switches)) - - ;; The following four extensions are useful with dired-man ("N" key) - ;; FIXME "man ./" does not work with dired-do-shell-command, - ;; because there seems to be no way for us to modify the filename, - ;; only the command. Hmph. `dired-man' works though. - (list "\\.\\(?:[0-9]\\|man\\)\\'" - '(let ((loc (Man-support-local-filenames))) - (cond ((eq loc 'man-db) "man -l") - ((eq loc 'man) "man ./") - (t - "cat * | tbl | nroff -man -h | col -b")))) - (list "\\.\\(?:[0-9]\\|man\\)\\.g?z\\'" - '(let ((loc (Man-support-local-filenames))) - (cond ((eq loc 'man-db) - "man -l") - ((eq loc 'man) - "man ./") - (t "gunzip -qc * | tbl | nroff -man -h | col -b"))) - ;; Optional decompression. - '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) - (list "\\.[0-9]\\.Z\\'" - '(let ((loc (Man-support-local-filenames))) - (cond ((eq loc 'man-db) "man -l") - ((eq loc 'man) "man ./") - (t "zcat * | tbl | nroff -man -h | col -b"))) - ;; Optional conversion to gzip format. - '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") - " " 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") - - ;; Some other popular archivers. - (list "\\.zip\\'" "unzip" "unzip -l" - ;; Extract files into a separate subdirectory - '(concat "unzip" (if dired-guess-shell-gzip-quiet " -q") - " -d " (file-name-sans-extension file))) - '("\\.zoo\\'" "zoo x//") - '("\\.lzh\\'" "lharc x") - '("\\.arc\\'" "arc x") - '("\\.shar\\'" "unshar") - '("\\.rar\\'" "unrar x") - '("\\.7z\\'" "7z x") - - ;; Compression. - (list "\\.g?z\\'" '(concat "gunzip" (if dired-guess-shell-gzip-quiet " -q"))) - (list "\\.dz\\'" "dictunzip") - (list "\\.bz2\\'" "bunzip2") - (list "\\.xz\\'" "unxz") - (list "\\.Z\\'" "uncompress" - ;; Optional conversion to gzip format. - '(concat "znew" (if dired-guess-shell-gzip-quiet " -q") - " " dired-guess-shell-znew-switches)) - - '("\\.sign?\\'" "gpg --verify")) - - "Default alist used for shell command guessing. -See `dired-guess-shell-alist-user'.") - -(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). - -Each element of this list looks like - - (REGEXP COMMAND...) - -COMMAND will be used if REGEXP matches the file to be processed. -If several files are to be processed, REGEXP has to match all the -files. - -Each COMMAND can either be a string or a Lisp expression that evaluates -to a string. If this expression needs to consult the name of the file for -which the shell commands are being requested, it can access that file name -as the variable `file'. - -If several COMMANDs are given, the first one will be the default -and the rest will be added temporarily to the history and can be retrieved -with `previous-history-element' (\\\\[previous-history-element]). - -The variable `dired-guess-shell-case-fold-search' controls whether -REGEXP is matched case-sensitively." - :group 'dired-x - :type '(alist :key-type regexp :value-type (repeat sexp))) - -(defcustom dired-guess-shell-case-fold-search t - "If non-nil, `dired-guess-shell-alist-default' and -`dired-guess-shell-alist-user' are matched case-insensitively." - :group 'dired-x - :type 'boolean) - -(defun dired-guess-default (files) - "Return a shell command, or a list of commands, appropriate for FILES. -See `dired-guess-shell-alist-user'." - (let* ((case-fold-search dired-guess-shell-case-fold-search) - (programs - (delete-dups - (mapcar - (lambda (command) - (eval command `((file . ,(car files))))) - (seq-reduce - #'append - (mapcar #'cdr - (seq-filter (lambda (elem) - (seq-every-p - (lambda (file) - (string-match-p (car elem) file)) - files)) - (append dired-guess-shell-alist-user - dired-guess-shell-alist-default))) - nil))))) - (if (length= programs 1) - (car programs) - programs))) - -(defun dired-guess-shell-command (prompt files) - "Ask user with PROMPT for a shell command, guessing a default from FILES." - (let ((default (dired-guess-default files)) - default-list val) - (if (null default) - ;; Nothing to guess - (read-shell-command prompt nil 'dired-shell-command-history) - (setq prompt (replace-regexp-in-string ": $" " " prompt)) - (if (listp default) - ;; More than one guess - (setq default-list default - default (car default) - prompt (concat - prompt - (format "{%d guesses} " (length default-list)))) - ;; Just one guess - (setq default-list (list default))) - ;; Put the first guess in the prompt but not in the initial value. - (setq prompt (concat prompt (format "[%s]: " default))) - ;; All guesses can be retrieved with M-n - (setq val (read-shell-command prompt nil - 'dired-shell-command-history - default-list)) - ;; If we got a return, then return default. - (if (equal val "") default val)))) - ;;; Visit all marked files simultaneously diff --git a/lisp/dired.el b/lisp/dired.el index 10813e56dff..799a9f4716b 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -53,6 +53,11 @@ :prefix "dired-" :group 'dired) +(defgroup dired-guess nil + "Guess shell command in Dired." + :prefix "dired-" + :group 'dired) + ;;;###autoload (defcustom dired-listing-switches (purecopy "-al") "Switches passed to `ls' for Dired. MUST contain the `l' option. @@ -419,6 +424,72 @@ is anywhere on its Dired line, except the beginning of the line." :type 'boolean :version "28.1") +(defcustom dired-guess-shell-case-fold-search t + "If non-nil, `dired-guess-shell-alist-default' and +`dired-guess-shell-alist-user' are matched case-insensitively." + :group 'dired-guess + :type 'boolean + :version "29.1") + +(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). + +Each element of this list looks like + + (REGEXP COMMAND...) + +COMMAND will be used if REGEXP matches the file to be processed. +If several files are to be processed, REGEXP has to match all the +files. + +Each COMMAND can either be a string or a Lisp expression that evaluates +to a string. If this expression needs to consult the name of the file for +which the shell commands are being requested, it can access that file name +as the variable `file'. + +If several COMMANDs are given, the first one will be the default +and the rest will be added temporarily to the history and can be retrieved +with `previous-history-element' (\\\\[previous-history-element]). + +The variable `dired-guess-shell-case-fold-search' controls whether +REGEXP is matched case-sensitively." + :group 'dired-guess + :type '(alist :key-type regexp :value-type (repeat sexp)) + :version "29.1") + +(defcustom dired-guess-shell-gnutar + (catch 'found + (dolist (exe '("tar" "gtar")) + (if (with-temp-buffer + (ignore-errors (call-process exe nil t nil "--version")) + (and (re-search-backward "GNU tar" nil t) t)) + (throw 'found exe)))) + "If non-nil, name of GNU tar executable. +\(E.g., \"tar\" or \"gtar\"). The `z' switch will be used with it for +compressed or gzip'ed tar files. If you don't have GNU tar, set this +to nil: a pipe using `zcat' or `gunzip -c' will be used." + ;; Changed from system-type test to testing --version output. + ;; Maybe test --help for -z instead? + :group 'dired-guess + :type '(choice (const :tag "Not GNU tar" nil) + (string :tag "Command name")) + :version "29.1") + +(defcustom dired-guess-shell-gzip-quiet t + "Non-nil says pass -q to gzip overriding verbose GZIP environment." + :group 'dired-guess + :type 'boolean + :version "29.1") + +(defcustom dired-guess-shell-znew-switches nil + "If non-nil, then string of switches passed to `znew', example: \"-K\"." + :group 'dired-guess + :type '(choice (const :tag "None" nil) + (string :tag "Switches")) + :version "29.1") + ;;; Internal variables diff --git a/test/lisp/dired-aux-tests.el b/test/lisp/dired-aux-tests.el index 694deaae4c2..e70898ab74e 100644 --- a/test/lisp/dired-aux-tests.el +++ b/test/lisp/dired-aux-tests.el @@ -154,5 +154,18 @@ (should (string-match (regexp-quote command) (nth 0 lines))) (dired-test--check-highlighting (nth 0 lines) '(8)))) +(ert-deftest dired-guess-default () + (let ((dired-guess-shell-alist-user nil) + (dired-guess-shell-alist-default + '(("\\.png\\'" "display") + ("\\.gif\\'" "display" "xloadimage") + ("\\.gif\\'" "feh") + ("\\.jpe?g\\'" "xloadimage")))) + (should (equal (dired-guess-default '("/tmp/foo.png")) "display")) + (should (equal (dired-guess-default '("/tmp/foo.gif")) + '("display" "xloadimage" "feh"))) + (should (equal (dired-guess-default '("/tmp/foo.png" "/tmp/foo.txt")) + nil)))) + (provide 'dired-aux-tests) ;;; dired-aux-tests.el ends here diff --git a/test/lisp/dired-x-tests.el b/test/lisp/dired-x-tests.el index cec266b0ef9..7acaa3c1319 100644 --- a/test/lisp/dired-x-tests.el +++ b/test/lisp/dired-x-tests.el @@ -47,19 +47,6 @@ (should (equal all-but-c (sort (dired-get-marked-files 'local) #'string<)))))) -(ert-deftest dired-guess-default () - (let ((dired-guess-shell-alist-user nil) - (dired-guess-shell-alist-default - '(("\\.png\\'" "display") - ("\\.gif\\'" "display" "xloadimage") - ("\\.gif\\'" "feh") - ("\\.jpe?g\\'" "xloadimage")))) - (should (equal (dired-guess-default '("/tmp/foo.png")) "display")) - (should (equal (dired-guess-default '("/tmp/foo.gif")) - '("display" "xloadimage" "feh"))) - (should (equal (dired-guess-default '("/tmp/foo.png" "/tmp/foo.txt")) - nil)))) - (ert-deftest dired-x--string-to-number () (should (= (dired-x--string-to-number "2.4K") 2457.6)) (should (= (dired-x--string-to-number "2400") 2400)) -- 2.39.2