├── README.md └── autobookmarks.el /README.md: -------------------------------------------------------------------------------- 1 | # autobookmarks 2 | 3 | Like `recentf` but better. 4 | 5 | # How it works 6 | 7 | This package doesn't provide any UI, it only implements the back-end 8 | "store" and an API you can use to retrieve the bookmarks. 9 | 10 | The two main functions are `abm-visited-buffers` and 11 | `abm-recent-buffers` to retrieve lists of visited and recent buffers. 12 | 13 | This package automatically adds all killed buffers, according to 14 | `abm-killed-buffer-functions`, to the list of recent buffers. When 15 | you visit a recent buffer, its associated bookmark is removed from the 16 | recent list and moved to the visited list. When you kill a visited 17 | buffer, its associated bookmark is moved to the recent list. 18 | 19 | When you visit a new buffer which is not recent it is added, according 20 | to `abm-visited-buffer-hooks`, to the visited list. This ensures that 21 | newly opened buffers are stored in case of a crash. When you restart 22 | emacs, they will be available as recent buffers (they are not stored 23 | as recent right away because they are active and so shouldn't be on 24 | the recent list---that list only stores buffers which aren't visited) 25 | 26 | You can restore a recent (killed) buffer by using 27 | `abm-restore-killed-buffer` to which you pass the stored bookmark. 28 | 29 | You can toggle this mode on and off by using `autobookmarks-mode`, 30 | which is a global minor mode. 31 | 32 | # The (lack of) UI 33 | 34 | The author uses `sallet` package to provide seamless integration of 35 | bookmarks with buffer switching/finding files. You can write an `ido` 36 | or `helm` interface fairly easily. If someone does so, feel free to 37 | contribute it back as a patch. 38 | 39 | The data is stored in the same format as emacs bookmarks (`(info 40 | "(emacs) Bookmarks")`), so it should be possible to add a bookmark 41 | interface for it as well. 42 | -------------------------------------------------------------------------------- /autobookmarks.el: -------------------------------------------------------------------------------- 1 | ;;; autobookmarks.el --- Save recently visited files and buffers 2 | 3 | ;; Copyright (C) 2015 Matúš Goljer 4 | 5 | ;; Author: Matúš Goljer 6 | ;; Maintainer: Matúš Goljer 7 | ;; Version: 0.0.1 8 | ;; Created: 28th February 2015 9 | ;; Package-requires: ((dash "2.10.0") (cl-lib "0.5")) 10 | ;; Keywords: files 11 | 12 | ;; This program is free software; you can redistribute it and/or 13 | ;; modify it under the terms of the GNU General Public License 14 | ;; as published by the Free Software Foundation; either version 3 15 | ;; of the License, or (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;;; Code: 28 | 29 | (require 'dash) 30 | (require 'cl-lib) 31 | (require 'bookmark) 32 | 33 | (defgroup autobookmarks () 34 | "Save recently visited files and buffers." 35 | :group 'files 36 | :prefix "abm-") 37 | 38 | (defcustom abm-file (locate-user-emacs-file "autobookmarks") 39 | "File where the bookmark data is persisted." 40 | :type 'file 41 | :group 'autobookmarks) 42 | 43 | (defcustom abm-old-bookmark-threshold 90 44 | "Number of days since last visit after which the bookmark is erased." 45 | :type 'integer 46 | :group 'autobookmarks) 47 | 48 | (defvar abm-visited-buffers nil 49 | "List of visited buffers. 50 | 51 | A buffer is added to this list as soon as it is visited. 52 | 53 | In case the session crashes, it is used to recover the recent 54 | buffer list.") 55 | 56 | (defun abm-visited-buffers () abm-visited-buffers) 57 | 58 | (defvar abm-recent-buffers nil 59 | "List of recent buffers. 60 | 61 | A buffer is added to this list as soon as it is closed.") 62 | 63 | (defun abm-recent-buffers () abm-recent-buffers) 64 | 65 | (defcustom abm-visited-buffer-hooks '((find-file-hook . abm-handle-visited-buffer) 66 | (write-file-functions . abm-handle-visited-buffer) 67 | (dired-mode-hook . abm-handle-visited-buffer)) 68 | "Hooks used to detect visited buffers." 69 | :type '(repeat 70 | (cons 71 | (symbol :tag "Hook") 72 | (function :tag "Function"))) 73 | :group 'autobookmarks) 74 | 75 | (defcustom abm-ignore-buffers '( 76 | "\\.ido\\.last" 77 | "\\.git" 78 | "\\.svn" 79 | "log\\'" 80 | "cache" 81 | "#\\'" 82 | ) 83 | "List of regular expressions to ignore buffers. 84 | 85 | If filename matches the expression it is ignored." 86 | :type '(repeat regexp) 87 | :group 'autobookmarks) 88 | 89 | (defcustom abm-killed-buffer-functions '( 90 | abm-handle-killed-file 91 | abm-handle-killed-dired 92 | abm-handle-killed-info 93 | ) 94 | "Functions used to handle killed buffers. 95 | 96 | Function should return non-nil if it handled the buffer." 97 | :type 'hook 98 | :group 'autobookmarks) 99 | 100 | (defun abm--make-record () 101 | "Call `bookmark-make-record' and change some values to more meaningful defaults." 102 | (let* ((record (--remove (memq (car it) '( 103 | front-context-string 104 | rear-context-string 105 | front-context-region-string 106 | rear-context-region-string 107 | )) 108 | (cdr (bookmark-make-record)))) 109 | (record (-concat record (list (cons 'time (current-time)) 110 | (cons 'visits 0))))) 111 | (cons (or (cdr (assoc 'filename record)) 112 | (cdr (assoc 'buffer-name record))) 113 | record))) 114 | 115 | (defun abm--remove-bookmark (bookmark list) 116 | "Remove BOOKMARK from bookmark list LIST." 117 | (--remove (equal (car bookmark) (car it)) list)) 118 | 119 | (defun abm--process-bookmark-p (bookmark) 120 | "Test if we should process this BOOKMARK. 121 | 122 | List of ignored buffers is customizable via `abm-ignore-buffers'." 123 | (let ((filename (cdr (assoc 'filename bookmark)))) 124 | (and filename (--none? (string-match-p it filename) abm-ignore-buffers)))) 125 | 126 | (defun abm--add-bookmark-to-visited (bookmark) 127 | (when (abm--process-bookmark-p bookmark) 128 | (let ((bookmark (or (--first (equal (car bookmark) (car it)) abm-recent-buffers) bookmark))) 129 | (unless (assoc (car bookmark) abm-visited-buffers) 130 | (push bookmark abm-visited-buffers)) 131 | (setq abm-recent-buffers (abm--remove-bookmark bookmark abm-recent-buffers))))) 132 | 133 | (defun abm--add-bookmark-to-recent (bookmark) 134 | (when (abm--process-bookmark-p bookmark) 135 | (let ((bookmark (or (--first (equal (car bookmark) (car it)) abm-visited-buffers) bookmark))) 136 | (unless (assoc (car bookmark) abm-recent-buffers) 137 | ;; remove all bookmarks which point to a parent of new bookmark 138 | (setq abm-recent-buffers 139 | (--remove (string-prefix-p (car it) (car bookmark)) abm-recent-buffers)) 140 | (push bookmark abm-recent-buffers)) 141 | (setq abm-visited-buffers (abm--remove-bookmark bookmark abm-visited-buffers))))) 142 | 143 | (defun abm-remove-recent (regexp) 144 | "Remove matching bookmarks from `abm-recent-buffers'." 145 | (interactive "sRegexp to match and remove: ") 146 | (when (equal regexp "") 147 | (user-error "The regexp to match against is empty")) 148 | (setq abm-recent-buffers (--remove (string-match-p regexp (car it)) abm-recent-buffers))) 149 | 150 | (defun abm-remove-nonexistant (check-remote-files-p) 151 | "Remove all bookmarks which point to non-existing files. 152 | 153 | This *will* also check remote files accessed with TRAMP unless 154 | invoked with prefix argument \\[universal-argument]." 155 | (interactive "P") 156 | (setq abm-recent-buffers 157 | (--filter (let ((file (cdr (assoc 'filename (cdr it))))) 158 | (if check-remote-files-p 159 | (file-exists-p file) 160 | (or (file-remote-p file) 161 | (file-exists-p file)))) 162 | abm-recent-buffers))) 163 | 164 | (defun abm-save-to-file () 165 | "Save visited and recent buffers to file. 166 | 167 | Additionally, before saving the data, it filters the 168 | `abm-recent-buffers' list and removes bookmarks older than 169 | `abm-old-bookmark-threshold'." 170 | (interactive) 171 | ;; in case this is called before init finished we might overwrite 172 | ;; the saved data, so load that first 173 | (unless abm-recent-buffers 174 | (abm-load-from-file)) 175 | ;; remove too old bookmarks 176 | (setq abm-recent-buffers (--remove 177 | (time-less-p 178 | ;; old threshold in days 179 | (days-to-time abm-old-bookmark-threshold) 180 | ;; minus "current - bookmark last used timestamp" (= number of days since last use) 181 | (time-subtract (current-time) (or (cdr (assoc 'time it)) (current-time)))) 182 | abm-recent-buffers)) 183 | (let ((print-level nil) 184 | (print-length nil)) 185 | (make-directory (file-name-directory abm-file) t) 186 | (with-temp-file abm-file 187 | (insert ";; This file is created automatically by autobookmarks.el\n\n") 188 | (insert (format "(setq abm-visited-buffers '%S)\n" abm-visited-buffers)) 189 | (insert (format "(setq abm-recent-buffers '%S)" abm-recent-buffers))))) 190 | 191 | (defun abm-load-from-file () 192 | "Load saved bookmarks." 193 | (interactive) 194 | (when (file-exists-p abm-file) 195 | (load-file abm-file) 196 | (setq abm-recent-buffers (-concat abm-visited-buffers abm-recent-buffers)) 197 | (setq abm-visited-buffers nil))) 198 | 199 | (if after-init-time 200 | (abm-load-from-file) 201 | (add-hook 'after-init-hook 'abm-load-from-file)) 202 | 203 | ;; handlers 204 | 205 | (defun abm-handle-visited-buffer () 206 | "Handle opened buffer." 207 | (let ((record (abm--make-record))) 208 | (abm--add-bookmark-to-visited record)) 209 | (abm-save-to-file)) 210 | 211 | ;; TODO: take care of the repetition here? Will we ever do something 212 | ;; else than make record and push it? 213 | (defun abm-handle-killed-file () 214 | "Handle killed file buffer." 215 | (when (buffer-file-name) 216 | (let ((record (abm--make-record))) 217 | (abm--add-bookmark-to-recent record)))) 218 | 219 | (defun abm-handle-killed-dired () 220 | "Handle killed dired buffer." 221 | (when (eq major-mode 'dired-mode) 222 | (let ((record (abm--make-record))) 223 | (abm--add-bookmark-to-recent record)))) 224 | 225 | (defun abm-handle-killed-info () 226 | "Handle killed info buffer." 227 | (when (eq major-mode 'Info-mode) 228 | (let ((record (abm--make-record))) 229 | (abm--add-bookmark-to-recent record)))) 230 | 231 | (defun abm-handle-killed-buffer () 232 | "Run \"killed-buffer\" handlers. 233 | 234 | The list is customizable via `abm-killed-buffer-functions'." 235 | (unless (equal " " (substring (buffer-name) 0 1)) 236 | (run-hook-with-args-until-success 'abm-killed-buffer-functions) 237 | (abm-save-to-file))) 238 | 239 | ;; visit the stored bookmark 240 | (defun abm-restore-killed-buffer (bookmark) 241 | "Restore killed buffer BOOKMARK." 242 | (-when-let (visits (assq 'visits (cdr bookmark))) 243 | (cl-incf (cdr visits))) 244 | (-when-let (time (assq 'time (cdr bookmark))) 245 | (setf (cdr time) (current-time))) 246 | (bookmark-jump bookmark)) 247 | 248 | ;; minor mode 249 | 250 | ;;;###autoload 251 | (define-minor-mode autobookmarks-mode 252 | "Autobookmarks." 253 | :group 'autobookmarks 254 | :global t 255 | (if autobookmarks-mode 256 | (progn 257 | (add-hook 'kill-emacs-hook 'abm-save-to-file) 258 | (--each abm-visited-buffer-hooks 259 | (add-hook (car it) (cdr it) :append)) 260 | (add-hook 'kill-buffer-hook 'abm-handle-killed-buffer)) 261 | (remove-hook 'kill-emacs-hook 'abm-save-to-file) 262 | (--each abm-visited-buffer-hooks 263 | (remove-hook (car it) (cdr it))) 264 | (remove-hook 'kill-buffer-hook 'abm-handle-killed-buffer))) 265 | 266 | (provide 'autobookmarks) 267 | ;;; autobookmarks.el ends here 268 | --------------------------------------------------------------------------------