diff options
| author | Karl Heuer | 1995-12-21 18:13:58 +0000 |
|---|---|---|
| committer | Karl Heuer | 1995-12-21 18:13:58 +0000 |
| commit | 4c61f92c4e09978219750fdc8ec9d25df37cb64b (patch) | |
| tree | d7864e85053852ee143a1eec7b92bb014867c62c | |
| parent | f42dc896db7b5bfa415b89dc0720940c8a650549 (diff) | |
| download | emacs-4c61f92c4e09978219750fdc8ec9d25df37cb64b.tar.gz emacs-4c61f92c4e09978219750fdc8ec9d25df37cb64b.zip | |
Initial revision
| -rw-r--r-- | lisp/uniquify.el | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/lisp/uniquify.el b/lisp/uniquify.el new file mode 100644 index 00000000000..70c7c43644c --- /dev/null +++ b/lisp/uniquify.el | |||
| @@ -0,0 +1,381 @@ | |||
| 1 | ;;; uniquify.el --- unique buffer names dependent on pathname | ||
| 2 | |||
| 3 | ;; Copyright (c) 1989, 1995 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Dick King <king@reasoning.com> | ||
| 6 | ;; Maintainer: Michael Ernst <mernst@theory.lcs.mit.edu> | ||
| 7 | ;; Created: 15 May 86 | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 14 | ;; any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 23 | ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 24 | |||
| 25 | ;;; Commentary: | ||
| 26 | |||
| 27 | ;; Emacs's standard method for making buffer names unique adds <2>, <3>, | ||
| 28 | ;; etc. to the end of (all but one of) the buffers. This file replaces | ||
| 29 | ;; that behavior, for buffers visiting files and dired buffers, with a | ||
| 30 | ;; uniquification that adds parts of the pathname until the buffer names | ||
| 31 | ;; are unique. For instance, buffers visiting /u/mernst/tmp/Makefile and | ||
| 32 | ;; /usr/projects/zaphod/Makefile would be named Makefile|tmp and | ||
| 33 | ;; Makefile|zaphod, respectively (instead of Makefile and Makefile<2>). | ||
| 34 | ;; Other buffer name styles are also available. | ||
| 35 | |||
| 36 | ;; To use this file, just load it. | ||
| 37 | ;; To disable it after loading, set variable uniquify-buffer-name-style to nil. | ||
| 38 | ;; For other options, see "User-visible variables", below. | ||
| 39 | |||
| 40 | ;; uniquify.el works under Emacs, XEmacs, and InfoDock. | ||
| 41 | |||
| 42 | ;; Doesn't correctly handle buffer names created by M-x write-file in Emacs 18. | ||
| 43 | |||
| 44 | ;;; Change Log: | ||
| 45 | |||
| 46 | ;; Originally by Dick King <king@reasoning.com> 15 May 86 | ||
| 47 | ;; Converted for Emacs 18 by Stephen Gildea <gildea@lcs.mit.edu> | ||
| 48 | ;; Make uniquify-min-dir-content 0 truly non-invasive. gildea 23 May 89 | ||
| 49 | ;; Some cleanup. uniquify-min-dir-content default 0. gildea 01 Jun 89 | ||
| 50 | ;; Don't rename to "". Michael Ernst <mernst@theory.lcs.mit.edu> 15 Jun 94 | ||
| 51 | ;; Add kill-buffer-hook. Kenneth Manheimer <ken.manheimer@nist.gov> 09 May 95 | ||
| 52 | ;; Add advice for rename-buffer and create-file-buffer, handle dired buffers, | ||
| 53 | ;; kill-buffer-rationalize-buffer-names-p, documentation. mernst 24 May 95 | ||
| 54 | ;; Remove free variables, fix typos. mernst 5 Jun 95 | ||
| 55 | ;; Efficiently support Emacs 19.27 & earlier. ken.manheimer, mernst 10 Jun 95 | ||
| 56 | ;; Rename user options to "uniquify-...", add uniquify-reverse-dir-content-p, | ||
| 57 | ;; add uniquify-ask-about-buffer-names-p. king, mernst 13 Jun 95 | ||
| 58 | ;; Prefix functions by "uniquify-..."; rename mnemonic-buffer-names to | ||
| 59 | ;; uniquify-buffer-name-style; add 'forward and 'post-forward-angle-brackets | ||
| 60 | ;; styles; remove uniquify-reverse-dir-content-p; add | ||
| 61 | ;; uniquify-trailing-separator-p. mernst 4 Aug 95 | ||
| 62 | |||
| 63 | ;; Valuable feedback was provided by | ||
| 64 | ;; Paul Smith <psmith@baynetworks.com>, | ||
| 65 | ;; Alastair Burt <burt@dfki.uni-kl.de>, | ||
| 66 | ;; Bob Weiner <weiner@footloose.sps.mot.com>, | ||
| 67 | ;; Albert L. Ting <alt@vlibs.com>, | ||
| 68 | ;; gyro@reasoning.com. | ||
| 69 | |||
| 70 | |||
| 71 | ;;; Code: | ||
| 72 | |||
| 73 | (provide 'uniquify) | ||
| 74 | |||
| 75 | ;;; User-visible variables | ||
| 76 | |||
| 77 | (defvar uniquify-buffer-name-style 'post-forward | ||
| 78 | "*If non-nil, buffer names are uniquified with parts of directory name. | ||
| 79 | The value determines the buffer name style and is one of 'forward, | ||
| 80 | 'reverse, 'post-forward (the default), or 'post-forward-angle-brackets. | ||
| 81 | For example, files /foo/bar/mumble/name and /baz/quux/mumble/name | ||
| 82 | would be in the following buffers: | ||
| 83 | forward bar/mumble/name quux/mumble/name | ||
| 84 | reverse name\\mumble\\bar name\\mumble\\quux | ||
| 85 | post-forward name|bar/mumble name|quux/mumble | ||
| 86 | post-forward-angle-brackets name<bar/mumble> name<quux/mumble> | ||
| 87 | nil name name<2>") | ||
| 88 | |||
| 89 | (defvar uniquify-after-kill-buffer-p nil | ||
| 90 | "*If non-nil, rerationalize buffer names after a buffer has been killed. | ||
| 91 | This can be dangerous if Emacs Lisp code is keeping track of buffers by their | ||
| 92 | names (rather than keeping pointers to the buffers themselves).") | ||
| 93 | |||
| 94 | (defconst uniquify-ask-about-buffer-names-p nil | ||
| 95 | "*If non-nil, permit user to choose names for buffers with same base file. | ||
| 96 | If the user chooses to name a buffer, uniquification is preempted and no | ||
| 97 | other buffer names are changed.") | ||
| 98 | |||
| 99 | (defvar uniquify-min-dir-content 0 | ||
| 100 | "*Minimum parts of directory pathname included in buffer name.") | ||
| 101 | |||
| 102 | (defvar uniquify-separator nil | ||
| 103 | "*String separator for buffer name components. | ||
| 104 | When `uniquify-buffer-name-style' is 'post-forward, separates | ||
| 105 | base file name from directory part in buffer names (default \"|\"). | ||
| 106 | When `uniquify-buffer-name-style' is 'reverse, separates all | ||
| 107 | pathname components (default \"\\\").") | ||
| 108 | |||
| 109 | (defvar uniquify-trailing-separator-p nil | ||
| 110 | "*If non-nil, add a pathname separator to dired buffer names. | ||
| 111 | If `uniquify-buffer-name-style' is 'forward, add the separator at the end; | ||
| 112 | if it's is 'reverse, add the separator at the beginning; otherwise, this | ||
| 113 | variable is ignored.") | ||
| 114 | |||
| 115 | |||
| 116 | ;;; Utilities | ||
| 117 | |||
| 118 | (defmacro uniquify-push (item list) | ||
| 119 | (` (setq (, list) (cons (, item) (, list))))) | ||
| 120 | |||
| 121 | (defmacro uniquify-fix-list-base (a) | ||
| 122 | (` (car (, a)))) | ||
| 123 | |||
| 124 | (defmacro uniquify-fix-list-filename (a) | ||
| 125 | (` (car (cdr (, a))))) | ||
| 126 | |||
| 127 | (defmacro uniquify-fix-list-buffer (a) | ||
| 128 | (` (car (cdr (cdr (, a)))))) | ||
| 129 | |||
| 130 | (defmacro uniquify-cadddr (a) | ||
| 131 | (` (car (cdr (cdr (cdr (, a))))))) | ||
| 132 | |||
| 133 | ;; Internal variables used free | ||
| 134 | (defvar uniquify-non-file-buffer-names nil) | ||
| 135 | (defvar uniquify-possibly-resolvable nil) | ||
| 136 | |||
| 137 | ;;; Main entry point. | ||
| 138 | |||
| 139 | (defun uniquify-rationalize-file-buffer-names (&optional newbuffile newbuf) | ||
| 140 | "Makes file buffer names unique by adding segments from pathname. | ||
| 141 | If `uniquify-min-dir-content' > 0, always pulls that many | ||
| 142 | pathname elements. Arguments cause only a subset of buffers to be renamed." | ||
| 143 | (interactive) | ||
| 144 | (let (fix-list | ||
| 145 | uniquify-non-file-buffer-names | ||
| 146 | (depth uniquify-min-dir-content)) | ||
| 147 | (let ((buffers (buffer-list))) | ||
| 148 | (while buffers | ||
| 149 | (let* ((buffer (car buffers)) | ||
| 150 | (bfn (if (eq buffer newbuf) | ||
| 151 | (expand-file-name newbuffile) | ||
| 152 | (uniquify-buffer-file-name buffer))) | ||
| 153 | (rawname (and bfn (file-name-nondirectory bfn))) | ||
| 154 | (deserving (and rawname | ||
| 155 | (or (not newbuffile) | ||
| 156 | (equal rawname | ||
| 157 | (file-name-nondirectory newbuffile)))))) | ||
| 158 | (if deserving | ||
| 159 | (uniquify-push (list rawname bfn buffer nil) fix-list) | ||
| 160 | (uniquify-push (list (buffer-name buffer)) | ||
| 161 | uniquify-non-file-buffer-names))) | ||
| 162 | (setq buffers (cdr buffers)))) | ||
| 163 | ;; selects buffers whose names may need changing, and others that | ||
| 164 | ;; may conflict. | ||
| 165 | (setq fix-list | ||
| 166 | (sort fix-list 'uniquify-filename-sort)) | ||
| 167 | ;; bringing conflicting names together | ||
| 168 | (uniquify-rationalize-a-list fix-list depth) | ||
| 169 | (mapcar 'uniquify-unrationalized-buffer fix-list))) | ||
| 170 | |||
| 171 | (defun uniquify-buffer-file-name (buffer) | ||
| 172 | "Return name of file BUFFER is visiting, or nil if none. | ||
| 173 | Works on dired buffers as well as ordinary file-visiting buffers." | ||
| 174 | (or (buffer-file-name buffer) | ||
| 175 | (save-excursion | ||
| 176 | (set-buffer buffer) | ||
| 177 | list-buffers-directory))) | ||
| 178 | |||
| 179 | (defun uniquify-filename-sort (s1 s2) | ||
| 180 | (uniquify-filename-lessp | ||
| 181 | (uniquify-fix-list-filename s1) (uniquify-fix-list-filename s2))) | ||
| 182 | |||
| 183 | (defun uniquify-filename-lessp (s1 s2) | ||
| 184 | (let ((s1f (file-name-nondirectory s1)) | ||
| 185 | (s2f (file-name-nondirectory s2))) | ||
| 186 | (and (not (equal s2f "")) | ||
| 187 | (or (string-lessp s1f s2f) | ||
| 188 | (and (equal s1f s2f) | ||
| 189 | (let ((s1d (file-name-directory s1)) | ||
| 190 | (s2d (file-name-directory s2))) | ||
| 191 | (and (not (<= (length s2d) 1)) | ||
| 192 | (or (<= (length s1d) 1) | ||
| 193 | (uniquify-filename-lessp | ||
| 194 | (substring s1d 0 -1) | ||
| 195 | (substring s2d 0 -1)))))))))) | ||
| 196 | |||
| 197 | ;; Was named do-the-buffers-you-couldnt-rationalize | ||
| 198 | (defun uniquify-unrationalized-buffer (item) | ||
| 199 | (or (uniquify-cadddr item) nil)) ;maybe better in the future | ||
| 200 | |||
| 201 | (defun uniquify-rationalize-a-list (fix-list depth) | ||
| 202 | (let (conflicting-sublist | ||
| 203 | (old-name "") | ||
| 204 | proposed-name uniquify-possibly-resolvable) | ||
| 205 | (while fix-list | ||
| 206 | (let ((item (car fix-list))) | ||
| 207 | (setq proposed-name (uniquify-get-proposed-name item depth)) | ||
| 208 | (if (not (equal proposed-name old-name)) | ||
| 209 | (progn | ||
| 210 | (uniquify-rationalize-conflicting-sublist | ||
| 211 | conflicting-sublist old-name depth) | ||
| 212 | (setq conflicting-sublist nil))) | ||
| 213 | (uniquify-push item conflicting-sublist) | ||
| 214 | (setq old-name proposed-name)) | ||
| 215 | (setq fix-list (cdr fix-list))) | ||
| 216 | (uniquify-rationalize-conflicting-sublist | ||
| 217 | conflicting-sublist old-name depth))) | ||
| 218 | |||
| 219 | (defun uniquify-get-proposed-name (item depth) | ||
| 220 | (let (index | ||
| 221 | (extra-string "") | ||
| 222 | (n depth) | ||
| 223 | (base (uniquify-fix-list-base item)) | ||
| 224 | (fn (uniquify-fix-list-filename item))) | ||
| 225 | (while (and (> n 0) | ||
| 226 | (setq index (string-match | ||
| 227 | (concat "\\(^\\|/[^/]*\\)/" | ||
| 228 | (regexp-quote extra-string) | ||
| 229 | (regexp-quote base) | ||
| 230 | "\\'") | ||
| 231 | fn))) | ||
| 232 | (setq extra-string (substring fn | ||
| 233 | (if (zerop index) 0 (1+ index)) | ||
| 234 | ;; (- (length base)) fails for base = "". | ||
| 235 | ;; Equivalently, we could have used | ||
| 236 | ;; (apply 'substring ... | ||
| 237 | ;; (and (not (string= "" base)) | ||
| 238 | ;; (list (- (length base))))) | ||
| 239 | (- (length fn) (length base))) | ||
| 240 | n (1- n))) | ||
| 241 | (if (zerop n) (setq uniquify-possibly-resolvable t)) | ||
| 242 | |||
| 243 | |||
| 244 | ;; Distinguish directories by adding extra separator. | ||
| 245 | (if (and uniquify-trailing-separator-p | ||
| 246 | (file-directory-p fn) | ||
| 247 | (not (string-equal base ""))) | ||
| 248 | (cond ((eq uniquify-buffer-name-style 'forward) | ||
| 249 | (setq base (concat base "/"))) | ||
| 250 | ((eq uniquify-buffer-name-style 'reverse) | ||
| 251 | (setq base (concat (or uniquify-separator "\\") base))))) | ||
| 252 | |||
| 253 | ;; Trim trailing separator on directory part | ||
| 254 | (if (and (not (string-equal extra-string "")) | ||
| 255 | (or (eq uniquify-buffer-name-style 'post-forward) | ||
| 256 | (eq uniquify-buffer-name-style 'post-forward-angle-brackets))) | ||
| 257 | (setq extra-string (substring extra-string 0 | ||
| 258 | (- (length extra-string) 1)))) | ||
| 259 | |||
| 260 | (cond ((string-equal extra-string "") | ||
| 261 | base) | ||
| 262 | ((string-equal base "") | ||
| 263 | extra-string) | ||
| 264 | ((eq uniquify-buffer-name-style 'forward) | ||
| 265 | (concat extra-string base)) | ||
| 266 | ((eq uniquify-buffer-name-style 'reverse) | ||
| 267 | (concat base (uniquify-reverse-components extra-string))) | ||
| 268 | ((eq uniquify-buffer-name-style 'post-forward) | ||
| 269 | (concat base (or uniquify-separator "|") extra-string)) | ||
| 270 | ((eq uniquify-buffer-name-style 'post-forward-angle-brackets) | ||
| 271 | (concat base "<" extra-string ">")) | ||
| 272 | (t (error "Bad value for uniquify-buffer-name-style: %s" | ||
| 273 | uniquify-buffer-name-style))))) | ||
| 274 | |||
| 275 | |||
| 276 | ;; Deal with conflicting-sublist, which is set by uniquify-rationalize-a-list. | ||
| 277 | ;; This is only called by uniquify-rationalize-a-list. | ||
| 278 | (defun uniquify-rationalize-conflicting-sublist (conflicting-sublist old-name depth) | ||
| 279 | (or (null conflicting-sublist) | ||
| 280 | (and (null (cdr conflicting-sublist)) | ||
| 281 | (not (assoc old-name uniquify-non-file-buffer-names)) | ||
| 282 | (or (and (not (string= old-name "")) | ||
| 283 | (uniquify-rename-buffer (car conflicting-sublist) old-name)) | ||
| 284 | t)) | ||
| 285 | (if uniquify-possibly-resolvable | ||
| 286 | (uniquify-rationalize-a-list conflicting-sublist (1+ depth))))) | ||
| 287 | |||
| 288 | (defun uniquify-rename-buffer (item newname) | ||
| 289 | (let ((buffer (uniquify-fix-list-buffer item))) | ||
| 290 | (if (not (equal newname (buffer-name buffer))) | ||
| 291 | (let ((unset (current-buffer)) | ||
| 292 | ;; avoid hooks on rename-buffer | ||
| 293 | (uniquify-buffer-name-style nil)) | ||
| 294 | (set-buffer buffer) | ||
| 295 | (rename-buffer newname) | ||
| 296 | (set-buffer unset)))) | ||
| 297 | (rplaca (nthcdr 3 item) t)) | ||
| 298 | |||
| 299 | (defun uniquify-reverse-components (instring) | ||
| 300 | (let ((sofar '()) | ||
| 301 | (cursor 0) | ||
| 302 | (len (length instring)) | ||
| 303 | (sep (or uniquify-separator "\\"))) | ||
| 304 | (while (< cursor len) | ||
| 305 | (if (= (aref instring cursor) ?/) | ||
| 306 | (setq sofar (cons sep sofar) | ||
| 307 | cursor (1+ cursor)) | ||
| 308 | (let ((first-slash (or (string-match "/" instring cursor) len))) | ||
| 309 | (setq sofar (cons (substring instring cursor first-slash) sofar) | ||
| 310 | cursor first-slash)))) | ||
| 311 | (apply (function concat) sofar))) | ||
| 312 | |||
| 313 | |||
| 314 | ;;; Hooks from the rest of Emacs | ||
| 315 | |||
| 316 | ;; Emacs 19 (GNU Emacs or XEmacs) | ||
| 317 | |||
| 318 | ;; The logical place to put all this code is in generate-new-buffer-name. | ||
| 319 | ;; It's written in C, so we would add a generate-new-buffer-name-function | ||
| 320 | ;; which, if non-nil, would be called instead of the C. One problem with | ||
| 321 | ;; that is that generate-new-buffer-name takes a potential buffer name as | ||
| 322 | ;; its argument -- not other information, such as what file the buffer will | ||
| 323 | ;; visit. | ||
| 324 | |||
| 325 | ;; The below solution works because generate-new-buffer-name is called | ||
| 326 | ;; only by rename-buffer (which, as of 19.29, is never called from C) and | ||
| 327 | ;; generate-new-buffer, which is called only by Lisp functions | ||
| 328 | ;; create-file-buffer and rename-uniquely. Rename-uniquely generally | ||
| 329 | ;; isn't used for buffers visiting files, so it's sufficient to hook | ||
| 330 | ;; rename-buffer and create-file-buffer. (Setting find-file-hooks isn't | ||
| 331 | ;; sufficient.) | ||
| 332 | |||
| 333 | (defadvice rename-buffer (after rename-buffer-uniquify activate) | ||
| 334 | "Uniquify buffer names with parts of directory name." | ||
| 335 | (if (and uniquify-buffer-name-style | ||
| 336 | ;; UNIQUE argument | ||
| 337 | (ad-get-arg 1)) | ||
| 338 | (progn | ||
| 339 | (if uniquify-after-kill-buffer-p | ||
| 340 | ;; call with no argument; rationalize vs. old name as well as new | ||
| 341 | (uniquify-rationalize-file-buffer-names) | ||
| 342 | ;; call with argument: rationalize vs. new name only | ||
| 343 | (uniquify-rationalize-file-buffer-names | ||
| 344 | (uniquify-buffer-file-name (current-buffer)) (current-buffer))) | ||
| 345 | (setq ad-return-value (buffer-name (current-buffer)))))) | ||
| 346 | |||
| 347 | (defadvice create-file-buffer (after create-file-buffer-uniquify activate) | ||
| 348 | "Uniquify buffer names with parts of directory name." | ||
| 349 | (if uniquify-buffer-name-style | ||
| 350 | (uniquify-rationalize-file-buffer-names (ad-get-arg 0) ad-return-value))) | ||
| 351 | |||
| 352 | ;; Buffer deletion | ||
| 353 | ;; Rerationalize after a buffer is killed, to reduce coinciding buffer names. | ||
| 354 | ;; This mechanism uses `kill-buffer-hook', which runs *before* deletion. | ||
| 355 | ;; That means that the kill-buffer-hook function cannot just delete the | ||
| 356 | ;; buffer -- it has to set something to do the rationalization *later*. | ||
| 357 | ;; It actually puts another function on `post-command-hook'. This other | ||
| 358 | ;; function runs the rationalization and then removes itself from the hook. | ||
| 359 | ;; Is there a better way to accomplish this? | ||
| 360 | ;; (This ought to set some global variables so the work is done only for | ||
| 361 | ;; buffers with names similar to the deleted buffer. -MDE) | ||
| 362 | |||
| 363 | (defun delay-uniquify-rationalize-file-buffer-names () | ||
| 364 | "Add `delayed-uniquify-rationalize-file-buffer-names' to `post-command-hook'. | ||
| 365 | For use on, eg, `kill-buffer-hook', to rationalize *after* buffer deletion." | ||
| 366 | (if (and uniquify-buffer-name-style | ||
| 367 | uniquify-after-kill-buffer-p) | ||
| 368 | (add-hook 'post-command-hook | ||
| 369 | 'delayed-uniquify-rationalize-file-buffer-names))) | ||
| 370 | |||
| 371 | (defun delayed-uniquify-rationalize-file-buffer-names () | ||
| 372 | "Rerationalize buffer names and remove self from `post-command-hook'. | ||
| 373 | See also `delay-rationalize-file-buffer-names' for hook setter." | ||
| 374 | (uniquify-rationalize-file-buffer-names) | ||
| 375 | (remove-hook 'post-command-hook | ||
| 376 | 'delayed-uniquify-rationalize-file-buffer-names)) | ||
| 377 | |||
| 378 | (add-hook 'kill-buffer-hook 'delay-uniquify-rationalize-file-buffer-names) | ||
| 379 | |||
| 380 | ;;; uniquify.el ends here | ||
| 381 | |||