;; -*- emacs-lisp -*- ;; ;; bookmarknav.el - Use a Netscape-like navigation when working ;; with etags and bookmarks. ;; ;; Copyright (C) 1999 Ovidiu Predescu ;; ;; Author: Ovidiu Predescu ;; Created: September 1999 ;; ;; Cleaned up by Christoph Conrad ;; January 2001 ;; ;; This program 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 ;; of the License, or (at your option) any later version. ;; ;; This program 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 this program; if not, write to the Free Software ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ;; ;; Setup: put this file in your Lisp path and add the following line in ;; your .emacs: ;; ;; (require 'bookmarknav) ;; (defvar bookmarknav-current-bookmark nil "The current bookmark which is displayed") (defvar bookmarknav-bookmark-stack nil "The stack of bookmarks to visit back") (defvar bookmarknav-bookmark-redo-stack nil "The stack of bookmarks to visit forward") (defvar bookmarknav-soundfile 'drum "The sound to be played when no more bookmarks are found") (defvar bookmarknav-tags-file (convert-standard-filename "~/.emacs.tag") "The name of the file to contain bookmarks saved accross sessions. This file contains bookmarks used by this package only, the normal bookmarks should be in the standard bookmark's package files.") (defun bookmarknav-reduce (function alist) (if (not (null alist)) (let ((arg (car alist))) (if (apply function (list arg)) (cons arg (bookmarknav-reduce function (cdr alist))) (bookmarknav-reduce function (cdr alist)))) nil)) (defun bookmarknav-bookmarks-equal (bk1 bk2) (if (or (null bk1) (null bk2)) nil (and (string= (bookmark-get-filename bk1) (bookmark-get-filename bk2)) (= (bookmark-get-position bk1) (bookmark-get-position bk2))))) (defun bookmarknav-no-bookmarks() (if (string-match "XEmacs\\|Lucid" emacs-version) (progn (message "No more bookmarks.") (play-sound bookmarknav-soundfile 100)) (ding 'noterminate))) (defun bookmarknav-new-bookmark () (interactive) ;; Remove all the bookmarks from the redo stack (mapcar '(lambda (x) (bookmark-delete x t)) bookmarknav-bookmark-redo-stack) (setq bookmarknav-bookmark-redo-stack (list)) ;; Setup a temporary bookmark at this point and see if it's equal to ;; the bookmarknav-current-bookmark. If so don't do anything as we ;; want to avoid creating multiple bookmarks at the same point. (let ((temp "temporary")) (bookmark-set temp) (if (not (bookmarknav-bookmarks-equal temp bookmarknav-current-bookmark)) ;; Move the bookmarknav-current-bookmark into ;; bookmarknav-bookmark-stack (progn (if bookmarknav-current-bookmark (setq bookmarknav-bookmark-stack (cons bookmarknav-current-bookmark bookmarknav-bookmark-stack))) (if (null bookmarknav-bookmark-stack) (setq bookmarknav-current-bookmark "1") (setq bookmarknav-current-bookmark (number-to-string (1+ (string-to-number (car bookmarknav-bookmark-stack)))))) (bookmark-set bookmarknav-current-bookmark) (message "Bookmark set."))) (bookmark-delete temp))) (defun bookmarknav-delete-bookmark () "Delete the bookmark at the current position. It doesn't have any effect if there's no bookmark setup at the current position." (interactive) ;; Setup a temporary bookmark at this point and check if it's equal ;; to the bookmarknav-current-bookmark; if so remove the ;; current-breakpoint (let ((temp "temporary")) (bookmark-set temp) (if (bookmarknav-bookmarks-equal temp bookmarknav-current-bookmark) (progn (message "Bookmark removed.") (bookmark-delete bookmarknav-current-bookmark) (setq bookmarknav-current-bookmark nil))) (bookmark-delete temp))) (defun bookmarknav-find-tag () (interactive) ;; Remember the current position (bookmarknav-new-bookmark) (find-tag (find-tag-tag "Find tag: ")) (bookmarknav-new-bookmark)) (defun bookmarknav-loop-continue () (interactive) (tags-loop-continue) (bookmarknav-new-bookmark)) (defun bookmarknav-go-back () (interactive) ;; Try to move to the location of the last bookmark if we are in a ;; different location than its position. We do this by setting up a ;; temporary bookmark at this location and comparing it with the ;; bookmarknav-current-bookmark. (let ((temp "temporary")) (bookmark-set temp) (if (and (null bookmarknav-current-bookmark) (not (null bookmarknav-bookmark-stack))) ;; Get the last element from bookmarknav-bookmark-stack and ;; assign it to bookmarknav-current-bookmark (progn (setq bookmarknav-current-bookmark (car bookmarknav-bookmark-stack)) (setq bookmarknav-bookmark-stack (cdr bookmarknav-bookmark-stack)))) (if (and bookmarknav-current-bookmark (not (bookmarknav-bookmarks-equal temp bookmarknav-current-bookmark))) (bookmark-jump bookmarknav-current-bookmark) (if (null bookmarknav-bookmark-stack) (progn (message "No more bookmarks.") (bookmarknav-no-bookmarks)) (progn (if bookmarknav-current-bookmark (setq bookmarknav-bookmark-redo-stack (cons bookmarknav-current-bookmark bookmarknav-bookmark-redo-stack))) (setq bookmarknav-current-bookmark (car bookmarknav-bookmark-stack)) (setq bookmarknav-bookmark-stack (cdr bookmarknav-bookmark-stack)) (bookmark-jump bookmarknav-current-bookmark) nil))) (bookmark-delete temp))) (defun bookmarknav-go-forward () (interactive) (if (null bookmarknav-bookmark-redo-stack) (progn (message "No more bookmarks.") (bookmarknav-no-bookmarks)) (progn (if bookmarknav-current-bookmark (setq bookmarknav-bookmark-stack (cons bookmarknav-current-bookmark bookmarknav-bookmark-stack))) (setq bookmarknav-current-bookmark (car bookmarknav-bookmark-redo-stack)) (setq bookmarknav-bookmark-redo-stack (cdr bookmarknav-bookmark-redo-stack)) (bookmark-jump bookmarknav-current-bookmark) nil))) (defun bookmarknav-load-bookmark-tags () ;; Load the .emacs.pos file that contains the saved bookmarks from a ;; previous session. (bookmark-maybe-load-default-file) (let ((bookmark-alist-saved bookmark-alist) (error nil)) (setq bookmark-alist nil) (condition-case nil (bookmark-load bookmarknav-tags-file nil nil) (error (setq error t))) (if (not error) ;; We successfully read the bookmarks file. Get the name of ;; the bookmarks and append them to ;; bookmarknav-bookmark-redo-stack. (progn (setq bookmarknav-bookmark-redo-stack (reverse (mapcar 'bookmark-name-from-full-record bookmark-alist))) (setq bookmark-alist-saved (append bookmark-alist-saved bookmark-alist)))) (setq bookmark-alist bookmark-alist-saved))) ;;;###autoload (add-hook 'bookmark-load-hook 'bookmarknav-load-bookmark-tags) (defun bookmarknav-update-bookmarks () (if (not (featurep 'bookmark)) nil (let* ((all-tags (append bookmarknav-bookmark-stack (list bookmarknav-current-bookmark) bookmarknav-bookmark-redo-stack)) (part-of-tags '(lambda (x) (member (bookmark-name-from-full-record x) all-tags))) (not-part-of-tags '(lambda (x) (not (member (bookmark-name-from-full-record x) all-tags)))) (my-bookmarks (bookmarknav-reduce part-of-tags bookmark-alist)) (remaining-bookmarks (bookmarknav-reduce not-part-of-tags bookmark-alist))) (setq bookmark-alist my-bookmarks) (bookmark-write-file bookmarknav-tags-file) (setq bookmark-alist remaining-bookmarks) (bookmark-write-file bookmark-default-file)))) ;;;###autoload (add-hook 'bookmark-exit-hook 'bookmarknav-update-bookmarks) (defun bookmarknav-bind-keys () (define-key global-map '(meta \.) 'bookmarknav-find-tag) (define-key global-map '(meta ,) 'bookmarknav-loop-continue) (define-key global-map '(meta left) 'bookmarknav-go-back) (define-key global-map '(meta right) 'bookmarknav-go-forward) (define-key global-map '(control meta \.) 'bookmarknav-new-bookmark) (define-key global-map '(control meta /) 'bookmarknav-delete-bookmark)) ;;;###autoload (bookmarknav-bind-keys) (require 'bookmark) (provide 'bookmarknav)