Package: emacs;
Reported by: Phil Sainty <psainty <at> orcon.net.nz>
Date: Sun, 1 Jun 2025 03:20:06 UTC
Severity: normal
Tags: patch
Found in version 30.1
View this message in rfc822 format
From: Drew Adams <drew.adams <at> oracle.com> To: Phil Sainty <psainty <at> orcon.net.nz> Cc: "78658 <at> debbugs.gnu.org" <78658 <at> debbugs.gnu.org>, Juri Linkov <juri <at> linkov.net> Subject: bug#78658: 30.1; [PATCH] Dired feature suggestion: dired-on-marked-files-in-all-buffers 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
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.