]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/ggtags/ggtags.el
Merge commit 'e312e04bc813a642483f0be67a6073cb93a24552'
[gnu-emacs-elpa] / packages / ggtags / ggtags.el
index 2e986e8627b251371feb5afe9cfc7a639ca6c15a..d3973b213feeba40572de59d09859923cf1ddb07 100644 (file)
@@ -1,9 +1,9 @@
 ;;; ggtags.el --- emacs frontend to GNU Global source code tagging system  -*- lexical-binding: t; -*-
 
-;; Copyright (C) 2013-2014  Free Software Foundation, Inc.
+;; Copyright (C) 2013-2015  Free Software Foundation, Inc.
 
 ;; Author: Leo Liu <sdl.web@gmail.com>
-;; Version: 0.8.2
+;; Version: 0.8.9
 ;; Keywords: tools, convenience
 ;; Created: 2013-01-29
 ;; URL: https://github.com/leoliu/ggtags
 ;;
 ;; All commands are available from the `Ggtags' menu in `ggtags-mode'.
 
+;;; NEWS 0.8.9 (2015-01-16):
+
+;; - `ggtags-visit-project-root' can visit past projects.
+;; - `eldoc' support enabled for emacs 24.4+.
+;;
+;; See full NEWS on https://github.com/leoliu/ggtags#news
+
 ;;; Code:
 
 (eval-when-compile
@@ -58,6 +65,9 @@
       (list 'progn (list 'defvar var val docstring)
             (list 'make-variable-buffer-local (list 'quote var)))))
 
