1 ;;; semantic-mru-bookmark.el --- Automatic bookmark tracking
3 ;; Copyright (C) 2007 Eric M. Ludlam
5 ;; Author: Eric M. Ludlam <eric@siege-engine.com>
6 ;; X-RCS: $Id: semantic-mru-bookmark.el,v 1.1 2007-11-26 15:10:41 michaels Exp $
8 ;; This program is free software; you can redistribute it and/or
9 ;; modify it under the terms of the GNU General Public License as
10 ;; published by the Free Software Foundation; either version 2, or (at
11 ;; your option) any later version.
13 ;; This program is distributed in the hope that it will be useful, but
14 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 ;; General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program; see the file COPYING. If not, write to
20 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 ;; Boston, MA 02110-1301, USA.
25 ;; Using editing hooks, track the most recently visited or poked tags,
26 ;; and keep a list of them, with the current point in from, and sorted
27 ;; by most recently used.
29 ;; I envision this would be used in place of switch-buffers once
30 ;; someone got the hang of it.
32 ;; I'd also like to see this used to provide some nice defaults for
33 ;; other programs where logical destinations or targets are the tags
34 ;; that have been recently edited.
38 ;; M-x global-semantic-mru-bookmark-mode RET
42 ;; C-x B <select a tag name> RET
44 ;; In the above, the history is pre-filled with the tags you recenetly
45 ;; edited in the order you edited them.
52 ;; Data structure for tracking MRU tag locations
54 (defclass semantic-bookmark (eieio-named)
57 :documentation "The TAG this bookmark belongs to.")
58 (parent :type (or semantic-tag null)
59 :documentation "The tag that is the parent of :tag.")
61 :documentation "The offset from `tag' start that is
62 somehow interesting.")
63 (filename :type string
64 :documentation "String the tag belongs to.
65 Set this when the tag gets unlinked from the buffer it belongs to.")
66 (frequency :type number
68 :documentation "Track the frequency this tag is visited.")
72 "The reason this tag is interesting.
73 Nice values are 'edit, 'read, 'jump, and 'mark.
74 edit - created because the tag text was edited.
75 read - created because point lingered in tag text.
76 jump - jumped to another tag from this tag.
77 mark - created a regular mark in this tag.")
81 (defmethod initialize-instance :AFTER ((sbm semantic-bookmark) &rest fields)
82 "Initialize the bookmark SBM with details about :tag."
85 (oset sbm filename (semantic-tag-file-name (oref sbm tag)))
87 (semantic-go-to-tag (oref sbm tag))
88 (oset sbm parent (semantic-current-tag-parent))))
89 (error (message "Error bookmarking tag.")))
92 (defmethod semantic-mrub-visit ((sbm semantic-bookmark))
93 "Visit the semantic tag bookmark SBM.
94 Uses `semantic-go-to-tag' and highlighting."
95 (with-slots (tag filename) sbm
97 (when (not (semantic-tag-buffer tag))
98 (let ((fn (or (semantic-tag-file-name tag)
100 (set-buffer (find-file-noselect fn))))
101 (semantic-go-to-tag (oref sbm tag) (oref sbm parent))
103 (switch-to-buffer (current-buffer))
104 (semantic-momentary-highlight-tag tag)
107 (defmethod semantic-mrub-update ((sbm semantic-bookmark) point reason)
108 "Update the existing bookmark SBM.
109 POINT is some important location.
110 REASON is a symbol. See slot `reason' on `semantic-bookmark'."
111 (with-slots (tag offset frequency) sbm
112 (setq offset (- point (semantic-tag-start tag)))
113 (setq frequency (1+ frequency))
115 (oset sbm reason reason)
118 (defmethod semantic-mrub-preflush ((sbm semantic-bookmark))
119 "Method called on a tag before the current buffer list of tags is flushed.
120 If there is a buffer match, unlink the tag."
121 (let ((tag (oref sbm tag))
122 (parent (oref sbm parent)))
123 (let ((b (semantic-tag-buffer tag)))
124 (when (and b (eq b (current-buffer)))
125 (semantic--tag-unlink-from-buffer tag)))
128 (let ((b (semantic-tag-buffer parent)))
129 (when (and b (eq b (current-buffer)))
130 (semantic--tag-unlink-from-buffer parent))))))
132 (defclass semantic-bookmark-ring ()
133 ((ring :initarg :ring
136 "List of `semantic-bookmark' objects.
137 This list is maintained as a list with the first item
138 being the current location, and the rest being a list of
139 items that were recently visited.")
140 (current-index :initform 0
143 "The current index into RING for some operation.
144 User commands use this to move through the ring, or reset.")
146 "Track the current MRU stack of bookmarks.
147 We can't use the built-in ring data structure because we need
148 to delete some items from the ring when we don't have the data.")
150 (defvar semantic-mru-bookmark-ring (semantic-bookmark-ring
152 :ring (make-ring 20))
153 "The MRU bookmark ring.
154 This ring tracks the most recent active tags of interest.")
156 (defmethod semantic-mrub-push ((sbr semantic-bookmark-ring) point
158 "Add a bookmark to the ring SBR from POINT.
159 REASON is why it is being pushed. See doc for `semantic-bookmark'
160 for possible reasons.
161 The resulting bookmark is then sorted within the ring."
162 (let* ((ring (oref sbr ring))
163 (tag (semantic-current-tag))
164 (elts (ring-elements ring))
165 (sbm (object-assoc tag 'tag elts)))
167 ;; Delete the old mark from the ringn
168 (let ((idx (- (length elts) (length (memq sbm elts)))))
169 (ring-remove ring idx))
171 (setq sbm (semantic-bookmark (semantic-tag-name tag)
173 ;; Take the mark, and update it for the current state.
174 (ring-insert ring sbm)
175 (semantic-mrub-update sbm point reason)
178 (defun semantic-mrub-cache-flush-fcn ()
179 "Function called in the `semantic-before-toplevel-cache-flush-hook`.
180 Cause tags in the ring to become unlinked."
181 (let ((elts (ring-elements (oref semantic-mru-bookmark-ring ring)))
182 (buf (current-buffer)))
184 (semantic-mrub-preflush e))))
186 (add-hook 'semantic-before-toplevel-cache-flush-hook
187 'semantic-mrub-cache-flush-fcn)
191 (defvar semantic-mrub-last-overlay nil
192 "The last overlay bumped by `semantic-mru-bookmark-change-hook-fcn'.")
194 (defun semantic-mru-bookmark-change-hook-fcn (overlay)
195 "Function set into `semantic-edits-new/move-change-hook's.
196 Argument OVERLAY is the overlay created to mark the change.
197 This function will set the face property on this overlay."
199 (when (not (eq overlay semantic-mrub-last-overlay))
200 (setq semantic-mrub-last-overlay overlay)
201 (semantic-mrub-push semantic-mru-bookmark-ring
207 ;; Tracking minor mode.
210 (defun global-semantic-mru-bookmark-mode (&optional arg)
211 "Toggle global use of option `semantic-mru-bookmark-mode'.
212 If ARG is positive, enable, if it is negative, disable.
213 If ARG is nil, then toggle."
215 (setq global-semantic-mru-bookmark-mode
216 (semantic-toggle-minor-mode-globally
217 'semantic-mru-bookmark-mode arg)))
220 (defcustom global-semantic-mru-bookmark-mode nil
221 "*If non-nil enable global use of variable `semantic-mru-bookmark-mode'.
222 When this mode is enabled, changes made to a buffer are highlighted
223 until the buffer is reparsed."
225 :group 'semantic-modes
227 :require 'semantic-util-modes
228 :initialize 'custom-initialize-default
229 :set (lambda (sym val)
230 (global-semantic-mru-bookmark-mode (if val 1 -1))))
232 (defcustom semantic-mru-bookmark-mode-hook nil
233 "*Hook run at the end of function `semantic-mru-bookmark-mode'."
237 (defvar semantic-mru-bookmark-mode-map
238 (let ((km (make-sparse-keymap)))
239 (define-key km "\C-xB" 'semantic-mrub-switch-tags)
241 "Keymap for mru-bookmark minor mode.")
243 (defvar semantic-mru-bookmark-mode nil
244 "Non-nil if mru-bookmark minor mode is enabled.
245 Use the command `semantic-mru-bookmark-mode' to change this variable.")
246 (make-variable-buffer-local 'semantic-mru-bookmark-mode)
248 (defun semantic-mru-bookmark-mode-setup ()
249 "Setup option `semantic-mru-bookmark-mode'.
250 The minor mode can be turned on only if semantic feature is available
251 and the current buffer was set up for parsing. When minor mode is
252 enabled parse the current buffer if needed. Return non-nil if the
253 minor mode is enabled."
254 (if semantic-mru-bookmark-mode
255 (if (not (and (featurep 'semantic) (semantic-active-p)))
257 ;; Disable minor mode if semantic stuff not available
258 (setq semantic-mru-bookmark-mode nil)
259 (error "Buffer %s was not set up for parsing"
261 (semantic-make-local-hook 'semantic-edits-new-change-hooks)
262 (add-hook 'semantic-edits-new-change-hooks
263 'semantic-mru-bookmark-change-hook-fcn nil t)
264 (add-hook 'semantic-edits-move-change-hooks
265 'semantic-mru-bookmark-change-hook-fcn nil t)
268 (remove-hook 'semantic-edits-new-change-hooks
269 'semantic-mru-bookmark-change-hook-fcn t)
270 (remove-hook 'semantic-edits-move-change-hooks
271 'semantic-mru-bookmark-change-hook-fcn t)
273 semantic-mru-bookmark-mode)
276 (defun semantic-mru-bookmark-mode (&optional arg)
277 "Minor mode for tracking tag-based bookmarks automatically.
278 Tag based bookmarks a tracked based on editing and viewing habits
279 and can then be navigated via the MRU bookmark keymap.
281 \\{semantic-mru-bookmark-mode-map}
283 With prefix argument ARG, turn on if positive, otherwise off. The
284 minor mode can be turned on only if semantic feature is available and
285 the current buffer was set up for parsing. Return non-nil if the
286 minor mode is enabled."
288 (list (or current-prefix-arg
289 (if semantic-mru-bookmark-mode 0 1))))
290 (setq semantic-mru-bookmark-mode
293 (prefix-numeric-value arg)
295 (not semantic-mru-bookmark-mode)))
296 (semantic-mru-bookmark-mode-setup)
297 (run-hooks 'semantic-mru-bookmark-mode-hook)
299 (message "mru-bookmark minor mode %sabled"
300 (if semantic-mru-bookmark-mode "en" "dis")))
301 (semantic-mode-line-update)
302 semantic-mru-bookmark-mode)
304 (semantic-add-minor-mode 'semantic-mru-bookmark-mode
306 semantic-mru-bookmark-mode-map)
310 ;; Ask the user for a tag in MRU order.
311 (defun semantic-mrub-read-history nil
312 "History of `semantic-mrub-completing-read'.")
314 (defun semantic-mrub-completing-read (prompt)
315 "Do a `completing-read' on elements from the mru bookmark ring.
316 Argument PROMPT is the promot to use when reading."
317 (if (ring-empty-p (oref semantic-mru-bookmark-ring ring))
318 (error "Semantic Bookmark ring is currently empty"))
319 (let* ((elts (ring-elements (oref semantic-mru-bookmark-ring ring)))
322 (alist (object-assoc-list :object-name elts))
323 (semantic-mrub-read-history nil)
325 ;; Don't include the current tag.. only those that come after.
326 (if (semantic-equivalent-tag-p (oref first tag)
327 (semantic-current-tag))
328 (setq first (car (cdr elts))))
329 ;; Create a fake history list so we don't have to bind
330 ;; M-p and M-n to our special cause.
332 (setq semantic-mrub-read-history (cons (oref (car elts) :object-name)
333 semantic-mrub-read-history))
334 (setq elts (cdr elts)))
335 (setq semantic-mrub-read-history (nreverse semantic-mrub-read-history))
336 ;; Do the read/prompt
337 (let ((prompt (if first (format "%s (%s): " prompt
338 (semantic-format-tag-name
341 (concat prompt ": ")))
344 (completing-read prompt alist nil nil nil 'semantic-mrub-read-history)))
345 ;; Calculate the return tag.
348 ;; Return the bookmark object.
349 (setq ans (assoc ans alist))
352 ;; no match. Custom word. Look it up somwhere?
356 (defun semantic-mrub-switch-tags (tagmark)
357 "Switch tags to TAGMARK.
358 Selects a new tag via promt through the mru tag ring.
359 Jumps to the tag and highlights it briefly."
360 (interactive (list (semantic-mrub-completing-read "Switch to tag")))
361 (if (not (semantic-bookmark-p tagmark))
362 (signal 'wrong-type-argument tagmark))
364 (semantic-mrub-push semantic-mru-bookmark-ring
367 (semantic-mrub-visit tagmark)
372 ;; Advise some commands to help set tag marks.
373 (defadvice set-mark-command (around semantic-mru-bookmark activate)
374 "Set this buffer's mark to POS.
375 If `semantic-mru-bookmark-mode' is active, also push a tag onto
376 the mru bookmark stack."
377 (when (and semantic-mru-bookmark-mode (interactive-p))
378 (semantic-mrub-push semantic-mru-bookmark-ring
386 (defun semantic-adebug-mrub ()
387 "Display a list of items in the MRU bookmarks list.
388 Useful for debugging mrub problems."
390 (let* ((out semantic-mru-bookmark-ring)
391 (ab (semantic-adebug-new-buffer "*TAG RING ADEBUG*"))
394 (semantic-adebug-insert-object-fields out "]")
398 (provide 'semantic-mru-bookmark)
400 ;;; semantic-mru-bookmark.el ends here