bookmark.el 76.4 KB
Newer Older
1
;;; bookmark.el --- set bookmarks, maybe annotate them, jump to them later
Richard M. Stallman's avatar
Richard M. Stallman committed
2

3
;; Copyright (C) 1993, 1994, 1995, 1996, 1997, 2001, 2003 Free Software Foundation
Richard M. Stallman's avatar
Richard M. Stallman committed
4

5 6
;; Author: Karl Fogel <kfogel@red-bean.com>
;; Maintainer: Karl Fogel <kfogel@red-bean.com>
Richard M. Stallman's avatar
Richard M. Stallman committed
7
;; Created: July, 1993
8
;; Keywords: bookmarks, placeholders, annotations
Richard M. Stallman's avatar
Richard M. Stallman committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22

;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
Erik Naggum's avatar
Erik Naggum committed
23 24 25 26 27 28 29 30 31 32
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This package is for setting "bookmarks" in files.  A bookmark
;; associates a string with a location in a certain file.  Thus, you
;; can navigate your way to that location by providing the string.
;; See the "User Variables" section for customizations.
Richard M. Stallman's avatar
Richard M. Stallman committed
33 34 35

;; Thanks to David Bremner <bremner@cs.sfu.ca> for thinking of and
;; then implementing the bookmark-current-bookmark idea.  He even
Karl Fogel's avatar
Karl Fogel committed
36
;; sent *patches*, bless his soul...
Richard M. Stallman's avatar
Richard M. Stallman committed
37 38 39 40

;; Thanks to Gregory M. Saunders <saunders@cis.ohio-state.edu> for
;; fixing and improving bookmark-time-to-save-p.

41 42
;; Thanks go to Andrew V. Klein <avk@cig.mot.com> for the code that
;; sorts the alist before presenting it to the user (in bookmark-bmenu-list
Karl Fogel's avatar
Karl Fogel committed
43 44
;; and the menu-bar).

45 46
;; And much thanks to David Hughes <djh@harston.cv.com> for many small
;; suggestions and the code to implement them (like
47
;; bookmark-bmenu-check-position, and some of the Lucid compatibility
48 49
;; stuff).

50
;; Kudos (whatever they are) go to Jim Blandy <jimb@red-bean.com>
Karl Fogel's avatar
Karl Fogel committed
51 52 53 54
;; for his eminently sensible suggestion to separate bookmark-jump
;; into bookmark-jump and bookmark-jump-noselect, which made many
;; other things cleaner as well.

55 56 57
;; Thanks to Roland McGrath for encouragement and help with defining
;; autoloads on the menu-bar.

Karl Heuer's avatar
Karl Heuer committed
58
;; Jonathan Stigelman <stig@hackvan.com> gave patches for default
Karl Fogel's avatar
Karl Fogel committed
59 60 61 62
;; values in bookmark-jump and bookmark-set.  Everybody please keep
;; all the keystrokes they save thereby and send them to him at the
;; end of each year :-)  (No, seriously, thanks Jonathan!)

63 64 65
;; Buckets of gratitude to John Grabowski <johng@media.mit.edu> for
;; thinking up the annotations feature and implementing it so well.

Richard M. Stallman's avatar
Richard M. Stallman committed
66 67 68
;; Based on info-bookmark.el, by Karl Fogel and Ken Olstad
;; <olstad@msc.edu>.

69 70 71
;; Thanks to Mikio Nakajima <PBC01764@niftyserve.or.jp> for many bugs
;; reported and fixed.

72 73
;; Thank you, Michael Kifer, for contributing the XEmacs support.

74
;; Enough with the credits already, get on to the good stuff:
Richard M. Stallman's avatar
Richard M. Stallman committed
75

