;;; hmouse-tag.el --- Smart Key support of programming language constructs ;; ;; Author: Bob Weiner ;; ;; Orig-Date: 24-Aug-91 ;; ;; Copyright (C) 1991-2019 Free Software Foundation, Inc. ;; See the "HY-COPY" file for license information. ;; ;; This file is part of GNU Hyperbole. ;;; Commentary: ;;; Code: ;;; ************************************************************************ ;;; Other required Elisp libraries ;;; ************************************************************************ (eval-and-compile (mapc #'require '(find-func hpath hui-select)) (cond ((or (featurep 'etags) (featurep 'tags)) nil) (t ;; 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))))) ;; If etags utilizes the new xref.el library, define some helper ;; functions to simplify programming. (when (and (featurep 'xref) (not (fboundp 'xref-definition))) (defun xref-definition (identifier) "Return the first definition of string IDENTIFIER." (car (xref-backend-definitions (xref-find-backend) identifier))) (defun xref-definitions (identifier) "Return a list of all definitions of string IDENTIFIER." (xref-backend-definitions (xref-find-backend) identifier)) (defun xref-item-buffer (item) "Return the buffer in which xref ITEM is defined." (marker-buffer (save-excursion (xref-location-marker (xref-item-location item))))) (defun xref-item-position (item) "Return the buffer position where xref ITEM is defined." (marker-position (save-excursion (xref-location-marker (xref-item-location item)))))) ;;; ************************************************************************ ;;; Public variables ;;; ************************************************************************ (defcustom smart-asm-include-path nil "*Ordered list of directories to search for assembly language include files. Each directory must end with a directory separator." :type '(repeat directory) :group 'hyperbole-commands) (define-obsolete-variable-alias 'smart-asm-include-dirs 'smart-asm-include-path "06.00") (defconst smart-asm-include-regexp "[ \t*#|;]*\\(include\\|lib\\)[ \t]+\\([^ \t\n\r]+\\)" "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\"") (defcustom smart-c-cpp-include-path '("/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-path'." :type '(repeat directory) :group 'hyperbole-commands) (define-obsolete-variable-alias 'smart-c-cpp-include-dirs 'smart-c-cpp-include-path "06.00") (defcustom smart-c-include-path 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-path'." :type '(repeat directory) :group 'hyperbole-commands) (define-obsolete-variable-alias 'smart-c-include-dirs 'smart-c-include-path "06.00") (defcustom smart-c-use-lib-man nil "When non-nil makes `smart-c' and `smart-c++' display man pages for recognized library symbols. When nil, the default, `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 included with Hyperbole 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." :type 'boolean :group 'hyperbole-commands) (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.") (defcustom smart-java-package-path (and (fboundp 'getenv) (getenv "JAVA_HOME") (list (expand-file-name "src/" (file-name-as-directory (getenv "JAVA_HOME"))))) "*Ordered list of directories to search for imported Java packages. Each directory must end with a directory separator." :type '(repeat directory) :group 'hyperbole-commands) (define-obsolete-variable-alias 'smart-java-include-dirs 'smart-java-include-path "06.00") (defconst smart-java-package-regexp "[ \t/*]*\\(package\\|import\\)[ \t]+\\([^; \t\n\r\f]+\\)" "Regexp to match to Java `package' and `import' lines. Keyword matched is grouping 1. Referent is grouping 2.") (defcustom smart-emacs-tags-file nil "*Full path name of etags file for InfoDock, XEmacs or GNU Emacs source." :type '(file :must-match t) :group 'hyperbole-commands) ;;; ************************************************************************ ;;; 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-path'; (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 t)))) (message "Looking for `%s'..." tag) (condition-case () (progn (smart-tags-display tag next) (message "Found definition for `%s'" tag)) (error (message "`%s' not found in tag tables" tag) (beep)))))) ;;;###autoload (defun smart-asm-at-tag-p (&optional no-flash) "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) (if no-flash (buffer-substring-no-properties (point) (match-end 0)) (smart-flash-tag (buffer-substring-no-properties (point) (match-end 0)) (point) (match-end 0))))))) ;;;###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. See the documentation for `c++-to-definition' for the behavior of this function when the OO-Browser has been loaded. Otherwise: (1) on a `#include' statement, the include file is displayed; Look for include file in directory lists `smart-c-cpp-include-path' and `smart-c-include-path'; (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) (if (fboundp 'c++-to-definition) ;; Only fboundp if the OO-Browser has been loaded. (c++-to-definition t) (or (if identifier nil (smart-c-include-file)) (smart-c++-tag identifier next)))) ;;;###autoload (defun smart-c++-tag (&optional identifier next) (let ((tag (or identifier (smart-c++-at-tag-p)))) (message "Looking for `%s'..." tag) (condition-case () (progn (smart-tags-display tag next) (message "Found definition for `%s'" tag) t) (error (if (or (not smart-c-use-lib-man) (not (file-readable-p "~/.CLIBS-LIST"))) (progn (message "`%s' not found in tag tables" tag) (beep) nil) (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) t) (message "`%s' not found in tag tables or C++ libraries" tag) (beep) nil)))))) (defconst smart-c++-keywords '("_pragma" "alignas" "alignof" "and" "and_eq" "asm" "atomic_cancel" "atomic_commit " "atomic_noexcept" "auto" "bitand" "bitor" "bool" "break" "case" "catch" "char" "char16_t" "char32_t" "class" "compl" "concept" "const" "const_cast" "constexpr" "continue" "decltype" "default" "define" "defined" "delete" "do" "double" "dynamic_cast" "elif" "else" "else" "endif" "enum" "error" "explicit" "export" "extern" "false" "final" "float" "for" "friend" "goto" "if" "if" "ifdef" "ifndef" "import" "include" "inline" "int" "line" "long" "module" "mutable" "namespace" "new" "noexcept" "not" "not_eq" "nullptr" "operator" "or" "or_eq" "override" "posix" "pragma" "private" "protected" "public" "register" "reinterpret_cast" "requires" "return" "short" "signed" "sizeof" "static" "static_assert" "static_cast" "std" "struct" "switch" "synchronized" "template" "this" "thread_local" "throw" "transaction_safe" "transaction_safe_dynamic" "true" "try" "typedef" "typeid" "typename" "undef" "union" "unsigned" "using" "virtual" "void" "volatile" "wchar_t" "while" "xor" "xor_eq")) (defun smart-c++-at-tag-p () "Return C++ tag name that point is within, else nil." (let* ((identifier-chars "_:~<>a-zA-Z0-9") (identifier-regexp (concat "\\([_~:" op-identifier) op-identifier identifier))))) (defun smart-c (&optional identifier next list-of-tags-tables) "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-path' and `smart-c-include-path'; (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 t)))) (message "Looking for `%s'..." tag) (condition-case () (progn (smart-tags-display tag next list-of-tags-tables) (message "Found definition for `%s'" tag)) (error (if (or (not smart-c-use-lib-man) (not (file-readable-p "~/.CLIBS-LIST"))) (progn (message "`%s' not found in tag tables" tag) (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 tag tables or C libraries" tag) (beep)))))))) (defconst smart-c-keywords '("_generic" "_pragma" "alignas" "alignof" "asm" "atomic_bool" "atomic_char" "atomic_char16_t" "atomic_char32_t" "atomic_int" "atomic_int_fast16_t" "atomic_int_fast32_t" "atomic_int_fast64_t" "atomic_int_fast8_t" "atomic_int_least16_t" "atomic_int_least32_t" "atomic_int_least64_t" "atomic_int_least8_t" "atomic_intmax_t" "atomic_intptr_t" "atomic_llong" "atomic_long" "atomic_ptrdiff_t" "atomic_schar" "atomic_short" "atomic_size_t" "atomic_uchar" "atomic_uint" "atomic_uint_fast16_t" "atomic_uint_fast32_t" "atomic_uint_fast64_t" "atomic_uint_fast8_t" "atomic_uint_least16_t" "atomic_uint_least32_t" "atomic_uint_least64_t" "atomic_uint_least8_t" "atomic_uintmax_t" "atomic_uintptr_t" "atomic_ullong" "atomic_ulong" "atomic_ushort" "atomic_wchar_t" "auto" "bool" "break" "case" "char" "complex" "const" "continue" "default" "define" "defined" "do" "double" "elif" "else" "endif" "enum" "error" "extern" "float" "for" "fortran" "goto" "if" "ifdef" "ifndef" "imaginary" "include" "inline" "int" "line" "long" "noreturn" "pragma" "register" "restrict" "return" "short" "signed" "sizeof" "static" "static_assert" "struct" "switch" "thread_local" "typedef" "undef" "union" "unsigned" "void" "volatile" "while") "Sorted list of C keywords, all in lowercase.") ;;;###autoload (defun smart-c-at-tag-p (&optional no-flash) "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 (and (looking-at identifier) (not (member (downcase (match-string 0)) smart-c++-keywords))) (if no-flash (buffer-substring-no-properties (point) (match-end 0)) (smart-flash-tag (buffer-substring-no-properties (point) (match-end 0)) (point) (match-end 0))))))) ;;;###autoload (defun smart-cc-mode-initialize () "Load and initialize cc-mode if possible and always return nil." (condition-case () (progn (require 'cc-mode) (c-initialize-cc-mode)) (error nil)) nil) (defun smart-emacs-lisp-mode-p () "Return t if in a mode which uses Emacs Lisp symbols." ;; Beyond Lisp files, Emacs Lisp symbols appear frequently in Byte-Compiled ;; buffers, debugger buffers, and Help buffers. (or (memq major-mode #'(emacs-lisp-mode lisp-interaction-mode debugger-mode)) (string-match "\\`\\*Compile-Log\\(-Show\\)?\\*" (buffer-name)) (and (or (eq major-mode #'help-mode) (string-match "\\`\\*Help\\|Help\\*\\'" (buffer-name))) (smart-lisp-at-known-identifier-p)))) (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 t)))) (message "Looking for `%s'..." tag) (condition-case () (progn (smart-tags-display tag next) (message "Found definition for `%s'" tag)) (error (message "`%s' not found in tag tables" tag) (beep))))) (defconst smart-fortran-keywords '("abstract" "all" "allocatable" "allocate" "assign" "associate" "asynchronous" "backspace" "bind" "block" "block" "call" "case" "class" "close" "codimension" "common" "concurrent" "contains" "contiguous" "continue" "critical" "cycle" "data" "data" "deallocate" "deferred" "dimension" "do" "elemental" "else" "else" "elsewhere" "end" "endfile" "endif" "entry" "enum" "enumerator" "equivalence" "error" "exit" "extends" "external" "final" "flush" "forall" "format" "function" "generic" "goto" "if" "if" "images" "implicit" "import" "include" "inquire" "intent" "interface" "intrinsic" "lock" "memory" "module" "namelist" "non_overridable" "nopass" "nullify" "only" "open" "operator" "optional" "parameter" "pass" "pause" "pointer" "print" "private" "procedure" "program" "protected" "public" "pure" "read" "recursive" "result" "return" "rewind" "rewrite" "save" "select" "sequence" "stop" "stop" "submodule" "subroutine" "sync" "target" "then" "unlock" "use" "value" "volatile" "wait" "where" "while" "write") "Sorted list of Fortran keywords, all in lowercase.") ;;;###autoload (defun smart-fortran-at-tag-p (&optional no-flash) "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) (if no-flash (buffer-substring-no-properties (point) (match-end 0)) (smart-flash-tag (buffer-substring-no-properties (point) (match-end 0)) (point) (match-end 0))))))) ;;;###autoload (defun smart-java (&optional identifier next) "Jumps to the definition of optional Java IDENTIFIER or the one at point. Optional second arg NEXT means jump to next matching Java 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. See the documentation for `smart-java-oo-browser' for the behavior of this function when the OO-Browser has been loaded. Otherwise: (1) within a commented @see cross-reference, the referent is displayed; (2) on a `package' or `import' statement, the referent is displayed; Look for referent files in the directory list `smart-java-package-path'; (3) on a Java 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) (if (fboundp 'java-to-definition) ;; Only fboundp if the OO-Browser has been loaded. (smart-java-oo-browser) (or (if identifier nil (or (smart-java-cross-reference) (smart-java-packages))) (smart-java-tag identifier next)))) ;;;###autoload (defun smart-java-tag (&optional identifier next) (let ((tag (or identifier (smart-java-at-tag-p t)))) (message "Looking for `%s'..." tag) (condition-case () (progn (smart-tags-display tag next) (message "Found definition for `%s'" tag)) (error (progn (message "`%s' not found in tag tables" tag) (beep)))))) ;;; The following should be called only if the OO-Browser is available. (defun smart-java-oo-browser (&optional junk) "Jumps to the definition of selected Java 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) within a commented @see cross-reference, the referent is displayed; (2) on a `package' or `import' statement, the referent is displayed; Look for referent files in the directory list `smart-java-package-path'; (3) within a method declaration, its definition is displayed; (4) on a class name, the class definition is shown; (5) on a unique identifier reference, its definition is shown (when possible)." (interactive) (or (smart-java-cross-reference) (smart-java-packages) (java-to-definition t))) (defconst smart-java-keywords '("abstract" "assert" "boolean" "break" "byte" "case" "catch" "char" "class" "const" "continue" "default" "do" "double" "else" "enum" "extends" "final" "finally" "float" "for" "goto" "if" "implements" "import" "instanceof" "int" "interface" "long" "native" "new" "package" "private" "protected" "public" "return" "short" "static" "strictfp" "super" "switch" "synchronized" "this" "throw" "throws" "transient" "try" "void" "volatile" "while") "Sorted list of Java keywords, all in lowercase.") ;;;###autoload (defun smart-java-at-tag-p (&optional no-flash) "Return Java 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 (and (/= (preceding-char) ?@) (looking-at identifier) (not (member (downcase (match-string 0)) smart-java-keywords))) (if no-flash (buffer-substring-no-properties (point) (match-end 0)) (smart-flash-tag (buffer-substring-no-properties (point) (match-end 0)) (point) (match-end 0))))))) ;;;###autoload (defun smart-javascript (&optional identifier next) "Jumps to the definition of optional JavaScript IDENTIFIER or the one at point. Optional second arg NEXT means jump to next matching JavaScript 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 JavaScript 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-javascript-at-tag-p t)))) (message "Looking for `%s'..." tag) (condition-case () (progn (smart-tags-display tag next) (message "Found definition for `%s'" tag)) (error (message "`%s' not found in tag tables" tag) (beep))))) (defconst smart-javascript-keywords '("break" "case" "catch" "class" "const" "continue" "debugger" "default" "delete" "do" "else" "extends" "export" "false" "finally" "for" "function" "if" "in" "instanceof" "import" "let" "new" "null" "return" "super" "switch" "this" "throw" "true" "try" "typeof" "var" "void" "while" "with" "yield") "Sorted list of JavaScript keywords, all in lowercase.") ;;;###autoload (defun smart-javascript-at-tag-p (&optional no-flash) "Return JavaScript tag name that point is within, else nil." (if (if (memq major-mode '(html-mode web-mode)) ;; Must be within a