GNU bug report logs - #31601
Dired/Wdired: Play nicely with recursive list of files and directories.

Previous Next

Package: emacs;

Reported by: Keith David Bershatsky <esq <at> lawlist.com>

Date: Sat, 26 May 2018 04:38:02 UTC

Severity: minor

To reply to this bug, email your comments to 31601 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#31601; Package emacs. (Sat, 26 May 2018 04:38:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to Keith David Bershatsky <esq <at> lawlist.com>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Sat, 26 May 2018 04:38:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Keith David Bershatsky <esq <at> lawlist.com>
To: Emacs Bug Reports <bug-gnu-emacs <at> gnu.org>
Subject: Dired/Wdired: Play nicely with recursive list of files and
 directories.
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




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#31601; Package emacs. (Sat, 26 May 2018 09:03:01 GMT) Full text and rfc822 format available.

Message #8 received at 31601 <at> debbugs.gnu.org (full text, mbox):

From: Tino Calancha <tino.calancha <at> gmail.com>
To: Keith David Bershatsky <esq <at> lawlist.com>
Cc: 31601 <at> debbugs.gnu.org, tino.calancha <at> gmail.com
Subject: Re: bug#31601: Dired/Wdired: Play nicely with recursive list of files
 and directories.
Date: Sat, 26 May 2018 18:02:26 +0900
Keith David Bershatsky <esq <at> lawlist.com> writes:

> 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.