+  (or (fboundp 'add-function) (defmacro add-function (&rest _))) ;24.4
+  (or (fboundp 'remove-function) (defmacro remove-function (&rest _)))
+
   (defmacro ignore-errors-unless-debug (&rest body)
     "Ignore all errors while executing BODY unless debug is on."
     (declare (debug t) (indent 0))
 
 (defcustom ggtags-oversize-limit (* 10 1024 1024)
   "The over size limit for the  GTAGS file.
-For large source trees, running 'global -u' can be expensive.
-Thus when GTAGS file is larger than this limit, ggtags
-automatically switches to 'global --single-update'."
+When the size of the GTAGS file is below this limit, ggtags
+always maintains up-to-date tags for the whole source tree by
+running `global -u'. For projects with GTAGS larger than this
+limit, only files edited in Ggtags mode are updated (via `global
+--single-update')."
   :safe 'numberp
   :type '(choice (const :tag "None" nil)
                  (const :tag "Always" t)
                  number)
   :group 'ggtags)
 
-(defcustom ggtags-global-always-update nil
-  "If non-nil always update tags for current file on save."
-  :safe 'booleanp
-  :type 'boolean
-  :group 'ggtags)
-
 (defcustom ggtags-include-pattern
-  '("^\\s-*#\\(?:include\\|import\\)\\s-*[\"<]\\(?:[./]*\\)?\\(.*?\\)[\">]" . 1)
+  '("^\\s-*#\\s-*\\(?:include\\|import\\)\\s-*[\"<]\\(?:[./]*\\)?\\(.*?\\)[\">]" . 1)
   "Pattern used to detect #include files.
 Value can be (REGEXP . SUB) or a function with no arguments.
 REGEXP should match from the beginning of line."
@@ -128,9 +134,14 @@ REGEXP should match from the beginning of line."
   :safe 'stringp
   :group 'ggtags)
 
+;; See also: http://article.gmane.org/gmane.comp.gnu.global.bugs/1751
 (defcustom ggtags-use-project-gtagsconf t
   "Non-nil to use GTAGSCONF file found at project root.
-File .globalrc and gtags.conf are checked in order."
+File .globalrc and gtags.conf are checked in order.
+
+Note: GNU Global v6.2.13 has the feature of using gtags.conf at
+project root. Setting this variable to nil doesn't disable this
+feature."
   :safe 'booleanp
   :type 'boolean
   :group 'ggtags)
@@ -187,6 +198,14 @@ If an integer abbreviate only names longer than that number."
   :type 'boolean
   :group 'ggtags)
 
+(defcustom ggtags-use-sqlite3 nil
+  "Use sqlite3 for storage instead of Berkeley DB.
+This feature requires GNU Global 6.3.3+ and is ignored if `gtags'
+isn't built with sqlite3 support."
+  :type 'boolean
+  :safe 'booleanp
+  :group 'ggtags)
+
 (defcustom ggtags-global-output-format 'grep
   "Global output format: path, ctags, ctags-x, grep or cscope."
   :type '(choice (const path)
@@ -217,6 +236,15 @@ This affects `ggtags-find-file' and `ggtags-grep'."
   :type 'boolean
   :group 'ggtags)
 
+;; See also https://github.com/leoliu/ggtags/issues/52
+(defcustom ggtags-global-search-libpath-for-reference t
+  "If non-nil global will search GTAGSLIBPATH for references.
+Search is only continued in GTAGSLIBPATH if it finds no matches
+in current project."
+  :safe 'booleanp
+  :type 'boolean
+  :group 'ggtags)
+
 (defcustom ggtags-global-large-output 1000
   "Number of lines in the Global buffer to indicate large output."
   :type 'number
@@ -278,6 +306,13 @@ properly update `ggtags-mode-map'."
   :type 'key-sequence
   :group 'ggtags)
 
+(defcustom ggtags-completing-read-function nil
+  "Ggtags specific `completing-read-function' (which see).
+Nil means using the value of `completing-read-function'."
+  :type '(choice (const :tag "Use completing-read-function" nil)
+                 function)
+  :group 'ggtags)
+
 (defcustom ggtags-highlight-tag-delay 0.25
   "Time in seconds before highlighting tag at point."
   :set (lambda (sym value)
@@ -301,6 +336,8 @@ properly update `ggtags-mode-map'."
 
 (defvar ggtags-global-last-buffer nil)
 
+(defvar ggtags-global-continuation nil)
+
 (defvar ggtags-current-tag-name nil)
 
 (defvar ggtags-highlight-tag-overlay nil)
@@ -335,7 +372,7 @@ properly update `ggtags-mode-map'."
                                 'compilation-finish-functions ,exit-args))))))
 
 (defmacro ggtags-ensure-global-buffer (&rest body)
-  (declare (indent 0))
+  (declare (debug t) (indent 0))
   `(progn
      (or (and (buffer-live-p ggtags-global-last-buffer)
               (with-current-buffer ggtags-global-last-buffer
@@ -362,11 +399,24 @@ properly update `ggtags-mode-map'."
     (goto-char (point-min))
     (forward-line (1- line))))
 
+(defun ggtags-kill-window ()
+  "Quit selected window and kill its buffer."
+  (interactive)
+  (quit-window t))
+
 (defun ggtags-program-path (name)
   (if ggtags-executable-directory
       (expand-file-name name ggtags-executable-directory)
     name))
 
+(defun ggtags-process-succeed-p (program &rest args)
+  "Return non-nil if successfully running PROGRAM with ARGS."
+  (let ((program (ggtags-program-path program)))
+    (condition-case err
+        (zerop (apply #'process-file program nil nil nil args))
+      (error (message "`%s' failed: %s" program (error-message-string err))
+             nil))))
+
 (defun ggtags-process-string (program &rest args)
   (with-temp-buffer
     (let ((exit (apply #'process-file
@@ -402,23 +452,16 @@ properly update `ggtags-mode-map'."
             (has-refs
              (when rtags-size
                (and (or (> rtags-size (* 32 1024))
-                        (with-demoted-errors
+                        (with-demoted-errors "ggtags-make-project: %S"
                           (not (equal "" (ggtags-process-string "global" "-crs")))))
                     'has-refs)))
             ;; http://thread.gmane.org/gmane.comp.gnu.global.bugs/1518
             (has-path-style
-             (with-demoted-errors       ; in case `global' not found
-               (and (zerop (process-file (ggtags-program-path "global")
-                                         nil nil nil
-                                         "--path-style" "shorter" "--help"))
-                    'has-path-style)))
+             (and (ggtags-process-succeed-p "global" "--path-style" "shorter" "--help")
+                  'has-path-style))
             ;; http://thread.gmane.org/gmane.comp.gnu.global.bugs/1542
-            (has-color
-             (with-demoted-errors
-               (and (zerop (process-file (ggtags-program-path "global")
-                                         nil nil nil
-                                         "--color" "--help"))
-                    'has-color))))
+            (has-color (and (ggtags-process-succeed-p "global" "--color" "--help")
+                            'has-color)))
        (puthash default-directory
                 (ggtags-project--make :root default-directory
                                       :tag-size tag-size
@@ -454,21 +497,18 @@ Value is new modtime if updated."
     (size (let ((project (or project (ggtags-find-project))))
             (and project (> (ggtags-project-tag-size project) size))))))
 
+(defvar-local ggtags-last-default-directory nil)
 (defvar-local ggtags-project-root 'unset
   "Internal variable for project root directory.")
 
-(defun ggtags-clear-project-root ()
-  (kill-local-variable 'ggtags-project-root))
-
 ;;;###autoload
 (defun ggtags-find-project ()
   ;; See https://github.com/leoliu/ggtags/issues/42
   ;;
-  ;; It is unsafe to cache `ggtags-project-root' in non-file buffers.
-  ;; But we keep the cache for a command's duration so that multiple
-  ;; calls of `ggtags-find-project' has no performance impact.
-  (unless buffer-file-name
-    (add-hook 'pre-command-hook #'ggtags-clear-project-root nil t))
+  ;; It is unsafe to cache `ggtags-project-root' in non-file buffers
+  ;; whose `default-directory' can often change.
+  (unless (equal ggtags-last-default-directory default-directory)
+    (kill-local-variable 'ggtags-project-root))
   (let ((project (gethash ggtags-project-root ggtags-projects)))
     (if (ggtags-project-p project)
         (if (ggtags-project-expired-p project)
@@ -476,6 +516,7 @@ Value is new modtime if updated."
               (remhash ggtags-project-root ggtags-projects)
               (ggtags-find-project))
           project)
+      (setq ggtags-last-default-directory default-directory)
       (setq ggtags-project-root
             (or (ignore-errors-unless-debug
                   (file-name-as-directory
@@ -508,12 +549,10 @@ Value is new modtime if updated."
 
 (defun ggtags-ensure-project ()
   (or (ggtags-find-project)
-      (when (or (yes-or-no-p "File GTAGS not found; run gtags? ")
-                (user-error "Aborted"))
-        (call-interactively #'ggtags-create-tags)
-        ;; Need checking because `ggtags-create-tags' can create tags
-        ;; in any directory.
-        (ggtags-check-project))))
+      (progn (call-interactively #'ggtags-create-tags)
+             ;; Need checking because `ggtags-create-tags' can create
+             ;; tags in any directory.
+             (ggtags-check-project))))
 
 (defvar delete-trailing-lines)          ;new in 24.3
 
@@ -570,14 +609,19 @@ Value is new modtime if updated."
       (message "Project read-only-mode is %s" (if val "on" "off")))
     val))
 
-(defun ggtags-visit-project-root ()
-  (interactive)
-  (ggtags-ensure-project)
-  (dired (ggtags-current-project-root)))
+(defun ggtags-visit-project-root (&optional project)
+  "Visit the root directory of (current) PROJECT in dired.
+When called with a prefix \\[universal-argument], choose from past projects."
+  (interactive (list (and current-prefix-arg
+                          (completing-read "Project: " ggtags-projects))))
+  (dired (cl-typecase project
+           (string project)
+           (ggtags-project (ggtags-project-root project))
+           (t (ggtags-ensure-project) (ggtags-current-project-root)))))
 
 (defmacro ggtags-with-current-project (&rest body)
   "Eval BODY in current project's `process-environment'."
-  (declare (debug t))
+  (declare (debug t) (indent 0))
   (let ((gtagsroot (make-symbol "-gtagsroot-"))
         (root (make-symbol "-ggtags-project-root-")))
     `(let* ((,root ggtags-project-root)
@@ -601,6 +645,37 @@ Value is new modtime if updated."
     (and path (mapcar (apply-partially #'concat (file-remote-p default-directory))
                       (split-string path (regexp-quote path-separator) t)))))
 
+(defun ggtags-project-relative-file (file)
+  "Get file name relative to current project root."
+  (ggtags-check-project)
+  (if (file-name-absolute-p file)
+      (file-relative-name file (if (string-prefix-p (ggtags-current-project-root)
+                                                    file)
+                                   (ggtags-current-project-root)
+                                 (locate-dominating-file file "GTAGS")))
+    file))
+
+(defun ggtags-project-file-p (file)
+  "Return non-nil if FILE is part of current project."
+  (when (ggtags-find-project)
+    (with-temp-buffer
+      (ggtags-with-current-project
+        ;; NOTE: `process-file' requires all files in ARGS be relative
+        ;; to `default-directory'; see its doc string for details.
+        (let ((default-directory (ggtags-current-project-root)))
+          (process-file (ggtags-program-path "global") nil t nil
+                        "-vP" (concat "^" (ggtags-project-relative-file file) "$"))))
+      (goto-char (point-min))
+      (not (re-search-forward "^file not found" nil t)))))
+
+(defun ggtags-invalidate-buffer-project-root (root)
+  (mapc (lambda (buf)
+          (with-current-buffer buf
+            (and buffer-file-truename
+                 (string-prefix-p root buffer-file-truename)
+                 (kill-local-variable 'ggtags-project-root))))
+        (buffer-list)))
+
 (defun ggtags-create-tags (root)
   "Create tag files (e.g. GTAGS) in directory ROOT.
 If file .globalrc or gtags.conf exists in ROOT, it will be used
@@ -616,48 +691,108 @@ source trees. See Info node `(global)gtags' for details."
                          (expand-file-name
                           (directory-file-name (file-name-as-directory root)))))
     (ggtags-with-current-project
-     (let ((conf (and ggtags-use-project-gtagsconf
-                      (cl-loop for name in '(".globalrc" "gtags.conf")
-                               for full = (expand-file-name name root)
-                               thereis (and (file-exists-p full) full)))))
-       (unless (or conf (getenv "GTAGSLABEL")
-                   (not (yes-or-no-p "Use `ctags' backend? ")))
-         (setenv "GTAGSLABEL" "ctags"))
-       (ggtags-with-temp-message "`gtags' in progress..."
-         (let ((default-directory (file-name-as-directory root))
-               (args (cl-remove-if
-                      ;; Place --idutils first
-                      #'null (list (and ggtags-use-idutils "--idutils")
-                                   (and conf "--gtagsconf")
-                                   (and conf (ggtags-ensure-localname conf))))))
-           (condition-case err
-               (apply #'ggtags-process-string "gtags" args)
-             (error (if (and ggtags-use-idutils
-                             (stringp (cadr err))
-                             (string-match-p "mkid not found" (cadr err)))
-                        ;; Retry without mkid
-                        (apply #'ggtags-process-string
-                               "gtags" (cl-remove "--idutils" args))
-                      (signal (car err) (cdr err)))))))))
+      (let ((conf (and ggtags-use-project-gtagsconf
+                       (cl-loop for name in '(".globalrc" "gtags.conf")
+                                for full = (expand-file-name name root)
+                                thereis (and (file-exists-p full) full)))))
+        (unless (or conf (getenv "GTAGSLABEL")
+                    (not (yes-or-no-p "Use `ctags' backend? ")))
+          (setenv "GTAGSLABEL" "ctags"))
+        (ggtags-with-temp-message "`gtags' in progress..."
+          (let ((default-directory (file-name-as-directory root))
+                (args (cl-remove-if
+                       #'null
+                       (list (and ggtags-use-idutils "--idutils")
+                             (and ggtags-use-sqlite3
+                                  (ggtags-process-succeed-p "gtags" "--sqlite3" "--help")
+                                  "--sqlite3")
+                             (and conf "--gtagsconf")
+                             (and conf (ggtags-ensure-localname conf))))))
+            (condition-case err
+                (apply #'ggtags-process-string "gtags" args)
+              (error (if (and ggtags-use-idutils
+                              (stringp (cadr err))
+                              (string-match-p "mkid not found" (cadr err)))
+                         ;; Retry without mkid
+                         (apply #'ggtags-process-string
+                                "gtags" (cl-remove "--idutils" args))
+                       (signal (car err) (cdr err)))))))))
+    (ggtags-invalidate-buffer-project-root (file-truename root))
     (message "GTAGS generated in `%s'" root)
     root))
 
 (defun ggtags-update-tags (&optional force)
   "Update GNU Global tag database.
-Do nothing if GTAGS exceeds the oversize limit unless FORCE."
+Do nothing if GTAGS exceeds the oversize limit unless FORCE.
+
+When called interactively on large (per `ggtags-oversize-limit')
+projects, the update process runs in the background without
+blocking emacs."
   (interactive (progn
                  (ggtags-check-project)
                  ;; Mark project info expired.
                  (setf (ggtags-project-timestamp (ggtags-find-project)) -1)
-                 (list t)))
-  (when (or force (and (ggtags-find-project)
-                       (not (ggtags-project-oversize-p))
-                       (ggtags-project-dirty-p (ggtags-find-project))))
+                 (list 'interactive)))
+  (cond ((and (eq force 'interactive) (ggtags-project-oversize-p))
+         (ggtags-with-current-project
+           (with-display-buffer-no-window
+             (with-current-buffer (compilation-start "global -u")
+               ;; A hack to fool compilation mode to display `global
+               ;; -u finished' on finish.
+               (setq mode-name "global -u")
+               (add-hook 'compilation-finish-functions
+                         #'ggtags-update-tags-finish nil t)))))
+        ((or force (and (ggtags-find-project)
+                        (not (ggtags-project-oversize-p))
+                        (ggtags-project-dirty-p (ggtags-find-project))))
+         (ggtags-with-current-project
+           (ggtags-with-temp-message "`global -u' in progress..."
+             (ggtags-process-string "global" "-u")
+             (ggtags-update-tags-finish))))))
+
+(defun ggtags-update-tags-finish (&optional buf how)
+  (if (and how buf (string-prefix-p "exited abnormally" how))
+      (display-buffer buf)
+    (setf (ggtags-project-dirty-p (ggtags-find-project)) nil)
+    (setf (ggtags-project-mtime (ggtags-find-project)) (float-time))))
+
+(defun ggtags-update-tags-single (file &optional nowait)
+  ;; NOTE: NOWAIT is ignored if file is remote file; see
+  ;; `tramp-sh-handle-process-file'.
+  (cl-check-type file string)
+  (let ((nowait (unless (file-remote-p file) nowait)))
     (ggtags-with-current-project
-     (ggtags-with-temp-message "`global -u' in progress..."
-       (ggtags-process-string "global" "-u")
-       (setf (ggtags-project-dirty-p (ggtags-find-project)) nil)
-       (setf (ggtags-project-mtime (ggtags-find-project)) (float-time))))))
+      ;; See comment in `ggtags-project-file-p'.
+      (let ((default-directory (ggtags-current-project-root)))
+        (process-file (ggtags-program-path "global") nil (and nowait 0) nil
+                      "--single-update" (ggtags-project-relative-file file))))))
+
+(defun ggtags-delete-tags ()
+  "Delete file GTAGS, GRTAGS, GPATH, ID etc. generated by gtags."
+  (interactive (ignore (ggtags-check-project)))
+  (when (ggtags-current-project-root)
+    (let* ((re (concat "\\`" (regexp-opt '("GPATH" "GRTAGS" "GTAGS" "ID")) "\\'"))
+           (files (cl-remove-if-not
+                   (lambda (file)
+                     ;; Don't trust `directory-files'.
+                     (let ((case-fold-search nil))
+                       (string-match-p re (file-name-nondirectory file))))
+                   (directory-files (ggtags-current-project-root) t re)))
+           (buffer "*GTags File List*"))
+      (or files (user-error "No tag files found"))
+      (with-output-to-temp-buffer buffer
+        (princ (mapconcat #'identity files "\n")))
+      (let ((win (get-buffer-window buffer)))
+        (unwind-protect
+            (progn
+              (fit-window-to-buffer win)
+              (when (yes-or-no-p "Remove GNU Global tag files? ")
+                (with-demoted-errors (mapc #'delete-file files))
+                (remhash (ggtags-current-project-root) ggtags-projects)
+                (and (overlayp ggtags-highlight-tag-overlay)
+                     (delete-overlay ggtags-highlight-tag-overlay))))
+          (when (window-live-p win)
+            (quit-window t win)))))))
 
 (defvar-local ggtags-completion-cache nil)
 
@@ -676,13 +811,13 @@ Do nothing if GTAGS exceeds the oversize limit unless FORCE."
                        ;; May throw global: only name char is allowed
                        ;; with -c option.
                        (ggtags-with-current-project
-                        (split-string
-                         (apply #'ggtags-process-string
-                                "global"
-                                (append (and completion-ignore-case '("--ignore-case"))
-                                        ;; Note -c alone returns only definitions
-                                        (list (concat "-c" ggtags-completion-flag) prefix)))
-                         "\n" t)))))))
+                         (split-string
+                          (apply #'ggtags-process-string
+                                 "global"
+                                 (append (and completion-ignore-case '("--ignore-case"))
+                                         ;; Note -c alone returns only definitions
+                                         (list (concat "-c" ggtags-completion-flag) prefix)))
+                          "\n" t)))))))
      (cdr ggtags-completion-cache))))
 
 (defun ggtags-completion-at-point ()
@@ -706,9 +841,12 @@ Do nothing if GTAGS exceeds the oversize limit unless FORCE."
     (setq ggtags-current-tag-name
           (cond (confirm
                  (ggtags-update-tags)
-                 (completing-read
-                  (format (if default "%s (default %s): " "%s: ") prompt default)
-                  ggtags-completion-table nil require-match nil nil default))
+                 (let ((completing-read-function
+                        (or ggtags-completing-read-function
+                            completing-read-function)))
+                   (completing-read
+                    (format (if default "%s (default %s): " "%s: ") prompt default)
+                    ggtags-completion-table nil require-match nil nil default)))
                 (default (substring-no-properties default))
                 (t (ggtags-read-tag type t prompt require-match default))))))
 
@@ -728,24 +866,26 @@ Do nothing if GTAGS exceeds the oversize limit unless FORCE."
                           (and ggtags-global-treat-text "--other")
                           (pcase cmd
                             ((pred stringp) cmd)
-                            (`definition "") ;-d not supported by Global 5.7.1
-                            (`reference "-r")
-                            (`symbol "-s")
+                            (`definition nil) ;-d not supported by Global 5.7.1
+                            (`reference "--reference")
+                            (`symbol "--symbol")
                             (`path "--path")
                             (`grep "--grep")
                             (`idutils "--idutils")))
                     args)))
     (mapconcat #'identity (delq nil xs) " ")))
 
-;; takes three values: nil, t and a marker
+;; Can be three values: nil, t and a marker; t means start marker has
+;; been saved in the tag ring.
 (defvar ggtags-global-start-marker nil)
-(defvar ggtags-global-exit-status 0)
-(defvar ggtags-global-match-count 0)
+(defvar ggtags-global-start-file nil)
 (defvar ggtags-tag-ring-index nil)
 (defvar ggtags-global-search-history nil)
 
 (defvar ggtags-auto-jump-to-match-target nil)
 
+(defvar-local ggtags-global-exit-info nil) ; (EXIT-STATUS COUNT DB)
+
 (defun ggtags-global-save-start-marker ()
   (when (markerp ggtags-global-start-marker)
     (setq ggtags-tag-ring-index nil)
@@ -756,19 +896,20 @@ Do nothing if GTAGS exceeds the oversize limit unless FORCE."
   (let* ((default-directory (or directory (ggtags-current-project-root)))
          (split-window-preferred-function ggtags-split-window-function)
          (env ggtags-process-environment))
-    (setq ggtags-global-start-marker (point-marker))
+    (unless (markerp ggtags-global-start-marker)
+      (setq ggtags-global-start-marker (point-marker)))
+    ;; Record the file name for `ggtags-navigation-start-file'.
+    (setq ggtags-global-start-file buffer-file-name)
     (setq ggtags-auto-jump-to-match-target
           (nth 4 (assoc (ggtags-global-search-id command default-directory)
                         ggtags-global-search-history)))
     (ggtags-navigation-mode +1)
-    (setq ggtags-global-exit-status 0
-          ggtags-global-match-count 0)
     (ggtags-update-tags)
     (ggtags-with-current-project
-     (with-current-buffer (with-display-buffer-no-window
-                            (compilation-start command 'ggtags-global-mode))
-       (setq-local ggtags-process-environment env)
-       (setq ggtags-global-last-buffer (current-buffer))))))
+      (with-current-buffer (with-display-buffer-no-window
+                             (compilation-start command 'ggtags-global-mode))
+        (setq-local ggtags-process-environment env)
+        (setq ggtags-global-last-buffer (current-buffer))))))
 
 (defun ggtags-find-tag-continue ()
   (interactive)
@@ -792,7 +933,7 @@ Do nothing if GTAGS exceeds the oversize limit unless FORCE."
      (save-excursion
        (beginning-of-line)
        (and (looking-at re) (match-string sub))))
-    (_ (warn "Invalid value for `ggtags-include-pattern':%s"
+    (_ (warn "Invalid value for `ggtags-include-pattern': %s"
              ggtags-include-pattern)
        nil)))
 
@@ -811,36 +952,61 @@ definition tags."
      (if include (list include 'include)
        (list (ggtags-read-tag 'definition current-prefix-arg)
              (and current-prefix-arg 'definition)))))
-  (ggtags-check-project)     ; for `ggtags-current-project-root' below
+  (ggtags-check-project)    ; For `ggtags-current-project-root' below.
   (cond
    ((eq what 'include)
     (ggtags-find-file name))
    ((or (eq what 'definition)
         (not buffer-file-name)
-        (and (ggtags-find-project)
-             (not (ggtags-project-has-refs (ggtags-find-project)))))
-    (ggtags-find-tag 'definition (shell-quote-argument name)))
-   (t (ggtags-find-tag
-       (format "--from-here=%d:%s"
-               (line-number-at-pos)
-               (shell-quote-argument
-                ;; Note `ggtags-global-start' binds default-directory to
-                ;; project root.
-                (file-relative-name
-                 buffer-file-name
-                 (if (string-prefix-p (ggtags-current-project-root)
-                                      buffer-file-name)
-                     (ggtags-current-project-root)
-                   (locate-dominating-file buffer-file-name "GTAGS")))))
-       (shell-quote-argument name)))))
+        (not (ggtags-project-has-refs (ggtags-find-project)))
+        (not (ggtags-project-file-p buffer-file-name)))
+    (ggtags-find-definition name))
+   (t (ggtags-find-tag (format "--from-here=%d:%s"
+                               (line-number-at-pos)
+                               (shell-quote-argument
+                                ;; Note `ggtags-global-start' binds
+                                ;; default-directory to project root.
+                                (ggtags-project-relative-file buffer-file-name)))
+                       (shell-quote-argument name)))))
+
+(defun ggtags-find-tag-mouse (event)
+  (interactive "e")
+  (with-selected-window (posn-window (event-start event))
+    (save-excursion
+      (goto-char (posn-point (event-start event)))
+      (call-interactively #'ggtags-find-tag-dwim))))
+
+;; Another option for `M-.'.
+(defun ggtags-find-definition (name)
+  (interactive (list (ggtags-read-tag 'definition current-prefix-arg)))
+  (ggtags-find-tag 'definition (shell-quote-argument name)))
+
+(defun ggtags-setup-libpath-search (type name)
+  (pcase (and ggtags-global-search-libpath-for-reference
+              (ggtags-get-libpath))
+    ((and libs (guard libs))
+     (cl-labels ((cont (buf how)
+                   (pcase ggtags-global-exit-info
+                     (`(0 0 ,_)
+                      (with-temp-buffer
+                        (setq default-directory
+                              (file-name-as-directory (pop libs)))
+                        (and libs (setq ggtags-global-continuation #'cont))
+                        (if (ggtags-find-project)
+                            (ggtags-find-tag type (shell-quote-argument name))
+                          (cont buf how))))
+                     (_ (ggtags-global-handle-exit buf how)))))
+       (setq ggtags-global-continuation #'cont)))))
 
 (defun ggtags-find-reference (name)
   (interactive (list (ggtags-read-tag 'reference current-prefix-arg)))
+  (ggtags-setup-libpath-search 'reference name)
   (ggtags-find-tag 'reference (shell-quote-argument name)))
 
 (defun ggtags-find-other-symbol (name)
   "Find tag NAME that is a reference without a definition."
   (interactive (list (ggtags-read-tag 'symbol current-prefix-arg)))
+  (ggtags-setup-libpath-search 'symbol name)
   (ggtags-find-tag 'symbol (shell-quote-argument name)))
 
 (defun ggtags-quote-pattern (pattern)
@@ -870,7 +1036,8 @@ Invert the match when called with a prefix arg \\[universal-argument]."
     (ggtags-find-tag 'path (and invert-match "--invert-match")
                      "--" (ggtags-quote-pattern pattern))))
 
-;; NOTE: Coloured output in grep requested: http://goo.gl/Y9IcX
+;; Note: Coloured output requested in http://goo.gl/Y9IcX and appeared
+;; in global v6.2.12.
 (defun ggtags-find-tag-regexp (regexp directory)
   "List tags matching REGEXP in DIRECTORY (default to project root).
 When called interactively with a prefix, ask for the directory."
@@ -888,6 +1055,20 @@ When called interactively with a prefix, ask for the directory."
 
 (defvar ggtags-navigation-mode)
 
+(defun ggtags-foreach-file (fn)
+  "Invoke FN with each file found.
+FN is invoked while *ggtags-global* buffer is current."
+  (ggtags-ensure-global-buffer
+    (save-excursion
+      (goto-char (point-min))
+      (while (with-demoted-errors "compilation-next-error: %S"
+               (compilation-next-error 1 'file)
+               t)
+        (funcall fn (caar
+                     (compilation--loc->file-struct
+                      (compilation--message->loc
+                       (get-text-property (point) 'compilation-message)))))))))
+
 (defun ggtags-query-replace (from to &optional delimited)
   "Query replace FROM with TO on files in the Global buffer.
 If not in navigation mode, do a grep on FROM first.
@@ -907,31 +1088,34 @@ Global and Emacs."
               (ggtags-with-temp-message "Waiting for Grep to finish..."
                 (while (get-buffer-process (current-buffer))
                   (sit-for 0.2)))
-              (goto-char (point-min))
-              (while (ignore-errors (compilation-next-file 1) t)
-                (let ((m (get-text-property (point) 'compilation-message)))
-                  (push (expand-file-name
-                         (caar (compilation--loc->file-struct
-                                (compilation--message->loc m))))
-                        files))))
+              (ggtags-foreach-file
+               (lambda (file) (push (expand-file-name file) files))))
             (ggtags-navigation-mode -1)
             (nreverse files))))
     (tags-query-replace from to delimited file-form)))
 
+(defun ggtags-global-normalise-command (cmd)
+  (if (string-match
+       (concat (regexp-quote (ggtags-global-build-command nil)) "\\s-*")
+       cmd)
+      (substring-no-properties cmd (match-end 0))
+    cmd))
+
 (defun ggtags-global-search-id (cmd directory)
-  (sha1 (concat directory (make-string 1 0) cmd)))
+  (sha1 (concat directory (make-string 1 0)
+                (ggtags-global-normalise-command cmd))))
 
 (defun ggtags-global-current-search ()
   ;; CMD DIR ENV LINE TEXT
   (ggtags-ensure-global-buffer
-    (list (car compilation-arguments)
+    (list (ggtags-global-normalise-command (car compilation-arguments))
           default-directory
           ggtags-process-environment
           (line-number-at-pos)
           (buffer-substring-no-properties
            (line-beginning-position) (line-end-position)))))
 
-(defun ggtags-global-rerun-search-1 (data)
+(defun ggtags-global-rerun-search (data)
   (pcase data
     (`(,cmd ,dir ,env ,line ,_text)
      (with-current-buffer (let ((ggtags-auto-jump-to-match nil)
@@ -939,7 +1123,8 @@ Global and Emacs."
                                 (default-directory dir)
                                 (ggtags-project-root dir)
                                 (ggtags-process-environment env))
-                            (ggtags-global-start cmd dir))
+                            (ggtags-global-start
+                             (ggtags-global-build-command cmd) dir))
        (add-hook 'compilation-finish-functions
                  (lambda (buf _msg)
                    (with-current-buffer buf
@@ -948,76 +1133,138 @@ Global and Emacs."
                  nil t)))))
 
 (defvar-local ggtags-global-search-ewoc nil)
-(defvar ggtags-global-rerun-search-last nil)
-
-(defvar ggtags-global-rerun-search-map
-  (cl-labels
-      ((save ()
-             (setq ggtags-global-rerun-search-last
-                   (ewoc-data (ewoc-locate ggtags-global-search-ewoc))))
-       (next (arg)
-             (interactive "p")
-             (ewoc-goto-next ggtags-global-search-ewoc arg)
-             (save))
-       (prev (arg)
-             (interactive "p")
-             (ewoc-goto-prev ggtags-global-search-ewoc arg)
-             (save))
-       (quit ()
-             (interactive)
-             (quit-windows-on (ewoc-buffer ggtags-global-search-ewoc) t))
-       (done ()
-             (interactive)
-             (let ((node (ewoc-locate ggtags-global-search-ewoc)))
-               (when node
-                 (save)
-                 (quit)
-                 (ggtags-global-rerun-search-1 (cdr (ewoc-data node)))))))
-    (let ((m (make-sparse-keymap)))
-      (set-keymap-parent m special-mode-map)
-      (define-key m "p"    #'prev)
-      (define-key m "\M-p" #'prev)
-      (define-key m "n"    #'next)
-      (define-key m "\M-n" #'next)
-      (define-key m "r"    #'ggtags-save-to-register)
-      (define-key m "q"    #'quit)
-      (define-key m "\r"   #'done)
-      m)))
+(defvar ggtags-view-search-history-last nil)
+
+(defvar ggtags-view-search-history-mode-map
+  (let ((m (make-sparse-keymap)))
+    (define-key m "p" 'ggtags-view-search-history-prev)
+    (define-key m "\M-p" 'ggtags-view-search-history-prev)
+    (define-key m "n" 'ggtags-view-search-history-next)
+    (define-key m "\M-n" 'ggtags-view-search-history-next)
+    (define-key m "\C-k" 'ggtags-view-search-history-kill)
+    (define-key m [remap yank] (lambda (&optional arg) (interactive "P") (yank arg)))
+    (define-key m "\C-c\C-c" 'ggtags-view-search-history-update)
+    (define-key m "r" 'ggtags-save-to-register)
+    (define-key m "\r" 'ggtags-view-search-history-action)
+    (define-key m "q" 'ggtags-kill-window)
+    m))
+
+(defun ggtags-view-search-history-remember ()
+  (setq ggtags-view-search-history-last
+        (pcase (ewoc-locate ggtags-global-search-ewoc)
+          (`nil nil)
+          (node (ewoc-data node)))))
+
+(defun ggtags-view-search-history-next (&optional arg)
+  (interactive "p")
+  (let ((arg (or arg 1)))
+    (prog1 (funcall (if (cl-minusp arg) #'ewoc-goto-prev #'ewoc-goto-next)
+                    ggtags-global-search-ewoc (abs arg))
+      (ggtags-view-search-history-remember))))
+
+(defun ggtags-view-search-history-prev (&optional arg)
+  (interactive "p")
+  (ggtags-view-search-history-next (- (or arg 1))))
+
+(defun ggtags-view-search-history-kill (&optional append)
+  (interactive "P")
+  (let* ((node (or (ewoc-locate ggtags-global-search-ewoc)
+                   (user-error "No node at point")))
+         (next (ewoc-next ggtags-global-search-ewoc node))
+         (text (filter-buffer-substring (ewoc-location node)
+                                        (if next (ewoc-location next)
+                                          (point-max)))))
+    (put-text-property
+     0 (length text) 'yank-handler
+     (list (lambda (arg)
+             (if (not ggtags-global-search-ewoc)
+                 (insert (car arg))
+               (let* ((inhibit-read-only t)
+                      (node (unless (looking-at-p "[ \t\n]*\\'")
+                              (ewoc-locate ggtags-global-search-ewoc))))
+                 (if node
+                     (ewoc-enter-before ggtags-global-search-ewoc
+                                        node (cadr arg))
+                   (ewoc-enter-last ggtags-global-search-ewoc (cadr arg)))
+                 (setq ggtags-view-search-history-last (cadr arg)))))
+           (list text (ewoc-data node)))
+     text)
+    (if append (kill-append text nil)
+      (kill-new text))
+    (let ((inhibit-read-only t))
+      (ewoc-delete ggtags-global-search-ewoc node))))
+
+(defun ggtags-view-search-history-update (&optional noconfirm)
+  "Update `ggtags-global-search-history' to current buffer."
+  (interactive "P")
+  (when (and (buffer-modified-p)
+             (or noconfirm
+                 (yes-or-no-p "Modify `ggtags-global-search-history'?")))
+    (setq ggtags-global-search-history
+          (ewoc-collect ggtags-global-search-ewoc #'identity))
+    (set-buffer-modified-p nil)))
+
+(defun ggtags-view-search-history-action ()
+  (interactive)
+  (let ((data (ewoc-data (or (ewoc-locate ggtags-global-search-ewoc)
+                             (user-error "No search at point")))))
+    (ggtags-view-search-history-remember)
+    (quit-window t)
+    (ggtags-global-rerun-search (cdr data))))
 
 (defvar bookmark-make-record-function)
 
-(defun ggtags-global-rerun-search ()
-  "Pop up a buffer to choose a past search to re-run.
+(define-derived-mode ggtags-view-search-history-mode special-mode "SearchHist"
+  "Major mode for viewing search history."
+  :group 'ggtags
+  (setq-local ggtags-enable-navigation-keys nil)
+  (setq-local bookmark-make-record-function #'ggtags-make-bookmark-record)
+  (setq truncate-lines t)
+  (add-hook 'kill-buffer-hook #'ggtags-view-search-history-update nil t))
+
+(defun ggtags-view-search-history-restore-last ()
+  (when ggtags-view-search-history-last
+    (cl-loop for n = (ewoc-nth ggtags-global-search-ewoc 0)
+             then (ewoc-next ggtags-global-search-ewoc n)
+             while n when (eq (ewoc-data n)
+                              ggtags-view-search-history-last)
+             do (progn (goto-char (ewoc-location n)) (cl-return t)))))
 
-\\{ggtags-global-rerun-search-map}"
+(defun ggtags-view-search-history ()
+  "Pop to a buffer to view or re-run past searches.
+
+\\{ggtags-view-search-history-mode-map}"
   (interactive)
   (or ggtags-global-search-history (user-error "No search history"))
   (let ((split-window-preferred-function ggtags-split-window-function)
         (inhibit-read-only t))
     (pop-to-buffer "*Ggtags Search History*")
     (erase-buffer)
-    (special-mode)
-    (use-local-map ggtags-global-rerun-search-map)
-    (setq-local ggtags-enable-navigation-keys nil)
-    (setq-local bookmark-make-record-function #'ggtags-make-bookmark-record)
-    (setq truncate-lines t)
-    (cl-labels ((prop (s) (propertize s 'face 'minibuffer-prompt))
+    (ggtags-view-search-history-mode)
+    (cl-labels ((prop (s)
+                  (propertize s 'face 'minibuffer-prompt))
+                (prop-tag (cmd)
+                  (with-temp-buffer
+                    (insert cmd)
+                    (forward-sexp -1)
+                    (if (eobp)
+                        cmd
+                      (put-text-property (point) (point-max)
+                                         'face font-lock-constant-face)
+                      (buffer-string))))
                 (pp (data)
-                    (pcase data
-                      (`(,_id ,cmd ,dir ,_env ,line ,text)
-                       (insert (prop " cmd: ") cmd "\n"
-                               (prop " dir: ") dir "\n"
-                               (prop "line: ") (number-to-string line) "\n"
-                               (prop "text: ") text "\n"
-                               (propertize (make-string 32 ?-) 'face 'shadow))))))
+                  (pcase data
+                    (`(,_id ,cmd ,dir ,_env ,line ,text)
+                     (insert (prop " cmd: ") (prop-tag cmd) "\n"
+                             (prop " dir: ") dir "\n"
+                             (prop "line: ") (number-to-string line) "\n"
+                             (prop "text: ") text "\n"
+                             (propertize (make-string 32 ?-) 'face 'shadow))))))
       (setq ggtags-global-search-ewoc
             (ewoc-create #'pp "Global search history keys:  n:next  p:prev  r:register  RET:choose\n")))
     (dolist (data ggtags-global-search-history)
       (ewoc-enter-last ggtags-global-search-ewoc data))
-    (and ggtags-global-rerun-search-last
-         (re-search-forward (cadr ggtags-global-rerun-search-last) nil t)
-         (ewoc-goto-node ggtags-global-search-ewoc
-                         (ewoc-locate ggtags-global-search-ewoc)))
+    (ggtags-view-search-history-restore-last)
     (set-buffer-modified-p nil)
     (fit-window-to-buffer nil (floor (frame-height) 2))))
 
@@ -1026,15 +1273,15 @@ Global and Emacs."
 Use \\[jump-to-register] to restore the search session."
   (interactive (list (register-read-with-preview "Save search to register: ")))
   (cl-labels ((prn (data)
-                   (pcase data
-                     (`(,command ,root ,_env ,line ,_)
-                      (princ (format "a ggtags search session `%s' in directory `%s' at line %d."
-                                     command root line))))))
+                (pcase data
+                  (`(,command ,root ,_env ,line ,_)
+                   (princ (format "a ggtags search session `%s' in directory `%s' at line %d."
+                                  command root line))))))
     (set-register r (registerv-make
                      (if ggtags-global-search-ewoc
                          (cdr (ewoc-data (ewoc-locate ggtags-global-search-ewoc)))
                        (ggtags-global-current-search))
-                     :jump-func #'ggtags-global-rerun-search-1
+                     :jump-func #'ggtags-global-rerun-search
                      :print-func #'prn))))
 
 (defun ggtags-make-bookmark-record ()
@@ -1047,34 +1294,7 @@ Use \\[jump-to-register] to restore the search session."
 (declare-function bookmark-prop-get "bookmark")
 
 (defun ggtags-bookmark-jump (bmk)
-  (ggtags-global-rerun-search-1 (bookmark-prop-get bmk 'ggtags-search)))
-
-(defun ggtags-delete-tag-files ()
-  "Delete the GTAGS, GRTAGS, GPATH etc. files generated by gtags."
-  (interactive (ignore (ggtags-check-project)))
-  (when (ggtags-current-project-root)
-    (let* ((re (concat "\\`" (regexp-opt '("GPATH" "GRTAGS" "GTAGS" "ID")) "\\'"))
-           (files (cl-remove-if-not
-                   (lambda (file)
-                     ;; Don't trust `directory-files'.
-                     (let ((case-fold-search nil))
-                       (string-match-p re (file-name-nondirectory file))))
-                   (directory-files (ggtags-current-project-root) t re)))
-           (buffer "*GTags File List*"))
-      (or files (user-error "No tag files found"))
-      (with-output-to-temp-buffer buffer
-        (princ (mapconcat #'identity files "\n")))
-      (let ((win (get-buffer-window buffer)))
-        (unwind-protect
-            (progn
-              (fit-window-to-buffer win)
-              (when (yes-or-no-p "Remove GNU Global tag files? ")
-                (with-demoted-errors (mapc #'delete-file files))
-                (remhash (ggtags-current-project-root) ggtags-projects)
-                (and (overlayp ggtags-highlight-tag-overlay)
-                     (delete-overlay ggtags-highlight-tag-overlay))))
-          (when (window-live-p win)
-            (quit-window t win)))))))
+  (ggtags-global-rerun-search (bookmark-prop-get bmk 'ggtags-search)))
 
 (defun ggtags-browse-file-as-hypertext (file line)
   "Browse FILE in hypertext (HTML) form."
@@ -1134,7 +1354,7 @@ Use \\[jump-to-register] to restore the search session."
   (let ((m (make-sparse-keymap)))
     (define-key m "\M-n" 'next-error-no-select)
     (define-key m "\M-p" 'previous-error-no-select)
-    (define-key m "q" (lambda () (interactive) (quit-window t)))
+    (define-key m "q"    'ggtags-kill-window)
     m))
 
 (define-derived-mode ggtags-view-tag-history-mode tabulated-list-mode "TagHist"
@@ -1223,41 +1443,45 @@ commands `next-error' and `previous-error'.
      (goto-char (marker-position m)))
     (_ (error "Dead marker"))))
 
+(defun ggtags-global-exit-message-1 ()
+  "Get the total of matches and db file used."
+  (save-excursion
+    (goto-char (point-max))
+    (if (re-search-backward
+         "^\\w+ \\(not found\\)\\|^\\([0-9]+\\) \\w+ located" nil t)
+        (cons (or (and (match-string 1) 0)
+                  (string-to-number (match-string 2)))
+              (when (re-search-forward
+                     "using \\(?:\\(idutils\\)\\|'[^']*/\\(\\w+\\)'\\)"
+                     (line-end-position)
+                     t)
+                (or (and (match-string 1) "ID")
+                    (match-string 2))))
+      (cons 0 nil))))
+
 (defun ggtags-global-exit-message-function (_process-status exit-status msg)
-  (setq ggtags-global-exit-status exit-status)
-  (pcase-let ((`(,count . ,db)
-               (save-excursion
-                 (goto-char (point-max))
-                 (if (re-search-backward
-                      "^\\w+ \\(not found\\)\\|^\\([0-9]+\\) \\w+ located" nil t)
-                     (cons (or (and (match-string 1) 0)
-                               (string-to-number (match-string 2)))
-                           (when (re-search-forward
-                                  "using \\(?:\\(idutils\\)\\|'[^']*/\\(\\w+\\)'\\)"
-                                  (line-end-position)
-                                  t)
-                             (or (and (match-string 1) "ID")
-                                 (match-string 2))))
-                   (cons 0 nil)))))
-    (setq ggtags-global-match-count count)
-    ;; Clear the start marker in case of zero matches.
-    (and (zerop count)
-         (markerp ggtags-global-start-marker)
-         (setq ggtags-global-start-marker nil))
-    (cons (if (> exit-status 0)
-              msg
-            (format "found %d %s"
-                    count
-                    (funcall (if (= count 1) #'car #'cadr)
-                             (pcase db
-                               ;; ` required for 24.1 and 24.2
-                               (`"GTAGS"  '("definition" "definitions"))
-                               (`"GSYMS"  '("symbol"     "symbols"))
-                               (`"GRTAGS" '("reference"  "references"))
-                               (`"GPATH"  '("file"       "files"))
-                               (`"ID"     '("identifier" "identifiers"))
-                               (_         '("match"      "matches"))))))
-          exit-status)))
+  "A function for `compilation-exit-message-function'."
+  (pcase (ggtags-global-exit-message-1)
+    (`(,count . ,db)
+     (setq ggtags-global-exit-info (list exit-status count db))
+     ;; Clear the start marker in case of zero matches.
+     (and (zerop count)
+          (markerp ggtags-global-start-marker)
+          (not ggtags-global-continuation)
+          (setq ggtags-global-start-marker nil))
+     (cons (if (> exit-status 0)
+               msg
+             (format "found %d %s" count
+                     (funcall (if (= count 1) #'car #'cadr)
+                              (pcase db
+                                ;; ` required for 24.1 and 24.2
+                                (`"GTAGS"  '("definition" "definitions"))
+                                (`"GSYMS"  '("symbol"     "symbols"))
+                                (`"GRTAGS" '("reference"  "references"))
+                                (`"GPATH"  '("file"       "files"))
+                                (`"ID"     '("identifier" "identifiers"))
+                                (_         '("match"      "matches"))))))
+           exit-status))))
 
 (defun ggtags-global-column (start)
   ;; START is the beginning position of source text.
@@ -1306,7 +1530,7 @@ commands `next-error' and `previous-error'.
 
 (defun ggtags-abbreviate-files (start end)
   (goto-char start)
-  (let* ((error-re (cdr (assq ggtags-global-output-format
+  (let* ((error-re (cdr (assq (car compilation-error-regexp-alist)
                               ggtags-global-error-regexp-alist-alist)))
          (sub (cadr error-re)))
     (when (and ggtags-global-abbreviate-filename error-re)
@@ -1383,9 +1607,23 @@ commands `next-error' and `previous-error'.
 (defun ggtags-global-handle-exit (buf how)
   "A function for `compilation-finish-functions' (which see)."
   (cond
+   (ggtags-global-continuation
+    (let ((cont (prog1 ggtags-global-continuation
+                  (setq ggtags-global-continuation nil))))
+      (funcall cont buf how)))
    ((string-prefix-p "exited abnormally" how)
     ;; If exit abnormally display the buffer for inspection.
-    (ggtags-global--display-buffer))
+    (ggtags-global--display-buffer)
+    (when (save-excursion
+            (goto-char (point-max))
+            (re-search-backward
+             (eval-when-compile
+               (format "^global: %s not found.$"
+                       (regexp-opt '("GTAGS" "GRTAGS" "GSYMS" "GPATH"))))
+             nil t))
+      (ggtags-echo "WARNING: Global tag files missing in `%s'"
+                   ggtags-project-root)
+      (remhash ggtags-project-root ggtags-projects)))
    (ggtags-auto-jump-to-match
     (if (pcase (compilation-next-single-property-change
                 (point-min) 'compilation-message)
@@ -1394,7 +1632,7 @@ commands `next-error' and `previous-error'.
             (save-excursion (goto-char pt) (end-of-line) (point))
             'compilation-message)))
         ;; There are multiple matches so pop up the buffer.
-        (ggtags-global--display-buffer)
+        (and ggtags-navigation-mode (ggtags-global--display-buffer))
       ;; For the `compilation-auto-jump' in idle timer to run.
       ;; See also: http://debbugs.gnu.org/13829
       (sit-for 0)
@@ -1411,10 +1649,13 @@ commands `next-error' and `previous-error'.
 
 (define-compilation-mode ggtags-global-mode "Global"
   "A mode for showing outputs from gnu global."
-  ;; Make it buffer local for `ggtags-abbreviate-files'.
-  (make-local-variable 'ggtags-global-output-format)
-  (setq-local compilation-error-regexp-alist
-              (list ggtags-global-output-format))
+  ;; Note: Place `ggtags-global-output-format' as first element for
+  ;; `ggtags-abbreviate-files'.
+  (setq-local compilation-error-regexp-alist (list ggtags-global-output-format))
+  (when (markerp ggtags-global-start-marker)
+    (setq ggtags-project-root
+          (buffer-local-value 'ggtags-project-root
+                              (marker-buffer ggtags-global-start-marker))))
   (pcase ggtags-auto-jump-to-match
     (`history (make-local-variable 'ggtags-auto-jump-to-match-target)
               (setq-local compilation-auto-jump-to-first-error
@@ -1449,11 +1690,15 @@ commands `next-error' and `previous-error'.
     (define-key map "\M-p" 'previous-error)
     (define-key map "\M-}" 'ggtags-navigation-next-file)
     (define-key map "\M-{" 'ggtags-navigation-previous-file)
+    (define-key map "\M-=" 'ggtags-navigation-start-file)
     (define-key map "\M->" 'ggtags-navigation-last-error)
-    (define-key map "\M-<" 'ggtags-navigation-first-error)
-    ;; Note: shadows `isearch-forward-regexp' but it can be invoked
-    ;; with C-u C-s instead.
+    (define-key map "\M-<" 'first-error)
+    ;; Note: shadows `isearch-forward-regexp' but it can still be
+    ;; invoked with `C-u C-s'.
     (define-key map "\C-\M-s" 'ggtags-navigation-isearch-forward)
+    ;; Add an alternative binding because C-M-s is reported not
+    ;; working on some systems.
+    (define-key map "\M-ss" 'ggtags-navigation-isearch-forward)
     (define-key map "\C-c\C-k"
       (lambda () (interactive)
         (ggtags-ensure-global-buffer (kill-compilation))))
@@ -1483,8 +1728,7 @@ commands `next-error' and `previous-error'.
       '(menu-item "Abort" ggtags-navigation-mode-abort))
     (define-key menu [last-match]
       '(menu-item "Last match" ggtags-navigation-last-error))
-    (define-key menu [first-match]
-      '(menu-item "First match" ggtags-navigation-first-error))
+    (define-key menu [first-match] '(menu-item "First match" first-error))
     (define-key menu [previous-file]
       '(menu-item "Previous file" ggtags-navigation-previous-file))
     (define-key menu [next-file]
@@ -1535,6 +1779,7 @@ commands `next-error' and `previous-error'.
   (ggtags-navigation-mode-cleanup))
 
 (defun ggtags-navigation-mode-abort ()
+  "Abort navigation and return to where the search was started."
   (interactive)
   (ggtags-navigation-mode -1)
   (ggtags-navigation-mode-cleanup nil 0)
@@ -1555,12 +1800,19 @@ commands `next-error' and `previous-error'.
   (interactive "p")
   (ggtags-navigation-next-file (- n)))
 
-(defun ggtags-navigation-first-error ()
+(defun ggtags-navigation-start-file ()
+  "Move to the file where navigation session starts."
   (interactive)
-  (ggtags-ensure-global-buffer
-    (goto-char (point-min))
-    (compilation-next-error 1)
-    (compile-goto-error)))
+  (let ((start-file (or ggtags-global-start-file
+                        (user-error "Cannot decide start file"))))
+    (ggtags-ensure-global-buffer
+      (pcase (cl-block nil
+               (ggtags-foreach-file
+                (lambda (file)
+                  (when (file-equal-p file start-file)
+                    (cl-return (point))))))
+        (`nil (user-error "No matches for `%s'" start-file))
+        (n (goto-char n) (compile-goto-error))))))
 
 (defun ggtags-navigation-last-error ()
   (interactive)
@@ -1613,25 +1865,39 @@ commands `next-error' and `previous-error'.
                           ggtags-global-history-length))))
     (run-hooks 'ggtags-find-tag-hook)))
 
+(put 'ggtags-navigation-mode-lighter 'risky-local-variable t)
+
+(defvar ggtags-navigation-mode-lighter
+  '(" GG["
+    (:eval
+     (if (not (buffer-live-p ggtags-global-last-buffer))
+         '(:propertize "??" face error help-echo "No Global buffer")
+       (with-current-buffer ggtags-global-last-buffer
+         (pcase (or ggtags-global-exit-info '(0 0 ""))
+           (`(,exit ,count ,db)
+            `((:propertize ,(pcase db
+                              (`"GTAGS"  "D")
+                              (`"GRTAGS" "R")
+                              (`"GSYMS"  "S")
+                              (`"GPATH"  "F")
+                              (`"ID"     "I"))
+                           face success)
+              (:propertize
+               ,(pcase (get-text-property (line-beginning-position)
+                                          'compilation-message)
+                  (`nil "?")
+                  ;; Assume the first match appears at line 5
+                  (_ (number-to-string (- (line-number-at-pos) 4))))
+               face success)
+              "/"
+              (:propertize ,(number-to-string count) face success)
+              ,(unless (zerop exit)
+                 `(":" (:propertize ,(number-to-string exit) face error)))))))))
+    "]")
+  "Ligher for `ggtags-navigation-mode'; set to nil to disable it.")
+
 (define-minor-mode ggtags-navigation-mode nil
-  :lighter
-  (" GG[" (:eval
-           (ignore-errors
-             (ggtags-ensure-global-buffer
-               (let ((index (when (get-text-property (line-beginning-position)
-                                                     'compilation-message)
-                              ;; Assume the first match appears at line 5
-                              (- (line-number-at-pos) 4))))
-                 `((:propertize ,(if index
-                                     (number-to-string (max index 0))
-                                   "?") face success) "/")))))
-   (:propertize (:eval (number-to-string ggtags-global-match-count))
-                face success)
-   (:eval
-    (unless (zerop ggtags-global-exit-status)
-      `(":" (:propertize ,(number-to-string ggtags-global-exit-status)
-                         face error))))
-   "]")
+  :lighter ggtags-navigation-mode-lighter
   :global t
   (if ggtags-navigation-mode
       (progn
@@ -1671,12 +1937,8 @@ commands `next-error' and `previous-error'.
 (defun ggtags-after-save-function ()
   (when (ggtags-find-project)
     (ggtags-project-update-mtime-maybe)
-    ;; When oversize update on a per-save basis.
-    (when (and buffer-file-name
-               (or ggtags-global-always-update (ggtags-project-oversize-p)))
-      (ggtags-with-current-project
-       (process-file (ggtags-program-path "global") nil 0 nil "--single-update"
-                     (file-relative-name buffer-file-name))))))
+    (and buffer-file-name
+         (ggtags-update-tags-single buffer-file-name 'nowait))))
 
 (defun ggtags-global-output (buffer cmds callback &optional cutoff)
   "Asynchronously pipe the output of running CMDS to BUFFER.
@@ -1713,9 +1975,29 @@ When finished invoke CALLBACK in BUFFER with process exit status."
     (set-process-sentinel proc sentinel)
     proc))
 
+(cl-defun ggtags-fontify-code (code &optional (mode major-mode))
+  (cl-check-type mode function)
+  (cl-typecase code
+    ((not string) code)
+    (string (cl-labels ((prepare-buffer ()
+                          (with-current-buffer
+                              (get-buffer-create " *Code-Fontify*")
+                            (delay-mode-hooks (funcall mode))
+                            (setq font-lock-mode t)
+                            (funcall font-lock-function font-lock-mode)
+                            (current-buffer))))
+              (with-current-buffer (prepare-buffer)
+                (let ((inhibit-read-only t))
+                  (erase-buffer)
+                  (insert code)
+                  (font-lock-default-fontify-region
+                   (point-min) (point-max) nil))
+                (buffer-string))))))
+
 (defun ggtags-get-definition-default (defs)
   (and (caar defs)
-       (concat (caar defs) (and (cdr defs) " [guess]"))))
+       (concat (ggtags-fontify-code (caar defs))
+               (and (cdr defs) " [guess]"))))
 
 (defun ggtags-show-definition (name)
   (interactive (list (ggtags-read-tag 'definition current-prefix-arg)))
@@ -1740,17 +2022,17 @@ When finished invoke CALLBACK in BUFFER with process exit status."
                    (with-current-buffer current
                      (funcall print-fn (funcall get-fn defs)))))))
     (ggtags-with-current-project
-     (ggtags-global-output
-      buffer
-      (list (ggtags-program-path "global")
-            "--result=grep" "--path-style=absolute" name)
-      show 100))))
+      (ggtags-global-output
+       buffer
+       (list (ggtags-program-path "global")
+             "--result=grep" "--path-style=absolute" name)
+       show 100))))
 
 (defvar ggtags-mode-prefix-map
   (let ((m (make-sparse-keymap)))
     ;; Globally bound to `M-g p'.
     ;; (define-key m "\M-'" 'previous-error)
-    (define-key m (kbd "M-DEL") 'ggtags-delete-tag-files)
+    (define-key m (kbd "M-DEL") 'ggtags-delete-tags)
     (define-key m "\M-p" 'ggtags-prev-mark)
     (define-key m "\M-n" 'ggtags-next-mark)
     (define-key m "\M-f" 'ggtags-find-file)
@@ -1761,7 +2043,7 @@ When finished invoke CALLBACK in BUFFER with process exit status."
     (define-key m "\M-k" 'ggtags-kill-file-buffers)
     (define-key m "\M-h" 'ggtags-view-tag-history)
     (define-key m "\M-j" 'ggtags-visit-project-root)
-    (define-key m "\M-/" 'ggtags-global-rerun-search)
+    (define-key m "\M-/" 'ggtags-view-search-history)
     (define-key m (kbd "M-SPC") 'ggtags-save-to-register)
     (define-key m (kbd "M-%") 'ggtags-query-replace)
     (define-key m "\M-?" 'ggtags-show-definition)
@@ -1800,8 +2082,9 @@ When finished invoke CALLBACK in BUFFER with process exit status."
       '(menu-item "Browse as hypertext" ggtags-browse-file-as-hypertext
                   :enable (ggtags-find-project)))
     (define-key menu [delete-tags]
-      '(menu-item "Delete tag files" ggtags-delete-tag-files
-                  :enable (ggtags-find-project)))
+      '(menu-item "Delete tags" ggtags-delete-tags
+                  :enable (ggtags-find-project)
+                  :help "Delete file GTAGS, GRTAGS, GPATH, ID etc."))
     (define-key menu [kill-buffers]
       '(menu-item "Kill project file buffers" ggtags-kill-file-buffers
                   :enable (ggtags-find-project)))
@@ -1815,14 +2098,14 @@ When finished invoke CALLBACK in BUFFER with process exit status."
     (define-key menu [prev-mark]
       '(menu-item "Previous mark" ggtags-prev-mark))
     (define-key menu [sep1] menu-bar-separator)
-    (define-key menu [rerun-search]
-      '(menu-item "Re-run past search" ggtags-global-rerun-search))
-    (define-key menu [save-to-register]
-      '(menu-item "Save search to register" ggtags-save-to-register))
     (define-key menu [previous-error]
       '(menu-item "Previous match" previous-error))
     (define-key menu [next-error]
       '(menu-item "Next match" next-error))
+    (define-key menu [rerun-search]
+      '(menu-item "View past searches" ggtags-view-search-history))
+    (define-key menu [save-to-register]
+      '(menu-item "Save search to register" ggtags-save-to-register))
     (define-key menu [find-file]
       '(menu-item "Find files" ggtags-find-file))
     (define-key menu [query-replace]
@@ -1832,7 +2115,8 @@ When finished invoke CALLBACK in BUFFER with process exit status."
     (define-key menu [grep]
       '(menu-item "Grep" ggtags-grep))
     (define-key menu [find-symbol]
-      '(menu-item "Find other symbol" ggtags-find-other-symbol))
+      '(menu-item "Find other symbol" ggtags-find-other-symbol
+                  :help "Find references without definition"))
     (define-key menu [find-tag-regexp]
       '(menu-item "Find tag matching regexp" ggtags-find-tag-regexp))
     (define-key menu [show-definition]
@@ -1887,6 +2171,12 @@ to nil disables displaying this information.")
         ;; Append to serve as a fallback method.
         (add-hook 'completion-at-point-functions
                   #'ggtags-completion-at-point t t)
+        ;; Work around http://debbugs.gnu.org/19324
+        (or eldoc-documentation-function
+            (setq-local eldoc-documentation-function #'ignore))
+        (add-function :after-until (local 'eldoc-documentation-function)
+                      #'ggtags-eldoc-function '((name . ggtags-eldoc-function)
+                                                (depth . -100)))
         (unless (memq 'ggtags-mode-line-project-name
                       mode-line-buffer-identification)
           (setq mode-line-buffer-identification
@@ -1894,6 +2184,7 @@ to nil disables displaying this information.")
                         '(ggtags-mode-line-project-name)))))
     (remove-hook 'after-save-hook 'ggtags-after-save-function t)
     (remove-hook 'completion-at-point-functions #'ggtags-completion-at-point t)
+    (remove-function (local 'eldoc-documentation-function) 'ggtags-eldoc-function)
     (setq mode-line-buffer-identification
           (delq 'ggtags-mode-line-project-name mode-line-buffer-identification))
     (and (overlayp ggtags-highlight-tag-overlay)
@@ -1979,10 +2270,10 @@ to nil disables displaying this information.")
   "A function suitable for `imenu-create-index-function'."
   (let ((file (and buffer-file-name (file-relative-name buffer-file-name))))
     (and file (with-temp-buffer
-                (when (with-demoted-errors
+                (when (with-demoted-errors "ggtags-build-imenu-index: %S"
                         (zerop (ggtags-with-current-project
-                                (process-file (ggtags-program-path "global")
-                                              nil t nil "-x" "-f" file))))
+                                 (process-file (ggtags-program-path "global")
+                                               nil t nil "-x" "-f" file))))
                   (goto-char (point-min))
                   (cl-loop while (re-search-forward
                                   "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)" nil t)