Subject: RE: [External] : bug#78658: 30.1; [PATCH] Dired feature suggestion: dired-on-marked-files-in-all-buffers

From: Drew Adams <[email-masked]>
Date: Tue, 3 Jun 2025 19:46:47 +0000

> Thank you for: (let ((common  (try-completion "" files)))

Thank you for your great idea, Phil.

> My brain had failed to make the connection between "completion"
> and programmatically obtaining a common prefix from list of
> strings.  It makes perfect sense in hindsight, but I'd simply
> never thought about completion in non-interactive terms, and was
> surprised when I hadn't found a documented function for doing
> this (but I wasn't looking under Completion at all).

Understood.  Same for everyone.  I defined utility function
`diredp-common-ancestor-dir' for this (see below).

> Eli, I reckon this is worth either documenting somewhere under
> (info "(elisp) Strings and Characters") or else creating a
> slightly more string-centric wrapper, something like:
> 
> (defun string-common-prefix (strings)
>    "Return the largest common prefix from a list of STRINGS."
>    (let ((prefix (try-completion "" strings)))
>      (if (stringp prefix) prefix "")))

Dunno how useful that alone is, but maybe.

I'm using this now:

(defun diredp-common-ancestor-dir (files)
  "Return the common ancestor directory of FILES, or nil if none."
  (let ((common  (try-completion "" files)))
    (if (stringp common)
        (unless (directory-name-p common) (setq common  (diredp-parent-dir common)))
      (when (eq t common) (setq common "")))
    common))

And I've long used this helper a fair amount:

(defun diredp-parent-dir (file &optional relativep)
  "Return the parent directory of FILE, or nil if none.
Optional arg RELATIVEP non-nil means return a relative name, that is,
just the parent component."
  (let ((parent  (file-name-directory (directory-file-name (expand-file-name file))))
        relparent)
    (when relativep (setq relparent  (file-name-nondirectory (directory-file-name parent))))
    (and (not (equal parent file))  (or relparent  parent))))
 
> Regarding the buffer naming:
> 
> >> (dired (cons "MARKED-ANYWHERE"
> 
> It hadn't occurred to me that this could be something other
> than a directory path.  I definitely agree it would be ideal
> not to conflict with other dired buffers, but I've seen some
> some oddities from doing it that way.  E.g. after creating
> that buffer, I can't obtain it with:
> 
> (dired-find-buffer-nocreate "MARKED-ANYWHERE" 'dired-mode)
> but rather I need to use:
> (dired-find-buffer-nocreate "/path/to/MARKED-ANYWHERE" 'dired-mode)
> 
> for the file path it thinks is relevant, which then has some
> possibility (albeit unlikely) of conflicting with a real path.

I don't worry about that.  When DIRNAME is a cons, its car is
just used as a buffer name.  It shouldn't (and needn't) ever
be taken as a directory.

> And also (for better or worse) the new command can create extra
> MARKED-ANYWHERE buffers under different paths.

For better, IMO.
You'll get additional buffers, named `MARKED-ANYWHERE <abc>' etc.

With my code a user can anyway provide a different buffer name.
You can have multiple such Dired buffers, with different names,
for different sets of markings, for different purposes/contexts.

> We could further reduce the chance of any conflict with an
> actual file by choosing an even less-likely name; but perhaps
> there's a cleaner solution.

It's a buffer name.  I don't see how conflicts with file names
are relevant.

> The version I'm currently playing with is attached.  I haven't
> tested your version yet, but I've grabbed some of the changes
> from the code you've shown.
> 
> I've split out a couple of extra functions which felt useful on
> their own:
> 
> `file-name-directory-common-prefix'
>    "The nearest common ancestor directory for FILES."
> 
> `dired-get-explicitly-marked-files'
>    "Like `dired-get-marked-files' but always returns nil when
>   nothing is marked."

I too have a few utility functions, but not the same:

  diredp-common-ancestor-dir (see above)
  diredp-parent-dir          (see above)
  diredp-live-dired-buffers
  diredp-explicit

This is what I'm using now:

(defun diredp-get-marked-files-in-all-buffers ()
  "Return names of files and directories marked in any Dired buffers.
Like `dired-get-marked-files', but for all Dired buffers."
  (delete-dups
   (let ((dired-bufs  (diredp-live-dired-buffers)))
     (apply #'nconc
            (mapcar (lambda (buf)
                      (with-current-buffer buf
                        (let ((files  (dired-get-marked-files nil nil nil t)))
                          (setq files  (and (cdr files)  (if (eq (car files) t)
                                                             (list (cadr files))
                                                           files))))))
                    dired-bufs)))))

(defun diredp-live-dired-buffers ()
  "Return a list of the live Dired buffers."
  (delq nil (mapcar (lambda (d.b)(and (buffer-live-p (cdr d.b))  (cdr d.b)))
                    dired-buffers)))

(defun diredp-marked-in-any-buffers (&optional files buffer-name)
  "Dired the files and directories marked in any Dired buffers.
Like `diredp-marked-files', but for all Dired buffers.
With a prefix argument you're prompted for the name of the resulting
Dired buffer.  Otherwise, the name is `MARKED-ANYWHERE'.
This command is only for interactive use."
  (interactive (let ((fils  (diredp-get-marked-files-in-all-buffers)))
                 (unless files (user-error "No marked files in any Dired buffer"))
                 (list fils (if current-prefix-arg
                                (read-string "Resulting Dired buffer name: ")
                              "MARKED-ANYWHERE"))))
  (diredp-explicit files buffer-name))

(defun diredp-explicit (files buffer-name &optional other-window-p)
  "Dired FILES (a list of absolute file names) in buffer BUFFER-NAME.
The names are listed relative to their common-ancestor directory or,
if none, relative to the current value of `default-directory'.
Non-nil OTHER-WINDOW-P means use `dired-other-window', not `dired'."
  (let ((common  (diredp-common-ancestor-dir files)))
    (let ((default-directory  (or common  default-directory)))
      (funcall (if other-window-p #'dired-other-window #'dired)
               (cons buffer-name (if common
                                     (mapcar (lambda (file)
                                               (file-relative-name file common))
                                             files)
                                   files))))))

(defun diredp-common-ancestor-dir (files)
  "Return the common ancestor directory of FILES, or nil if none."
  (let ((common  (try-completion "" files)))
    (if (stringp common)
        (unless (directory-name-p common) (setq common  (diredp-parent-dir common)))
      (when (eq t common) (setq common "")))
    common))

(defun diredp-parent-dir (file &optional relativep)
  "Return the parent directory of FILE, or nil if none.
Optional arg RELATIVEP non-nil means return a relative name, that is,
just the parent component."
  (let ((parent  (file-name-directory (directory-file-name (expand-file-name file))))
        relparent)
    (when relativep
      (setq relparent  (file-name-nondirectory (directory-file-name parent))))
    (and (not (equal parent file))  (or relparent  parent))))

Dunno whether it's worth having something like your
`dired-get-explicitly-marked-files'.  But IMO it's anyway
better not to use `length' - better to just test for the
`t' explicit-marked case:

 (and (cdr files)             ; Return nil for unmarked singleton
      (if (eq (car files) t)  ; Marked singleton - listify it.
          (list (cadr files))
        files))               ; Multiple files