> Dired:  The second problem is a dired-mode problem in that the `dired-directory' variable is not updated when renaming a file

Thank you for your report Keith!
You are right, it looks annoying.

Though I haven't arrived with an ideal solution, following
are two ways how I would try to perform a similar task.

I)
If you are lucky and "sh" points to a shell that
supports globstar (zsh, fish), or if you are using dired
via `em-ls', then you can do:
M-x dired /path/to/directory/**/* RET
;; Now both, wdired and `dired-do-rename' works fine.

[If you are using bash or ksh, that support globstar but
disable it by default, then you might be interestd in my
proposal in Bug#31495: it gives the chance to enable such
feature on dired via an user option.]

II)
Another way could be to use a command of the `find-dired' family:
M-x find-name-dired /path/to/directory/ RET * RET
;; I just noticed that wdired resets `revert-buffer-function' also for
;; these dired buffers.  IMO it shouldn't, so I would propose
;; something like this:
--8<-----------------------------cut here---------------start------------->8---
commit 58842a20f570d32f5d3f90aced8f0e8c1b1535a7
Author: Tino Calancha <tino.calancha <at> gmail.com>
Date:   Sat May 26 17:39:29 2018 +0900

    Preserve revert-buffer-function on find-dired buffers
    
    * lisp/wdired.el (wdired-find-dired-buffer-p): New predicate.
    (wdired-change-to-wdired-mode, wdired-change-to-dired-mode):
    Preserve `revert-buffer-function' on dired buffers created
    with `find-dired' commands.

diff --git a/lisp/wdired.el b/lisp/wdired.el
index bb60e77776..de5b327d75 100644
--- a/lisp/wdired.el
+++ b/lisp/wdired.el
@@ -228,6 +228,12 @@ wdired-mode
   (error "This mode can be enabled only by `wdired-change-to-wdired-mode'"))
 (put 'wdired-mode 'mode-class 'special)
 
+(defun wdired-find-dired-buffer-p ()
+  "Return non-nil if the dired buffer comes from a `find-dired' command."
+  (save-excursion
+    (goto-char (point-min))
+    (forward-line 1)
+    (re-search-forward (format "^\\s-*%s " find-program) nil t)))
 
 ;;;###autoload
 (defun wdired-change-to-wdired-mode ()
@@ -257,7 +263,8 @@ wdired-change-to-wdired-mode
   (add-hook 'kill-buffer-hook 'wdired-check-kill-buffer nil t)
   (setq major-mode 'wdired-mode)
   (setq mode-name "Editable Dired")
-  (setq revert-buffer-function 'wdired-revert)
+  (unless (wdired-find-dired-buffer-p)
+    (setq revert-buffer-function 'wdired-revert))
   ;; I temp disable undo for performance: since I'm going to clear the
   ;; undo list, it can save more than a 9% of time with big
   ;; directories because setting properties modify the undo-list.
@@ -363,7 +370,8 @@ wdired-change-to-dired-mode
   (setq mode-name "Dired")
   (dired-advertise)
   (remove-hook 'kill-buffer-hook 'wdired-check-kill-buffer t)
-  (set (make-local-variable 'revert-buffer-function) 'dired-revert))
+  (unless (wdired-find-dired-buffer-p)
+    (set (make-local-variable 'revert-buffer-function) 'dired-revert)))
 
 
 (defun wdired-abort-changes ()
--8<-----------------------------cut here---------------end--------------->8---
In GNU Emacs 27.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.22.11)
 of 2018-05-26 built on calancha-pc
Repository revision: 66c9ab90d5f8b566467549bf1d48c936bc6d296b





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#31601; Package emacs. (Sat, 26 May 2018 18:52:02 GMT) Full text and rfc822 format available.

Message #11 received at 31601 <at> debbugs.gnu.org (full text, mbox):

From: Keith David Bershatsky <esq <at> lawlist.com>
To: Tino Calancha <tino.calancha <at> gmail.com>
Cc: 31601 <at> debbugs.gnu.org
Subject: Re: bug#31601: Dired/Wdired: Play nicely with recursive list of files
 and directories.
Date: Sat, 26 May 2018 11:51:35 -0700
Thank you for the alternative approaches.  I applied the new diff and was able to successfully use the second option with `find-dired`.

As to the first option, however, I would need to spend time learning about zsh, fish and em-ls.  So, that exploration would need to wait for a rainy day.

Oh, by the way, please consider adding a `nil` to the end of `(defvar wdired-old-marks)` so that the user does not experience this:

funcall-interactively: Symbol’s value as variable is void: wdired-old-marks

In my spare time, I will most likely continue implementing functionality for wdired playing nice with:

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

That will entail:  (1) placing read-only text properties over areas that cannot be changed; (2) when I modify a directory, I will need to propagate that change throughout the applicable entries of `dired-directory'; (3) I will consider implementing a hash-table approach with `dired-directory` entries for increased speed.

Keith

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

DATE:  [05-26-2018 02:02:26] <26 May 2018 18:02:26 +0900>
FROM:  Tino Calancha <tino.calancha <at> gmail.com>
> 
> * * *

> I)
> If you are lucky and "sh" points to a shell that
> supports globstar (zsh, fish), or if you are using dired
> via `em-ls', then you can do:
> M-x dired /path/to/directory/**/* RET
> ;; Now both, wdired and `dired-do-rename' works fine.
> 
> [If you are using bash or ksh, that support globstar but
> disable it by default, then you might be interestd in my
> proposal in Bug#31495: it gives the chance to enable such
> feature on dired via an user option.]
> 
> II)
> Another way could be to use a command of the `find-dired' family:
> M-x find-name-dired /path/to/directory/ RET * RET

* * *




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#31601; Package emacs. (Mon, 28 May 2018 23:12:02 GMT) Full text and rfc822 format available.

Message #14 received at 31601 <at> debbugs.gnu.org (full text, mbox):

From: Keith David Bershatsky <esq <at> lawlist.com>
To: Tino Calancha <tino.calancha <at> gmail.com>
Cc: 31601 <at> debbugs.gnu.org
Subject: Re: bug#31601: Dired/Wdired: Play nicely with recursive list of files
 and directories.
Date: Mon, 28 May 2018 16:11:38 -0700
[Message part 1 (text/plain, inline)]
Attached is a draft/proof-concept patch.diff of my ideas regarding how to make dired/wdired play nicely with an arbitrary list of files and folders, including a recursive list.  I asked for some help on emacs.stackexchange.com to implement these new approaches, and used the answers by @Drew, @Tobias and @Andreas Röhler.  Citations to their answers are included in the draft/proof-concept patch.  I would need some help regarding how to update the dired-directory variable when deleting a file in a regular dired-mode buffer, and I have added a FIXME notation in the likely spot of `dired.el` -- the approach would be similar to renaming a file (already implemented in the attached draft patch), but is a bit more complicated due to potential recursive deletions.

The file-name-nondirectory components in wdired-mode now have read-only attributes so that a user no longer erroneously believes that he/she can modify them, and only latter see an error message when trying to commit the changes:  file-error Renaming No such file or directory ....

I have only done some basic renaming of files and directories in wdired mode using both `find-name-dired` and `dired-list-files-folders' to populate the dired- buffer.  I did not spend any time implementing hash-tables to speed up the modification of the dired-directory variable, as that would likely involve modifying a few or more aspects of dired-mode.

Thanks,

Keith

[patch.diff (application/diff, attachment)]

This bug report was last modified 7 years and 19 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.