changeset 104507:51e316109fba

lisp/cedet/semantic/mru-bookmark.el: New file.
author Chong Yidong <cyd@stupidchicken.com>
date Mon, 21 Sep 2009 02:26:07 +0000
parents 801834237f9c
children 90ca5d588aa9
files lisp/cedet/semantic/mru-bookmark.el
diffstat 1 files changed, 458 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/cedet/semantic/mru-bookmark.el	Mon Sep 21 02:26:07 2009 +0000
@@ -0,0 +1,458 @@
+;;; semantic/mru-bookmark.el --- Automatic bookmark tracking
+
+;; Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
+
+;; Author: Eric M. Ludlam <eric@siege-engine.com>
+
+;; 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 3 of the License, 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
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; Using editing hooks, track the most recently visited or poked tags,
+;; and keep a list of them, with the current point in from, and sorted
+;; by most recently used.
+;;
+;; I envision this would be used in place of switch-buffers once
+;; someone got the hang of it.
+;;
+;; I'd also like to see this used to provide some nice defaults for
+;; other programs where logical destinations or targets are the tags
+;; that have been recently edited.
+;;
+;; Quick Start:
+;;
+;; M-x global-semantic-mru-bookmark-mode RET
+;;
+;; < edit some code >
+;;
+;; C-x B  <select a tag name> RET
+;;
+;; In the above, the history is pre-filled with the tags you recenetly
+;; edited in the order you edited them.
+
+;;; Code:
+
+(require 'semantic)
+(require 'eieio-base)
+(require 'ring)
+
+(declare-function data-debug-new-buffer "data-debug")
+(declare-function data-debug-insert-object-slots "eieio-datadebug")
+(declare-function semantic-momentary-highlight-tag "semantic/decorate")
+
+;;; TRACKING CORE
+;;
+;; Data structure for tracking MRU tag locations
+
+(defclass semantic-bookmark (eieio-named)
+  ((tag :initarg :tag
+	:type semantic-tag
+	:documentation "The TAG this bookmark belongs to.")
+   (parent :type (or semantic-tag null)
+	   :documentation "The tag that is the parent of :tag.")
+   (offset :type number
+	 :documentation "The offset from `tag' start that is
+somehow interesting.")
+   (filename :type string
+	     :documentation "String the tag belongs to.
+Set this when the tag gets unlinked from the buffer it belongs to.")
+   (frequency :type number
+	      :initform 0
+	      :documentation "Track the frequency this tag is visited.")
+   (reason :type symbol
+	   :initform t
+	   :documentation
+	   "The reason this tag is interesting.
+Nice values are 'edit, 'read, 'jump, and 'mark.
+ edit - created because the tag text was edited.
+ read - created because point lingered in tag text.
+ jump - jumped to another tag from this tag.
+ mark - created a regular mark in this tag.")
+   )
+  "A single bookmark.")
+
+(defmethod initialize-instance :AFTER ((sbm semantic-bookmark) &rest fields)
+  "Initialize the bookmark SBM with details about :tag."
+  (condition-case nil
+      (save-excursion
+	(oset sbm filename (semantic-tag-file-name (oref sbm tag)))
+	(semantic-go-to-tag (oref sbm tag))
+	(oset sbm parent (semantic-current-tag-parent)))
+    (error (message "Error bookmarking tag.")))
+  )
+
+(defmethod semantic-mrub-visit ((sbm semantic-bookmark))
+  "Visit the semantic tag bookmark SBM.
+Uses `semantic-go-to-tag' and highlighting."
+  (require 'semantic/decorate)
+  (with-slots (tag filename) sbm
+    ;; Go to the tag
+    (when (not (semantic-tag-in-buffer-p tag))
+      (let ((fn (or (semantic-tag-file-name tag)
+ 		    filename)))
+ 	(set-buffer (find-file-noselect fn))))
+    (semantic-go-to-tag (oref sbm tag) (oref sbm parent))
+    ;; Go back to the offset.
+    (condition-case nil
+	(let ((o (oref sbm offset)))
+	  (forward-char o))
+      (error nil))
+    ;; make it visible
+    (switch-to-buffer (current-buffer))
+    (semantic-momentary-highlight-tag tag)
+    ))
+
+(defmethod semantic-mrub-update ((sbm semantic-bookmark) point reason)
+  "Update the existing bookmark SBM.
+POINT is some important location.
+REASON is a symbol.  See slot `reason' on `semantic-bookmark'."
+  (condition-case nil
+      (progn
+	(with-slots (tag offset frequency) sbm
+	  (setq offset (- point (semantic-tag-start tag)))
+	  (setq frequency (1+ frequency))
+	  )
+	(oset sbm reason reason))
+    ;; This can fail on XEmacs at miscelaneous times.
+    (error nil))
+  )
+
+(defmethod semantic-mrub-preflush ((sbm semantic-bookmark))
+  "Method called on a tag before the current buffer list of tags is flushed.
+If there is a buffer match, unlink the tag."
+  (let ((tag (oref sbm tag))
+	(parent (when (slot-boundp sbm 'parent)
+		  (oref sbm parent))))
+    (let ((b (semantic-tag-in-buffer-p tag)))
+      (when (and b (eq b (current-buffer)))
+	(semantic--tag-unlink-from-buffer tag)))
+
+    (when parent
+      (let ((b (semantic-tag-in-buffer-p parent)))
+	(when (and b (eq b (current-buffer)))
+	  (semantic--tag-unlink-from-buffer parent))))))
+
+(defclass semantic-bookmark-ring ()
+  ((ring :initarg :ring
+	 :type ring
+	 :documentation
+	 "List of `semantic-bookmark' objects.
+This list is maintained as a list with the first item
+being the current location, and the rest being a list of
+items that were recently visited.")
+   (current-index :initform 0
+		  :type number
+		  :documentation
+		  "The current index into RING for some operation.
+User commands use this to move through the ring, or reset.")
+   )
+  "Track the current MRU stack of bookmarks.
+We can't use the built-in ring data structure because we need
+to delete some items from the ring when we don't have the data.")
+
+(defvar semantic-mru-bookmark-ring (semantic-bookmark-ring
+				    "Ring"
+				    :ring (make-ring 20))
+  "The MRU bookmark ring.
+This ring tracks the most recent active tags of interest.")
+
+(defun semantic-mrub-find-nearby-tag (point)
+  "Find a nearby tag to be pushed for this current location.
+Argument POINT is where to find the tag near."
+  ;; I thought this was a good idea, but it is not!
+  ;;(semantic-fetch-tags) ;; Make sure everything is up-to-date.
+  (let ((tag (semantic-current-tag)))
+    (when (or (not tag) (semantic-tag-of-class-p tag 'type))
+      (let ((nearby (or (semantic-find-tag-by-overlay-next point)
+			(semantic-find-tag-by-overlay-prev point))))
+	(when nearby (setq tag nearby))))
+    tag))
+
+(defmethod semantic-mrub-push ((sbr semantic-bookmark-ring) point
+			       &optional reason)
+  "Add a bookmark to the ring SBR from POINT.
+REASON is why it is being pushed.  See doc for `semantic-bookmark'
+for possible reasons.
+The resulting bookmark is then sorted within the ring."
+  (let* ((ring (oref sbr ring))
+	 (tag (semantic-mrub-find-nearby-tag (point)))
+	 (idx 0))
+    (when tag
+      (while (and (not (ring-empty-p ring)) (< idx (ring-size ring)))
+	(if (semantic-tag-similar-p (oref (ring-ref ring idx) tag)
+				    tag)
+	    (ring-remove ring idx))
+	(setq idx (1+ idx)))
+      ;; Create a new mark
+      (let ((sbm (semantic-bookmark (semantic-tag-name tag)
+				    :tag tag)))
+	;; Take the mark, and update it for the current state.
+	(ring-insert ring sbm)
+	(semantic-mrub-update sbm point reason))
+      )))
+
+(defun semantic-mrub-cache-flush-fcn ()
+  "Function called in the `semantic-before-toplevel-cache-flush-hook`.
+Cause tags in the ring to become unlinked."
+  (let* ((ring (oref semantic-mru-bookmark-ring ring))
+	 (len (ring-length ring))
+	 (idx 0)
+	 )
+    (while (< idx len)
+      (semantic-mrub-preflush (ring-ref ring idx))
+      (setq idx (1+ idx)))))
+
+(add-hook 'semantic-before-toplevel-cache-flush-hook
+	  'semantic-mrub-cache-flush-fcn)
+
+;;; EDIT tracker
+;;
+(defvar semantic-mrub-last-overlay nil
+  "The last overlay bumped by `semantic-mru-bookmark-change-hook-fcn'.")
+
+(defun semantic-mru-bookmark-change-hook-fcn (overlay)
+  "Function set into `semantic-edits-new/move-change-hook's.
+Argument OVERLAY is the overlay created to mark the change.
+This function pushes tags onto the tag ring."
+  ;; Dup?
+  (when (not (eq overlay semantic-mrub-last-overlay))
+    (setq semantic-mrub-last-overlay overlay)
+    (semantic-mrub-push semantic-mru-bookmark-ring
+			(point)
+			'edit)))
+
+;;; MINOR MODE
+;;
+;; Tracking minor mode.
+
+(defcustom global-semantic-mru-bookmark-mode nil
+  "*If non-nil enable global use of variable `semantic-mru-bookmark-mode'.
+When this mode is enabled, changes made to a buffer are highlighted
+until the buffer is reparsed."
+  :group 'semantic
+  :group 'semantic-modes
+  :type 'boolean
+  :require 'semantic-util-modes
+  :initialize 'custom-initialize-default
+  :set (lambda (sym val)
+         (global-semantic-mru-bookmark-mode (if val 1 -1))))
+
+;;;###autoload
+(defun global-semantic-mru-bookmark-mode (&optional arg)
+  "Toggle global use of option `semantic-mru-bookmark-mode'.
+If ARG is positive, enable, if it is negative, disable.
+If ARG is nil, then toggle."
+  (interactive "P")
+  (setq global-semantic-mru-bookmark-mode
+        (semantic-toggle-minor-mode-globally
+         'semantic-mru-bookmark-mode arg)))
+
+(defcustom semantic-mru-bookmark-mode-hook nil
+  "*Hook run at the end of function `semantic-mru-bookmark-mode'."
+  :group 'semantic
+  :type 'hook)
+
+(defvar semantic-mru-bookmark-mode-map
+  (let ((km (make-sparse-keymap)))
+    (define-key km "\C-xB" 'semantic-mrub-switch-tags)
+    km)
+  "Keymap for mru-bookmark minor mode.")
+
+(defvar semantic-mru-bookmark-mode nil
+  "Non-nil if mru-bookmark minor mode is enabled.
+Use the command `semantic-mru-bookmark-mode' to change this variable.")
+(make-variable-buffer-local 'semantic-mru-bookmark-mode)
+
+(defun semantic-mru-bookmark-mode-setup ()
+  "Setup option `semantic-mru-bookmark-mode'.
+The minor mode can be turned on only if semantic feature is available
+and the current buffer was set up for parsing.  When minor mode is
+enabled parse the current buffer if needed.  Return non-nil if the
+minor mode is enabled."
+  (if semantic-mru-bookmark-mode
+      (if (not (and (featurep 'semantic) (semantic-active-p)))
+	  (progn
+            ;; Disable minor mode if semantic stuff not available
+            (setq semantic-mru-bookmark-mode nil)
+            (error "Buffer %s was not set up for parsing"
+                   (buffer-name)))
+        (semantic-make-local-hook 'semantic-edits-new-change-hooks)
+        (add-hook 'semantic-edits-new-change-hooks
+                  'semantic-mru-bookmark-change-hook-fcn nil t)
+        (add-hook 'semantic-edits-move-change-hooks
+                  'semantic-mru-bookmark-change-hook-fcn nil t)
+        )
+    ;; Remove hooks
+    (remove-hook 'semantic-edits-new-change-hooks
+		 'semantic-mru-bookmark-change-hook-fcn t)
+    (remove-hook 'semantic-edits-move-change-hooks
+		 'semantic-mru-bookmark-change-hook-fcn t)
+    )
+  semantic-mru-bookmark-mode)
+
+(defun semantic-mru-bookmark-mode (&optional arg)
+  "Minor mode for tracking tag-based bookmarks automatically.
+Tag based bookmarks a tracked based on editing and viewing habits
+and can then be navigated via the MRU bookmark keymap.
+
+\\{semantic-mru-bookmark-mode-map}
+
+With prefix argument ARG, turn on if positive, otherwise off.  The
+minor mode can be turned on only if semantic feature is available and
+the current buffer was set up for parsing.  Return non-nil if the
+minor mode is enabled."
+  (interactive
+   (list (or current-prefix-arg
+             (if semantic-mru-bookmark-mode 0 1))))
+  (setq semantic-mru-bookmark-mode
+        (if arg
+            (>
+             (prefix-numeric-value arg)
+             0)
+          (not semantic-mru-bookmark-mode)))
+  (semantic-mru-bookmark-mode-setup)
+  (run-hooks 'semantic-mru-bookmark-mode-hook)
+  (if (interactive-p)
+      (message "mru-bookmark minor mode %sabled"
+               (if semantic-mru-bookmark-mode "en" "dis")))
+  (semantic-mode-line-update)
+  semantic-mru-bookmark-mode)
+
+(semantic-add-minor-mode 'semantic-mru-bookmark-mode
+                         "k"
+                         semantic-mru-bookmark-mode-map)
+
+;;; COMPLETING READ
+;;
+;; Ask the user for a tag in MRU order.
+(defun semantic-mrub-read-history nil
+  "History of `semantic-mrub-completing-read'.")
+
+(defun semantic-mrub-ring-to-assoc-list (ring)
+  "Convert RING into an association list for completion."
+  (let ((idx 0)
+	(len (ring-length ring))
+	(al nil))
+    (while (< idx len)
+      (let ((r (ring-ref ring idx)))
+	(setq al (cons (cons (oref r :object-name) r)
+		       al)))
+      (setq idx (1+ idx)))
+    (nreverse al)))
+
+(defun semantic-mrub-completing-read (prompt)
+  "Do a `completing-read' on elements from the mru bookmark ring.
+Argument PROMPT is the promot to use when reading."
+  (if (ring-empty-p (oref semantic-mru-bookmark-ring ring))
+      (error "Semantic Bookmark ring is currently empty"))
+  (let* ((ring (oref semantic-mru-bookmark-ring ring))
+	 (ans nil)
+	 (alist (semantic-mrub-ring-to-assoc-list ring))
+	 (first (cdr (car alist)))
+	 (semantic-mrub-read-history nil)
+	 )
+    ;; Don't include the current tag.. only those that come after.
+    (if (semantic-equivalent-tag-p (oref first tag)
+				   (semantic-current-tag))
+	(setq first (cdr (car (cdr alist)))))
+    ;; Create a fake history list so we don't have to bind
+    ;; M-p and M-n to our special cause.
+    (let ((elts (reverse alist)))
+      (while elts
+	(setq semantic-mrub-read-history
+	      (cons (car (car elts)) semantic-mrub-read-history))
+	(setq elts (cdr elts))))
+    (setq semantic-mrub-read-history (nreverse semantic-mrub-read-history))
+
+    ;; Do the read/prompt
+    (let ((prompt (if first (format "%s (%s): " prompt
+				    (semantic-format-tag-name
+				     (oref first tag) t)
+				    )
+		    (concat prompt ": ")))
+	  )
+      (setq ans
+	    (completing-read prompt alist nil nil nil 'semantic-mrub-read-history)))
+    ;; Calculate the return tag.
+    (if (string= ans "")
+	(setq ans first)
+      ;; Return the bookmark object.
+      (setq ans (assoc ans alist))
+      (if ans
+	  (cdr ans)
+	;; no match.  Custom word.  Look it up somwhere?
+	nil)
+      )))
+
+(defun semantic-mrub-switch-tags (tagmark)
+  "Switch tags to TAGMARK.
+Selects a new tag via promt through the mru tag ring.
+Jumps to the tag and highlights it briefly."
+  (interactive (list (semantic-mrub-completing-read "Switch to tag")))
+  (if (not (semantic-bookmark-p tagmark))
+      (signal 'wrong-type-argument tagmark))
+
+  (semantic-mrub-push semantic-mru-bookmark-ring
+		      (point)
+		      'jump)
+  (semantic-mrub-visit tagmark)
+  )
+
+;;; ADVICE
+;;
+;; Advise some commands to help set tag marks.
+;; (defadvice push-mark (around semantic-mru-bookmark activate)
+;;   "Push a mark at LOCATION with NOMSG and ACTIVATE passed to `push-mark'.
+;; If `semantic-mru-bookmark-mode' is active, also push a tag onto
+;; the mru bookmark stack."
+;;   (semantic-mrub-push semantic-mru-bookmark-ring
+;; 		      (point)
+;; 		      'mark)
+;;   ad-do-it)
+
+;(defadvice set-mark-command (around semantic-mru-bookmark activate)
+;  "Set this buffer's mark to POS.
+;If `semantic-mru-bookmark-mode' is active, also push a tag onto
+;the mru bookmark stack."
+;  (when (and semantic-mru-bookmark-mode (interactive-p))
+;    (semantic-mrub-push semantic-mru-bookmark-ring
+;			(point)
+;			'mark))
+;  ad-do-it)
+
+
+;;; Debugging
+;;
+(defun semantic-adebug-mrub ()
+  "Display a list of items in the MRU bookmarks list.
+Useful for debugging mrub problems."
+  (interactive)
+  (require 'eieio-datadebug)
+  (let* ((out semantic-mru-bookmark-ring))
+    (data-debug-new-buffer "*TAG RING ADEBUG*")
+    (data-debug-insert-object-slots out "]")
+    ))
+
+
+(provide 'semantic/mru-bookmark)
+
+;; Local variables:
+;; generated-autoload-file: "loaddefs.el"
+;; generated-autoload-feature: semantic/loaddefs
+;; generated-autoload-load-name: "semantic/mru-bookmark"
+;; End:
+
+;;; semantic/mru-bookmark.el ends here