Sam Steingold's avatar
Sam Steingold committed
76
;; FAVORITE CHINESE RESTAURANT:
Richard M. Stallman's avatar
Richard M. Stallman committed
77 78
;; Boy, that's a tough one.  Probably Hong Min, or maybe Emperor's
;; Choice (both in Chicago's Chinatown).  Well, both.  How about you?
79

80
;;; Code:
Richard M. Stallman's avatar
Richard M. Stallman committed
81

Erik Naggum's avatar
Erik Naggum committed
82 83
(require 'pp)

84
;;; Misc comments:
Richard M. Stallman's avatar
Richard M. Stallman committed
85
;;
86
;; If variable bookmark-use-annotations is non-nil, an annotation is
Sam Steingold's avatar
Sam Steingold committed
87
;; queried for when setting a bookmark.
Richard M. Stallman's avatar
Richard M. Stallman committed
88
;;
89 90 91 92
;; The bookmark list is sorted lexically by default, but you can turn
;; this off by setting bookmark-sort-flag to nil.  If it is nil, then
;; the list will be presented in the order it is recorded
;; (chronologically), which is actually fairly useful as well.
Richard M. Stallman's avatar
Richard M. Stallman committed
93

94 95
;;; User Variables

96
(defgroup bookmark nil
97
  "Setting, annotation and jumping to bookmarks."
98 99 100 101
  :group 'matching)


(defcustom bookmark-use-annotations nil
102
  "*If non-nil, saving a bookmark queries for an annotation in a buffer."
103 104
  :type 'boolean
  :group 'bookmark)
105 106


107
(defcustom bookmark-save-flag t
108
  "*Controls when Emacs saves bookmarks to a file.
109
--> nil means never save bookmarks, except when `bookmark-save' is
110 111
    explicitly called \(\\[bookmark-save]\).
--> t means save bookmarks when Emacs is killed.
112
--> Otherwise, it should be a number that is the frequency with which
113 114 115 116 117 118 119 120 121 122
    the bookmark list is saved \(i.e.: the number of times which
    Emacs' bookmark list may be modified before it is automatically
    saved.\).  If it is a number, Emacs will also automatically save
    bookmarks when it is killed.

Therefore, the way to get it to save every time you make or delete a
bookmark is to set this variable to 1 \(or 0, which produces the same
behavior.\)

To specify the file in which to save them, modify the variable
123
`bookmark-default-file', which is `~/.emacs.bmk' by default."
124
  :type '(choice (const nil) integer (other t))
125
  :group 'bookmark)
126 127 128


(defconst bookmark-old-default-file "~/.emacs-bkmrks"
129
  "*The `.emacs.bmk' file used to be called this name.")
130 131 132 133 134 135


;; defvarred to avoid a compilation warning:
(defvar bookmark-file nil
  "Old name for `bookmark-default-file'.")

136
(defcustom bookmark-default-file
137 138 139
  (if bookmark-file
      ;; In case user set `bookmark-file' in her .emacs:
      bookmark-file
140
    (convert-standard-filename "~/.emacs.bmk"))
141 142 143
  "*File in which to save bookmarks by default."
  :type 'file
  :group 'bookmark)
144 145


146
(defcustom bookmark-version-control 'nospecial
147 148 149 150
  "*Whether or not to make numbered backups of the bookmark file.
It can have four values: t, nil, `never', and `nospecial'.
The first three have the same meaning that they do for the
variable `version-control', and the final value `nospecial' means just
151
use the value of `version-control'."
152 153
  :type '(choice (const nil) (const never) (const nospecial)
		 (other t))
154
  :group 'bookmark)
155 156


157 158 159 160
(defcustom bookmark-completion-ignore-case t
  "*Non-nil means bookmark functions ignore case in completion."
  :type 'boolean
  :group 'bookmark)
161 162


163
(defcustom bookmark-sort-flag t
164 165
  "*Non-nil means that bookmarks will be displayed sorted by bookmark name.
Otherwise they will be displayed in LIFO order (that is, most
166 167 168
recently set ones come first, oldest ones come last)."
  :type 'boolean
  :group 'bookmark)
169 170


171
(defcustom bookmark-automatically-show-annotations t
Pavel Janík's avatar
Pavel Janík committed
172
  "*nil means don't show annotations when jumping to a bookmark."
173 174
  :type 'boolean
  :group 'bookmark)
175 176


177
(defcustom bookmark-bmenu-file-column 30
178
  "*Column at which to display filenames in a buffer listing bookmarks.
179 180 181
You can toggle whether files are shown with \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-toggle-filenames]."
  :type 'integer
  :group 'bookmark)
182 183


184
(defcustom bookmark-bmenu-toggle-filenames t
185 186
  "*Non-nil means show filenames when listing bookmarks.
This may result in truncated bookmark names.  To disable this, put the
187
following in your `.emacs' file:
188

189 190 191
\(setq bookmark-bmenu-toggle-filenames nil\)"
  :type 'boolean
  :group 'bookmark)
192 193


194 195 196
(defcustom bookmark-menu-length 70
  "*Maximum length of a bookmark name displayed on a popup menu."
  :type 'integer
197
  :group 'bookmark)
198 199 200


;;; No user-serviceable parts beyond this point.
Richard M. Stallman's avatar
Richard M. Stallman committed
201

202 203 204 205 206
;; Is it XEmacs?
(defconst bookmark-xemacsp
  (string-match "\\(Lucid\\|Xemacs\\)" emacs-version))


Richard M. Stallman's avatar
Richard M. Stallman committed
207 208 209
;; Added  for lucid emacs  compatibility, db
(or (fboundp 'defalias)  (fset 'defalias 'fset))

210
;; suggested for lucid compatibility by david hughes:
211
(or (fboundp 'frame-height)  (defalias 'frame-height 'screen-height))
212

213 214
;; This variable is probably obsolete now...
(or (boundp 'baud-rate)
Sam Steingold's avatar
Sam Steingold committed
215
    ;; some random value higher than 9600
216 217
    (setq baud-rate 19200))

218 219 220


;;; Keymap stuff:
Richard M. Stallman's avatar
Richard M. Stallman committed
221

222 223 224 225 226 227
;; Set up these bindings dumping time *only*;
;; if the user alters them, don't override the user when loading bookmark.el.

;;;###autoload (define-key ctl-x-map "rb" 'bookmark-jump)
;;;###autoload (define-key ctl-x-map "rm" 'bookmark-set)
;;;###autoload (define-key ctl-x-map "rl" 'bookmark-bmenu-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
228

229
;;;###autoload
Richard M. Stallman's avatar
Richard M. Stallman committed
230 231 232 233 234
(defvar bookmark-map nil
  "Keymap containing bindings to bookmark functions.
It is not bound to any key by default: to bind it
so that you have a bookmark prefix, just use `global-set-key' and bind a
key of your choice to `bookmark-map'.  All interactive bookmark
Richard M. Stallman's avatar
Richard M. Stallman committed
235 236
functions have a binding in this keymap.")

237
;;;###autoload (define-prefix-command 'bookmark-map)
Richard M. Stallman's avatar
Richard M. Stallman committed
238 239

;; Read the help on all of these functions for details...
240 241 242 243 244 245 246 247 248 249 250 251
;;;###autoload (define-key bookmark-map "x" 'bookmark-set)
;;;###autoload (define-key bookmark-map "m" 'bookmark-set) ; "m" for "mark"
;;;###autoload (define-key bookmark-map "j" 'bookmark-jump)
;;;###autoload (define-key bookmark-map "g" 'bookmark-jump) ; "g" for "go"
;;;###autoload (define-key bookmark-map "i" 'bookmark-insert)
;;;###autoload (define-key bookmark-map "e" 'edit-bookmarks)
;;;###autoload (define-key bookmark-map "f" 'bookmark-insert-location) ; "f" for "find"
;;;###autoload (define-key bookmark-map "r" 'bookmark-rename)
;;;###autoload (define-key bookmark-map "d" 'bookmark-delete)
;;;###autoload (define-key bookmark-map "l" 'bookmark-load)
;;;###autoload (define-key bookmark-map "w" 'bookmark-write)
;;;###autoload (define-key bookmark-map "s" 'bookmark-save)
Richard M. Stallman's avatar
Richard M. Stallman committed
252

253 254 255 256 257 258 259 260 261 262 263

;;; The annotation maps.
(defvar bookmark-read-annotation-mode-map (copy-keymap text-mode-map)
  "Keymap for composing an annotation for a bookmark.")

(define-key bookmark-read-annotation-mode-map "\C-c\C-c"
  'bookmark-send-annotation)



;;; Core variables and data structures:
264
(defvar bookmark-alist ()
265
  "Association list of bookmarks and their records.
266
You probably don't want to change the value of this alist yourself;
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
instead, let the various bookmark functions do it for you.

The format of the alist is

       \(BOOKMARK1 BOOKMARK2 ...\)

where each BOOKMARK is of the form

\(NAME
  \(filename . FILE\)
  \(front-context-string . FRONT-STR\)
  \(rear-context-string  . REAR-STR\)
  \(position . POS\)
  \(info-node . POS\)
  \(annotation . ANNOTATION\)\)

So the cdr of each bookmark is an alist too.
`info-node' is optional, by the way.")
285

286

287 288
(defvar bookmarks-already-loaded nil)

289

Richard M. Stallman's avatar
Richard M. Stallman committed
290
;; more stuff added by db.
291

Sam Steingold's avatar
Sam Steingold committed
292
(defvar bookmark-current-bookmark nil
Richard M. Stallman's avatar
Richard M. Stallman committed
293 294
  "Name of bookmark most recently used in the current file.
It is buffer local, used to make moving a bookmark forward
Richard M. Stallman's avatar
Richard M. Stallman committed
295
through a file easier.")
Richard M. Stallman's avatar
Richard M. Stallman committed
296 297 298

(make-variable-buffer-local 'bookmark-current-bookmark)

299

Richard M. Stallman's avatar
Richard M. Stallman committed
300
(defvar bookmark-alist-modification-count 0
Richard M. Stallman's avatar
Richard M. Stallman committed
301
  "Number of modifications to bookmark list since it was last saved.")
Richard M. Stallman's avatar
Richard M. Stallman committed
302

303 304

(defvar bookmark-search-size 16
Richard M. Stallman's avatar
Richard M. Stallman committed
305
  "Length of the context strings recorded on either side of a bookmark.")
Richard M. Stallman's avatar
Richard M. Stallman committed
306

307

Richard M. Stallman's avatar
Richard M. Stallman committed
308 309 310 311
(defvar bookmark-current-point 0)
(defvar bookmark-yank-point 0)
(defvar bookmark-current-buffer nil)

312 313 314 315 316 317 318 319 320


;; Helper functions.

;; Only functions on this page and the next one (file formats) need to
;; know anything about the format of bookmark-alist entries.
;; Everyone else should go through them.

(defun bookmark-name-from-full-record (full-record)
321
  "Return name of FULL-RECORD \(an alist element instead of a string\)."
322 323 324 325 326 327 328 329 330 331 332 333 334
  (car full-record))


(defun bookmark-all-names ()
  "Return a list of all current bookmark names."
  (bookmark-maybe-load-default-file)
  (mapcar
   (lambda (full-record)
     (bookmark-name-from-full-record full-record))
   bookmark-alist))


(defun bookmark-get-bookmark (bookmark)
335 336 337 338 339 340 341
  "Return the full entry for BOOKMARK in bookmark-alist.
If BOOKMARK is not a string, return nil."
  (when (stringp bookmark)
    (apply (if bookmark-completion-ignore-case
	       #'assoc-ignore-case
	     #'assoc)
	   (list bookmark bookmark-alist))))
342 343 344 345 346 347 348 349 350 351


(defun bookmark-get-bookmark-record (bookmark)
  "Return the guts of the entry for BOOKMARK in bookmark-alist.
That is, all information but the name."
  (car (cdr (bookmark-get-bookmark bookmark))))


(defun bookmark-set-name (bookmark newname)
  "Set BOOKMARK's name to NEWNAME."
352 353 354
  (setcar
   (if (stringp bookmark) (bookmark-get-bookmark bookmark) bookmark)
   newname))
355 356 357 358 359 360 361 362


(defun bookmark-get-annotation (bookmark)
  "Return the annotation of BOOKMARK, or nil if none."
  (cdr (assq 'annotation (bookmark-get-bookmark-record bookmark))))


(defun bookmark-set-annotation (bookmark ann)
363
  "Set the annotation of BOOKMARK to ANN."
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
  (let ((cell (assq 'annotation (bookmark-get-bookmark-record bookmark))))
    (if cell
        (setcdr cell ann)
      (nconc (bookmark-get-bookmark-record bookmark)
             (list (cons 'annotation ann))))))


(defun bookmark-get-filename (bookmark)
  "Return the full filename of BOOKMARK."
  (cdr (assq 'filename (bookmark-get-bookmark-record bookmark))))


(defun bookmark-set-filename (bookmark filename)
  "Set the full filename of BOOKMARK to FILENAME."
  (let ((cell (assq 'filename (bookmark-get-bookmark-record bookmark))))
    (if cell
        (setcdr cell filename)
      (nconc (bookmark-get-bookmark-record bookmark)
             (list (cons 'filename filename))))))


(defun bookmark-get-position (bookmark)
  "Return the position \(i.e.: point\) of BOOKMARK."
  (cdr (assq 'position (bookmark-get-bookmark-record bookmark))))


(defun bookmark-set-position (bookmark position)
  "Set the position \(i.e.: point\) of BOOKMARK to POSITION."
  (let ((cell (assq 'position (bookmark-get-bookmark-record bookmark))))
    (if cell
        (setcdr cell position)
      (nconc (bookmark-get-bookmark-record bookmark)
             (list (cons 'position position))))))


(defun bookmark-get-front-context-string (bookmark)
  "Return the front-context-string of BOOKMARK."
  (cdr (assq 'front-context-string (bookmark-get-bookmark-record bookmark))))


(defun bookmark-set-front-context-string (bookmark string)
  "Set the front-context-string of BOOKMARK to STRING."
  (let ((cell (assq 'front-context-string
                    (bookmark-get-bookmark-record bookmark))))
    (if cell
        (setcdr cell string)
      (nconc (bookmark-get-bookmark-record bookmark)
             (list (cons 'front-context-string string))))))


(defun bookmark-get-rear-context-string (bookmark)
  "Return the rear-context-string of BOOKMARK."
  (cdr (assq 'rear-context-string (bookmark-get-bookmark-record bookmark))))


(defun bookmark-set-rear-context-string (bookmark string)
  "Set the rear-context-string of BOOKMARK to STRING."
  (let ((cell (assq 'rear-context-string
                    (bookmark-get-bookmark-record bookmark))))
    (if cell
        (setcdr cell string)
      (nconc (bookmark-get-bookmark-record bookmark)
             (list (cons 'rear-context-string string))))))


(defun bookmark-get-info-node (bookmark)
430
  "Get the info node associated with BOOKMARK."
431
  (cdr (assq 'info-node (bookmark-get-bookmark-record bookmark))))
Sam Steingold's avatar
Sam Steingold committed
432

433 434 435 436 437 438 439 440

(defun bookmark-set-info-node (bookmark node)
  "Set the Info node of BOOKMARK to NODE."
  (let ((cell (assq 'info-node
                    (bookmark-get-bookmark-record bookmark))))
    (if cell
        (setcdr cell node)
      (nconc (bookmark-get-bookmark-record bookmark)
441 442 443 444 445
             (list (cons 'info-node node)))))

  (message "%S" (assq 'info-node (bookmark-get-bookmark-record bookmark)))
  (sit-for 4)
  )
Sam Steingold's avatar
Sam Steingold committed
446

447

448 449 450 451
(defvar bookmark-history nil
  "The history list for bookmark functions.")


452 453 454 455 456 457 458
(defun bookmark-completing-read (prompt &optional default)
  "Prompting with PROMPT, read a bookmark name in completion.
PROMPT will get a \": \" stuck on the end no matter what, so you
probably don't want to include one yourself.
Optional second arg DEFAULT is a string to return if the user enters
the empty string."
  (bookmark-maybe-load-default-file) ; paranoia
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
  (if (listp last-nonmenu-event)
      (bookmark-menu-popup-paned-menu t prompt (bookmark-all-names))
    (let* ((completion-ignore-case bookmark-completion-ignore-case)
	   (default default)
	   (prompt (if default
		       (concat prompt (format " (%s): " default))
		     (concat prompt ": ")))
	   (str
	    (completing-read prompt
			     bookmark-alist
			     nil
			     0
			     nil
			     'bookmark-history)))
      (if (string-equal "" str) default str))))
474 475


476 477 478 479
(defmacro bookmark-maybe-historicize-string (string)
  "Put STRING into the bookmark prompt history, if caller non-interactive.
We need this because sometimes bookmark functions are invoked from
menus, so `completing-read' never gets a chance to set `bookmark-history'."
Sam Steingold's avatar
Sam Steingold committed
480 481 482
  `(or
    (interactive-p)
    (setq bookmark-history (cons ,string bookmark-history))))
483 484


485
(defun bookmark-make (name &optional annotation overwrite info-node)
486 487 488
  "Make a bookmark named NAME.
Optional second arg ANNOTATION gives it an annotation.
Optional third arg OVERWRITE means replace any existing bookmarks with
489 490 491
this name.
Optional fourth arg INFO-NODE means this bookmark is at info node
INFO-NODE, so record this fact in the bookmark's entry."
492
  (bookmark-maybe-load-default-file)
493
  (let ((stripped-name (copy-sequence name)))
494 495 496 497
    (or bookmark-xemacsp
        ;; XEmacs's `set-text-properties' doesn't work on
        ;; free-standing strings, apparently.
        (set-text-properties 0 (length stripped-name) nil stripped-name))
498
    (if (and (bookmark-get-bookmark stripped-name) (not overwrite))
499
        ;; already existing bookmark under that name and
500 501
        ;; no prefix arg means just overwrite old bookmark
        (setcdr (bookmark-get-bookmark stripped-name)
502
                (list (bookmark-make-cell annotation info-node)))
Sam Steingold's avatar
Sam Steingold committed
503

504 505 506
      ;; otherwise just cons it onto the front (either the bookmark
      ;; doesn't exist already, or there is no prefix arg.  In either
      ;; case, we want the new bookmark consed onto the alist...)
Sam Steingold's avatar
Sam Steingold committed
507

508 509
      (setq bookmark-alist
            (cons
Sam Steingold's avatar
Sam Steingold committed
510
             (list stripped-name
511
                   (bookmark-make-cell annotation info-node))
512
             bookmark-alist)))
Sam Steingold's avatar
Sam Steingold committed
513

514 515 516 517 518 519
    ;; Added by db
    (setq bookmark-current-bookmark stripped-name)
    (setq bookmark-alist-modification-count
          (1+ bookmark-alist-modification-count))
    (if (bookmark-time-to-save-p)
        (bookmark-save))))
520 521


522
(defun bookmark-make-cell (annotation &optional info-node)
523
  "Return the record part of a new bookmark, given ANNOTATION.
524
Must be at the correct position in the buffer in which the bookmark is
525 526 527 528
being set.  This might change someday.
Optional second arg INFO-NODE means this bookmark is at info node
INFO-NODE, so record this fact in the bookmark's entry."
  (let ((the-record
529 530 531 532 533 534 535 536 537 538 539 540 541 542
         `((filename . ,(bookmark-buffer-file-name))
           (front-context-string
            . ,(if (>= (- (point-max) (point)) bookmark-search-size)
                   (buffer-substring-no-properties
                    (point)
                    (+ (point) bookmark-search-size))
                   nil))
           (rear-context-string
            . ,(if (>= (- (point) (point-min)) bookmark-search-size)
                   (buffer-substring-no-properties
                    (point)
                    (- (point) bookmark-search-size))
                   nil))
           (position . ,(point)))))
543 544

    ;; Now fill in the optional parts:
545 546 547 548 549

    ;; Take no chances with text properties
    (set-text-properties 0 (length annotation) nil annotation)
    (set-text-properties 0 (length info-node) nil info-node)

550 551 552 553 554 555 556
    (if annotation
        (nconc the-record (list (cons 'annotation annotation))))
    (if info-node
        (nconc the-record (list (cons 'info-node info-node))))

    ;; Finally, return the completed record.
    the-record))
Sam Steingold's avatar
Sam Steingold committed
557 558


559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589

;;; File format stuff

;; The OLD format of the bookmark-alist was:
;;
;;       ((bookmark-name (filename
;;                        string-in-front
;;                        string-behind
;;                        point))
;;        ...)
;;
;; The NEW format of the bookmark-alist is:
;;
;;       ((bookmark-name ((filename . FILENAME)
;;                        (front-context-string . string-in-front)
;;                        (rear-context-string  . string-behind)
;;                        (position . POINT)
;;                        (annotation . annotation)
;;                        (whatever   . VALUE)
;;                        ...
;;                        ))
;;        ...)
;;
;;
;; I switched to using an internal as well as external alist because I
;; felt that would be a more flexible framework in which to add
;; features.  It means that the order in which values appear doesn't
;; matter, and it means that arbitrary values can be added without
;; risk of interfering with existing ones.
;;
;; BOOKMARK-NAME is the string the user gives the bookmark and
Sam Steingold's avatar
Sam Steingold committed
590
;; accesses it by from then on.
591 592 593 594 595 596
;;
;; FILENAME is the location of the file in which the bookmark is set.
;;
;; STRING-IN-FRONT is a string of `bookmark-search-size' chars of
;; context in front of the point at which the bookmark is set.
;;
Sam Steingold's avatar
Sam Steingold committed
597
;; STRING-BEHIND is the same thing, but after the point.
598 599
;;
;; The context strings exist so that modifications to a file don't
Sam Steingold's avatar
Sam Steingold committed
600
;; necessarily cause a bookmark's position to be invalidated.
601 602 603
;; bookmark-jump will search for STRING-BEHIND and STRING-IN-FRONT in
;; case the file has changed since the bookmark was set.  It will
;; attempt to place the user before the changes, if there were any.
604
;; ANNOTATION is the annotation for the bookmark; it may not exist
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
;; (for backward compatibility), be nil (no annotation), or be a
;; string.


(defconst bookmark-file-format-version 1
  "The current version of the format used by bookmark files.
You should never need to change this.")


(defconst bookmark-end-of-version-stamp-marker
  "-*- End Of Bookmark File Format Version Stamp -*-\n"
  "This string marks the end of the version stamp in a bookmark file.")


(defun bookmark-alist-from-buffer ()
  "Return a bookmark-alist (in any format) from the current buffer.
The buffer must of course contain bookmark format information.
Does not care from where in the buffer it is called, and does not
affect point."
  (save-excursion
    (goto-char (point-min))
    (if (search-forward bookmark-end-of-version-stamp-marker nil t)
        (read (current-buffer))
      ;; Else we're dealing with format version 0
      (if (search-forward "(" nil t)
          (progn
            (forward-char -1)
            (read (current-buffer)))
        ;; Else no hope of getting information here.
634
        (error "Not bookmark format")))))
635 636 637


(defun bookmark-upgrade-version-0-alist (old-list)
638
  "Upgrade a version 0 alist OLD-LIST to the current version."
639 640 641 642 643 644 645 646 647 648 649
  (mapcar
   (lambda (bookmark)
     (let* ((name      (car bookmark))
            (record    (car (cdr bookmark)))
            (filename  (nth 0 record))
            (front-str (nth 1 record))
            (rear-str  (nth 2 record))
            (position  (nth 3 record))
            (ann       (nth 4 record)))
       (list
        name
650 651 652 653 654
        `((filename             .    ,filename)
          (front-context-string .    ,(or front-str ""))
          (rear-context-string  .    ,(or rear-str  ""))
          (position             .    ,position)
          (annotation           .    ,ann)))))
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
   old-list))


(defun bookmark-upgrade-file-format-from-0 ()
  "Upgrade a bookmark file of format 0 (the original format) to format 1.
This expects to be called from point-min in a bookmark file."
  (message "Upgrading bookmark format from 0 to %d..."
           bookmark-file-format-version)
  (let* ((old-list (bookmark-alist-from-buffer))
         (new-list (bookmark-upgrade-version-0-alist old-list)))
    (delete-region (point-min) (point-max))
    (bookmark-insert-file-format-version-stamp)
    (pp new-list (current-buffer))
    (save-buffer))
  (goto-char (point-min))
670
  (message "Upgrading bookmark format from 0 to %d...done"
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
           bookmark-file-format-version)
  )


(defun bookmark-grok-file-format-version ()
  "Return an integer which is the file-format version of this bookmark file.
This expects to be called from point-min in a bookmark file."
  (if (looking-at "^;;;;")
      (save-excursion
        (save-match-data
          (re-search-forward "[0-9]")
          (forward-char -1)
          (read (current-buffer))))
    ;; Else this is format version 0, the original one, which didn't
    ;; even have version stamps.
    0))


(defun bookmark-maybe-upgrade-file-format ()
  "Check the file-format version of this bookmark file.
If the version is not up-to-date, upgrade it automatically.
This expects to be called from point-min in a bookmark file."
  (let ((version (bookmark-grok-file-format-version)))
    (cond
     ((= version bookmark-file-format-version)
      ) ; home free -- version is current
     ((= version 0)
      (bookmark-upgrade-file-format-from-0))
     (t
700
      (error "Bookmark file format version strangeness")))))
701 702 703


(defun bookmark-insert-file-format-version-stamp ()
704
  "Insert text indicating current version of bookmark file format."
705 706 707 708 709 710 711 712 713 714 715 716 717 718
  (insert
   (format ";;;; Emacs Bookmark Format Version %d ;;;;\n"
           bookmark-file-format-version))
  (insert ";;; This format is meant to be slightly human-readable;\n"
          ";;; nevertheless, you probably don't want to edit it.\n"
          ";;; "
          bookmark-end-of-version-stamp-marker))


;;; end file-format stuff


;;; Core code:

719
;;;###autoload
720 721 722
(defun bookmark-set (&optional name parg)
  "Set a bookmark named NAME inside a file.
If name is nil, then the user will be prompted.
Richard M. Stallman's avatar
Richard M. Stallman committed
723 724 725 726 727 728
With prefix arg, will not overwrite a bookmark that has the same name
as NAME if such a bookmark already exists, but instead will \"push\"
the new bookmark onto the bookmark alist.  Thus the most recently set
bookmark with name NAME would be the one in effect at any given time,
but the others are still there, should you decide to delete the most
recent one.
Richard M. Stallman's avatar
Richard M. Stallman committed
729 730

To yank words from the text of the buffer and use them as part of the
Richard M. Stallman's avatar
Richard M. Stallman committed
731
bookmark name, type C-w while setting a bookmark.  Successive C-w's
Richard M. Stallman's avatar
Richard M. Stallman committed
732 733
yank successive words.

734 735 736 737
Typing C-u inserts the name of the last bookmark used in the buffer
\(as an aid in using a single bookmark name to track your progress
through a large file\).  If no bookmark was used, then C-u inserts the
name of the file being visited.
Richard M. Stallman's avatar
Richard M. Stallman committed
738 739 740 741

Use \\[bookmark-delete] to remove bookmarks \(you give it a name,
and it removes only the first instance of a bookmark with that name from
the list of bookmarks.\)"
742
  (interactive (list nil current-prefix-arg))
743 744
  (or
   (bookmark-buffer-file-name)
745
   (error "Buffer not visiting a file or directory"))
746 747 748

  (bookmark-maybe-load-default-file)

Richard M. Stallman's avatar
Richard M. Stallman committed
749 750 751
  (setq bookmark-current-point (point))
  (setq bookmark-yank-point (point))
  (setq bookmark-current-buffer (current-buffer))
752

753
  (let* ((default (or bookmark-current-bookmark
754
                      (bookmark-buffer-name)))
755
	 (str
756 757 758 759 760
	  (or name
              (read-from-minibuffer
               (format "Set bookmark (%s): " default)
               nil
               (let ((now-map (copy-keymap minibuffer-local-map)))
761 762
                 (define-key now-map "\C-w" 'bookmark-yank-word)
                 (define-key now-map "\C-u" 'bookmark-insert-current-bookmark)
763
                 now-map))))
764 765
	 (annotation nil))
    (and (string-equal str "") (setq str default))
Sam Steingold's avatar
Sam Steingold committed
766
    ;; Ask for an annotation buffer for this bookmark
767 768
    (if bookmark-use-annotations
	(bookmark-read-annotation parg str)
769 770 771 772
      (bookmark-make str annotation parg (bookmark-info-current-node))
      (setq bookmark-current-bookmark str)
      (bookmark-bmenu-surreptitiously-rebuild-list)
      (goto-char bookmark-current-point))))
773 774


775 776 777 778 779 780
(defun bookmark-info-current-node ()
  "If in Info-mode, return current node name (a string), else nil."
  (if (eq major-mode 'Info-mode)
      Info-current-node))


781 782 783 784 785 786 787 788 789 790
(defun bookmark-kill-line (&optional newline-too)
  "Kill from point to end of line.
If optional arg NEWLINE-TOO is non-nil, delete the newline too.
Does not affect the kill-ring."
  (let ((eol (save-excursion (end-of-line) (point))))
    (delete-region (point) eol)
    (if (and newline-too (looking-at "\n"))
        (delete-char 1))))


791 792 793 794 795 796 797 798
;; Defvars to avoid compilation warnings:
(defvar bookmark-annotation-paragraph nil)
(defvar bookmark-annotation-name nil)
(defvar bookmark-annotation-buffer nil)
(defvar bookmark-annotation-file nil)
(defvar bookmark-annotation-point nil)


799
(defun bookmark-send-annotation ()
800 801 802
  "Use buffer contents as the annotation for a bookmark.
Exclude lines that begin with `#'.
Store the annotation text in the bookmark list with
803 804 805
the bookmark (and file, and point) specified in buffer local variables."
  (interactive)
  (if (not (eq major-mode 'bookmark-read-annotation-mode))
806
      (error "Not in bookmark-read-annotation-mode"))
807 808 809 810 811
  (goto-char (point-min))
  (while (< (point) (point-max))
    (if (looking-at "^#")
        (bookmark-kill-line t)
      (forward-line 1)))
812
  (let ((annotation (buffer-string))
813 814 815 816 817 818 819 820 821
	(parg bookmark-annotation-paragraph)
	(bookmark bookmark-annotation-name)
	(pt bookmark-annotation-point)
	(buf bookmark-annotation-buffer))
    ;; for bookmark-make-cell to work, we need to be
    ;; in the relevant buffer, at the relevant point.
    ;; Actually, bookmark-make-cell should probably be re-written,
    ;; to avoid this need.  Should I handle the error if a buffer is
    ;; killed between "C-x r m" and a "C-c C-c" in the annotation buffer?
Sam Steingold's avatar
Sam Steingold committed
822
    (save-excursion
823 824
      (pop-to-buffer buf)
      (goto-char pt)
825
      (bookmark-make bookmark annotation parg (bookmark-info-current-node))
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
      (setq bookmark-current-bookmark bookmark))
    (bookmark-bmenu-surreptitiously-rebuild-list)
    (goto-char bookmark-current-point))
  (kill-buffer (current-buffer)))


(defun bookmark-default-annotation-text (bookmark)
  (concat "#  Type the annotation for bookmark '" bookmark "' here.\n"
	  "#  All lines which start with a '#' will be deleted.\n"
	  "#  Type C-c C-c when done.\n#\n"
	  "#  Author: " (user-full-name) " <" (user-login-name) "@"
	  (system-name) ">\n"
	  "#  Date:    " (current-time-string) "\n"))


(defvar bookmark-read-annotation-text-func 'bookmark-default-annotation-text
842 843
  "Function to return default text to use for a bookmark annotation.
It takes the name of the bookmark, as a string, as an arg.")
844 845 846

(defun bookmark-read-annotation-mode (buf point parg bookmark)
  "Mode for composing annotations for a bookmark.
847
Wants BUF POINT PARG and BOOKMARK.
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871
When you have finished composing, type \\[bookmark-send-annotation] to send
the annotation.

\\{bookmark-read-annotation-mode-map}
"
  (interactive)
  (kill-all-local-variables)
  (make-local-variable 'bookmark-annotation-paragraph)
  (make-local-variable 'bookmark-annotation-name)
  (make-local-variable 'bookmark-annotation-buffer)
  (make-local-variable 'bookmark-annotation-file)
  (make-local-variable 'bookmark-annotation-point)
  (setq bookmark-annotation-paragraph parg)
  (setq bookmark-annotation-name bookmark)
  (setq bookmark-annotation-buffer buf)
  (setq bookmark-annotation-file (buffer-file-name buf))
  (setq bookmark-annotation-point point)
  (use-local-map bookmark-read-annotation-mode-map)
  (setq major-mode 'bookmark-read-annotation-mode)
  (insert (funcall bookmark-read-annotation-text-func bookmark))
  (run-hooks 'text-mode-hook))


(defun bookmark-read-annotation (parg bookmark)
872 873
  "Pop up a buffer for entering a bookmark annotation.
Text surrounding the bookmark is PARG; the bookmark name is BOOKMARK."
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
  (let ((buf (current-buffer))
	(point (point)))
    (pop-to-buffer (generate-new-buffer-name "*Bookmark Annotation Compose*"))
    (bookmark-read-annotation-mode buf point parg bookmark)))


(defvar bookmark-edit-annotation-mode-map (copy-keymap text-mode-map)
  "Keymap for editing an annotation of a bookmark.")


(define-key bookmark-edit-annotation-mode-map "\C-c\C-c"
  'bookmark-send-edited-annotation)


(defun bookmark-edit-annotation-mode (bookmark)
  "Mode for editing the annotation of bookmark BOOKMARK.
When you have finished composing, type \\[bookmark-send-annotation].

\\{bookmark-edit-annotation-mode-map}
"
  (interactive)
  (kill-all-local-variables)
  (make-local-variable 'bookmark-annotation-name)
  (setq bookmark-annotation-name bookmark)
  (use-local-map bookmark-edit-annotation-mode-map)
  (setq major-mode 'bookmark-edit-annotation-mode)
  (insert (funcall bookmark-read-annotation-text-func bookmark))
  (let ((annotation (bookmark-get-annotation bookmark)))
902
    (if (and annotation (not (string-equal annotation "")))
903 904 905 906 907
	(insert annotation)))
  (run-hooks 'text-mode-hook))


(defun bookmark-send-edited-annotation ()
908
  "Use buffer contents (minus beginning with `#' as annotation for a bookmark."
909 910
  (interactive)
  (if (not (eq major-mode 'bookmark-edit-annotation-mode))
911
      (error "Not in bookmark-edit-annotation-mode"))
912 913 914 915 916
  (goto-char (point-min))
  (while (< (point) (point-max))
    (if (looking-at "^#")
        (bookmark-kill-line t)
      (forward-line 1)))
917
  (let ((annotation (buffer-string))
918 919 920 921 922 923 924 925 926
	(bookmark bookmark-annotation-name))
    (bookmark-set-annotation bookmark annotation)
    (bookmark-bmenu-surreptitiously-rebuild-list)
    (goto-char bookmark-current-point))
  (kill-buffer (current-buffer)))


(defun bookmark-edit-annotation (bookmark)
  "Pop up a buffer for editing bookmark BOOKMARK's annotation."
927 928
  (pop-to-buffer (generate-new-buffer-name "*Bookmark Annotation Compose*"))
  (bookmark-edit-annotation-mode bookmark))
929

Richard M. Stallman's avatar
Richard M. Stallman committed
930 931

(defun bookmark-insert-current-bookmark ()
932 933
  "Insert this buffer's value of bookmark-current-bookmark.
Default to file name if it's nil."
Richard M. Stallman's avatar
Richard M. Stallman committed
934 935 936 937 938
  (interactive)
  (let ((str
	 (save-excursion
	   (set-buffer bookmark-current-buffer)
	   bookmark-current-bookmark)))
939
    (if str (insert str) (bookmark-insert-buffer-name))))
Richard M. Stallman's avatar
Richard M. Stallman committed
940

941

942
(defun bookmark-insert-buffer-name ()
943 944
  "Insert the current file name into the bookmark name being set.
The directory part of the file name is not used."
Richard M. Stallman's avatar
Richard M. Stallman committed
945
  (interactive)
946 947 948
  (let ((str
         (save-excursion
           (set-buffer bookmark-current-buffer)
949
           (bookmark-buffer-name))))
950 951
    (insert str)))

Richard M. Stallman's avatar
Richard M. Stallman committed
952

953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
(defun bookmark-buffer-name ()
  "Return the name of the current buffer's file, non-directory.
In Info, return the current node."
  (cond
   ;; Are we in Info?
   ((string-equal mode-name "Info") Info-current-node)
   ;; Or are we a file?
   (buffer-file-name (file-name-nondirectory buffer-file-name))
   ;; Or are we a directory?
   ((and (boundp 'dired-directory) dired-directory)
    (let* ((dirname (if (stringp dired-directory)
                        dired-directory
                      (car dired-directory)))
           (idx (1- (length dirname))))
      ;; Strip the trailing slash.
      (if (= ?/ (aref dirname idx))
          (file-name-nondirectory (substring dirname 0 idx))
        ;; Else return the current-buffer
        (buffer-name (current-buffer)))))
   ;; If all else fails, use the buffer's name.
   (t
    (buffer-name (current-buffer)))))


Richard M. Stallman's avatar
Richard M. Stallman committed
977 978 979 980 981
(defun bookmark-yank-word ()
  (interactive)
  ;; get the next word from the buffer and append it to the name of
  ;; the bookmark currently being set.
  (let ((string (save-excursion
982 983
                    (set-buffer bookmark-current-buffer)
                    (goto-char bookmark-yank-point)
984
                    (buffer-substring-no-properties
985
                     (point)
986
                     (progn
987 988
                       (forward-word 1)
                       (setq bookmark-yank-point (point)))))))
Richard M. Stallman's avatar
Richard M. Stallman committed
989 990 991 992
    (insert string)))


(defun bookmark-buffer-file-name ()
993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
  "Return the current buffer's file in a way useful for bookmarks.
For example, if this is a Info buffer, return the Info file's name."
  (if (eq major-mode 'Info-mode)
        Info-current-file
    (or
     buffer-file-name
     (if (and (boundp 'dired-directory) dired-directory)
         (if (stringp dired-directory)
             dired-directory
           (car dired-directory))))))


(defun bookmark-maybe-load-default-file ()
1006 1007
  (and (not bookmarks-already-loaded)
       (null bookmark-alist)
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
       (prog2
           (and
            ;; Possibly the old bookmark file, "~/.emacs-bkmrks", needs
            ;; to be renamed.
            (file-exists-p (expand-file-name bookmark-old-default-file))
            (not (file-exists-p (expand-file-name bookmark-default-file)))
            (rename-file (expand-file-name bookmark-old-default-file)
                         (expand-file-name bookmark-default-file)))
           ;; return t so the `and' will continue...
           t)
Sam Steingold's avatar
Sam Steingold committed
1018

1019
       (file-readable-p (expand-file-name bookmark-default-file))
1020 1021
       (bookmark-load bookmark-default-file t t)
       (setq bookmarks-already-loaded t)))
1022

1023

1024 1025 1026 1027 1028 1029 1030 1031 1032
(defun bookmark-maybe-sort-alist ()
  ;;Return the bookmark-alist for display.  If the bookmark-sort-flag
  ;;is non-nil, then return a sorted copy of the alist.
  (if bookmark-sort-flag
      (setq bookmark-alist
            (sort (copy-alist bookmark-alist)
                  (function
                   (lambda (x y) (string-lessp (car x) (car y))))))))

1033

1034
;;;###autoload
1035
(defun bookmark-jump (bookmark)
Sam Steingold's avatar
Sam Steingold committed
1036
  "Jump to bookmark BOOKMARK (a point in some file).
Richard M. Stallman's avatar
Richard M. Stallman committed
1037 1038 1039
You may have a problem using this function if the value of variable
`bookmark-alist' is nil.  If that happens, you need to load in some
bookmarks.  See help on function `bookmark-load' for more about
1040 1041
this.

1042 1043 1044 1045 1046
If the file pointed to by BOOKMARK no longer exists, you will be asked
if you wish to give the bookmark a new location, and bookmark-jump
will then jump to the new location, as well as recording it in place
of the old one in the permanent bookmark record."
  (interactive
1047 1048
   (list (bookmark-completing-read "Jump to bookmark"
				   bookmark-current-bookmark)))
1049 1050
  (bookmark-maybe-historicize-string bookmark)
  (let ((cell (bookmark-jump-noselect bookmark)))
1051 1052
    (and cell
         (switch-to-buffer (car cell))
1053
         (goto-char (cdr cell))
1054 1055 1056
	 (if bookmark-automatically-show-annotations
             ;; if there is an annotation for this bookmark,
             ;; show it in a buffer.
1057
             (bookmark-show-annotation bookmark)))))
1058

1059

1060
(defun bookmark-file-or-variation-thereof (file)
1061 1062 1063 1064 1065
  "Return FILE (a string) if it exists, or return a reasonable
variation of FILE if that exists.  Reasonable variations are checked
by appending suffixes defined in `Info-suffix-list'.  If cannot find FILE
nor a reasonable variation thereof, then still return FILE if it can
be retrieved from a VC backend, else return nil."
1066 1067
  (if (file-exists-p file)
      file
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
    (or
     (progn (require 'info)  ; ensure Info-suffix-list is bound
            (catch 'found
              (mapc (lambda (elt)
                      (let ((suffixed-file (concat file (car elt))))
                        (if (file-exists-p suffixed-file)
                            (throw 'found suffixed-file))))
                    Info-suffix-list)
              nil))
     ;; Last possibility: try VC
     (if (vc-backend file) file))))

1080

1081 1082 1083
(defun bookmark-jump-noselect (str)
  ;; a leetle helper for bookmark-jump :-)
  ;; returns (BUFFER . POINT)
1084 1085 1086 1087 1088 1089 1090 1091
  (bookmark-maybe-load-default-file)
  (let* ((file (expand-file-name (bookmark-get-filename str)))
         (forward-str            (bookmark-get-front-context-string str))
         (behind-str             (bookmark-get-rear-context-string str))
         (place                  (bookmark-get-position str))
         (info-node              (bookmark-get-info-node str))
         (orig-file              file)
         )
1092
    (if (setq file (bookmark-file-or-variation-thereof file))
1093
        (save-excursion
1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
          (save-window-excursion
            (if info-node
                ;; Info nodes must be visited with care.
                (progn
                  (require 'info)
                  (Info-find-node file info-node))
              ;; Else no Info.  Can do an ordinary find-file:
              (set-buffer (find-file-noselect file))
              (goto-char place))

            ;; Go searching forward first.  Then, if forward-str exists and
            ;; was found in the file, we can search backward for behind-str.
            ;; Rationale is that if text was inserted between the two in the
            ;; file, it's better to be put before it so you can read it,
            ;; rather than after and remain perhaps unaware of the changes.
            (if forward-str
                (if (search-forward forward-str (point-max) t)
                    (goto-char (match-beginning 0))))
            (if behind-str
                (if (search-backward behind-str (point-min) t)
                    (goto-char (match-end 0))))
            ;; added by db
            (setq bookmark-current-bookmark str)
            (cons (current-buffer) (point))))

1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
      ;; Else unable to find the marked file, so ask if user wants to
      ;; relocate the bookmark, else remind them to consider deletion.
      (ding)
      (if (y-or-n-p (concat (file-name-nondirectory orig-file)
                            " nonexistent.  Relocate \""
                            str
                            "\"? "))
          (progn
            (bookmark-relocate str)
            ;; gasp!  It's a recursive function call in Emacs Lisp!
            (bookmark-jump-noselect str))
Sam Steingold's avatar
Sam Steingold committed
1130
        (message
1131 1132
         "Bookmark not relocated; consider removing it \(%s\)." str)
        nil))))
1133