Subject: Dired/Wdired: Play nicely with recursive list of files and directories.

From: Keith David Bershatsky <[email-masked]>
Date: Fri, 25 May 2018 21:36:37 -0700

I occasionally have the need to rename files and directories that are recursively located.  I create a dired-mode buffer as follows:

    (dired (directory-files-recursively "/path/to/directory" "" 'include-directories))

Wdired:  The first problem is that `wdired-get-filename' needs to be modified so that it handles absolute paths differently than relative filenames.  Here is an example that works -- using `file-name-absolute-p`:

(defun wdired-get-filename (&optional no-dir old)
  "Return the filename at line.
Similar to `dired-get-filename' but it doesn't rely on regexps.  It
relies on WDired buffer's properties.  Optional arg NO-DIR with value
non-nil means don't include directory.  Optional arg OLD with value
non-nil means return old filename."
  ;; FIXME: Use dired-get-filename's new properties.
  (let (beg end file)
    (save-excursion
      (setq end (line-end-position))
      (beginning-of-line)
      (setq beg (next-single-property-change (point) 'old-name nil end))
      (unless (eq beg end)
  (if old
      (setq file (get-text-property beg 'old-name))
    ;; In the following form changed `(1+ beg)' to `beg' so that
    ;; the filename end is found even when the filename is empty.
    ;; Fixes error and spurious newlines when marking files for
    ;; deletion.
    (if (= (point-at-eol) (point-max))
      (setq end (point-max)) ;; fix for @lawlist eliminating final new line at eob.
      (setq end (next-single-property-change beg 'end-name)))
    (setq file (buffer-substring-no-properties (1+ beg) end)))
  (and file (setq file (wdired-normalize-filename file))))
      (if (or no-dir old)
        file
        (cond
          ;;; When FILE is relative, concatenate default-directory to beginning.
          ((and file
                (> (length file) 0)
                (not (file-name-absolute-p file))
                (concat (dired-current-directory) file)))
          ;;; When FILE is absolute, no need to concatenate the default-directory.
          ((and file
                (> (length file) 0)
                (file-name-absolute-p file)
                file)))))))

Dired:  The second problem is a dired-mode problem in that the `dired-directory' variable is not updated when renaming a file.  I haven't tested deleting a file, but that probably suffers the same problem.  Absent updating the `dired-directory` with the renamed filename, the `revert-buffer` will encounter errors because `ls` or `gls` will try to find files that no longer exist, and that errors will be inserted into the dired- buffer.  A quick solution for renaming the file is to modify `dired-rename-file` as follows:

;;; (setq mylist '("apple" "pear" "peach" "nectarine" "watermelon"))
;;; (ar-replace--in-list "apple" "cherry" mylist)
;;; Written by @Andreas Röhler:  https://emacs.stackexchange.com/a/41631/2287
(defun ar-replace--in-list (elem replacement list)
  "Expects a LIST of strings.
ELEM: element to replace by arg REPLACEMENT"
  (let (newlist)
    (dolist (ele list)
      (if (string= ele elem)
      (push replacement newlist)
    (push ele newlist)))
    (nreverse newlist)))

(defun dired-rename-file (file newname ok-if-already-exists)
  (dired-handle-overwrite newname)
  (rename-file file newname ok-if-already-exists) ; error is caught in -create-files
  ;;; Update the `dired-directory'
  (when (and (listp dired-directory)
             (member file dired-directory))
    (setq dired-directory
      (ar-replace--in-list file newname dired-directory)))
  ;; Silently rename the visited file of any buffer visiting this file.
  (and (get-file-buffer file)
       (with-current-buffer (get-file-buffer file)
   (set-visited-file-name newname nil t)))
  (dired-remove-file file)
  ;; See if it's an inserted subdir, and rename that, too.
  (dired-rename-subdir file newname))

However, this is understandably slow if there are a lot of files.  Perhaps a hash-table system would be better suited to keep track of files and directories instead of just a plain old list with file/directory names.

I haven't yet figured out the best approach to modify a wdired-mode buffer that has a combination of directories and files.  Presumably some portion of the absolute filename/directory-name will need read-only attributes ....  But, that is as far as my thinking has gone on this issue.

There may be many other situations that I haven't thought of because I've never used all of the features of dired-mode and/or wdired-mode.

Thanks,

Keith