Commit 0f4d368f authored by Roland Winkler's avatar Roland Winkler

* bookmark.el: Watch bookmark file. Use lexical binding.

(bookmark-watch-bookmark-file): New user variable.
(bookmark-alist): Fix docstring.
(bookmark-bookmarks-timestamp): Renamed from bookmarks-already-loaded.
(bookmark-maybe-load-default-file, bookmark-save, bookmark-load):
Use bookmark-bookmarks-timestamp.
(bookmark-bmenu-mode-map): Define menu bar menu.
(bookmark-show-annotation, bookmark-show-all-annotations):
Make bookmarks buffer read-only.
(bookmark-bmenu-save): Use call-interactively.
parent 64767008
Pipeline #2140 failed with stage
in 51 minutes and 7 seconds
...@@ -1526,6 +1526,9 @@ buffer periodically when 'auto-revert-avoid-polling' is non-nil. ...@@ -1526,6 +1526,9 @@ buffer periodically when 'auto-revert-avoid-polling' is non-nil.
*** 'bookmark-file' and 'bookmark-old-default-file' are now obsolete *** 'bookmark-file' and 'bookmark-old-default-file' are now obsolete
aliases of 'bookmark-default-file'. aliases of 'bookmark-default-file'.
*** New user option 'bookmark-watch-bookmark-file'.
When non-nil, watch whether the bookmark file has changed on disk.
* New Modes and Packages in Emacs 27.1 * New Modes and Packages in Emacs 27.1
......
;;; bookmark.el --- set bookmarks, maybe annotate them, jump to them later ;;; bookmark.el --- set bookmarks, maybe annotate them, jump to them later -*- lexical-binding: t -*-
;; Copyright (C) 1993-1997, 2001-2019 Free Software Foundation, Inc. ;; Copyright (C) 1993-1997, 2001-2019 Free Software Foundation, Inc.
...@@ -86,9 +86,21 @@ To specify the file in which to save them, modify the variable ...@@ -86,9 +86,21 @@ To specify the file in which to save them, modify the variable
(defcustom bookmark-default-file (defcustom bookmark-default-file
(locate-user-emacs-file "bookmarks" ".emacs.bmk") (locate-user-emacs-file "bookmarks" ".emacs.bmk")
"File in which to save bookmarks by default." "File in which to save bookmarks by default."
;; The current default file is defined via the internal variable
;; `bookmark-bookmarks-timestamp'. This does not affect the value
;; of `bookmark-default-file'.
:type 'file :type 'file
:group 'bookmark) :group 'bookmark)
(defcustom bookmark-watch-bookmark-file t
"If non-nil watch the default bookmark file.
If this file has changed on disk since it was last loaded, query the user
whether to load it again. If the value is `silent' reload without querying.
This file defaults to `bookmark-default-file'. But during an Emacs session,
`bookmark-load' and `bookmark-save' can redefine the current default file."
:version "27.1"
:type 'boolean
:group 'bookmark)
(defcustom bookmark-version-control 'nospecial (defcustom bookmark-version-control 'nospecial
"Whether or not to make numbered backups of the bookmark file. "Whether or not to make numbered backups of the bookmark file.
...@@ -222,39 +234,50 @@ functions have a binding in this keymap.") ...@@ -222,39 +234,50 @@ functions have a binding in this keymap.")
Bookmark functions update the value automatically. Bookmark functions update the value automatically.
You probably do NOT want to change the value yourself. You probably do NOT want to change the value yourself.
The value is an alist with entries of the form The value is an alist with bookmarks of the form
(BOOKMARK-NAME . PARAM-ALIST) (BOOKMARK-NAME . PARAM-ALIST)
or the deprecated form (BOOKMARK-NAME PARAM-ALIST). or the deprecated form (BOOKMARK-NAME PARAM-ALIST).
BOOKMARK-NAME is the name you gave to the bookmark when creating it. BOOKMARK-NAME is the name you gave to the bookmark when creating it.
PARAM-ALIST is an alist of bookmark information. The order of the PARAM-ALIST is an alist of bookmark information. The order of the
entries in PARAM-ALIST is not important. The possible entries are entries in PARAM-ALIST is not important. The default entries are
described below. An entry with a key but null value means the entry described below. An entry with a key but null value means the entry
is not used. is not used.
(filename . FILENAME) (filename . FILENAME)
(position . POS) (buf . BUFFER-OR-NAME)
(front-context-string . STR-AFTER-POS) (position . POS)
(rear-context-string . STR-BEFORE-POS) (front-context-string . STR-AFTER-POS)
(handler . HANDLER) (rear-context-string . STR-BEFORE-POS)
(annotation . ANNOTATION) (handler . HANDLER)
(annotation . ANNOTATION)
FILENAME names the bookmarked file.
POS is the bookmarked buffer position. FILENAME names the bookmarked file.
STR-AFTER-POS is buffer text that immediately follows POS. BUFFER-OR-NAME is a buffer or the name of a buffer that is used
STR-BEFORE-POS is buffer text that immediately precedes POS. if FILENAME is not defined or it refers to a non-existent file.
ANNOTATION is a string that describes the bookmark. POS is the bookmarked buffer position.
See options `bookmark-use-annotations' and STR-AFTER-POS is buffer text that immediately follows POS.
`bookmark-automatically-show-annotations'. STR-BEFORE-POS is buffer text that immediately precedes POS.
HANDLER is a function that provides the bookmark-jump behavior for a ANNOTATION is a string that describes the bookmark.
specific kind of bookmark. This is the case for Info bookmarks, See options `bookmark-use-annotations' and
for instance. HANDLER must accept a bookmark as its single argument.") `bookmark-automatically-show-annotations'.
HANDLER is a function that provides the bookmark-jump behavior for a
(defvar bookmarks-already-loaded nil specific kind of bookmark instead of the default `bookmark-default-handler'.
"Non-nil if and only if bookmarks have been loaded from `bookmark-default-file'.") This is the case for Info bookmarks, for instance. HANDLER must accept
a bookmark as its single argument.
A function `bookmark-make-record-function' may define additional entries
in PARAM-LIST that can be used by HANDLER.")
(defvar bookmark-bookmarks-timestamp nil
"Timestamp of current default bookmark file.
The value is actually (FILE . MODTIME), where FILE is a bookmark file that
defaults to `bookmark-default-file' and MODTIME is its modification time.")
(define-obsolete-variable-alias 'bookmarks-already-loaded
'bookmark-bookmarks-timestamp "27.1")
(defvar bookmark-file-coding-system nil (defvar bookmark-file-coding-system nil
"The coding-system of the last loaded or saved bookmark file.") "The coding-system of the last loaded or saved bookmark file.")
...@@ -1013,12 +1036,19 @@ it to the name of the bookmark currently being set, advancing ...@@ -1013,12 +1036,19 @@ it to the name of the bookmark currently being set, advancing
(defun bookmark-maybe-load-default-file () (defun bookmark-maybe-load-default-file ()
"If bookmarks have not been loaded from the default place, load them." "If bookmarks have not been loaded from the default place, load them."
(and (not bookmarks-already-loaded) (cond ((and (not bookmark-bookmarks-timestamp)
(null bookmark-alist) (null bookmark-alist)
(file-readable-p bookmark-default-file) (file-readable-p bookmark-default-file)
(bookmark-load bookmark-default-file t t) (bookmark-load bookmark-default-file t t)))
(setq bookmarks-already-loaded t))) ((and bookmark-watch-bookmark-file
(not (equal (nth 5 (file-attributes
(car bookmark-bookmarks-timestamp)))
(cdr bookmark-bookmarks-timestamp)))
(or (eq 'silent bookmark-watch-bookmark-file)
(yes-or-no-p
(format "Bookmarks %s changed on disk. Reload? "
(car bookmark-bookmarks-timestamp)))))
(bookmark-load (car bookmark-bookmarks-timestamp) t t))))
(defun bookmark-maybe-sort-alist () (defun bookmark-maybe-sort-alist ()
"Return `bookmark-alist' for display. "Return `bookmark-alist' for display.
...@@ -1181,7 +1211,7 @@ Changes current buffer and point and returns nil, or signals a `file-error'." ...@@ -1181,7 +1211,7 @@ Changes current buffer and point and returns nil, or signals a `file-error'."
(cond (cond
((and file (file-readable-p file) (not (buffer-live-p buf))) ((and file (file-readable-p file) (not (buffer-live-p buf)))
(find-file-noselect file)) (find-file-noselect file))
;; No file found. See if buffer BUF have been created. ;; No file found. See if buffer BUF has been created.
((and buf (get-buffer buf))) ((and buf (get-buffer buf)))
(t ;; If not, raise error. (t ;; If not, raise error.
(signal 'bookmark-error-no-filename (list 'stringp file))))) (signal 'bookmark-error-no-filename (list 'stringp file)))))
...@@ -1360,42 +1390,44 @@ is greater than `bookmark-alist-modification-count'." ...@@ -1360,42 +1390,44 @@ is greater than `bookmark-alist-modification-count'."
;;;###autoload ;;;###autoload
(defun bookmark-save (&optional parg file) (defun bookmark-save (&optional parg file make-default)
"Save currently defined bookmarks. "Save currently defined bookmarks in FILE.
Saves by default in the file defined by the variable FILE defaults to `bookmark-default-file'.
`bookmark-default-file'. With a prefix arg, save it in file FILE With prefix PARG, query user for a file to save in.
\(second argument). If MAKE-DEFAULT is non-nil (interactively with prefix C-u C-u)
the file we save in becomes the new default in the current Emacs
If you are calling this from Lisp, the two arguments are PARG and session (without affecting the value of `bookmark-default-file'.).
FILE, and if you just want it to write to the default file, then
pass no arguments. Or pass in nil and FILE, and it will save in FILE
instead. If you pass in one argument, and it is non-nil, then the
user will be interactively queried for a file to save in.
When you want to load in the bookmarks from a file, use When you want to load in the bookmarks from a file, use
`bookmark-load', \\[bookmark-load]. That function will prompt you `bookmark-load', \\[bookmark-load]. That function will prompt you
for a file, defaulting to the file defined by variable for a file, defaulting to the file defined by variable
`bookmark-default-file'." `bookmark-default-file'."
(interactive "P") (interactive
(list current-prefix-arg nil (equal '(16) current-prefix-arg)))
(bookmark-maybe-load-default-file) (bookmark-maybe-load-default-file)
(cond (unless file
((and (null parg) (null file)) (setq file
;;whether interactive or not, write to default file (let ((default (or (car bookmark-bookmarks-timestamp)
(bookmark-write-file bookmark-default-file)) bookmark-default-file)))
((and (null parg) file) (if parg
;;whether interactive or not, write to given file ;; This should be part of the `interactive' spec.
(bookmark-write-file file)) (read-file-name (format "File to save bookmarks in: (%s) "
((and parg (not file)) default)
;;have been called interactively w/ prefix arg (file-name-directory default) default)
(let ((file (read-file-name "File to save bookmarks in: "))) default))))
(bookmark-write-file file))) (bookmark-write-file file)
(t ; someone called us with prefix-arg *and* a file, so just write to file ;; Signal that we have synced the bookmark file by setting this to 0.
(bookmark-write-file file))) ;; If there was an error at any point before, it will not get set,
;; signal that we have synced the bookmark file by setting this to ;; which is what we want.
;; 0. If there was an error at any point before, it will not get (setq bookmark-alist-modification-count 0)
;; set, which is what we want. (if make-default
(setq bookmark-alist-modification-count 0)) (let ((default (expand-file-name file)))
(setq bookmark-bookmarks-timestamp
(cons default (nth 5 (file-attributes default)))))
(let ((default (car bookmark-bookmarks-timestamp)))
(if (string= default (expand-file-name file))
(setq bookmark-bookmarks-timestamp
(cons default (nth 5 (file-attributes default))))))))
(defun bookmark-write-file (file) (defun bookmark-write-file (file)
...@@ -1469,12 +1501,13 @@ This is a helper for `bookmark-import-new-list'." ...@@ -1469,12 +1501,13 @@ This is a helper for `bookmark-import-new-list'."
;;;###autoload ;;;###autoload
(defun bookmark-load (file &optional overwrite no-msg) (defun bookmark-load (file &optional overwrite no-msg default)
"Load bookmarks from FILE (which must be in bookmark format). "Load bookmarks from FILE (which must be in bookmark format).
Appends loaded bookmarks to the front of the list of bookmarks. If Appends loaded bookmarks to the front of the list of bookmarks.
optional second argument OVERWRITE is non-nil, existing bookmarks are If argument OVERWRITE is non-nil, existing bookmarks are destroyed.
destroyed. Optional third arg NO-MSG means don't display any messages Optional third arg NO-MSG means don't display any messages while loading.
while loading. If DEFAULT is non-nil make FILE the new bookmark file to watch.
Interactively, a prefix arg makes OVERWRITE and DEFAULT non-nil.
If you load a file that doesn't contain a proper bookmark alist, you If you load a file that doesn't contain a proper bookmark alist, you
will corrupt Emacs's bookmark list. Generally, you should only load will corrupt Emacs's bookmark list. Generally, you should only load
...@@ -1487,48 +1520,51 @@ If you load a file containing bookmarks with the same names as ...@@ -1487,48 +1520,51 @@ If you load a file containing bookmarks with the same names as
bookmarks already present in your Emacs, the new bookmarks will get bookmarks already present in your Emacs, the new bookmarks will get
unique numeric suffixes \"<2>\", \"<3>\", etc." unique numeric suffixes \"<2>\", \"<3>\", etc."
(interactive (interactive
(list (read-file-name (let ((default (abbreviate-file-name
(format "Load bookmarks from: (%s) " (or (car bookmark-bookmarks-timestamp)
bookmark-default-file) (expand-file-name bookmark-default-file))))
;;Default might not be used often, (prefix current-prefix-arg))
;;but there's no better default, and (list (read-file-name (format "Load bookmarks from: (%s) " default)
;;I guess it's better than none at all. (file-name-directory default) default 'confirm)
"~/" bookmark-default-file 'confirm))) prefix nil prefix)))
(setq file (abbreviate-file-name (expand-file-name file))) (let* ((file (expand-file-name file))
(if (not (file-readable-p file)) (afile (abbreviate-file-name file)))
(error "Cannot read bookmark file %s" file) (unless (file-readable-p file)
(user-error "Cannot read bookmark file %s" afile))
(let ((reporter (let ((reporter
(when (null no-msg) (unless no-msg
(make-progress-reporter (make-progress-reporter
(format "Loading bookmarks from %s..." file))))) (format "Loading bookmarks from %s..." file)))))
(with-current-buffer (let ((enable-local-variables nil)) (with-current-buffer (let (enable-local-variables)
(find-file-noselect file)) (find-file-noselect file))
(goto-char (point-min)) (goto-char (point-min))
(bookmark-maybe-upgrade-file-format) (bookmark-maybe-upgrade-file-format)
(let ((blist (bookmark-alist-from-buffer))) (let ((blist (bookmark-alist-from-buffer)))
(if (listp blist) (unless (listp blist)
(progn (error "Invalid bookmark list in %s" file))
(if overwrite ;; RW: Upon loading the bookmarks, we could add to each bookmark
(progn ;; in `bookmark-alist' an extra key `bookmark-file', so that
(setq bookmark-alist blist) ;; upon reloading the bookmarks with OVERWRITE non-nil,
(setq bookmark-alist-modification-count 0)) ;; we overwrite only those bookmarks for which the key `bookmark-file'
;; else ;; matches FILE. `bookmark-save' can ignore this key.
(bookmark-import-new-list blist) ;; Would this be a useful option?
(setq bookmark-alist-modification-count (if overwrite
(1+ bookmark-alist-modification-count))) (setq bookmark-alist blist
(if (string-equal bookmark-alist-modification-count 0)
(abbreviate-file-name (bookmark-import-new-list blist)
(expand-file-name bookmark-default-file)) (setq bookmark-alist-modification-count
file) (1+ bookmark-alist-modification-count)))
(setq bookmarks-already-loaded t)) (if (or default
(bookmark-bmenu-surreptitiously-rebuild-list) (string= file (or (car bookmark-bookmarks-timestamp)
(setq bookmark-file-coding-system buffer-file-coding-system)) (expand-file-name bookmark-default-file))))
(error "Invalid bookmark list in %s" file))) (setq bookmark-bookmarks-timestamp
(cons file (nth 5 (file-attributes file)))))
(bookmark-bmenu-surreptitiously-rebuild-list)
(setq bookmark-file-coding-system buffer-file-coding-system))
(kill-buffer (current-buffer))) (kill-buffer (current-buffer)))
(when (null no-msg) (unless no-msg
(progress-reporter-done reporter))))) (progress-reporter-done reporter)))))
;;; Code supporting the dired-like bookmark menu. ;;; Code supporting the dired-like bookmark menu.
;; Prefix is "bookmark-bmenu" for "buffer-menu": ;; Prefix is "bookmark-bmenu" for "buffer-menu":
...@@ -1552,6 +1588,7 @@ unique numeric suffixes \"<2>\", \"<3>\", etc." ...@@ -1552,6 +1588,7 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
(define-key map "o" 'bookmark-bmenu-other-window) (define-key map "o" 'bookmark-bmenu-other-window)
(define-key map "\C-o" 'bookmark-bmenu-switch-other-window) (define-key map "\C-o" 'bookmark-bmenu-switch-other-window)
(define-key map "s" 'bookmark-bmenu-save) (define-key map "s" 'bookmark-bmenu-save)
(define-key map "\C-x\C-s" 'bookmark-bmenu-save)
(define-key map "k" 'bookmark-bmenu-delete) (define-key map "k" 'bookmark-bmenu-delete)
(define-key map "\C-d" 'bookmark-bmenu-delete-backwards) (define-key map "\C-d" 'bookmark-bmenu-delete-backwards)
(define-key map "x" 'bookmark-bmenu-execute-deletions) (define-key map "x" 'bookmark-bmenu-execute-deletions)
...@@ -1573,6 +1610,34 @@ unique numeric suffixes \"<2>\", \"<3>\", etc." ...@@ -1573,6 +1610,34 @@ unique numeric suffixes \"<2>\", \"<3>\", etc."
(define-key map [mouse-2] 'bookmark-bmenu-other-window-with-mouse) (define-key map [mouse-2] 'bookmark-bmenu-other-window-with-mouse)
map)) map))
(easy-menu-define
bookmark-menu bookmark-bmenu-mode-map "Bookmark Menu"
'("Bookmark"
["Select Bookmark in This Window" bookmark-bmenu-this-window t]
["Select Bookmark in Full-Frame Window" bookmark-bmenu-1-window t]
["Select Bookmark in Other Window" bookmark-bmenu-other-window t]
["Select Bookmark in Other Frame" bookmark-bmenu-other-frame t]
["Select Marked Bookmarks" bookmark-bmenu-select t]
"---"
["Mark Bookmark" bookmark-bmenu-mark t]
["Unmark Bookmark" bookmark-bmenu-unmark t]
["Unmark Backwards" bookmark-bmenu-backup-unmark t]
["Toggle Display of Filenames" bookmark-bmenu-toggle-filenames t]
["Display Location of Bookmark" bookmark-bmenu-locate t]
"---"
("Edit Bookmarks"
["Rename Bookmark" bookmark-bmenu-rename t]
["Relocate Bookmark's File" bookmark-bmenu-relocate t]
["Mark Bookmark for Deletion" bookmark-bmenu-delete t]
["Delete Marked Bookmarks" bookmark-bmenu-execute-deletions t])
("Annotations"
["Show Annotation for Current Bookmark" bookmark-bmenu-show-annotation t]
["Show Annotations for All Bookmarks" bookmark-bmenu-show-all-annotations t]
["Edit Annotation for Current Bookmark." bookmark-bmenu-edit-annotation t])
"---"
["Save Bookmarks" bookmark-bmenu-save t]
["Load Bookmarks" bookmark-bmenu-load t]))
;; Bookmark Buffer Menu mode is suitable only for specially formatted ;; Bookmark Buffer Menu mode is suitable only for specially formatted
;; data. ;; data.
(put 'bookmark-bmenu-mode 'mode-class 'special) (put 'bookmark-bmenu-mode 'mode-class 'special)
...@@ -1647,6 +1712,8 @@ deletion, or > if it is flagged for displaying." ...@@ -1647,6 +1712,8 @@ deletion, or > if it is flagged for displaying."
;;;###autoload ;;;###autoload
(defalias 'edit-bookmarks 'bookmark-bmenu-list) (defalias 'edit-bookmarks 'bookmark-bmenu-list)
;; FIXME: This could also display the current default bookmark file
;; according to `bookmark-bookmarks-timestamp'.
(defun bookmark-bmenu-set-header () (defun bookmark-bmenu-set-header ()
"Set the immutable header line." "Set the immutable header line."
(let ((header (concat "%% " "Bookmark"))) (let ((header (concat "%% " "Bookmark")))
...@@ -1815,9 +1882,12 @@ If the annotation does not exist, do nothing." ...@@ -1815,9 +1882,12 @@ If the annotation does not exist, do nothing."
(save-excursion (save-excursion
(let ((old-buf (current-buffer))) (let ((old-buf (current-buffer)))
(pop-to-buffer (get-buffer-create "*Bookmark Annotation*") t) (pop-to-buffer (get-buffer-create "*Bookmark Annotation*") t)
(delete-region (point-min) (point-max)) (let (buffer-read-only)
(insert annotation) (erase-buffer)
(goto-char (point-min)) (insert annotation)
(goto-char (point-min))
(set-buffer-modified-p nil))
(setq buffer-read-only t)
(switch-to-buffer-other-window old-buf)))))) (switch-to-buffer-other-window old-buf))))))
...@@ -1825,22 +1895,25 @@ If the annotation does not exist, do nothing." ...@@ -1825,22 +1895,25 @@ If the annotation does not exist, do nothing."
"Display the annotations for all bookmarks in a buffer." "Display the annotations for all bookmarks in a buffer."
(save-selected-window (save-selected-window
(pop-to-buffer (get-buffer-create "*Bookmark Annotation*") t) (pop-to-buffer (get-buffer-create "*Bookmark Annotation*") t)
(delete-region (point-min) (point-max)) (let (buffer-read-only)
(dolist (full-record (bookmark-maybe-sort-alist)) (erase-buffer)
(let* ((name (bookmark-name-from-full-record full-record)) (dolist (full-record (bookmark-maybe-sort-alist))
(ann (bookmark-get-annotation full-record))) (let* ((name (bookmark-name-from-full-record full-record))
(insert (concat name ":\n")) (ann (bookmark-get-annotation full-record)))
(if (and ann (not (string-equal ann ""))) (insert (concat name ":\n"))
;; insert the annotation, indented by 4 spaces. (if (and ann (not (string-equal ann "")))
(progn ;; insert the annotation, indented by 4 spaces.
(save-excursion (insert ann) (unless (bolp) (progn
(insert "\n"))) (save-excursion (insert ann) (unless (bolp)
(while (< (point) (point-max)) (insert "\n")))
(beginning-of-line) ; paranoia (while (< (point) (point-max))
(insert " ") (beginning-of-line) ; paranoia
(forward-line) (insert " ")
(end-of-line)))))) (forward-line)
(goto-char (point-min)))) (end-of-line))))))
(goto-char (point-min))
(set-buffer-modified-p nil))
(setq buffer-read-only t)))
(defun bookmark-bmenu-mark () (defun bookmark-bmenu-mark ()
...@@ -1901,13 +1974,13 @@ You can mark bookmarks with the \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mar ...@@ -1901,13 +1974,13 @@ You can mark bookmarks with the \\<bookmark-bmenu-mode-map>\\[bookmark-bmenu-mar
nil))) nil)))
(defun bookmark-bmenu-save (parg) (defun bookmark-bmenu-save ()
"Save the current list into a bookmark file. "Save the current list into a bookmark file.
With a prefix arg, prompts for a file to save them in." With a prefix arg, prompts for a file to save them in."
(interactive "P") (interactive)
(save-excursion (save-excursion
(save-window-excursion (save-window-excursion
(bookmark-save parg) (call-interactively 'bookmark-save)
(set-buffer-modified-p nil)))) (set-buffer-modified-p nil))))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment