Subject: [PATCH] Add uniquify-get-unique-names
From: Spencer Baugh <[email-masked]>
To: [email-masked]
Date: Thu, 27 Mar 2025 11:03:17 -0400
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

This new function provides an interface to uniquify.el which doesn't
change the actual names of the buffers.  This is useful for any commands
which deal with a subset of all buffers; for example, project.el.

* lisp/uniquify.el (uniquify-rationalize--generic): Add.
(uniquify-rationalize, uniquify-rationalize-a-list)
(uniquify-rationalize-conflicting-sublist): Explicitly pass
RENAME-BUFFER-FN and GET-BUFFER-FN.
(uniquify--stateless-curname, uniquify-get-unique-names): Add.

In GNU Emacs 30.1.50 (build 1, x86_64-pc-linux-gnu, X toolkit, cairo
 version 1.15.12, Xaw scroll bars) of 2025-03-11 built on
 igm-qws-u22796a
Repository revision: 516d1e6463a9659951f7567e038efc5ee2a19bbf
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
System Description: Rocky Linux 8.10 (Green Obsidian)

Configured using:
 'configure --config-cache --with-x-toolkit=lucid --without-gpm
 --without-gconf --without-selinux --without-imagemagick --with-modules
 --with-gif=no --with-cairo --with-rsvg --without-compress-install
 --with-tree-sitter --with-native-compilation=aot
 --prefix=/usr/local/home/garnish/raw-emacs/30-20250311_131404'

From afd867ef7577bab1cf83a62c5acaa1bfc5da6305 Mon Sep 17 00:00:00 2001
From: Spencer Baugh <[email-masked]>
Date: Thu, 27 Mar 2025 09:32:47 -0400
Subject: [PATCH] Add uniquify-get-unique-names

This new function provides an interface to uniquify.el which doesn't
change the actual names of the buffers.  This is useful for any commands
which deal with a subset of all buffers; for example, project.el.

* lisp/uniquify.el (uniquify-rationalize--generic): Add.
(uniquify-rationalize, uniquify-rationalize-a-list)
(uniquify-rationalize-conflicting-sublist): Explicitly pass
RENAME-BUFFER-FN and GET-BUFFER-FN.
(uniquify--stateless-curname, uniquify-get-unique-names): Add.
---
 lisp/uniquify.el | 65 ++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 54 insertions(+), 11 deletions(-)

diff --git a/lisp/uniquify.el b/lisp/uniquify.el
index 358ae6af651..50971e115b0 100644
--- a/lisp/uniquify.el
+++ b/lisp/uniquify.el
@@ -320,14 +320,19 @@ uniquify-rerationalize-w/o-cb
 (defun uniquify-rationalize (fix-list)
   ;; Set up uniquify to re-rationalize after killing/renaming
   ;; if there is a conflict.
+  (dolist (item fix-list)
+    (with-current-buffer (uniquify-item-buffer item)
+      (setq uniquify-managed fix-list)))
+  (uniquify-rationalize--generic fix-list #'uniquify-rename-buffer #'get-buffer))
+
+(defun uniquify-rationalize--generic (fix-list rename-buffer-fn get-buffer-fn)
   (dolist (item fix-list)
     (with-current-buffer (uniquify-item-buffer item)
       ;; Refresh the dirnames and proposed names.
       (setf (uniquify-item-proposed item)
 	    (uniquify-get-proposed-name (uniquify-item-base item)
 					(uniquify-item-dirname item)
-                                        nil))
-      (setq uniquify-managed fix-list)))
+                                        nil))))
   ;; Strip any shared last directory names of the dirname.
   (when (and (cdr fix-list) uniquify-strip-common-suffix)
     (let ((strip t))
@@ -353,13 +358,13 @@ uniquify-rationalize
 		fix-list)))))
   ;; If uniquify-min-dir-content is 0, this will end up just
   ;; passing fix-list to uniquify-rationalize-conflicting-sublist.
-  (uniquify-rationalize-a-list fix-list))
+  (uniquify-rationalize-a-list fix-list nil rename-buffer-fn get-buffer-fn))
 
 (defun uniquify-item-greaterp (item1 item2)
   (string-lessp (uniquify-item-proposed item2)
 		(uniquify-item-proposed item1)))
 
