diff lisp/hyperbole/hmouse-tag.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/hyperbole/hmouse-tag.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,663 @@
+;;!emacs
+;;
+;; FILE:         hmouse-tag.el
+;; SUMMARY:      Smart Key support of programming language tags location.
+;; USAGE:        GNU Emacs Lisp Library
+;; KEYWORDS:     c, hypermedia, mouse, oop, tools
+;;
+;; AUTHOR:       Bob Weiner
+;; ORG:          Brown U.
+;;
+;; ORIG-DATE:    24-Aug-91
+;; LAST-MOD:     11-Sep-95 at 18:19:48 by Bob Weiner
+;;
+;; This file is part of Hyperbole.
+;; Available for use and distribution under the same terms as GNU Emacs.
+;;
+;; Copyright (C) 1991-1995, Free Software Foundation, Inc.
+;; Developed with support from Motorola Inc.
+;;
+;; DESCRIPTION:
+;;
+;;   Supports C, C++, Objective-C, Lisp, Fortran, and Assembly.
+;;   See the GNU Emacs manual for information on how to create a TAGS file
+;;     from the `etags' program.
+;;   Does not support the `ctags' tags file format.
+;;
+;;   YOU MUST APPROPRIATELY SET THE PUBLIC VARIABLES BELOW BEFORE USE.
+;;
+;; DESCRIP-END.
+
+;;; ************************************************************************
+;;; Other required Elisp libraries
+;;; ************************************************************************
+
+(if (cond ((or (featurep 'etags) (featurep 'tags))
+	   nil)
+	  ((or hyperb:lemacs-p hyperb:emacs19-p)
+	   ;; Force use of .elc file here since otherwise the bin/etags
+	   ;; executable might be found in a user's load-path by the load
+	   ;; command.
+	   (or (load "etags.elc" t nil t)
+	       (load "tags-fix" t)))
+	  ((load "tags" t)))
+    (provide 'tags))
+
+;;; ************************************************************************
+;;; Public variables
+;;; ************************************************************************
+
+(defvar smart-asm-include-dirs nil
+  "*Ordered list of directories to search for assembly language include files.
+Each directory must end with a directory separator.")
+
+(defconst smart-asm-include-regexp
+  "[ \t*#|;]*\\(include\\|lib\\)[ \t]+\\([^ \t\n\^M]+\\)"
+  "Regexp to match to assembly language include file lines.
+Include keyword matched is grouping 1.  File name is grouping 2 but may be
+missing its suffix, so add \".ins\" or \".inc\" if need be.
+Examples include:
+       INCLUDE GLOBALS
+         should jump to file \"globals.ins\"
+       lib conditionals_equ.inc
+         should include \"conditionals_equ.inc\"")
+
+(defvar smart-c-cpp-include-dirs '("/usr/include/")
+  "*Ordered list of include directories by default searched by C/C++ preprocessor.
+Each directory must end with a directory separator.  See also
+'smart-c-include-dirs'.")
+
+(defvar smart-c-include-dirs nil
+  "*Ordered list of directories to search for C/C++ include files.
+Each directory must end with a directory separator.  Directories normally
+searched by the C/C++ pre-processor should be set instead in
+'smart-c-cpp-include-dirs'.")
+
+(defvar smart-c-use-lib-man nil
+  "When non-nil makes 'smart-c' and 'smart-c++' display man pages for recognized lib symbols.
+When nil, 'smart-c' and 'smart-c++' look up only symbols defined in an etags
+TAGS file.
+
+Create the file ~/.CLIBS-LIST and populate it with the full pathnames (one per
+line) of all of the C/C++ libraries whose symbols you want to match against.
+Your MANPATH environment variable must include paths for the man pages of
+these libraries also.
+
+Your smart-clib-sym executable script must output a 1 if a symbol is from a
+C/C++ library listed in ~/.CLIBS-LIST or 0 if not!  Otherwise, don't set this
+variable to t.")
+
+(defconst smart-c-include-regexp
+  "[ \t/*]*#[ \t]*\\(include\\|import\\)[ \t]+\\([\"<]\\)\\([^\">]+\\)[\">]"
+  "Regexp to match to C, C++, or Objective-C include file lines.
+Include keyword matched is grouping 1.  Type of include, user-specified via
+double quote, or system-related starting with '<' is given by grouping 2.
+File name is grouping 3.")
+
+(defvar smart-emacs-tags-file nil
+  "*Full path name of etags file for GNU Emacs source.")
+
+;;; ************************************************************************
+;;; Public functions
+;;; ************************************************************************
+
+(defun smart-asm (&optional identifier next)
+  "Jumps to the definition of optional assembly IDENTIFIER or the one at point.
+Optional second arg NEXT means jump to next matching assembly tag.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If:
+ (1) on an include statement, the include file is displayed;
+     Look for include file in directory list 'smart-asm-include-dirs'.
+ (2) on an identifier, the identifier definition is displayed,
+     assuming the identifier is found within an 'etags' generated tag file
+     in the current directory or any of its ancestor directories."
+
+  (interactive)
+  (or
+   (if identifier nil (smart-asm-include-file))
+   (let ((tag (or identifier (smart-asm-at-tag-p))))
+     ;; Set free variable tags-file-name so that next 'find-tag' command uses
+     ;; whatever tags file is set here.
+     (setq tags-file-name (smart-tags-file buffer-file-name))
+     (message "Looking for '%s' in '%s'..." tag tags-file-name)
+     (condition-case ()
+	 (progn
+	   (funcall (if (br-in-browser)
+			'find-tag 'find-tag-other-window)
+		    tag next)
+	   (message "Found definition for '%s'." tag))
+       (error (message "'%s' not found in '%s'." tag tags-file-name)
+	      (beep))))))
+
+;;;###autoload
+(defun smart-asm-at-tag-p ()
+  "Return assembly tag name that point is within, else nil."
+  (let* ((identifier-chars "_.$a-zA-Z0-9")
+	 (identifier (concat "[_.$a-zA-Z][" identifier-chars "]*")))
+    (save-excursion
+      (skip-chars-backward identifier-chars)
+      (if (looking-at identifier)
+	  (buffer-substring (point) (match-end 0))))))
+
+(defun smart-asm-include-file ()
+  "If point is on an include file line, tries to display file.
+Returns non-nil iff on an include file line, even if file is not found.
+Look for include file in 'smart-asm-include-dirs' and add suffix \".ins\" or
+\".inc\" to filename if it lacks a suffix." 
+  (let ((opoint (point)))
+    ;; Some assemblers utilize the C preprocessor, so try that first.
+    (cond ((smart-c-include-file))
+	  ((progn (beginning-of-line)
+		  (looking-at smart-asm-include-regexp))
+	   (let ((file (buffer-substring (match-beginning 2) (match-end 2)))
+		 (path)
+		 (dir-list smart-asm-include-dirs))
+	     (goto-char opoint)
+	     (setq dir-list (cons (file-name-directory buffer-file-name)
+				  dir-list))
+	     (if (string-match "\\." file)
+		 (setq file (regexp-quote file))
+	       (setq file (concat (regexp-quote file) "\\.in[sc]$")))
+	     (while dir-list
+	       (setq dir-list
+		     (if (setq path (car (directory-files
+					   (car dir-list) t file)))
+			 nil
+		       (cdr dir-list))))
+	     ;;
+	     ;; If path exists, display file
+	     ;;
+	     (if path
+		 (if (and (file-readable-p path)
+			  (progn
+			    (if (br-in-browser)
+				(find-file path)
+			      (find-file-other-window path))
+			    (cond ((featurep 'asm-mode) t)
+				  ((load "asm-mode" nil 'nomessage)
+				   (provide 'asm-mode))
+				  (t
+				    (beep)
+				    (message
+				      "(smart-asm-include-file):  asm-mode undefined.")
+				    nil
+				    ))))
+		     nil
+		   (beep)
+		   (message "(smart-asm-include-file):  '%s' unreadable." path))
+	       (beep)
+	       (message "(smart-asm-include-file):  '%s' not found." file))
+	     path))
+	  ;; not on an include file line
+	  (t (goto-char opoint)
+	     nil))))
+
+
+(defun smart-c (&optional identifier next)
+  "Jumps to the definition of optional C IDENTIFIER or the one at point.
+Optional second arg NEXT means jump to next matching C tag.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If:
+ (1) on a '#include' statement, the include file is displayed;
+     Look for include file in directory lists 'smart-c-cpp-include-dirs'
+     and 'smart-c-include-dirs'.
+ (2) on a C identifier, the identifier definition is displayed,
+     assuming the identifier is found within an 'etags' generated tag file
+     in the current directory or any of its ancestor directories.
+ (3) if 'smart-c-use-lib-man' is non-nil, the C identifier is
+     recognized as a library symbol, and a man page is found for the
+     identifier, then the man page is displayed."
+
+  (interactive)
+  (or
+   (if identifier nil (smart-c-include-file))
+   (let ((tag (or identifier (smart-c-at-tag-p))))
+     ;; Set free variable tags-file-name so that next 'find-tag' command uses
+     ;; whatever tags file is set here.
+     (setq tags-file-name (smart-tags-file buffer-file-name))
+     (message "Looking for '%s' in '%s'..." tag tags-file-name)
+     (condition-case ()
+	 (progn
+	   (funcall (if (br-in-browser)
+			'find-tag 'find-tag-other-window)
+		    tag next)
+	   (message "Found definition for '%s'." tag))
+       (error
+	(if (not smart-c-use-lib-man)
+	    (progn (message "'%s' not found in '%s'." tag tags-file-name)
+		   (beep))
+	  (message "Checking if '%s' is a C library function..." tag)
+	  (if (smart-library-symbol tag)
+	      (progn (message "Displaying C library man page for '%s'." tag)
+		     (manual-entry tag))
+	    (message "'%s' not found in '%s' or C libraries."
+		     tag tags-file-name)
+	    (beep))))))))
+
+;;;###autoload
+(defun smart-c-at-tag-p ()
+  "Return C tag name that point is within, else nil."
+  (let* ((identifier-chars "_a-zA-Z0-9")
+	 (identifier (concat "[_a-zA-Z][" identifier-chars "]*")))
+    (save-excursion
+      (skip-chars-backward identifier-chars)
+      (if (looking-at identifier)
+	  (buffer-substring (point) (match-end 0))))))
+
+(defun smart-c-include-file ()
+  "If point is on an include file line, tries to display file.
+Returns non-nil iff on an include file line, even if file is not found.
+Look for include file in 'smart-c-cpp-include-dirs' and in directory list
+'smart-c-include-dirs'."
+  (let ((opoint (point)))
+    (beginning-of-line)
+    (if (looking-at smart-c-include-regexp)
+	(let ((incl-type (string-to-char
+			  (buffer-substring (match-beginning 2)
+					    (1+ (match-beginning 2)))))
+	      (file (buffer-substring (match-beginning 3) (match-end 3)))
+	      (path)
+	      (dir-list smart-c-include-dirs)
+	      (found))
+	  (goto-char opoint)
+	  (setq dir-list (if (= incl-type ?<)
+			     (append dir-list smart-c-cpp-include-dirs)
+			   (cons (file-name-directory buffer-file-name)
+				 dir-list)))
+	  (while dir-list
+	    (setq path (expand-file-name file (car dir-list))
+		  dir-list (if (setq found (file-exists-p path))
+			       nil
+			     (cdr dir-list))))
+	  ;;
+	  ;; If found, display file
+	  ;;
+	  (if found
+	      (if (and (file-readable-p path)
+		       (progn
+			 (if (br-in-browser)
+			     (find-file path)
+			   (find-file-other-window path))
+			 (cond ((or (featurep 'cc-mode)
+				    (featurep 'c-mode))
+				t)
+			       ((or (load "cc-mode" 'missing-ok 'nomessage)
+				    (load "c-mode" 'missing-ok 'nomessage))
+				(provide 'c-mode))
+			       (t
+				(beep)
+				(message
+				 "(smart-c-include-file):  c-mode undefined.")
+				nil
+				))))
+		  nil
+		(beep)
+		(message "(smart-c-include-file):  '%s' unreadable." path))
+	    (beep)
+	    (message "(smart-c-include-file):  '%s' not found." file))
+	  path)
+      (goto-char opoint)
+      nil)))
+
+
+;;;###autoload
+(defun smart-c++ (&optional identifier next)
+  "Jumps to the definition of optional C++ IDENTIFIER or the one at point.
+Optional second arg NEXT means jump to next matching C++ tag.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If:
+ (1) on a '#include' statement, the include file is displayed;
+     Look for include file in directory lists 'smart-c-cpp-include-dirs'
+     and 'smart-c-include-dirs'.
+ (2) on a C++ identifier, the identifier definition is displayed,
+     assuming the identifier is found within an 'etags' generated tag file
+     in the current directory or any of its ancestor directories.
+ (3) if 'smart-c-use-lib-man' is non-nil, the C++ identifier is
+     recognized as a library symbol, and a man page is found for the
+     identifier, then the man page is displayed."
+
+  (interactive)
+  (or
+   (if identifier nil (smart-c-include-file))
+   (let ((tag (or identifier (smart-c++-at-tag-p))))
+     ;; Set free variable tags-file-name so that next 'find-tag' command uses
+     ;; whatever tags file is set here.
+     (setq tags-file-name (smart-tags-file buffer-file-name))
+     (message "Looking for '%s' in '%s'..." tag tags-file-name)
+     (condition-case ()
+	 (progn
+	   (funcall (if (br-in-browser)
+			'find-tag 'find-tag-other-window)
+		    tag next)
+	   (message "Found definition for '%s'." tag))
+       (error
+	(if (not smart-c-use-lib-man)
+	    (progn (message "'%s' not found in '%s'." tag tags-file-name)
+		   (beep))
+	  (message "Checking if '%s' is a C++ library function..." tag)
+	  (if (smart-library-symbol tag)
+	      (progn (message "Displaying C++ library man page for '%s'." tag)
+		     (manual-entry tag))
+	    (message "'%s' not found in '%s' or C++ libraries."
+		     tag tags-file-name)
+	    (beep))))))))
+
+;;; The following should be called only if the OO-Browser is available.
+;;;###autoload
+(defun smart-c++-oobr (&optional junk)
+  "Jumps to the definition of selected C++ construct via OO-Browser support.
+Optional JUNK is ignored.  Does nothing if the OO-Browser is not available.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If key is pressed:
+ (1) on a '#include' statement, the include file is displayed;
+     Look for include file in directory lists 'smart-c-cpp-include-dirs'
+     and 'smart-c-include-dirs'.
+ (2) within a method declaration, its definition is displayed;
+ (3) on a class name, the class definition is shown.
+
+ (2) and (3) require that an OO-Browser Environment has been loaded with
+     the {M-x br-env-load RTN} command."
+
+  (interactive)
+  (c++-to-definition 'other-win))
+
+(defun smart-c++-at-tag-p ()
+  "Return C++ tag name that point is within, else nil."
+  (let* ((identifier-chars "_:~<>a-zA-Z0-9")
+	 (identifier (concat "\\([_~:<a-zA-Z][" identifier-chars "]*"
+			     "[ \t]*[^]) \t:;.,?~{}][^[( \t:;.,~^!|?{}]?[=*]?\\)[ \t\n]*(")))
+    (save-excursion
+      (skip-chars-backward identifier-chars)
+      (if (looking-at identifier)
+	  (buffer-substring (point) (match-end 1))))))
+
+(defun smart-emacs-lisp-mode-p ()
+  "Return t if in a mode which uses Emacs Lisp symbols."
+  (or (eq major-mode 'emacs-lisp-mode)
+      (eq major-mode 'lisp-interaction-mode)
+      (eq major-mode 'debugger-mode)
+      ;; Emacs Lisp symbols appear in Help buffers frequently.
+      (string-match "Help\\*$" (buffer-name))))
+
+(defun smart-fortran (&optional identifier next)
+  "Jumps to the definition of optional Fortran IDENTIFIER or the one at point.
+Optional second arg NEXT means jump to next matching Fortran tag.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If on a Fortran identifier, the identifier definition is displayed,
+assuming the identifier is found within an 'etags' generated tag file
+in the current directory or any of its ancestor directories."
+  (interactive)
+  (let ((tag (or identifier (smart-fortran-at-tag-p))))
+    ;; Set free variable tags-file-name so that next 'find-tag' command uses
+    ;; whatever tags file is set here.
+    (setq tags-file-name (smart-tags-file buffer-file-name))
+    (message "Looking for '%s' in '%s'..." tag tags-file-name)
+    (condition-case ()
+	(progn
+	  (funcall (if (br-in-browser)
+		       'find-tag 'find-tag-other-window)
+		   tag next)
+	  (message "Found definition for '%s'." tag))
+      (error
+       (message "'%s' not found in '%s'." tag tags-file-name)
+       (beep)))))
+
+;;;###autoload
+(defun smart-fortran-at-tag-p ()
+  "Return Fortran tag name that point is within, else nil."
+  (let* ((identifier-chars "_a-zA-Z0-9")
+	 (identifier (concat "[_a-zA-Z][" identifier-chars "]*")))
+    (save-excursion
+      (skip-chars-backward identifier-chars)
+      (if (looking-at identifier)
+	  (buffer-substring (point) (match-end 0))))))
+
+(defun smart-lisp (&optional next)
+  "Jumps to the definition of any selected Lisp construct.
+If on an Emacs Lisp require, load, or autoload clause and 'find-library'
+from load-library package by Hallvard Furuseth (hallvard@ifi.uio.no) has
+been loaded, jumps to library source, if possible.
+
+Otherwise, the construct must be found within an 'etags' generated tag file
+in the current directory or any of its ancestor directories in order for its
+definition to be located.
+
+Optional NEXT means jump to next matching Lisp tag.  When matching to an Emacs
+Lisp tag using 'wtags' (Bob Weiner's personal modifications to 'etags'),
+there is no next tag, so display documentation for current tag instead.
+
+This command assumes that its caller has already checked that the key was
+pressed in an appropriate buffer and has moved the cursor to the selected
+buffer."
+
+  (interactive)
+  ;; Handle 'require', 'load', and 'autoload' clauses in Emacs Lisp.
+  (or (and (fboundp 'find-library)
+	   (smart-emacs-lisp-mode-p)
+	   (let ((req)
+		 (opoint (point)))
+	     (setq req (and (search-backward "\(" nil t)
+			    (looking-at (concat
+					 "(\\(require\\|load\\|autoload\\)"
+					 "[ \t]+.*['\"]"
+					 "\\([^][() \t\n\^M`'\"]+\\)"))))
+	     (goto-char opoint)
+	     (if req (progn
+		       (setq req (buffer-substring (match-beginning 2)
+						   (match-end 2)))
+		       (pop-to-buffer nil t)
+		       (find-library req)
+		       t))))
+      (let ((tag (smart-lisp-at-tag-p)))
+	;; Set free variable tags-file-name so that next 'find-tag' command
+	;; uses whatever tags file is set here.
+	(setq tags-file-name (smart-tags-file default-directory))
+	;; This part only works properly for Emacs Lisp, so is conditionalized.
+	(if (and next (smart-emacs-lisp-mode-p) (featurep 'wtags))
+	    (progn (setq tag (intern tag))
+		   (cond ((fboundp tag) (describe-function tag))
+			 ((boundp tag) (describe-variable tag))
+			 (t (error "(smart-lisp): Unbound symbol: %s" tag))))
+	  (condition-case ()
+	      (funcall (if (br-in-browser)
+			   'find-tag 'find-tag-other-window)
+		       tag next)
+	    (error (if (equal tags-file-name smart-emacs-tags-file)
+		       nil
+		     (setq tags-file-name smart-emacs-tags-file)
+		     (funcall (if (br-in-browser)
+				  'find-tag 'find-tag-other-window)
+			      tag next))))))))
+
+(defun smart-lisp-at-tag-p ()
+  "Returns Lisp tag name that point is within, else nil.
+Returns nil when point is on the first line of a 'def' form past the first 4
+characters."
+  (let* ((identifier-chars "-_*:+%$#!<>a-zA-Z0-9")
+	 (identifier (concat "[-<*a-zA-Z][" identifier-chars "]*"))
+	 (opoint (point)))
+    (save-excursion
+      (beginning-of-line)
+      (if (and (looking-at "\\(;*[ \t]*\\)?(def[^- \n\t]+[ \n\t]")
+	       (> opoint (match-end 0)))
+	  nil
+	(goto-char opoint)
+	(skip-chars-backward identifier-chars)
+	(if (looking-at identifier)
+	    (buffer-substring (point) (match-end 0)))))))
+
+(defun smart-lisp-mode-p ()
+  "Return t if in a mode which uses Lisp symbols."
+  (or (smart-emacs-lisp-mode-p)
+      (eq major-mode 'lisp-mode)
+      (eq major-mode 'scheme-mode)))
+
+;;;###autoload
+(defun smart-objc (&optional identifier next)
+  "Jumps to the definition of optional Objective-C IDENTIFIER or the one at point.
+Optional second arg NEXT means jump to next matching Objective-C tag.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If:
+ (1) on a '#include' statement, the include file is displayed;
+     Look for include file in directory lists 'smart-c-cpp-include-dirs'
+     and 'smart-c-include-dirs'.
+ (2) on an Objective-C identifier, the identifier definition is displayed,
+     assuming the identifier is found within an 'etags' generated tag file
+     in the current directory or any of its ancestor directories.
+ (3) if 'smart-c-use-lib-man' is non-nil, the Objective-C identifier is
+     recognized as a library symbol, and a man page is found for the
+     identifier, then the man page is displayed."
+
+  (interactive)
+  (or
+   (if identifier nil (smart-c-include-file))
+   (let ((tag (or identifier (smart-objc-at-tag-p))))
+     ;; Set free variable tags-file-name so that next 'find-tag' command uses
+     ;; whatever tags file is set here.
+     (setq tags-file-name (smart-tags-file buffer-file-name))
+     (message "Looking for '%s' in '%s'..." tag tags-file-name)
+     (condition-case ()
+	 (progn
+	   (funcall (if (br-in-browser)
+			'find-tag 'find-tag-other-window)
+		    tag next)
+	   (message "Found definition for '%s'." tag))
+       (error
+	(if (not smart-c-use-lib-man)
+	    (progn (message "'%s' not found in '%s'." tag tags-file-name)
+		   (beep))
+	  (message
+	   "Checking if '%s' is an Objective-C library function..." tag)
+	  (if (smart-library-symbol tag)
+	      (progn
+		(message
+		 "Displaying Objective-C library man page for '%s'." tag)
+		(manual-entry tag))
+	    (message "'%s' not found in '%s' or Objective-C libraries."
+		     tag tags-file-name)
+	    (beep))))))))
+
+;;; The following should be called only if the OO-Browser is available.
+;;;###autoload
+(defun smart-objc-oobr (&optional junk)
+  "Jumps to the definition of selected Objective-C construct via OO-Browser support.
+Optional JUNK is ignored.  Does nothing if the OO-Browser is not available.
+
+It assumes that its caller has already checked that the key was pressed in an
+appropriate buffer and has moved the cursor to the selected buffer.
+
+If key is pressed:
+ (1) on a '#include' statement, the include file is displayed;
+     Look for include file in directory lists 'smart-c-cpp-include-dirs'
+     and 'smart-c-include-dirs'.
+ (2) within a method declaration, its definition is displayed;
+ (3) on a class name, the class definition is shown.
+
+ (2) and (3) require that an OO-Browser Environment has been loaded with
+     the {M-x br-env-load RTN} command."
+
+  (interactive)
+  (objc-to-definition 'other-win))
+
+(defun smart-objc-at-tag-p ()
+  "Return Objective-C tag name that point is within, else nil."
+  (let* ((identifier-chars "_a-zA-Z0-9")
+	 (identifier
+	  (concat "\\([-+][ \t]*\\)?\\([_a-zA-Z][" identifier-chars "]*\\)")))
+    (save-excursion
+      (skip-chars-backward identifier-chars)
+      (if (looking-at identifier)
+	  (buffer-substring (match-beginning 2) (match-end 2))))))
+
+;;; ************************************************************************
+;;; Private functions
+;;; ************************************************************************
+
+(defun smart-library-symbol (tag)
+  "Return non-nil if TAG is a library symbol listed in cache of such symbols.
+See the \"${hyperb:dir}/smart-clib-sym\" script for more information."
+  (let ((buf (get-buffer-create "*junk*"))
+	(found))
+    (save-excursion
+      (set-buffer buf)
+      (setq buffer-read-only nil)
+      (erase-buffer)
+      (call-process (expand-file-name "smart-clib-sym" hyperb:dir)
+		    nil buf nil tag)
+      (setq found (string-equal (buffer-substring 1 2) "1"))
+      (set-buffer-modified-p nil)
+      (kill-buffer buf)
+      found)))
+
+;;;###autoload
+(defun smart-tags-file-path (file)
+  "Expand relative FILE name by looking it up in the nearest tags file.
+Return FILE unchanged if it exists relative to the current directory or
+cannot be expanded via a tags file."
+  (or (cond ((or (file-exists-p file) (file-name-absolute-p file)) file)
+	    (t (let ((tags-file (smart-tags-file default-directory))
+		     (file-regexp
+		      (concat "\^L\n\\(.*/\\)?" (regexp-quote file) ",")))
+		 (if tags-file
+		     (progn
+		       (set-buffer (find-file-noselect tags-file))
+		       (goto-char (point-min))
+		       (if (re-search-forward file-regexp nil t)
+			   (expand-file-name
+			    (buffer-substring (1- (match-end 0))
+					      (progn (beginning-of-line)
+						     (point))))))))))
+      file))
+
+;;;###autoload
+(defun smart-tags-file (curr-filename)
+  "Return appropriate tags file name for CURR-FILENAME or 'tags-file-name'."
+  (let ((path curr-filename)
+	(tags-file))
+    (while (and
+	    (stringp path)
+	    (setq path (file-name-directory path))
+	    (setq path (directory-file-name path))
+	    ;; Not at root directory
+	    (not (string-match ":?/\\'" path))
+	    ;; No tags file
+	    (not (file-exists-p
+		  (setq tags-file (expand-file-name "TAGS" path)))))
+      (setq tags-file nil))
+    (if (and (not tags-file)
+	     (stringp curr-filename)
+	     (smart-emacs-lisp-mode-p)
+	     (let ((path (file-name-directory curr-filename)))
+	       (delq nil (mapcar
+			  (function
+			   (lambda (p)
+			     (and p (equal (file-name-as-directory p)
+					   path))))
+			  load-path))))
+	(setq tags-file smart-emacs-tags-file))
+    (or tags-file tags-file-name
+	(call-interactively 'visit-tags-table))))
+
+;;; ************************************************************************
+;;; Private variables
+;;; ************************************************************************
+
+(provide 'hmouse-tag)