Initial Commit
[packages] / xemacs-packages / hyperbole / hmouse-tag.el
1 ;;; hmouse-tag.el --- Smart Key support of programming language tags location.
2
3 ;; Copyright (C) 1991-1995, 2006 Free Software Foundation, Inc.
4 ;; Developed with support from Motorola Inc.
5
6 ;; Author: Bob Weiner, Brown U.
7 ;; Maintainer: Mats Lidell <matsl@contactor.se>
8 ;; Keywords: c, hypermedia, mouse, oop, tools
9
10 ;; This file is part of GNU Hyperbole.
11
12 ;; GNU Hyperbole is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation; either version 3, or (at
15 ;; your option) any later version.
16
17 ;; GNU Hyperbole 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 GNU
20 ;; General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
26
27 ;;; Commentary:
28 ;;
29 ;;   Supports C, C++, Objective-C, Lisp, Fortran, and Assembly.
30 ;;   See the GNU Emacs manual for information on how to create a TAGS file
31 ;;     from the `etags' program.
32 ;;   Does not support the `ctags' tags file format.
33 ;;
34 ;;   YOU MUST APPROPRIATELY SET THE PUBLIC VARIABLES BELOW BEFORE USE.
35 ;;
36
37 ;;; Code:
38
39 ;;;
40 ;;; Other required Elisp libraries
41 ;;;
42
43 (if (cond ((or (featurep 'etags) (featurep 'tags))
44            nil)
45           ((or hyperb:xemacs-p hyperb:emacs19-p)
46            ;; Force use of .elc file here since otherwise the bin/etags
47            ;; executable might be found in a user's load-path by the load
48            ;; command.
49            (or (load "etags.elc" t nil t)
50                (load "tags-fix" t)))
51           ((load "tags" t)))
52     (provide 'tags))
53
54 ;;;
55 ;;; Public variables
56 ;;;
57
58 (defvar smart-asm-include-dirs nil
59   "*Ordered list of directories to search for assembly language include files.
60 Each directory must end with a directory separator.")
61
62 (defconst smart-asm-include-regexp
63   "[ \t*#|;]*\\(include\\|lib\\)[ \t]+\\([^ \t\n\^M]+\\)"
64   "Regexp to match to assembly language include file lines.
65 Include keyword matched is grouping 1.  File name is grouping 2 but may be
66 missing its suffix, so add \".ins\" or \".inc\" if need be.
67 Examples include:
68        INCLUDE GLOBALS
69          should jump to file \"globals.ins\"
70        lib conditionals_equ.inc
71          should include \"conditionals_equ.inc\"")
72
73 (defvar smart-c-cpp-include-dirs '("/usr/include/")
74   "*Ordered list of include directories by default searched by C/C++ preprocessor.
75 Each directory must end with a directory separator.  See also
76 'smart-c-include-dirs'.")
77
78 (defvar smart-c-include-dirs nil
79   "*Ordered list of directories to search for C/C++ include files.
80 Each directory must end with a directory separator.  Directories normally
81 searched by the C/C++ pre-processor should be set instead in
82 'smart-c-cpp-include-dirs'.")
83
84 (defvar smart-c-use-lib-man nil
85   "When non-nil makes 'smart-c' and 'smart-c++' display man pages for recognized lib symbols.
86 When nil, 'smart-c' and 'smart-c++' look up only symbols defined in an etags
87 TAGS file.
88
89 Create the file ~/.CLIBS-LIST and populate it with the full pathnames (one per
90 line) of all of the C/C++ libraries whose symbols you want to match against.
91 Your MANPATH environment variable must include paths for the man pages of
92 these libraries also.
93
94 Your smart-clib-sym executable script must output a 1 if a symbol is from a
95 C/C++ library listed in ~/.CLIBS-LIST or 0 if not!  Otherwise, don't set this
96 variable to t.")
97
98 (defconst smart-c-include-regexp
99   "[ \t/*]*#[ \t]*\\(include\\|import\\)[ \t]+\\([\"<]\\)\\([^\">]+\\)[\">]"
100   "Regexp to match to C, C++, or Objective-C include file lines.
101 Include keyword matched is grouping 1.  Type of include, user-specified via
102 double quote, or system-related starting with '<' is given by grouping 2.
103 File name is grouping 3.")
104
105 (defvar smart-emacs-tags-file nil
106   "*Full path name of etags file for GNU Emacs source.")
107
108 ;;;
109 ;;; Public functions
110 ;;;
111
112 (defun smart-asm (&optional identifier next)
113   "Jumps to the definition of optional assembly IDENTIFIER or the one at point.
114 Optional second arg NEXT means jump to next matching assembly tag.
115
116 It assumes that its caller has already checked that the key was pressed in an
117 appropriate buffer and has moved the cursor to the selected buffer.
118
119 If:
120  (1) on an include statement, the include file is displayed;
121      Look for include file in directory list 'smart-asm-include-dirs'.
122  (2) on an identifier, the identifier definition is displayed,
123      assuming the identifier is found within an 'etags' generated tag file
124      in the current directory or any of its ancestor directories."
125
126   (interactive)
127   (or
128    (if identifier nil (smart-asm-include-file))
129    (let ((tag (or identifier (smart-asm-at-tag-p))))
130      ;; Set free variable tags-file-name so that next 'find-tag' command uses
131      ;; whatever tags file is set here.
132      (setq tags-file-name (smart-tags-file buffer-file-name))
133      (message "Looking for '%s' in '%s'..." tag tags-file-name)
134      (condition-case ()
135          (progn
136            (funcall (if (br-in-browser)
137                         'find-tag 'find-tag-other-window)
138                     tag next)
139            (message "Found definition for '%s'." tag))
140        (error (message "'%s' not found in '%s'." tag tags-file-name)
141               (beep))))))
142
143 ;;;###autoload
144 (defun smart-asm-at-tag-p ()
145   "Return assembly tag name that point is within, else nil."
146   (let* ((identifier-chars "_.$a-zA-Z0-9")
147          (identifier (concat "[_.$a-zA-Z][" identifier-chars "]*")))
148     (save-excursion
149       (skip-chars-backward identifier-chars)
150       (if (looking-at identifier)
151           (buffer-substring (point) (match-end 0))))))
152
153 (defun smart-asm-include-file ()
154   "If point is on an include file line, tries to display file.
155 Returns non-nil iff on an include file line, even if file is not found.
156 Look for include file in 'smart-asm-include-dirs' and add suffix \".ins\" or
157 \".inc\" to filename if it lacks a suffix." 
158   (let ((opoint (point)))
159     ;; Some assemblers utilize the C preprocessor, so try that first.
160     (cond ((smart-c-include-file))
161           ((progn (beginning-of-line)
162                   (looking-at smart-asm-include-regexp))
163            (let ((file (buffer-substring (match-beginning 2) (match-end 2)))
164                  (path)
165                  (dir-list smart-asm-include-dirs))
166              (goto-char opoint)
167              (setq dir-list (cons (file-name-directory buffer-file-name)
168                                   dir-list))
169              (if (string-match "\\." file)
170                  (setq file (regexp-quote file))
171                (setq file (concat (regexp-quote file) "\\.in[sc]$")))
172              (while dir-list
173                (setq dir-list
174                      (if (setq path (car (directory-files
175                                            (car dir-list) t file)))
176                          nil
177                        (cdr dir-list))))
178              ;;
179              ;; If path exists, display file
180              ;;
181              (if path
182                  (if (and (file-readable-p path)
183                           (progn
184                             (if (br-in-browser)
185                                 (find-file path)
186                               (find-file-other-window path))
187                             (cond ((featurep 'asm-mode) t)
188                                   ((load "asm-mode" nil 'nomessage)
189                                    (provide 'asm-mode))
190                                   (t
191                                     (beep)
192                                     (message
193                                       "(smart-asm-include-file):  asm-mode undefined.")
194                                     nil
195                                     ))))
196                      nil
197                    (beep)
198                    (message "(smart-asm-include-file):  '%s' unreadable." path))
199                (beep)
200                (message "(smart-asm-include-file):  '%s' not found." file))
201              path))
202           ;; not on an include file line
203           (t (goto-char opoint)
204              nil))))
205
206
207 (defun smart-c (&optional identifier next)
208   "Jumps to the definition of optional C IDENTIFIER or the one at point.
209 Optional second arg NEXT means jump to next matching C tag.
210
211 It assumes that its caller has already checked that the key was pressed in an
212 appropriate buffer and has moved the cursor to the selected buffer.
213
214 If:
215  (1) on a '#include' statement, the include file is displayed;
216      Look for include file in directory lists 'smart-c-cpp-include-dirs'
217      and 'smart-c-include-dirs'.
218  (2) on a C identifier, the identifier definition is displayed,
219      assuming the identifier is found within an 'etags' generated tag file
220      in the current directory or any of its ancestor directories.
221  (3) if 'smart-c-use-lib-man' is non-nil, the C identifier is
222      recognized as a library symbol, and a man page is found for the
223      identifier, then the man page is displayed."
224
225   (interactive)
226   (or
227    (if identifier nil (smart-c-include-file))
228    (let ((tag (or identifier (smart-c-at-tag-p))))
229      ;; Set free variable tags-file-name so that next 'find-tag' command uses
230      ;; whatever tags file is set here.
231      (setq tags-file-name (smart-tags-file buffer-file-name))
232      (message "Looking for '%s' in '%s'..." tag tags-file-name)
233      (condition-case ()
234          (progn
235            (funcall (if (br-in-browser)
236                         'find-tag 'find-tag-other-window)
237                     tag next)
238            (message "Found definition for '%s'." tag))
239        (error
240         (if (not smart-c-use-lib-man)
241             (progn (message "'%s' not found in '%s'." tag tags-file-name)
242                    (beep))
243           (message "Checking if '%s' is a C library function..." tag)
244           (if (smart-library-symbol tag)
245               (progn (message "Displaying C library man page for '%s'." tag)
246                      (manual-entry tag))
247             (message "'%s' not found in '%s' or C libraries."
248                      tag tags-file-name)
249             (beep))))))))
250
251 ;;;###autoload
252 (defun smart-c-at-tag-p ()
253   "Return C tag name that point is within, else nil."
254   (let* ((identifier-chars "_a-zA-Z0-9")
255          (identifier (concat "[_a-zA-Z][" identifier-chars "]*")))
256     (save-excursion
257       (skip-chars-backward identifier-chars)
258       (if (looking-at identifier)
259           (buffer-substring (point) (match-end 0))))))
260
261 (defun smart-c-include-file ()
262   "If point is on an include file line, tries to display file.
263 Returns non-nil iff on an include file line, even if file is not found.
264 Look for include file in 'smart-c-cpp-include-dirs' and in directory list
265 'smart-c-include-dirs'."
266   (let ((opoint (point)))
267     (beginning-of-line)
268     (if (looking-at smart-c-include-regexp)
269         (let ((incl-type (string-to-char
270                           (buffer-substring (match-beginning 2)
271                                             (1+ (match-beginning 2)))))
272               (file (buffer-substring (match-beginning 3) (match-end 3)))
273               (path)
274               (dir-list smart-c-include-dirs)
275               (found))
276           (goto-char opoint)
277           (setq dir-list (if (= incl-type ?<)
278                              (append dir-list smart-c-cpp-include-dirs)
279                            (cons (file-name-directory buffer-file-name)
280                                  dir-list)))
281           (while dir-list
282             (setq path (expand-file-name file (car dir-list))
283                   dir-list (if (setq found (file-exists-p path))
284                                nil
285                              (cdr dir-list))))
286           ;;
287           ;; If found, display file
288           ;;
289           (if found
290               (if (and (file-readable-p path)
291                        (progn
292                          (if (br-in-browser)
293                              (find-file path)
294                            (find-file-other-window path))
295                          (cond ((or (featurep 'cc-mode)
296                                     (featurep 'c-mode))
297                                 t)
298                                ((or (load "cc-mode" 'missing-ok 'nomessage)
299                                     (load "c-mode" 'missing-ok 'nomessage))
300                                 (provide 'c-mode))
301                                (t
302                                 (beep)
303                                 (message
304                                  "(smart-c-include-file):  c-mode undefined.")
305                                 nil
306                                 ))))
307                   nil
308                 (beep)
309                 (message "(smart-c-include-file):  '%s' unreadable." path))
310             (beep)
311             (message "(smart-c-include-file):  '%s' not found." file))
312           path)
313       (goto-char opoint)
314       nil)))
315
316
317 ;;;###autoload
318 (defun smart-c++ (&optional identifier next)
319   "Jumps to the definition of optional C++ IDENTIFIER or the one at point.
320 Optional second arg NEXT means jump to next matching C++ tag.
321
322 It assumes that its caller has already checked that the key was pressed in an
323 appropriate buffer and has moved the cursor to the selected buffer.
324
325 If:
326  (1) on a '#include' statement, the include file is displayed;
327      Look for include file in directory lists 'smart-c-cpp-include-dirs'
328      and 'smart-c-include-dirs'.
329  (2) on a C++ identifier, the identifier definition is displayed,
330      assuming the identifier is found within an 'etags' generated tag file
331      in the current directory or any of its ancestor directories.
332  (3) if 'smart-c-use-lib-man' is non-nil, the C++ identifier is
333      recognized as a library symbol, and a man page is found for the
334      identifier, then the man page is displayed."
335
336   (interactive)
337   (or
338    (if identifier nil (smart-c-include-file))
339    (let ((tag (or identifier (smart-c++-at-tag-p))))
340      ;; Set free variable tags-file-name so that next 'find-tag' command uses
341      ;; whatever tags file is set here.
342      (setq tags-file-name (smart-tags-file buffer-file-name))
343      (message "Looking for '%s' in '%s'..." tag tags-file-name)
344      (condition-case ()
345          (progn
346            (funcall (if (br-in-browser)
347                         'find-tag 'find-tag-other-window)
348                     tag next)
349            (message "Found definition for '%s'." tag))
350        (error
351         (if (not smart-c-use-lib-man)
352             (progn (message "'%s' not found in '%s'." tag tags-file-name)
353                    (beep))
354           (message "Checking if '%s' is a C++ library function..." tag)
355           (if (smart-library-symbol tag)
356               (progn (message "Displaying C++ library man page for '%s'." tag)
357                      (manual-entry tag))
358             (message "'%s' not found in '%s' or C++ libraries."
359                      tag tags-file-name)
360             (beep))))))))
361
362 ;;; The following should be called only if the OO-Browser is available.
363 ;;;###autoload
364 (defun smart-c++-oobr (&optional junk)
365   "Jumps to the definition of selected C++ construct via OO-Browser support.
366 Optional JUNK is ignored.  Does nothing if the OO-Browser is not available.
367
368 It assumes that its caller has already checked that the key was pressed in an
369 appropriate buffer and has moved the cursor to the selected buffer.
370
371 If key is pressed:
372  (1) on a '#include' statement, the include file is displayed;
373      Look for include file in directory lists 'smart-c-cpp-include-dirs'
374      and 'smart-c-include-dirs'.
375  (2) within a method declaration, its definition is displayed;
376  (3) on a class name, the class definition is shown.
377
378  (2) and (3) require that an OO-Browser Environment has been loaded with
379      the {M-x br-env-load RTN} command."
380
381   (interactive)
382   (c++-to-definition 'other-win))
383
384 (defun smart-c++-at-tag-p ()
385   "Return C++ tag name that point is within, else nil."
386   (let* ((identifier-chars "_:~<>a-zA-Z0-9")
387          (identifier (concat "\\([_~:<a-zA-Z][" identifier-chars "]*"
388                              "[ \t]*[^]) \t:;.,?~{}][^[( \t:;.,~^!|?{}\7f]?[=*]?\\)[ \t\n]*(")))
389     (save-excursion
390       (skip-chars-backward identifier-chars)
391       (if (looking-at identifier)
392           (buffer-substring (point) (match-end 1))))))
393
394 (defun smart-emacs-lisp-mode-p ()
395   "Return t if in a mode which uses Emacs Lisp symbols."
396   (or (eq major-mode 'emacs-lisp-mode)
397       (eq major-mode 'lisp-interaction-mode)
398       (eq major-mode 'debugger-mode)
399       ;; Emacs Lisp symbols appear in Help buffers frequently.
400       (string-match "Help\\*$" (buffer-name))))
401
402 (defun smart-fortran (&optional identifier next)
403   "Jumps to the definition of optional Fortran IDENTIFIER or the one at point.
404 Optional second arg NEXT means jump to next matching Fortran tag.
405
406 It assumes that its caller has already checked that the key was pressed in an
407 appropriate buffer and has moved the cursor to the selected buffer.
408
409 If on a Fortran identifier, the identifier definition is displayed,
410 assuming the identifier is found within an 'etags' generated tag file
411 in the current directory or any of its ancestor directories."
412   (interactive)
413   (let ((tag (or identifier (smart-fortran-at-tag-p))))
414     ;; Set free variable tags-file-name so that next 'find-tag' command uses
415     ;; whatever tags file is set here.
416     (setq tags-file-name (smart-tags-file buffer-file-name))
417     (message "Looking for '%s' in '%s'..." tag tags-file-name)
418     (condition-case ()
419         (progn
420           (funcall (if (br-in-browser)
421                        'find-tag 'find-tag-other-window)
422                    tag next)
423           (message "Found definition for '%s'." tag))
424       (error
425        (message "'%s' not found in '%s'." tag tags-file-name)
426        (beep)))))
427
428 ;;;###autoload
429 (defun smart-fortran-at-tag-p ()
430   "Return Fortran tag name that point is within, else nil."
431   (let* ((identifier-chars "_a-zA-Z0-9")
432          (identifier (concat "[_a-zA-Z][" identifier-chars "]*")))
433     (save-excursion
434       (skip-chars-backward identifier-chars)
435       (if (looking-at identifier)
436           (buffer-substring (point) (match-end 0))))))
437
438 (defun smart-lisp (&optional next)
439   "Jumps to the definition of any selected Lisp construct.
440 If on an Emacs Lisp require, load, or autoload clause and 'find-library'
441 from load-library package by Hallvard Furuseth (hallvard@ifi.uio.no) has
442 been loaded, jumps to library source, if possible.
443
444 Otherwise, the construct must be found within an 'etags' generated tag file
445 in the current directory or any of its ancestor directories in order for its
446 definition to be located.
447
448 Optional NEXT means jump to next matching Lisp tag.  When matching to an Emacs
449 Lisp tag using 'wtags' (Bob Weiner's personal modifications to 'etags'),
450 there is no next tag, so display documentation for current tag instead.
451
452 This command assumes that its caller has already checked that the key was
453 pressed in an appropriate buffer and has moved the cursor to the selected
454 buffer."
455
456   (interactive)
457   ;; Handle 'require', 'load', and 'autoload' clauses in Emacs Lisp.
458   (or (and (fboundp 'find-library)
459            (smart-emacs-lisp-mode-p)
460            (let ((req)
461                  (opoint (point)))
462              (setq req (and (search-backward "\(" nil t)
463                             (looking-at (concat
464                                          "(\\(require\\|load\\|autoload\\)"
465                                          "[ \t]+.*['\"]"
466                                          "\\([^][() \t\n\^M`'\"]+\\)"))))
467              (goto-char opoint)
468              (if req (progn
469                        (setq req (buffer-substring (match-beginning 2)
470                                                    (match-end 2)))
471                        (pop-to-buffer nil t)
472                        (find-library req)
473                        t))))
474       (let ((tag (smart-lisp-at-tag-p)))
475         ;; Set free variable tags-file-name so that next 'find-tag' command
476         ;; uses whatever tags file is set here.
477         (setq tags-file-name (smart-tags-file default-directory))
478         ;; This part only works properly for Emacs Lisp, so is conditionalized.
479         (if (and next (smart-emacs-lisp-mode-p) (featurep 'wtags))
480             (progn (setq tag (intern tag))
481                    (cond ((fboundp tag) (describe-function tag))
482                          ((boundp tag) (describe-variable tag))
483                          (t (error "(smart-lisp): Unbound symbol: %s" tag))))
484           (condition-case ()
485               (funcall (if (br-in-browser)
486                            'find-tag 'find-tag-other-window)
487                        tag next)
488             (error (if (equal tags-file-name smart-emacs-tags-file)
489                        nil
490                      (setq tags-file-name smart-emacs-tags-file)
491                      (funcall (if (br-in-browser)
492                                   'find-tag 'find-tag-other-window)
493                               tag next))))))))
494
495 (defun smart-lisp-at-tag-p ()
496   "Returns Lisp tag name that point is within, else nil.
497 Returns nil when point is on the first line of a 'def' form past the first 4
498 characters."
499   (let* ((identifier-chars "-_*:+%$#!<>a-zA-Z0-9")
500          (identifier (concat "[-<*a-zA-Z][" identifier-chars "]*"))
501          (opoint (point)))
502     (save-excursion
503       (beginning-of-line)
504       (if (and (looking-at "\\(;*[ \t]*\\)?(def[^- \n\t]+[ \n\t]")
505                (> opoint (match-end 0)))
506           nil
507         (goto-char opoint)
508         (skip-chars-backward identifier-chars)
509         (if (looking-at identifier)
510             (buffer-substring (point) (match-end 0)))))))
511
512 (defun smart-lisp-mode-p ()
513   "Return t if in a mode which uses Lisp symbols."
514   (or (smart-emacs-lisp-mode-p)
515       (eq major-mode 'lisp-mode)
516       (eq major-mode 'scheme-mode)))
517
518 ;;;###autoload
519 (defun smart-objc (&optional identifier next)
520   "Jumps to the definition of optional Objective-C IDENTIFIER or the one at point.
521 Optional second arg NEXT means jump to next matching Objective-C tag.
522
523 It assumes that its caller has already checked that the key was pressed in an
524 appropriate buffer and has moved the cursor to the selected buffer.
525
526 If:
527  (1) on a '#include' statement, the include file is displayed;
528      Look for include file in directory lists 'smart-c-cpp-include-dirs'
529      and 'smart-c-include-dirs'.
530  (2) on an Objective-C identifier, the identifier definition is displayed,
531      assuming the identifier is found within an 'etags' generated tag file
532      in the current directory or any of its ancestor directories.
533  (3) if 'smart-c-use-lib-man' is non-nil, the Objective-C identifier is
534      recognized as a library symbol, and a man page is found for the
535      identifier, then the man page is displayed."
536
537   (interactive)
538   (or
539    (if identifier nil (smart-c-include-file))
540    (let ((tag (or identifier (smart-objc-at-tag-p))))
541      ;; Set free variable tags-file-name so that next 'find-tag' command uses
542      ;; whatever tags file is set here.
543      (setq tags-file-name (smart-tags-file buffer-file-name))
544      (message "Looking for '%s' in '%s'..." tag tags-file-name)
545      (condition-case ()
546          (progn
547            (funcall (if (br-in-browser)
548                         'find-tag 'find-tag-other-window)
549                     tag next)
550            (message "Found definition for '%s'." tag))
551        (error
552         (if (not smart-c-use-lib-man)
553             (progn (message "'%s' not found in '%s'." tag tags-file-name)
554                    (beep))
555           (message
556            "Checking if '%s' is an Objective-C library function..." tag)
557           (if (smart-library-symbol tag)
558               (progn
559                 (message
560                  "Displaying Objective-C library man page for '%s'." tag)
561                 (manual-entry tag))
562             (message "'%s' not found in '%s' or Objective-C libraries."
563                      tag tags-file-name)
564             (beep))))))))
565
566 ;;; The following should be called only if the OO-Browser is available.
567 ;;;###autoload
568 (defun smart-objc-oobr (&optional junk)
569   "Jumps to the definition of selected Objective-C construct via OO-Browser support.
570 Optional JUNK is ignored.  Does nothing if the OO-Browser is not available.
571
572 It assumes that its caller has already checked that the key was pressed in an
573 appropriate buffer and has moved the cursor to the selected buffer.
574
575 If key is pressed:
576  (1) on a '#include' statement, the include file is displayed;
577      Look for include file in directory lists 'smart-c-cpp-include-dirs'
578      and 'smart-c-include-dirs'.
579  (2) within a method declaration, its definition is displayed;
580  (3) on a class name, the class definition is shown.
581
582  (2) and (3) require that an OO-Browser Environment has been loaded with
583      the {M-x br-env-load RTN} command."
584
585   (interactive)
586   (objc-to-definition 'other-win))
587
588 (defun smart-objc-at-tag-p ()
589   "Return Objective-C tag name that point is within, else nil."
590   (let* ((identifier-chars "_a-zA-Z0-9")
591          (identifier
592           (concat "\\([-+][ \t]*\\)?\\([_a-zA-Z][" identifier-chars "]*\\)")))
593     (save-excursion
594       (skip-chars-backward identifier-chars)
595       (if (looking-at identifier)
596           (buffer-substring (match-beginning 2) (match-end 2))))))
597
598 ;;;
599 ;;; Private functions
600 ;;;
601
602 (defun smart-library-symbol (tag)
603   "Return non-nil if TAG is a library symbol listed in cache of such symbols.
604 See the \"${hyperb:dir}/smart-clib-sym\" script for more information."
605   (let ((buf (get-buffer-create "*junk*"))
606         (found))
607     (save-excursion
608       (set-buffer buf)
609       (setq buffer-read-only nil)
610       (erase-buffer)
611       (call-process (expand-file-name "smart-clib-sym" hyperb:dir)
612                     nil buf nil tag)
613       (setq found (string-equal (buffer-substring 1 2) "1"))
614       (set-buffer-modified-p nil)
615       (kill-buffer buf)
616       found)))
617
618 ;;;###autoload
619 (defun smart-tags-file-path (file)
620   "Expand relative FILE name by looking it up in the nearest tags file.
621 Return FILE unchanged if it exists relative to the current directory or
622 cannot be expanded via a tags file."
623   (or (cond ((or (file-exists-p file) (file-name-absolute-p file)) file)
624             (t (let ((tags-file (smart-tags-file default-directory))
625                      (file-regexp
626                       (concat "\^L\n\\(.*/\\)?" (regexp-quote file) ",")))
627                  (if tags-file
628                      (progn
629                        (set-buffer (find-file-noselect tags-file))
630                        (goto-char (point-min))
631                        (if (re-search-forward file-regexp nil t)
632                            (expand-file-name
633                             (buffer-substring (1- (match-end 0))
634                                               (progn (beginning-of-line)
635                                                      (point))))))))))
636       file))
637
638 ;;;###autoload
639 (defun smart-tags-file (curr-filename)
640   "Return appropriate tags file name for CURR-FILENAME or 'tags-file-name'."
641   (let ((path curr-filename)
642         (tags-file))
643     (while (and
644             (stringp path)
645             (setq path (file-name-directory path))
646             (setq path (directory-file-name path))
647             ;; Not at root directory
648             (not (string-match ":?/\\'" path))
649             ;; No tags file
650             (not (file-exists-p
651                   (setq tags-file (expand-file-name "TAGS" path)))))
652       (setq tags-file nil))
653     (if (and (not tags-file)
654              (stringp curr-filename)
655              (smart-emacs-lisp-mode-p)
656              (let ((path (file-name-directory curr-filename)))
657                (delq nil (mapcar
658                           (function
659                            (lambda (p)
660                              (and p (equal (file-name-as-directory p)
661                                            path))))
662                           load-path))))
663         (setq tags-file smart-emacs-tags-file))
664     (or tags-file tags-file-name
665         (call-interactively 'visit-tags-table))))
666
667 ;;;
668 ;;; Private variables
669 ;;;
670
671 (provide 'hmouse-tag)
672
673 ;;; hmouse-tag.el ends here