-(defun uniquify-rationalize-a-list (fix-list &optional depth)
+(defun uniquify-rationalize-a-list (fix-list depth rename-buffer-fn get-buffer-fn)
   (unless depth (setq depth uniquify-min-dir-content))
   (let (conflicting-sublist	; all elements have the same proposed name
 	(old-proposed "")
@@ -370,12 +375,14 @@ uniquify-rationalize-a-list
       (setq proposed (uniquify-item-proposed item))
       (unless (equal proposed old-proposed)
 	(uniquify-rationalize-conflicting-sublist conflicting-sublist
-						  old-proposed depth)
+						  old-proposed depth
+                                                  rename-buffer-fn get-buffer-fn)
 	(setq conflicting-sublist nil))
       (push item conflicting-sublist)
       (setq old-proposed proposed))
     (uniquify-rationalize-conflicting-sublist conflicting-sublist
-					      old-proposed depth)))
+					      old-proposed depth
+                                              rename-buffer-fn get-buffer-fn)))
 
 (defun uniquify-get-proposed-name (base dirname &optional depth)
   (unless depth (setq depth uniquify-min-dir-content))
@@ -427,12 +434,12 @@ uniquify-get-proposed-name
 
 ;; Deal with conflicting-sublist, all of whose elements have identical
 ;; "base" components.
-(defun uniquify-rationalize-conflicting-sublist (conf-list old-name depth)
+(defun uniquify-rationalize-conflicting-sublist (conf-list old-name depth rename-buffer-fn get-buffer-fn)
   (when conf-list
     (if (or (cdr conf-list)
 	    ;; Check that the proposed name doesn't conflict with some
 	    ;; existing buffer.
-	    (let ((buf (get-buffer old-name)))
+	    (let ((buf (funcall get-buffer-fn old-name)))
 	      (and buf (not (eq buf (uniquify-item-buffer (car conf-list)))))))
 	(when uniquify-possibly-resolvable
 	  (setq uniquify-possibly-resolvable nil
@@ -443,10 +450,9 @@ uniquify-rationalize-conflicting-sublist
 		   (uniquify-item-base item)
 		   (uniquify-item-dirname item)
 		   depth)))
-	  (uniquify-rationalize-a-list conf-list depth))
+	  (uniquify-rationalize-a-list conf-list depth rename-buffer-fn get-buffer-fn))
       (unless (string= old-name "")
-	(uniquify-rename-buffer (car conf-list) old-name)))))
-
+	(funcall rename-buffer-fn (car conf-list) old-name)))))
 
 (defun uniquify-rename-buffer (item newname)
   (let ((buffer (uniquify-item-buffer item)))
@@ -456,6 +462,43 @@ uniquify-rename-buffer
 	  ;; Pass the `unique' arg, so the advice doesn't mark it as unmanaged.
 	  (rename-buffer newname t))))))
 
+(defvar-local uniquify--stateless-curname nil
+  "The current unique name of this buffer in `uniquify-get-unique-names'.")
+
+(defun uniquify-get-unique-names (buffers)
+  "Return a hash table with a unique name for each buffer in BUFFERS.
+
+The names are unique only among BUFFERS, and may conflict with other
+buffers not in that list.
+
+This does not rename the buffers or change any state; the unique name is
+only present in the returned alist."
+  (let ((buffer-names (make-hash-table :size (length buffers) :test 'equal)))
+    (let (fix-lists-by-base)
+      (dolist (buf buffers)
+        (with-current-buffer buf
+          (setq uniquify--stateless-curname (buffer-name buf))
+          (puthash (buffer-name buf) buf buffer-names)
+          (when uniquify-managed
+            (let ((base (uniquify-item-base (car uniquify-managed))))
+              (push
+               (uniquify-make-item base (uniquify-buffer-file-name buf) buf nil)
+               (alist-get base fix-lists-by-base nil nil #'equal))))))
+      (dolist (pair fix-lists-by-base)
+        (uniquify-rationalize--generic
+         (cdr pair)
+         (lambda (item name)            ; rename-buffer
+           (with-current-buffer (uniquify-item-buffer item)
+             (remhash uniquify--stateless-curname buffer-names)
+             (setq uniquify--stateless-curname name)
+             (puthash name (current-buffer) buffer-names)))
+         (lambda (name)                 ; get-buffer
+           (gethash name buffer-names)))))
+    (dolist (buf buffers)
+      (with-current-buffer buf
+        (kill-local-variable 'uniquify--stateless-curname)))
+    buffer-names))
+
 ;;; Hooks from the rest of Emacs
 
 (defun uniquify-maybe-rerationalize-w/o-cb ()
-- 
2.39.3