├── README.org └── fd-dired.el /README.org: -------------------------------------------------------------------------------- 1 | * fd-dired 2 | Provide a dired-mode interface for [[https://github.com/sharkdp/fd][fd]]'s result. Same functionality as 3 | find-dired/find-grep-dired, using fd/rg instead. Depend on find-dired. 4 | 5 | Check out [[https://www.masteringemacs.org/article/working-multiple-files-dired][this]] for functionality and use case of ~find-dired~. 6 | 7 | * FAQ 8 | 9 | ** macOS system command "ls" illegal option "--quoting-style=literal" 10 | 11 | The macOS system default '~ls~' command does not support option =--quoting-style=literal=. 12 | 13 | Please install with: ~brew install coreutils~ 14 | -------------------------------------------------------------------------------- /fd-dired.el: -------------------------------------------------------------------------------- 1 | ;;; fd-dired.el --- find-dired alternative using fd -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright © 2018, Free Software Foundation, Inc. 4 | 5 | ;; Version: 0.1.0 6 | ;; URL: https://github.com/yqrashawn/fd-dired 7 | ;; Package-Requires: ((emacs "25")) 8 | ;; Author: Rashawn Zhang 9 | ;; Created: 3 July 2018 10 | ;; Keywords: tools, fd, find, dired 11 | 12 | ;; This program is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; Provide a dired-mode interface for fd's result. Same functionality as 28 | ;; find-dired, use fd instead. Depend on find-dired. 29 | 30 | ;; Just call `fd-dired'. 31 | 32 | ;;; Code: 33 | 34 | (require 'find-dired) 35 | (require 'ibuffer) 36 | (require 'ibuf-ext) 37 | 38 | (defvar fd-dired-program "fd" 39 | "The default fd program.") 40 | 41 | (defvar fd-grep-dired-program "rg" 42 | "The default fd grep program.") 43 | 44 | (defvar fd-dired-input-fd-args "" 45 | "Last used fd arguments.") 46 | 47 | (defvar fd-dired-args-history nil 48 | "History list of fd arguments entered in the minibuffer.") 49 | 50 | (defgroup fd-dired nil 51 | "fd-dired customize group." 52 | :prefix "fd-dired-" 53 | :group 'fd-dired) 54 | 55 | (defcustom fd-dired-display-in-current-window t 56 | "Whether display result" 57 | :type 'boolean 58 | :safe #'booleanp 59 | :group 'fd-dired) 60 | 61 | (defcustom fd-dired-pre-fd-args "-0 -c never" 62 | "Fd arguments inserted before user arguments." 63 | :type 'string 64 | :group 'fd-dired) 65 | 66 | (defcustom fd-grep-dired-pre-grep-args "--color never --regexp" 67 | "Fd grep arguments inserted before user arguments." 68 | :type 'string 69 | :group 'fd-dired) 70 | 71 | (defcustom fd-dired-ls-option 72 | (pcase system-type 73 | ('darwin 74 | ;; NOTE: here `gls' need to `brew install coreutils' 75 | (if (executable-find "gls") 76 | `(,(concat "| xargs -0 " "gls -ld --quoting-style=literal | uniq") . "-ld") 77 | (warn "macOS system default 'ls' command does not support option --quoting-style=literal.\n Please install with: brew install coreutils"))) 78 | (_ 79 | `(,(concat "| xargs -0 " insert-directory-program " -ld --quoting-style=literal | uniq") . "-ld"))) 80 | "A pair of options to produce and parse an `ls -l'-type list from `fd'. 81 | This is a cons of two strings (FD-ARGUMENTS . LS-SWITCHES). 82 | FD-ARGUMENTS is the option passed to `fd' to produce a file 83 | listing in the desired format. LS-SWITCHES is a set of `ls' 84 | switches that tell dired how to parse the output of `fd'. 85 | 86 | For more information, see `FIND-LS-OPTION'." 87 | :type '(cons :tag "Fd arguments pair" 88 | (string :tag "Fd arguments") 89 | (string :tag "Ls Switches")) 90 | :group 'fd-dired) 91 | 92 | (defcustom fd-dired-generate-random-buffer nil 93 | "Generate random buffer name. 94 | If this variable is non-nil, new fd-dired buffers will have 95 | random and hidden names." 96 | :type 'boolean 97 | :group 'fd-dired) 98 | 99 | 100 | ;;;###autoload 101 | (defun fd-dired (dir args) 102 | "Run `fd' and go into Dired mode on a buffer of the output. 103 | The command run (after changing into DIR) is essentially 104 | 105 | fd . ARGS -ls 106 | 107 | except that the car of the variable `fd-dired-ls-option' specifies what to 108 | use in place of \"-ls\" as the final argument." 109 | (interactive (list (read-directory-name "Run fd in directory: " nil "" t) 110 | (read-string "Run fd (with args and search): " fd-dired-input-fd-args 111 | '(fd-dired-args-history . 1)))) 112 | (let ((dired-buffers dired-buffers) 113 | (fd-dired-buffer-name (if fd-dired-generate-random-buffer 114 | (format " *%s*" (make-temp-name "Fd ")) 115 | "*Fd*"))) 116 | ;; Expand DIR ("" means default-directory), and make sure it has a 117 | ;; trailing slash. 118 | (setq dir (file-name-as-directory (expand-file-name (or dir default-directory)))) 119 | ;; Check that it's really a directory. 120 | (or (file-directory-p dir) 121 | (error "Fd-dired needs a directory: %s" dir)) 122 | 123 | (get-buffer-create fd-dired-buffer-name) 124 | (if fd-dired-display-in-current-window 125 | (display-buffer (get-buffer fd-dired-buffer-name) nil) 126 | (display-buffer-below-selected (get-buffer fd-dired-buffer-name) nil) 127 | (select-window (get-buffer-window fd-dired-buffer-name))) 128 | 129 | (with-current-buffer (get-buffer fd-dired-buffer-name) 130 | ;; prepare buffer 131 | (widen) 132 | (kill-all-local-variables) 133 | (setq buffer-read-only nil) 134 | (erase-buffer) 135 | 136 | ;; Start the process. 137 | (setq default-directory dir 138 | fd-dired-input-fd-args args ; save for next interactive call 139 | args (concat fd-dired-program " " fd-dired-pre-fd-args 140 | ;; " . " 141 | (if (string= args "") 142 | "" 143 | (concat 144 | " " args " " 145 | " ")) 146 | (if (string-match "\\`\\(.*\\) {} \\(\\\\;\\|+\\)\\'" 147 | (car fd-dired-ls-option)) 148 | (format "%s %s %s" 149 | (match-string 1 (car fd-dired-ls-option)) 150 | (shell-quote-argument "{}") 151 | find-exec-terminator) 152 | (car fd-dired-ls-option)))) 153 | (shell-command (concat args " &") (get-buffer-create fd-dired-buffer-name)) 154 | 155 | ;; enable Dired mode 156 | ;; The next statement will bomb in classic dired (no optional arg allowed) 157 | (dired-mode dir (cdr fd-dired-ls-option)) 158 | ;; provide a keybinding to kill the find process 159 | (let ((map (make-sparse-keymap))) 160 | (set-keymap-parent map (current-local-map)) 161 | (define-key map "\C-c\C-k" #'kill-find) 162 | (define-key map (kbd "M-S") #'fd-dired-save-search-as-name) 163 | (use-local-map map)) 164 | ;; disable Dired sort 165 | (make-local-variable 'dired-sort-inhibit) 166 | (setq dired-sort-inhibit t) 167 | (set (make-local-variable 'revert-buffer-function) 168 | `(lambda (ignore-auto noconfirm) 169 | (fd-dired ,dir ,fd-dired-input-fd-args))) 170 | ;; Set `subdir-alist' so that Tree Dired will work: 171 | (if (fboundp 'dired-simple-subdir-alist) 172 | ;; will work even with nested dired format (dired-nstd.el,v 1.15 173 | ;; and later) 174 | (dired-simple-subdir-alist) 175 | ;; else we have an ancient tree dired (or classic dired, where 176 | ;; this does no harm) 177 | (set (make-local-variable 'dired-subdir-alist) 178 | (list (cons default-directory (point-min-marker))))) 179 | (set (make-local-variable 'dired-subdir-switches) find-ls-subdir-switches) 180 | (setq buffer-read-only nil) 181 | ;; Subdir headlerline must come first because the first marker in 182 | ;; `subdir-alist' points there. 183 | (insert " " dir ":\n") 184 | 185 | ;; Make second line a ``find'' line in analogy to the ``total'' or 186 | ;; ``wildcard'' line. 187 | (let ((point (point))) 188 | (insert " " args "\n") 189 | (dired-insert-set-properties point (point))) 190 | (setq buffer-read-only t) 191 | 192 | (let ((proc (get-buffer-process (get-buffer fd-dired-buffer-name)))) 193 | (set-process-filter proc (function find-dired-filter)) 194 | (set-process-sentinel proc (function find-dired-sentinel)) 195 | ;; Initialize the process marker; it is used by the filter. 196 | (move-marker (process-mark proc) (point) (get-buffer fd-dired-buffer-name))) 197 | (setq mode-line-process '(":%s"))))) 198 | 199 | ;;;###autoload 200 | (defun fd-name-dired (dir pattern) 201 | "Search DIR recursively for files matching the globbing pattern PATTERN, 202 | and run Dired on those files. 203 | PATTERN is a shell wildcard (not an Emacs regexp) and need not be quoted. 204 | The default command run (after changing into DIR) is 205 | 206 | fd . ARGS \\='PATTERN\\=' | fd-dired-ls-option" 207 | (interactive 208 | "DFd-name (directory): \nsFd-name (filename regexp): ") 209 | (fd-dired dir (shell-quote-argument pattern))) 210 | 211 | ;;;###autoload 212 | (defun fd-grep-dired (dir regexp) 213 | "Find files in DIR that contain matches for REGEXP and start Dired on output. 214 | The command run (after changing into DIR) is 215 | 216 | fd . ARGS --exec rg --regexp REGEXP -0 -ls | fd-dired-ls-option" 217 | (interactive "DFd-grep (directory): \nsFd-grep (rg regexp): ") 218 | (fd-dired dir (concat "--exec " fd-grep-dired-program 219 | " " fd-grep-dired-pre-grep-args " " 220 | (shell-quote-argument regexp) 221 | " -0 -ls "))) 222 | 223 | (defun fd-dired-cleanup () 224 | "Clean up fd-dired created temp buffers for multiple searching processes." 225 | (mapcar 'kill-buffer 226 | (seq-filter 227 | (lambda (buffer-name) 228 | (string-match-p (rx (seq "*Fd " (zero-or-more nonl) "*")) buffer-name)) 229 | (mapcar 'buffer-name (buffer-list))))) 230 | 231 | (add-hook 'kill-emacs-hook #'fd-dired-cleanup) 232 | 233 | (defun fd-dired-save-search-as-name (newname) 234 | "Save the search result in current result buffer. 235 | NEWNAME will be added to the result buffer name. New searches will use the 236 | standard buffer unless the search is done from a saved buffer in 237 | which case the saved buffer will be reused." 238 | (interactive "sSave search as name: ") 239 | (let ((buffer (current-buffer))) 240 | (when (and (string-match-p (rx bos (? " ") "*Fd") (buffer-name buffer)) 241 | (derived-mode-p 'dired-mode)) 242 | (with-current-buffer buffer 243 | (rename-buffer (format "*Fd %s*" newname) t))))) 244 | 245 | (defun fd-dired-list-searches () 246 | "List all fd buffers in `ibuffer'." 247 | (interactive) 248 | (let ((buffer-name "*Searches fd*") 249 | (other-window (equal current-prefix-arg '(4)))) 250 | (ibuffer other-window buffer-name 251 | `((mode . dired-mode) (name . ,(rx bos "*Fd")))) 252 | (with-current-buffer buffer-name 253 | (ibuffer-auto-mode) 254 | (set (make-local-variable 'ibuffer-use-header-line) nil) 255 | (ibuffer-clear-filter-groups)))) 256 | 257 | (provide 'fd-dired) 258 | 259 | ;;; fd-dired.el ends here 260 | --------------------------------------------------------------------------------