]> code.delx.au - gnu-emacs-elpa/blob - packages/ggtags/ggtags.el
Merge commit '3db1ea76a02993663d40e90c58da989212b9e81a' into gnorb-1.0.1
[gnu-emacs-elpa] / packages / ggtags / ggtags.el
1 ;;; ggtags.el --- emacs frontend to GNU Global source code tagging system -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2013-2014 Free Software Foundation, Inc.
4
5 ;; Author: Leo Liu <sdl.web@gmail.com>
6 ;; Version: 0.8.6
7 ;; Keywords: tools, convenience
8 ;; Created: 2013-01-29
9 ;; URL: https://github.com/leoliu/ggtags
10 ;; Package-Requires: ((emacs "24") (cl-lib "0.5"))
11
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
16
17 ;; This program 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
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
24
25 ;;; Commentary:
26
27 ;; A package to integrate GNU Global source code tagging system
28 ;; (http://www.gnu.org/software/global) with Emacs.
29 ;;
30 ;; Usage:
31 ;;
32 ;; `ggtags' is similar to the standard `etags' package. These keys
33 ;; `M-.', `M-,', `M-*' and `C-M-.' should work as expected in
34 ;; `ggtags-mode'. See the README in https://github.com/leoliu/ggtags
35 ;; for more details.
36 ;;
37 ;; All commands are available from the `Ggtags' menu in `ggtags-mode'.
38
39 ;;; Code:
40
41 (eval-when-compile
42 (require 'url-parse))
43
44 (require 'cl-lib)
45 (require 'ewoc)
46 (require 'compile)
47 (require 'etags)
48 (require 'tabulated-list) ;preloaded since 24.3
49
50 (eval-when-compile
51 (unless (fboundp 'setq-local)
52 (defmacro setq-local (var val)
53 (list 'set (list 'make-local-variable (list 'quote var)) val)))
54
55 (unless (fboundp 'defvar-local)
56 (defmacro defvar-local (var val &optional docstring)
57 (declare (debug defvar) (doc-string 3))
58 (list 'progn (list 'defvar var val docstring)
59 (list 'make-variable-buffer-local (list 'quote var)))))
60
61 (defmacro ignore-errors-unless-debug (&rest body)
62 "Ignore all errors while executing BODY unless debug is on."
63 (declare (debug t) (indent 0))
64 `(condition-case-unless-debug nil (progn ,@body) (error nil)))
65
66 (defmacro with-display-buffer-no-window (&rest body)
67 (declare (debug t) (indent 0))
68 ;; See http://debbugs.gnu.org/13594
69 `(let ((display-buffer-overriding-action
70 (if (and ggtags-auto-jump-to-match
71 ;; Appeared in emacs 24.4.
72 (fboundp 'display-buffer-no-window))
73 (list #'display-buffer-no-window)
74 display-buffer-overriding-action)))
75 ,@body)))
76
77 (eval-and-compile
78 (or (fboundp 'user-error) ;24.3
79 (defalias 'user-error 'error))
80 (or (fboundp 'read-only-mode) ;24.3
81 (defalias 'read-only-mode 'toggle-read-only))
82 (or (fboundp 'register-read-with-preview) ;24.4
83 (defalias 'register-read-with-preview 'read-char)))
84
85 (defgroup ggtags nil
86 "GNU Global source code tagging system."
87 :group 'tools)
88
89 (defface ggtags-highlight '((t (:underline t)))
90 "Face used to highlight a valid tag at point."
91 :group 'ggtags)
92
93 (defface ggtags-global-line '((t (:inherit secondary-selection)))
94 "Face used to highlight matched line in Global buffer."
95 :group 'ggtags)
96
97 (defcustom ggtags-executable-directory nil
98 "If non-nil the directory to search global executables."
99 :type '(choice (const :tag "Unset" nil) directory)
100 :risky t
101 :group 'ggtags)
102
103 (defcustom ggtags-oversize-limit (* 10 1024 1024)
104 "The over size limit for the GTAGS file.
105 When the size of the GTAGS file is below this limit, ggtags
106 always maintains up-to-date tags for the whole source tree by
107 running `global -u'. For projects with GTAGS larger than this
108 limit, only files edited in Ggtags mode are updated (via `global
109 --single-update')."
110 :safe 'numberp
111 :type '(choice (const :tag "None" nil)
112 (const :tag "Always" t)
113 number)
114 :group 'ggtags)
115
116 (defcustom ggtags-include-pattern
117 '("^\\s-*#\\s-*\\(?:include\\|import\\)\\s-*[\"<]\\(?:[./]*\\)?\\(.*?\\)[\">]" . 1)
118 "Pattern used to detect #include files.
119 Value can be (REGEXP . SUB) or a function with no arguments.
120 REGEXP should match from the beginning of line."
121 :type '(choice (const :tag "Disable" nil)
122 (cons regexp integer)
123 function)
124 :safe 'stringp
125 :group 'ggtags)
126
127 ;; See also: http://article.gmane.org/gmane.comp.gnu.global.bugs/1751
128 (defcustom ggtags-use-project-gtagsconf t
129 "Non-nil to use GTAGSCONF file found at project root.
130 File .globalrc and gtags.conf are checked in order.
131
132 Note: GNU Global v6.2.13 has the feature of using gtags.conf at
133 project root. Setting this variable to nil doesn't disable this
134 feature."
135 :safe 'booleanp
136 :type 'boolean
137 :group 'ggtags)
138
139 (defcustom ggtags-project-duration 600
140 "Seconds to keep information of a project in memory."
141 :type 'number
142 :group 'ggtags)
143
144 (defcustom ggtags-process-environment nil
145 "Similar to `process-environment' with higher precedence.
146 Elements are run through `substitute-env-vars' before use.
147 GTAGSROOT will always be expanded to current project root
148 directory. This is intended for project-wise ggtags-specific
149 process environment settings. Note on remote hosts (e.g. tramp)
150 directory local variables is not enabled by default per
151 `enable-remote-dir-locals' (which see)."
152 :safe 'ggtags-list-of-string-p
153 :type '(repeat string)
154 :group 'ggtags)
155
156 (defcustom ggtags-auto-jump-to-match 'history
157 "Strategy on how to jump to match: nil, first or history.
158
159 nil: never automatically jump to any match;
160 first: jump to the first match;
161 history: jump to the match stored in search history."
162 :type '(choice (const :tag "First match" first)
163 (const :tag "Search History" history)
164 (const :tag "Never" nil))
165 :group 'ggtags)
166
167 (defcustom ggtags-global-window-height 8 ; ggtags-global-mode
168 "Number of lines for the *ggtags-global* popup window.
169 If nil, use Emacs default."
170 :type '(choice (const :tag "Default" nil) integer)
171 :group 'ggtags)
172
173 (defcustom ggtags-global-abbreviate-filename 40
174 "Non-nil to display file names abbreviated e.g. \"/u/b/env\".
175 If an integer abbreviate only names longer than that number."
176 :type '(choice (const :tag "No" nil)
177 (const :tag "Always" t)
178 integer)
179 :group 'ggtags)
180
181 (defcustom ggtags-split-window-function split-window-preferred-function
182 "A function to control how ggtags pops up the auxiliary window."
183 :type 'function
184 :group 'ggtags)
185
186 (defcustom ggtags-use-idutils (and (executable-find "mkid") t)
187 "Non-nil to also generate the idutils DB."
188 :type 'boolean
189 :group 'ggtags)
190
191 (defcustom ggtags-global-output-format 'grep
192 "Global output format: path, ctags, ctags-x, grep or cscope."
193 :type '(choice (const path)
194 (const ctags)
195 (const ctags-x)
196 (const grep)
197 (const cscope))
198 :group 'ggtags)
199
200 (defcustom ggtags-global-use-color t
201 "Non-nil to use color in output if supported by Global.
202 Note: processing colored output takes noticeable time
203 particularly when the output is large."
204 :type 'boolean
205 :safe 'booleanp
206 :group 'ggtags)
207
208 (defcustom ggtags-global-ignore-case nil
209 "Non-nil if Global should ignore case in the search pattern."
210 :safe 'booleanp
211 :type 'boolean
212 :group 'ggtags)
213
214 (defcustom ggtags-global-treat-text nil
215 "Non-nil if Global should include matches from text files.
216 This affects `ggtags-find-file' and `ggtags-grep'."
217 :safe 'booleanp
218 :type 'boolean
219 :group 'ggtags)
220
221 ;; See also https://github.com/leoliu/ggtags/issues/52
222 (defcustom ggtags-global-search-libpath-for-reference t
223 "If non-nil global will search GTAGSLIBPATH for references.
224 Search is only continued in GTAGSLIBPATH if it finds no matches
225 in current project."
226 :safe 'booleanp
227 :type 'boolean
228 :group 'ggtags)
229
230 (defcustom ggtags-global-large-output 1000
231 "Number of lines in the Global buffer to indicate large output."
232 :type 'number
233 :group 'ggtags)
234
235 (defcustom ggtags-global-history-length history-length
236 "Maximum number of items to keep in `ggtags-global-search-history'."
237 :type 'integer
238 :group 'ggtags)
239
240 (defcustom ggtags-enable-navigation-keys t
241 "If non-nil key bindings in `ggtags-navigation-map' are enabled."
242 :safe 'booleanp
243 :type 'boolean
244 :group 'ggtags)
245
246 (defcustom ggtags-find-tag-hook nil
247 "Hook run immediately after finding a tag."
248 :options '(recenter reposition-window)
249 :type 'hook
250 :group 'ggtags)
251
252 (defcustom ggtags-get-definition-function #'ggtags-get-definition-default
253 "Function called by `ggtags-show-definition' to get definition.
254 It is passed a list of definition candidates of the form:
255
256 (TEXT NAME FILE LINE)
257
258 where TEXT is usually the source line of the definition.
259
260 The return value is passed to `ggtags-print-definition-function'."
261 :type 'function
262 :group 'ggtags)
263
264 (defcustom ggtags-print-definition-function
265 (lambda (s) (ggtags-echo "%s" (or s "[definition not found]")))
266 "Function used by `ggtags-show-definition' to print definition."
267 :type 'function
268 :group 'ggtags)
269
270 (defcustom ggtags-mode-sticky t
271 "If non-nil enable Ggtags Mode in files visited."
272 :safe 'booleanp
273 :type 'boolean
274 :group 'ggtags)
275
276 (defcustom ggtags-mode-prefix-key "\C-c"
277 "Key binding used for `ggtags-mode-prefix-map'.
278 Users should change the value using `customize-variable' to
279 properly update `ggtags-mode-map'."
280 :set (lambda (sym value)
281 (when (bound-and-true-p ggtags-mode-map)
282 (let ((old (and (boundp sym) (symbol-value sym))))
283 (and old (define-key ggtags-mode-map old nil)))
284 (and value
285 (bound-and-true-p ggtags-mode-prefix-map)
286 (define-key ggtags-mode-map value ggtags-mode-prefix-map)))
287 (set-default sym value))
288 :type 'key-sequence
289 :group 'ggtags)
290
291 (defcustom ggtags-completing-read-function nil
292 "Ggtags specific `completing-read-function' (which see).
293 Nil means using the value of `completing-read-function'."
294 :type '(choice (const :tag "Use completing-read-function" nil)
295 function)
296 :group 'ggtags)
297
298 (defcustom ggtags-highlight-tag-delay 0.25
299 "Time in seconds before highlighting tag at point."
300 :set (lambda (sym value)
301 (when (bound-and-true-p ggtags-highlight-tag-timer)
302 (timer-set-idle-time ggtags-highlight-tag-timer value t))
303 (set-default sym value))
304 :type 'number
305 :group 'ggtags)
306
307 (defcustom ggtags-bounds-of-tag-function (lambda ()
308 (bounds-of-thing-at-point 'symbol))
309 "Function to get the start and end positions of the tag at point."
310 :type 'function
311 :group 'ggtags)
312
313 ;; Used by ggtags-global-mode
314 (defvar ggtags-global-error "match"
315 "Stem of message to print when no matches are found.")
316
317 (defconst ggtags-bug-url "https://github.com/leoliu/ggtags/issues")
318
319 (defvar ggtags-global-last-buffer nil)
320
321 (defvar ggtags-global-continuation nil)
322
323 (defvar ggtags-current-tag-name nil)
324
325 (defvar ggtags-highlight-tag-overlay nil)
326
327 (defvar ggtags-highlight-tag-timer nil)
328
329 (defmacro ggtags-with-temp-message (message &rest body)
330 (declare (debug t) (indent 1))
331 (let ((init-time (make-symbol "-init-time-"))
332 (tmp-msg (make-symbol "-tmp-msg-")))
333 `(let ((,init-time (float-time))
334 (,tmp-msg ,message))
335 (with-temp-message ,tmp-msg
336 (prog1 (progn ,@body)
337 (message "%sdone (%.2fs)" ,(or tmp-msg "")
338 (- (float-time) ,init-time)))))))
339
340 (defmacro ggtags-delay-finish-functions (&rest body)
341 "Delay running `compilation-finish-functions' until after BODY."
342 (declare (indent 0) (debug t))
343 (let ((saved (make-symbol "-saved-"))
344 (exit-args (make-symbol "-exit-args-")))
345 `(let ((,saved compilation-finish-functions)
346 ,exit-args)
347 (setq-local compilation-finish-functions nil)
348 (add-hook 'compilation-finish-functions
349 (lambda (&rest args) (setq ,exit-args args))
350 nil t)
351 (unwind-protect (progn ,@body)
352 (setq-local compilation-finish-functions ,saved)
353 (and ,exit-args (apply #'run-hook-with-args
354 'compilation-finish-functions ,exit-args))))))
355
356 (defmacro ggtags-ensure-global-buffer (&rest body)
357 (declare (debug t) (indent 0))
358 `(progn
359 (or (and (buffer-live-p ggtags-global-last-buffer)
360 (with-current-buffer ggtags-global-last-buffer
361 (derived-mode-p 'ggtags-global-mode)))
362 (error "No global buffer found"))
363 (with-current-buffer ggtags-global-last-buffer ,@body)))
364
365 (defun ggtags-list-of-string-p (xs)
366 "Return non-nil if XS is a list of strings."
367 (cl-every #'stringp xs))
368
369 (defun ggtags-ensure-localname (file)
370 (and file (or (file-remote-p file 'localname) file)))
371
372 (defun ggtags-echo (format-string &rest args)
373 "Print formatted text to echo area."
374 (let (message-log-max) (apply #'message format-string args)))
375
376 (defun ggtags-forward-to-line (line)
377 "Move to line number LINE in current buffer."
378 (cl-check-type line (integer 1))
379 (save-restriction
380 (widen)
381 (goto-char (point-min))
382 (forward-line (1- line))))
383
384 (defun ggtags-kill-window ()
385 "Quit selected window and kill its buffer."
386 (interactive)
387 (quit-window t))
388
389 (defun ggtags-program-path (name)
390 (if ggtags-executable-directory
391 (expand-file-name name ggtags-executable-directory)
392 name))
393
394 (defun ggtags-process-string (program &rest args)
395 (with-temp-buffer
396 (let ((exit (apply #'process-file
397 (ggtags-program-path program) nil t nil args))
398 (output (progn
399 (goto-char (point-max))
400 (skip-chars-backward " \t\n")
401 (buffer-substring (point-min) (point)))))
402 (or (zerop exit)
403 (error "`%s' non-zero exit: %s" program output))
404 output)))
405
406 (defun ggtags-tag-at-point ()
407 (pcase (funcall ggtags-bounds-of-tag-function)
408 (`(,beg . ,end) (buffer-substring beg end))))
409
410 ;;; Store for project info and settings
411
412 (defvar ggtags-projects (make-hash-table :size 7 :test #'equal))
413
414 (cl-defstruct (ggtags-project (:constructor ggtags-project--make)
415 (:copier nil)
416 (:type vector)
417 :named)
418 root tag-size has-refs has-path-style has-color dirty-p mtime timestamp)
419
420 (defun ggtags-make-project (root)
421 (cl-check-type root string)
422 (pcase (nthcdr 5 (file-attributes (expand-file-name "GTAGS" root)))
423 (`(,mtime ,_ ,tag-size . ,_)
424 (let* ((default-directory (file-name-as-directory root))
425 (rtags-size (nth 7 (file-attributes "GRTAGS")))
426 (has-refs
427 (when rtags-size
428 (and (or (> rtags-size (* 32 1024))
429 (with-demoted-errors "ggtags-make-project: %S"
430 (not (equal "" (ggtags-process-string "global" "-crs")))))
431 'has-refs)))
432 ;; http://thread.gmane.org/gmane.comp.gnu.global.bugs/1518
433 (has-path-style
434 (with-demoted-errors "ggtags-make-project: %S"
435 ;; in case `global' not found
436 (and (zerop (process-file (ggtags-program-path "global")
437 nil nil nil
438 "--path-style" "shorter" "--help"))
439 'has-path-style)))
440 ;; http://thread.gmane.org/gmane.comp.gnu.global.bugs/1542
441 (has-color
442 (with-demoted-errors "ggtags-make-project: %S"
443 (and (zerop (process-file (ggtags-program-path "global")
444 nil nil nil
445 "--color" "--help"))
446 'has-color))))
447 (puthash default-directory
448 (ggtags-project--make :root default-directory
449 :tag-size tag-size
450 :has-refs has-refs
451 :has-path-style has-path-style
452 :has-color has-color
453 :mtime (float-time mtime)
454 :timestamp (float-time))
455 ggtags-projects)))))
456
457 (defun ggtags-project-expired-p (project)
458 (or (< (ggtags-project-timestamp project) 0)
459 (> (- (float-time)
460 (ggtags-project-timestamp project))
461 ggtags-project-duration)))
462
463 (defun ggtags-project-update-mtime-maybe (&optional project)
464 "Update PROJECT's modtime and if current file is newer.
465 Value is new modtime if updated."
466 (let ((project (or project (ggtags-find-project))))
467 (when (and (ggtags-project-p project)
468 (consp (visited-file-modtime))
469 (> (float-time (visited-file-modtime))
470 (ggtags-project-mtime project)))
471 (setf (ggtags-project-dirty-p project) t)
472 (setf (ggtags-project-mtime project)
473 (float-time (visited-file-modtime))))))
474
475 (defun ggtags-project-oversize-p (&optional project)
476 (pcase ggtags-oversize-limit
477 (`nil nil)
478 (`t t)
479 (size (let ((project (or project (ggtags-find-project))))
480 (and project (> (ggtags-project-tag-size project) size))))))
481
482 (defvar-local ggtags-last-default-directory nil)
483 (defvar-local ggtags-project-root 'unset
484 "Internal variable for project root directory.")
485
486 ;;;###autoload
487 (defun ggtags-find-project ()
488 ;; See https://github.com/leoliu/ggtags/issues/42
489 ;;
490 ;; It is unsafe to cache `ggtags-project-root' in non-file buffers
491 ;; whose `default-directory' can often change.
492 (unless (equal ggtags-last-default-directory default-directory)
493 (kill-local-variable 'ggtags-project-root))
494 (let ((project (gethash ggtags-project-root ggtags-projects)))
495 (if (ggtags-project-p project)
496 (if (ggtags-project-expired-p project)
497 (progn
498 (remhash ggtags-project-root ggtags-projects)
499 (ggtags-find-project))
500 project)
501 (setq ggtags-last-default-directory default-directory)
502 (setq ggtags-project-root
503 (or (ignore-errors-unless-debug
504 (file-name-as-directory
505 (concat (file-remote-p default-directory)
506 ;; Resolves symbolic links
507 (ggtags-process-string "global" "-pr"))))
508 ;; 'global -pr' resolves symlinks before checking the
509 ;; GTAGS file which could cause issues such as
510 ;; https://github.com/leoliu/ggtags/issues/22, so
511 ;; let's help it out.
512 ;;
513 ;; Note: `locate-dominating-file' doesn't accept
514 ;; function for NAME before 24.3.
515 (let ((dir (locate-dominating-file default-directory "GTAGS")))
516 ;; `file-truename' may strip the trailing '/' on
517 ;; remote hosts, see http://debbugs.gnu.org/16851
518 (and dir (file-regular-p (expand-file-name "GTAGS" dir))
519 (file-name-as-directory (file-truename dir))))))
520 (when ggtags-project-root
521 (if (gethash ggtags-project-root ggtags-projects)
522 (ggtags-find-project)
523 (ggtags-make-project ggtags-project-root))))))
524
525 (defun ggtags-current-project-root ()
526 (and (ggtags-find-project)
527 (ggtags-project-root (ggtags-find-project))))
528
529 (defun ggtags-check-project ()
530 (or (ggtags-find-project) (error "File GTAGS not found")))
531
532 (defun ggtags-ensure-project ()
533 (or (ggtags-find-project)
534 (when (or (yes-or-no-p "File GTAGS not found; run gtags? ")
535 (user-error "Aborted"))
536 (call-interactively #'ggtags-create-tags)
537 ;; Need checking because `ggtags-create-tags' can create tags
538 ;; in any directory.
539 (ggtags-check-project))))
540
541 (defvar delete-trailing-lines) ;new in 24.3
542
543 (defun ggtags-save-project-settings (&optional noconfirm)
544 "Save Gnu Global's specific environment variables."
545 (interactive "P")
546 (ggtags-check-project)
547 (let* ((inhibit-read-only t) ; for `add-dir-local-variable'
548 (default-directory (ggtags-current-project-root))
549 ;; Not using `ggtags-with-current-project' to preserve
550 ;; environment variables that may be present in
551 ;; `ggtags-process-environment'.
552 (process-environment
553 (append ggtags-process-environment
554 process-environment
555 (and (not (ggtags-project-has-refs (ggtags-find-project)))
556 (list "GTAGSLABEL=ctags"))))
557 (envlist (delete-dups
558 (cl-loop for x in process-environment
559 when (string-match
560 "^\\(GTAGS[^=\n]*\\|MAKEOBJDIRPREFIX\\)=" x)
561 ;; May have duplicates thus `delete-dups'.
562 collect (concat (match-string 1 x)
563 "="
564 (getenv (match-string 1 x))))))
565 (help-form (format "y: save\nn: don't save\n=: diff\n?: help\n")))
566 (add-dir-local-variable nil 'ggtags-process-environment envlist)
567 ;; Remove trailing newlines by `add-dir-local-variable'.
568 (let ((delete-trailing-lines t)) (delete-trailing-whitespace))
569 (or noconfirm
570 (while (pcase (read-char-choice
571 (format "Save `%s'? (y/n/=/?) " buffer-file-name)
572 '(?y ?n ?= ??))
573 ;; ` required for 24.1 and 24.2
574 (`?n (user-error "Aborted"))
575 (`?y nil)
576 (`?= (diff-buffer-with-file) 'loop)
577 (`?? (help-form-show) 'loop))))
578 (save-buffer)
579 (kill-buffer)))
580
581 (defun ggtags-toggle-project-read-only ()
582 (interactive)
583 (ggtags-check-project)
584 (let ((inhibit-read-only t) ; for `add-dir-local-variable'
585 (val (not buffer-read-only))
586 (default-directory (ggtags-current-project-root)))
587 (add-dir-local-variable nil 'buffer-read-only val)
588 (save-buffer)
589 (kill-buffer)
590 (when buffer-file-name
591 (read-only-mode (if val +1 -1)))
592 (when (called-interactively-p 'interactive)
593 (message "Project read-only-mode is %s" (if val "on" "off")))
594 val))
595
596 (defun ggtags-visit-project-root ()
597 (interactive)
598 (ggtags-ensure-project)
599 (dired (ggtags-current-project-root)))
600
601 (defmacro ggtags-with-current-project (&rest body)
602 "Eval BODY in current project's `process-environment'."
603 (declare (debug t) (indent 0))
604 (let ((gtagsroot (make-symbol "-gtagsroot-"))
605 (root (make-symbol "-ggtags-project-root-")))
606 `(let* ((,root ggtags-project-root)
607 (,gtagsroot (when (ggtags-find-project)
608 (ggtags-ensure-localname
609 (directory-file-name (ggtags-current-project-root)))))
610 (process-environment
611 (append (let ((process-environment process-environment))
612 (and ,gtagsroot (setenv "GTAGSROOT" ,gtagsroot))
613 (mapcar #'substitute-env-vars ggtags-process-environment))
614 process-environment
615 (and ,gtagsroot (list (concat "GTAGSROOT=" ,gtagsroot)))
616 (and (ggtags-find-project)
617 (not (ggtags-project-has-refs (ggtags-find-project)))
618 (list "GTAGSLABEL=ctags")))))
619 (unwind-protect (save-current-buffer ,@body)
620 (setq ggtags-project-root ,root)))))
621
622 (defun ggtags-get-libpath ()
623 (let ((path (ggtags-with-current-project (getenv "GTAGSLIBPATH"))))
624 (and path (mapcar (apply-partially #'concat (file-remote-p default-directory))
625 (split-string path (regexp-quote path-separator) t)))))
626
627 (defun ggtags-project-relative-file (file)
628 "Get file name relative to current project root."
629 (ggtags-check-project)
630 (if (file-name-absolute-p file)
631 (file-relative-name file (if (string-prefix-p (ggtags-current-project-root)
632 file)
633 (ggtags-current-project-root)
634 (locate-dominating-file file "GTAGS")))
635 file))
636
637 (defun ggtags-project-file-p (file)
638 "Return non-nil if FILE is part of current project."
639 (when (ggtags-find-project)
640 (with-temp-buffer
641 (ggtags-with-current-project
642 (process-file (ggtags-program-path "global") nil t nil
643 "-vP" (concat "^" (ggtags-project-relative-file file) "$")))
644 (goto-char (point-min))
645 (not (re-search-forward "^file not found" nil t)))))
646
647 (defun ggtags-invalidate-buffer-project-root (root)
648 (mapc (lambda (buf)
649 (with-current-buffer buf
650 (and buffer-file-truename
651 (string-prefix-p root buffer-file-truename)
652 (kill-local-variable 'ggtags-project-root))))
653 (buffer-list)))
654
655 (defun ggtags-create-tags (root)
656 "Create tag files (e.g. GTAGS) in directory ROOT.
657 If file .globalrc or gtags.conf exists in ROOT, it will be used
658 as configuration file per `ggtags-use-project-gtagsconf'.
659
660 If file gtags.files exists in ROOT, it should be a list of source
661 files to index, which can be used to speed gtags up in large
662 source trees. See Info node `(global)gtags' for details."
663 (interactive "DRoot directory: ")
664 (let ((process-environment process-environment))
665 (when (zerop (length root)) (error "No root directory provided"))
666 (setenv "GTAGSROOT" (ggtags-ensure-localname
667 (expand-file-name
668 (directory-file-name (file-name-as-directory root)))))
669 (ggtags-with-current-project
670 (let ((conf (and ggtags-use-project-gtagsconf
671 (cl-loop for name in '(".globalrc" "gtags.conf")
672 for full = (expand-file-name name root)
673 thereis (and (file-exists-p full) full)))))
674 (unless (or conf (getenv "GTAGSLABEL")
675 (not (yes-or-no-p "Use `ctags' backend? ")))
676 (setenv "GTAGSLABEL" "ctags"))
677 (ggtags-with-temp-message "`gtags' in progress..."
678 (let ((default-directory (file-name-as-directory root))
679 (args (cl-remove-if #'null
680 (list (and ggtags-use-idutils "--idutils")
681 (and conf "--gtagsconf")
682 (and conf (ggtags-ensure-localname conf))))))
683 (condition-case err
684 (apply #'ggtags-process-string "gtags" args)
685 (error (if (and ggtags-use-idutils
686 (stringp (cadr err))
687 (string-match-p "mkid not found" (cadr err)))
688 ;; Retry without mkid
689 (apply #'ggtags-process-string
690 "gtags" (cl-remove "--idutils" args))
691 (signal (car err) (cdr err)))))))))
692 (ggtags-invalidate-buffer-project-root (file-truename root))
693 (message "GTAGS generated in `%s'" root)
694 root))
695
696 (defun ggtags-update-tags (&optional force)
697 "Update GNU Global tag database.
698 Do nothing if GTAGS exceeds the oversize limit unless FORCE."
699 (interactive (progn
700 (ggtags-check-project)
701 ;; Mark project info expired.
702 (setf (ggtags-project-timestamp (ggtags-find-project)) -1)
703 (list t)))
704 (when (or force (and (ggtags-find-project)
705 (not (ggtags-project-oversize-p))
706 (ggtags-project-dirty-p (ggtags-find-project))))
707 (ggtags-with-current-project
708 (ggtags-with-temp-message "`global -u' in progress..."
709 (ggtags-process-string "global" "-u")
710 (setf (ggtags-project-dirty-p (ggtags-find-project)) nil)
711 (setf (ggtags-project-mtime (ggtags-find-project)) (float-time))))))
712
713 (defun ggtags-update-tags-single (file &optional nowait)
714 (cl-check-type file string)
715 (ggtags-with-current-project
716 (process-file (ggtags-program-path "global") nil (and nowait 0) nil
717 "--single-update" (ggtags-project-relative-file file))))
718
719 (defun ggtags-delete-tags ()
720 "Delete file GTAGS, GRTAGS, GPATH, ID etc. generated by gtags."
721 (interactive (ignore (ggtags-check-project)))
722 (when (ggtags-current-project-root)
723 (let* ((re (concat "\\`" (regexp-opt '("GPATH" "GRTAGS" "GTAGS" "ID")) "\\'"))
724 (files (cl-remove-if-not
725 (lambda (file)
726 ;; Don't trust `directory-files'.
727 (let ((case-fold-search nil))
728 (string-match-p re (file-name-nondirectory file))))
729 (directory-files (ggtags-current-project-root) t re)))
730 (buffer "*GTags File List*"))
731 (or files (user-error "No tag files found"))
732 (with-output-to-temp-buffer buffer
733 (princ (mapconcat #'identity files "\n")))
734 (let ((win (get-buffer-window buffer)))
735 (unwind-protect
736 (progn
737 (fit-window-to-buffer win)
738 (when (yes-or-no-p "Remove GNU Global tag files? ")
739 (with-demoted-errors (mapc #'delete-file files))
740 (remhash (ggtags-current-project-root) ggtags-projects)
741 (and (overlayp ggtags-highlight-tag-overlay)
742 (delete-overlay ggtags-highlight-tag-overlay))))
743 (when (window-live-p win)
744 (quit-window t win)))))))
745
746 (defvar-local ggtags-completion-cache nil)
747
748 ;; See global/libutil/char.c
749 ;; (defconst ggtags-regexp-metachars "[][$()*+.?\\{}|^]")
750 (defvar ggtags-completion-flag "") ;internal use
751
752 (defvar ggtags-completion-table
753 (completion-table-dynamic
754 (lambda (prefix)
755 (let ((cache-key (concat prefix "$" ggtags-completion-flag)))
756 (unless (equal cache-key (car ggtags-completion-cache))
757 (setq ggtags-completion-cache
758 (cons cache-key
759 (ignore-errors-unless-debug
760 ;; May throw global: only name char is allowed
761 ;; with -c option.
762 (ggtags-with-current-project
763 (split-string
764 (apply #'ggtags-process-string
765 "global"
766 (append (and completion-ignore-case '("--ignore-case"))
767 ;; Note -c alone returns only definitions
768 (list (concat "-c" ggtags-completion-flag) prefix)))
769 "\n" t)))))))
770 (cdr ggtags-completion-cache))))
771
772 (defun ggtags-completion-at-point ()
773 "A function for `completion-at-point-functions'."
774 (pcase (funcall ggtags-bounds-of-tag-function)
775 (`(,beg . ,end)
776 (and (< beg end) (list beg end ggtags-completion-table)))))
777
778 (defun ggtags-read-tag (&optional type confirm prompt require-match default)
779 (ggtags-ensure-project)
780 (let ((default (or default (ggtags-tag-at-point)))
781 (prompt (or prompt (capitalize (symbol-name (or type 'tag)))))
782 (ggtags-completion-flag (pcase type
783 (`(or nil definition) "T")
784 (`symbol "s")
785 (`reference "r")
786 (`id "I")
787 (`path "P")
788 ((pred stringp) type)
789 (_ ggtags-completion-flag))))
790 (setq ggtags-current-tag-name
791 (cond (confirm
792 (ggtags-update-tags)
793 (let ((completing-read-function
794 (or ggtags-completing-read-function
795 completing-read-function)))
796 (completing-read
797 (format (if default "%s (default %s): " "%s: ") prompt default)
798 ggtags-completion-table nil require-match nil nil default)))
799 (default (substring-no-properties default))
800 (t (ggtags-read-tag type t prompt require-match default))))))
801
802 (defun ggtags-global-build-command (cmd &rest args)
803 ;; CMD can be definition, reference, symbol, grep, idutils
804 (let ((xs (append (list (shell-quote-argument (ggtags-program-path "global"))
805 "-v"
806 (format "--result=%s" ggtags-global-output-format)
807 (and ggtags-global-ignore-case "--ignore-case")
808 (and ggtags-global-use-color
809 (ggtags-find-project)
810 (ggtags-project-has-color (ggtags-find-project))
811 "--color=always")
812 (and (ggtags-find-project)
813 (ggtags-project-has-path-style (ggtags-find-project))
814 "--path-style=shorter")
815 (and ggtags-global-treat-text "--other")
816 (pcase cmd
817 ((pred stringp) cmd)
818 (`definition nil) ;-d not supported by Global 5.7.1
819 (`reference "--reference")
820 (`symbol "--symbol")
821 (`path "--path")
822 (`grep "--grep")
823 (`idutils "--idutils")))
824 args)))
825 (mapconcat #'identity (delq nil xs) " ")))
826
827 ;; Can be three values: nil, t and a marker; t means start marker has
828 ;; been saved in the tag ring.
829 (defvar ggtags-global-start-marker nil)
830 (defvar ggtags-tag-ring-index nil)
831 (defvar ggtags-global-search-history nil)
832
833 (defvar ggtags-auto-jump-to-match-target nil)
834
835 (defvar-local ggtags-global-exit-info nil) ; (EXIT-STATUS COUNT DB)
836
837 (defun ggtags-global-save-start-marker ()
838 (when (markerp ggtags-global-start-marker)
839 (setq ggtags-tag-ring-index nil)
840 (ring-insert find-tag-marker-ring ggtags-global-start-marker)
841 (setq ggtags-global-start-marker t)))
842
843 (defun ggtags-global-start (command &optional directory)
844 (let* ((default-directory (or directory (ggtags-current-project-root)))
845 (split-window-preferred-function ggtags-split-window-function)
846 (env ggtags-process-environment))
847 (unless (markerp ggtags-global-start-marker)
848 (setq ggtags-global-start-marker (point-marker)))
849 (setq ggtags-auto-jump-to-match-target
850 (nth 4 (assoc (ggtags-global-search-id command default-directory)
851 ggtags-global-search-history)))
852 (ggtags-navigation-mode +1)
853 (ggtags-update-tags)
854 (ggtags-with-current-project
855 (with-current-buffer (with-display-buffer-no-window
856 (compilation-start command 'ggtags-global-mode))
857 (setq-local ggtags-process-environment env)
858 (setq ggtags-global-last-buffer (current-buffer))))))
859
860 (defun ggtags-find-tag-continue ()
861 (interactive)
862 (ggtags-ensure-global-buffer
863 (ggtags-navigation-mode +1)
864 (let ((split-window-preferred-function ggtags-split-window-function))
865 (ignore-errors (compilation-next-error 1))
866 (compile-goto-error))))
867
868 (defun ggtags-find-tag (cmd &rest args)
869 (ggtags-check-project)
870 (ggtags-global-start (apply #'ggtags-global-build-command cmd args)))
871
872 (defun ggtags-include-file ()
873 "Calculate the include file based on `ggtags-include-pattern'."
874 (pcase ggtags-include-pattern
875 (`nil nil)
876 ((pred functionp)
877 (funcall ggtags-include-pattern))
878 (`(,re . ,sub)
879 (save-excursion
880 (beginning-of-line)
881 (and (looking-at re) (match-string sub))))
882 (_ (warn "Invalid value for `ggtags-include-pattern': %s"
883 ggtags-include-pattern)
884 nil)))
885
886 ;;;###autoload
887 (defun ggtags-find-tag-dwim (name &optional what)
888 "Find NAME by context.
889 If point is at a definition tag, find references, and vice versa.
890 If point is at a line that matches `ggtags-include-pattern', find
891 the include file instead.
892
893 When called interactively with a prefix arg, always find
894 definition tags."
895 (interactive
896 (let ((include (and (not current-prefix-arg) (ggtags-include-file))))
897 (ggtags-ensure-project)
898 (if include (list include 'include)
899 (list (ggtags-read-tag 'definition current-prefix-arg)
900 (and current-prefix-arg 'definition)))))
901 (ggtags-check-project) ; For `ggtags-current-project-root' below.
902 (cond
903 ((eq what 'include)
904 (ggtags-find-file name))
905 ((or (eq what 'definition)
906 (not buffer-file-name)
907 (not (ggtags-project-has-refs (ggtags-find-project)))
908 (not (ggtags-project-file-p buffer-file-name)))
909 (ggtags-find-definition name))
910 (t (ggtags-find-tag (format "--from-here=%d:%s"
911 (line-number-at-pos)
912 (shell-quote-argument
913 ;; Note `ggtags-global-start' binds
914 ;; default-directory to project root.
915 (ggtags-project-relative-file buffer-file-name)))
916 (shell-quote-argument name)))))
917
918 (defun ggtags-find-tag-mouse (event)
919 (interactive "e")
920 (with-selected-window (posn-window (event-start event))
921 (save-excursion
922 (goto-char (posn-point (event-start event)))
923 (call-interactively #'ggtags-find-tag-dwim))))
924
925 ;; Another option for `M-.'.
926 (defun ggtags-find-definition (name)
927 (interactive (list (ggtags-read-tag 'definition current-prefix-arg)))
928 (ggtags-find-tag 'definition (shell-quote-argument name)))
929
930 (defun ggtags-setup-libpath-search (type name)
931 (pcase (and ggtags-global-search-libpath-for-reference
932 (ggtags-get-libpath))
933 ((and libs (guard libs))
934 (cl-labels ((cont (buf how)
935 (pcase ggtags-global-exit-info
936 (`(0 0 ,_)
937 (with-temp-buffer
938 (setq default-directory
939 (file-name-as-directory (pop libs)))
940 (and libs (setq ggtags-global-continuation #'cont))
941 (if (ggtags-find-project)
942 (ggtags-find-tag type (shell-quote-argument name))
943 (cont buf how))))
944 (_ (ggtags-global-handle-exit buf how)))))
945 (setq ggtags-global-continuation #'cont)))))
946
947 (defun ggtags-find-reference (name)
948 (interactive (list (ggtags-read-tag 'reference current-prefix-arg)))
949 (ggtags-setup-libpath-search 'reference name)
950 (ggtags-find-tag 'reference (shell-quote-argument name)))
951
952 (defun ggtags-find-other-symbol (name)
953 "Find tag NAME that is a reference without a definition."
954 (interactive (list (ggtags-read-tag 'symbol current-prefix-arg)))
955 (ggtags-setup-libpath-search 'symbol name)
956 (ggtags-find-tag 'symbol (shell-quote-argument name)))
957
958 (defun ggtags-quote-pattern (pattern)
959 (prin1-to-string (substring-no-properties pattern)))
960
961 (defun ggtags-idutils-query (pattern)
962 (interactive (list (ggtags-read-tag 'id t)))
963 (ggtags-find-tag 'idutils "--" (ggtags-quote-pattern pattern)))
964
965 (defun ggtags-grep (pattern &optional invert-match)
966 "Grep for lines matching PATTERN.
967 Invert the match when called with a prefix arg \\[universal-argument]."
968 (interactive (list (ggtags-read-tag 'definition 'confirm
969 (if current-prefix-arg
970 "Inverted grep pattern" "Grep pattern"))
971 current-prefix-arg))
972 (ggtags-find-tag 'grep (and invert-match "--invert-match")
973 "--" (ggtags-quote-pattern pattern)))
974
975 (defun ggtags-find-file (pattern &optional invert-match)
976 (interactive (list (ggtags-read-tag 'path 'confirm (if current-prefix-arg
977 "Inverted path pattern"
978 "Path pattern")
979 nil (thing-at-point 'filename))
980 current-prefix-arg))
981 (let ((ggtags-global-output-format 'path))
982 (ggtags-find-tag 'path (and invert-match "--invert-match")
983 "--" (ggtags-quote-pattern pattern))))
984
985 ;; Note: Coloured output requested in http://goo.gl/Y9IcX and appeared
986 ;; in global v6.2.12.
987 (defun ggtags-find-tag-regexp (regexp directory)
988 "List tags matching REGEXP in DIRECTORY (default to project root).
989 When called interactively with a prefix, ask for the directory."
990 (interactive
991 (progn
992 (ggtags-check-project)
993 (list (ggtags-read-tag "" t "POSIX regexp")
994 (if current-prefix-arg
995 (read-directory-name "Directory: " nil nil t)
996 (ggtags-current-project-root)))))
997 (ggtags-check-project)
998 (ggtags-global-start
999 (ggtags-global-build-command nil nil "-l" "--" (ggtags-quote-pattern regexp))
1000 (file-name-as-directory directory)))
1001
1002 (defvar ggtags-navigation-mode)
1003
1004 (defun ggtags-query-replace (from to &optional delimited)
1005 "Query replace FROM with TO on files in the Global buffer.
1006 If not in navigation mode, do a grep on FROM first.
1007
1008 Note: the regular expression FROM must be supported by both
1009 Global and Emacs."
1010 (interactive
1011 ;; Note: in 24.4 query-replace-read-args returns a list of 4 elements.
1012 (let ((args (query-replace-read-args "Query replace (regexp)" t t)))
1013 (list (nth 0 args) (nth 1 args) (nth 2 args))))
1014 (unless ggtags-navigation-mode
1015 (let ((ggtags-auto-jump-to-match nil))
1016 (ggtags-grep from)))
1017 (let ((file-form
1018 '(let ((files))
1019 (ggtags-ensure-global-buffer
1020 (ggtags-with-temp-message "Waiting for Grep to finish..."
1021 (while (get-buffer-process (current-buffer))
1022 (sit-for 0.2)))
1023 (goto-char (point-min))
1024 (while (ignore-errors (compilation-next-file 1) t)
1025 (let ((m (get-text-property (point) 'compilation-message)))
1026 (push (expand-file-name
1027 (caar (compilation--loc->file-struct
1028 (compilation--message->loc m))))
1029 files))))
1030 (ggtags-navigation-mode -1)
1031 (nreverse files))))
1032 (tags-query-replace from to delimited file-form)))
1033
1034 (defun ggtags-global-normalise-command (cmd)
1035 (if (string-match
1036 (concat (regexp-quote (ggtags-global-build-command nil)) "\\s-*")
1037 cmd)
1038 (substring-no-properties cmd (match-end 0))
1039 cmd))
1040
1041 (defun ggtags-global-search-id (cmd directory)
1042 (sha1 (concat directory (make-string 1 0)
1043 (ggtags-global-normalise-command cmd))))
1044
1045 (defun ggtags-global-current-search ()
1046 ;; CMD DIR ENV LINE TEXT
1047 (ggtags-ensure-global-buffer
1048 (list (ggtags-global-normalise-command (car compilation-arguments))
1049 default-directory
1050 ggtags-process-environment
1051 (line-number-at-pos)
1052 (buffer-substring-no-properties
1053 (line-beginning-position) (line-end-position)))))
1054
1055 (defun ggtags-global-rerun-search (data)
1056 (pcase data
1057 (`(,cmd ,dir ,env ,line ,_text)
1058 (with-current-buffer (let ((ggtags-auto-jump-to-match nil)
1059 ;; Switch current project to DIR.
1060 (default-directory dir)
1061 (ggtags-project-root dir)
1062 (ggtags-process-environment env))
1063 (ggtags-global-start
1064 (ggtags-global-build-command cmd) dir))
1065 (add-hook 'compilation-finish-functions
1066 (lambda (buf _msg)
1067 (with-current-buffer buf
1068 (ggtags-forward-to-line line)
1069 (compile-goto-error)))
1070 nil t)))))
1071
1072 (defvar-local ggtags-global-search-ewoc nil)
1073 (defvar ggtags-view-search-history-last nil)
1074
1075 (defvar ggtags-view-search-history-mode-map
1076 (let ((m (make-sparse-keymap)))
1077 (define-key m "p" 'ggtags-view-search-history-prev)
1078 (define-key m "\M-p" 'ggtags-view-search-history-prev)
1079 (define-key m "n" 'ggtags-view-search-history-next)
1080 (define-key m "\M-n" 'ggtags-view-search-history-next)
1081 (define-key m "\C-k" 'ggtags-view-search-history-kill)
1082 (define-key m [remap yank] (lambda (&optional arg) (interactive "P") (yank arg)))
1083 (define-key m "\C-c\C-c" 'ggtags-view-search-history-update)
1084 (define-key m "r" 'ggtags-save-to-register)
1085 (define-key m "\r" 'ggtags-view-search-history-action)
1086 (define-key m "q" 'ggtags-kill-window)
1087 m))
1088
1089 (defun ggtags-view-search-history-remember ()
1090 (setq ggtags-view-search-history-last
1091 (pcase (ewoc-locate ggtags-global-search-ewoc)
1092 (`nil nil)
1093 (node (ewoc-data node)))))
1094
1095 (defun ggtags-view-search-history-next (&optional arg)
1096 (interactive "p")
1097 (let ((arg (or arg 1)))
1098 (prog1 (funcall (if (cl-minusp arg) #'ewoc-goto-prev #'ewoc-goto-next)
1099 ggtags-global-search-ewoc (abs arg))
1100 (ggtags-view-search-history-remember))))
1101
1102 (defun ggtags-view-search-history-prev (&optional arg)
1103 (interactive "p")
1104 (ggtags-view-search-history-next (- (or arg 1))))
1105
1106 (defun ggtags-view-search-history-kill (&optional append)
1107 (interactive "P")
1108 (let* ((node (or (ewoc-locate ggtags-global-search-ewoc)
1109 (user-error "No node at point")))
1110 (next (ewoc-next ggtags-global-search-ewoc node))
1111 (text (filter-buffer-substring (ewoc-location node)
1112 (if next (ewoc-location next)
1113 (point-max)))))
1114 (put-text-property
1115 0 (length text) 'yank-handler
1116 (list (lambda (arg)
1117 (if (not ggtags-global-search-ewoc)
1118 (insert (car arg))
1119 (let* ((inhibit-read-only t)
1120 (node (unless (looking-at-p "[ \t\n]*\\'")
1121 (ewoc-locate ggtags-global-search-ewoc))))
1122 (if node
1123 (ewoc-enter-before ggtags-global-search-ewoc
1124 node (cadr arg))
1125 (ewoc-enter-last ggtags-global-search-ewoc (cadr arg)))
1126 (setq ggtags-view-search-history-last (cadr arg)))))
1127 (list text (ewoc-data node)))
1128 text)
1129 (if append (kill-append text nil)
1130 (kill-new text))
1131 (let ((inhibit-read-only t))
1132 (ewoc-delete ggtags-global-search-ewoc node))))
1133
1134 (defun ggtags-view-search-history-update (&optional noconfirm)
1135 "Update `ggtags-global-search-history' to current buffer."
1136 (interactive "P")
1137 (when (and (buffer-modified-p)
1138 (or noconfirm
1139 (yes-or-no-p "Modify `ggtags-global-search-history'?")))
1140 (setq ggtags-global-search-history
1141 (ewoc-collect ggtags-global-search-ewoc #'identity))
1142 (set-buffer-modified-p nil)))
1143
1144 (defun ggtags-view-search-history-action ()
1145 (interactive)
1146 (let ((data (ewoc-data (or (ewoc-locate ggtags-global-search-ewoc)
1147 (user-error "No search at point")))))
1148 (ggtags-view-search-history-remember)
1149 (quit-window t)
1150 (ggtags-global-rerun-search (cdr data))))
1151
1152 (defvar bookmark-make-record-function)
1153
1154 (define-derived-mode ggtags-view-search-history-mode special-mode "SearchHist"
1155 "Major mode for viewing search history."
1156 :group 'ggtags
1157 (setq-local ggtags-enable-navigation-keys nil)
1158 (setq-local bookmark-make-record-function #'ggtags-make-bookmark-record)
1159 (setq truncate-lines t)
1160 (add-hook 'kill-buffer-hook #'ggtags-view-search-history-update nil t))
1161
1162 (defun ggtags-view-search-history-restore-last ()
1163 (when ggtags-view-search-history-last
1164 (cl-loop for n = (ewoc-nth ggtags-global-search-ewoc 0)
1165 then (ewoc-next ggtags-global-search-ewoc n)
1166 while n when (eq (ewoc-data n)
1167 ggtags-view-search-history-last)
1168 do (progn (goto-char (ewoc-location n)) (cl-return t)))))
1169
1170 (defun ggtags-view-search-history ()
1171 "Pop to a buffer to view or re-run past searches.
1172
1173 \\{ggtags-view-search-history-mode-map}"
1174 (interactive)
1175 (or ggtags-global-search-history (user-error "No search history"))
1176 (let ((split-window-preferred-function ggtags-split-window-function)
1177 (inhibit-read-only t))
1178 (pop-to-buffer "*Ggtags Search History*")
1179 (erase-buffer)
1180 (ggtags-view-search-history-mode)
1181 (cl-labels ((prop (s)
1182 (propertize s 'face 'minibuffer-prompt))
1183 (prop-tag (cmd)
1184 (with-temp-buffer
1185 (insert cmd)
1186 (forward-sexp -1)
1187 (if (eobp)
1188 cmd
1189 (put-text-property (point) (point-max)
1190 'face font-lock-constant-face)
1191 (buffer-string))))
1192 (pp (data)
1193 (pcase data
1194 (`(,_id ,cmd ,dir ,_env ,line ,text)
1195 (insert (prop " cmd: ") (prop-tag cmd) "\n"
1196 (prop " dir: ") dir "\n"
1197 (prop "line: ") (number-to-string line) "\n"
1198 (prop "text: ") text "\n"
1199 (propertize (make-string 32 ?-) 'face 'shadow))))))
1200 (setq ggtags-global-search-ewoc
1201 (ewoc-create #'pp "Global search history keys: n:next p:prev r:register RET:choose\n")))
1202 (dolist (data ggtags-global-search-history)
1203 (ewoc-enter-last ggtags-global-search-ewoc data))
1204 (ggtags-view-search-history-restore-last)
1205 (set-buffer-modified-p nil)
1206 (fit-window-to-buffer nil (floor (frame-height) 2))))
1207
1208 (defun ggtags-save-to-register (r)
1209 "Save current search session to register R.
1210 Use \\[jump-to-register] to restore the search session."
1211 (interactive (list (register-read-with-preview "Save search to register: ")))
1212 (cl-labels ((prn (data)
1213 (pcase data
1214 (`(,command ,root ,_env ,line ,_)
1215 (princ (format "a ggtags search session `%s' in directory `%s' at line %d."
1216 command root line))))))
1217 (set-register r (registerv-make
1218 (if ggtags-global-search-ewoc
1219 (cdr (ewoc-data (ewoc-locate ggtags-global-search-ewoc)))
1220 (ggtags-global-current-search))
1221 :jump-func #'ggtags-global-rerun-search
1222 :print-func #'prn))))
1223
1224 (defun ggtags-make-bookmark-record ()
1225 `(,(and ggtags-current-tag-name (format "*ggtags %s*" ggtags-current-tag-name))
1226 (ggtags-search . ,(if ggtags-global-search-ewoc
1227 (cdr (ewoc-data (ewoc-locate ggtags-global-search-ewoc)))
1228 (ggtags-global-current-search)))
1229 (handler . ggtags-bookmark-jump)))
1230
1231 (declare-function bookmark-prop-get "bookmark")
1232
1233 (defun ggtags-bookmark-jump (bmk)
1234 (ggtags-global-rerun-search (bookmark-prop-get bmk 'ggtags-search)))
1235
1236 (defun ggtags-browse-file-as-hypertext (file line)
1237 "Browse FILE in hypertext (HTML) form."
1238 (interactive (if (or current-prefix-arg (not buffer-file-name))
1239 (list (read-file-name "Browse file: " nil nil t)
1240 (read-number "Line: " 1))
1241 (list buffer-file-name (line-number-at-pos))))
1242 (cl-check-type line (integer 1))
1243 (or (and file (file-exists-p file)) (error "File `%s' doesn't exist" file))
1244 (ggtags-check-project)
1245 (or (file-exists-p (expand-file-name "HTML" (ggtags-current-project-root)))
1246 (if (yes-or-no-p "No hypertext form exists; run htags? ")
1247 (let ((default-directory (ggtags-current-project-root)))
1248 (ggtags-with-current-project (ggtags-process-string "htags")))
1249 (user-error "Aborted")))
1250 (let ((url (ggtags-process-string "gozilla" "-p" (format "+%d" line)
1251 (file-relative-name file))))
1252 (or (equal (file-name-extension
1253 (url-filename (url-generic-parse-url url))) "html")
1254 (user-error "No hypertext form for `%s'" file))
1255 (when (called-interactively-p 'interactive)
1256 (message "Browsing %s" url))
1257 (browse-url url)))
1258
1259 (defun ggtags-next-mark (&optional arg)
1260 "Move to the next (newer) mark in the tag marker ring."
1261 (interactive)
1262 (and (ring-empty-p find-tag-marker-ring) (user-error "Tag ring empty"))
1263 (setq ggtags-tag-ring-index
1264 ;; Note `ring-minus1' gets newer item.
1265 (funcall (if arg #'ring-plus1 #'ring-minus1)
1266 (or ggtags-tag-ring-index
1267 (progn
1268 (ring-insert find-tag-marker-ring (point-marker))
1269 0))
1270 (ring-length find-tag-marker-ring)))
1271 (let ((m (ring-ref find-tag-marker-ring ggtags-tag-ring-index))
1272 (i (- (ring-length find-tag-marker-ring) ggtags-tag-ring-index)))
1273 (ggtags-echo "%d%s marker%s" i (pcase (mod i 10)
1274 ;; ` required for 24.1 and 24.2
1275 (`1 "st")
1276 (`2 "nd")
1277 (`3 "rd")
1278 (_ "th"))
1279 (if (marker-buffer m) "" " (dead)"))
1280 (if (not (marker-buffer m))
1281 (ding)
1282 (switch-to-buffer (marker-buffer m))
1283 (goto-char m))))
1284
1285 (defun ggtags-prev-mark ()
1286 "Move to the previous (older) mark in the tag marker ring."
1287 (interactive)
1288 (ggtags-next-mark 'previous))
1289
1290 (defvar ggtags-view-tag-history-mode-map
1291 (let ((m (make-sparse-keymap)))
1292 (define-key m "\M-n" 'next-error-no-select)
1293 (define-key m "\M-p" 'previous-error-no-select)
1294 (define-key m "q" 'ggtags-kill-window)
1295 m))
1296
1297 (define-derived-mode ggtags-view-tag-history-mode tabulated-list-mode "TagHist"
1298 :abbrev-table nil :group 'ggtags)
1299
1300 (defun ggtags-view-tag-history ()
1301 "Pop to a buffer listing visited locations from newest to oldest.
1302 The buffer is a next error buffer and works with standard
1303 commands `next-error' and `previous-error'.
1304
1305 \\{ggtags-view-tag-history-mode-map}"
1306 (interactive)
1307 (and (ring-empty-p find-tag-marker-ring)
1308 (user-error "Tag ring empty"))
1309 (let ((split-window-preferred-function ggtags-split-window-function)
1310 (inhibit-read-only t))
1311 (pop-to-buffer "*Tag Ring*")
1312 (erase-buffer)
1313 (ggtags-view-tag-history-mode)
1314 (setq next-error-function #'ggtags-view-tag-history-next-error
1315 next-error-last-buffer (current-buffer))
1316 (setq tabulated-list-entries
1317 ;; Use a function so that revert can work properly.
1318 (lambda ()
1319 (let ((counter (ring-length find-tag-marker-ring))
1320 (elements (or (ring-elements find-tag-marker-ring)
1321 (user-error "Tag ring empty")))
1322 (action (lambda (_button) (next-error 0)))
1323 (get-line (lambda (m)
1324 (with-current-buffer (marker-buffer m)
1325 (save-excursion
1326 (goto-char m)
1327 (buffer-substring (line-beginning-position)
1328 (line-end-position)))))))
1329 (setq tabulated-list-format
1330 `[("ID" ,(max (1+ (floor (log counter 10))) 2)
1331 car-less-than-car)
1332 ("Buffer" ,(max (cl-loop for m in elements
1333 for b = (marker-buffer m)
1334 maximize
1335 (length (and b (buffer-name b))))
1336 6)
1337 t :right-align t)
1338 ("Position" ,(max (cl-loop for m in elements
1339 for p = (or (marker-position m) 1)
1340 maximize (1+ (floor (log p 10))))
1341 8)
1342 (lambda (x y)
1343 (< (string-to-number (aref (cadr x) 2))
1344 (string-to-number (aref (cadr y) 2))))
1345 :right-align t)
1346 ("Contents" 100 t)])
1347 (tabulated-list-init-header)
1348 (mapcar (lambda (x)
1349 (prog1
1350 (list counter
1351 (if (marker-buffer x)
1352 (vector (number-to-string counter)
1353 `(,(buffer-name (marker-buffer x))
1354 face link
1355 follow-link t
1356 marker ,x
1357 action ,action)
1358 (number-to-string (marker-position x))
1359 (funcall get-line x))
1360 (vector (number-to-string counter)
1361 "(dead)" "?" "?")))
1362 (cl-decf counter)))
1363 elements))))
1364 (setq tabulated-list-sort-key '("ID" . t))
1365 (tabulated-list-print)
1366 (fit-window-to-buffer nil (floor (frame-height) 2))))
1367
1368 (defun ggtags-view-tag-history-next-error (&optional arg reset)
1369 (if (not reset)
1370 (forward-button arg)
1371 (goto-char (point-min))
1372 (forward-button (if (button-at (point)) 0 1)))
1373 (when (get-buffer-window)
1374 (set-window-point (get-buffer-window) (point)))
1375 (pcase (button-get (button-at (point)) 'marker)
1376 ((and (pred markerp) m)
1377 (if (eq (get-buffer-window) (selected-window))
1378 (pop-to-buffer (marker-buffer m))
1379 (switch-to-buffer (marker-buffer m)))
1380 (goto-char (marker-position m)))
1381 (_ (error "Dead marker"))))
1382
1383 (defun ggtags-global-exit-message-1 ()
1384 "Get the total of matches and db file used."
1385 (save-excursion
1386 (goto-char (point-max))
1387 (if (re-search-backward
1388 "^\\w+ \\(not found\\)\\|^\\([0-9]+\\) \\w+ located" nil t)
1389 (cons (or (and (match-string 1) 0)
1390 (string-to-number (match-string 2)))
1391 (when (re-search-forward
1392 "using \\(?:\\(idutils\\)\\|'[^']*/\\(\\w+\\)'\\)"
1393 (line-end-position)
1394 t)
1395 (or (and (match-string 1) "ID")
1396 (match-string 2))))
1397 (cons 0 nil))))
1398
1399 (defun ggtags-global-exit-message-function (_process-status exit-status msg)
1400 "A function for `compilation-exit-message-function'."
1401 (pcase (ggtags-global-exit-message-1)
1402 (`(,count . ,db)
1403 (setq ggtags-global-exit-info (list exit-status count db))
1404 ;; Clear the start marker in case of zero matches.
1405 (and (zerop count)
1406 (markerp ggtags-global-start-marker)
1407 (not ggtags-global-continuation)
1408 (setq ggtags-global-start-marker nil))
1409 (cons (if (> exit-status 0)
1410 msg
1411 (format "found %d %s" count
1412 (funcall (if (= count 1) #'car #'cadr)
1413 (pcase db
1414 ;; ` required for 24.1 and 24.2
1415 (`"GTAGS" '("definition" "definitions"))
1416 (`"GSYMS" '("symbol" "symbols"))
1417 (`"GRTAGS" '("reference" "references"))
1418 (`"GPATH" '("file" "files"))
1419 (`"ID" '("identifier" "identifiers"))
1420 (_ '("match" "matches"))))))
1421 exit-status))))
1422
1423 (defun ggtags-global-column (start)
1424 ;; START is the beginning position of source text.
1425 (let ((mbeg (text-property-any start (line-end-position) 'global-color t)))
1426 (and mbeg (- mbeg start))))
1427
1428 ;;; NOTE: Must not match the 'Global started at Mon Jun 3 10:24:13'
1429 ;;; line or `compilation-auto-jump' will jump there and fail. See
1430 ;;; comments before the 'gnu' entry in
1431 ;;; `compilation-error-regexp-alist-alist'.
1432 (defvar ggtags-global-error-regexp-alist-alist
1433 (append
1434 `((path "^\\(?:[^\"'\n]*/\\)?[^ )\t\n]+$" 0)
1435 ;; ACTIVE_ESCAPE src/dialog.cc 172
1436 (ctags "^\\([^ \t\n]+\\)[ \t]+\\(.*?\\)[ \t]+\\([0-9]+\\)$"
1437 2 3 nil nil 2 (1 font-lock-function-name-face))
1438 ;; ACTIVE_ESCAPE 172 src/dialog.cc #undef ACTIVE_ESCAPE
1439 (ctags-x "^\\([^ \t\n]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\(\\(?:[^/\n]*/\\)?[^ \t\n]+\\)"
1440 3 2 (,(lambda () (ggtags-global-column (1+ (match-end 0)))))
1441 nil 3 (1 font-lock-function-name-face))
1442 ;; src/dialog.cc:172:#undef ACTIVE_ESCAPE
1443 (grep "^\\(.+?\\):\\([0-9]+\\):\\(?:$\\|[^0-9\n]\\|[0-9][^0-9\n]\\|[0-9][0-9].\\)"
1444 1 2 (,(lambda () (ggtags-global-column (1+ (match-end 2))))) nil 1)
1445 ;; src/dialog.cc ACTIVE_ESCAPE 172 #undef ACTIVE_ESCAPE
1446 (cscope "^\\(.+?\\)[ \t]+\\([^ \t\n]+\\)[ \t]+\\([0-9]+\\).*\\(?:[^0-9\n]\\|[^0-9\n][0-9]\\|[^:\n][0-9][0-9]\\)$"
1447 1 3 nil nil 1 (2 font-lock-function-name-face)))
1448 compilation-error-regexp-alist-alist))
1449
1450 (defun ggtags-abbreviate-file (start end)
1451 (let ((inhibit-read-only t)
1452 (amount (if (numberp ggtags-global-abbreviate-filename)
1453 (- (- end start) ggtags-global-abbreviate-filename)
1454 999))
1455 (advance-word (lambda ()
1456 "Return the length of the text made invisible."
1457 (let ((wend (min end (progn (forward-word 1) (point))))
1458 (wbeg (max start (progn (backward-word 1) (point)))))
1459 (goto-char wend)
1460 (if (<= (- wend wbeg) 1)
1461 0
1462 (put-text-property (1+ wbeg) wend 'invisible t)
1463 (1- (- wend wbeg)))))))
1464 (goto-char start)
1465 (while (and (> amount 0) (> end (point)))
1466 (cl-decf amount (funcall advance-word)))))
1467
1468 (defun ggtags-abbreviate-files (start end)
1469 (goto-char start)
1470 (let* ((error-re (cdr (assq (car compilation-error-regexp-alist)
1471 ggtags-global-error-regexp-alist-alist)))
1472 (sub (cadr error-re)))
1473 (when (and ggtags-global-abbreviate-filename error-re)
1474 (while (re-search-forward (car error-re) end t)
1475 (when (and (or (not (numberp ggtags-global-abbreviate-filename))
1476 (> (length (match-string sub))
1477 ggtags-global-abbreviate-filename))
1478 ;; Ignore bogus file lines such as:
1479 ;; Global found 2 matches at Thu Jan 31 13:45:19
1480 (get-text-property (match-beginning sub) 'compilation-message))
1481 (ggtags-abbreviate-file (match-beginning sub) (match-end sub)))))))
1482
1483 (defvar-local ggtags-global-output-lines 0)
1484
1485 (defun ggtags-global--display-buffer (&optional buffer desired-point)
1486 (pcase (let ((buffer (or buffer (current-buffer)))
1487 (split-window-preferred-function ggtags-split-window-function))
1488 (and (not (get-buffer-window buffer))
1489 (display-buffer buffer '(nil (allow-no-window . t)))))
1490 ((and (pred windowp) w)
1491 (with-selected-window w
1492 (compilation-set-window-height w)
1493 (and desired-point (goto-char desired-point))))))
1494
1495 (defun ggtags-global-filter ()
1496 "Called from `compilation-filter-hook' (which see)."
1497 (let ((ansi-color-apply-face-function
1498 (lambda (beg end face)
1499 (when face
1500 (ansi-color-apply-overlay-face beg end face)
1501 (put-text-property beg end 'global-color t)))))
1502 (ansi-color-apply-on-region compilation-filter-start (point)))
1503 ;; Get rid of line "Using config file '/PATH/TO/.globalrc'." or
1504 ;; "Using default configuration."
1505 (when (re-search-backward
1506 "^ *Using \\(?:config file '.*\\|default configuration.\\)\n"
1507 compilation-filter-start t)
1508 (replace-match ""))
1509 (cl-incf ggtags-global-output-lines
1510 (count-lines compilation-filter-start (point)))
1511 ;; If the number of output lines is small
1512 ;; `ggtags-global-handle-exit' takes care of displaying the buffer.
1513 (when (and (> ggtags-global-output-lines 30) ggtags-navigation-mode)
1514 (ggtags-global--display-buffer nil (or compilation-current-error (point-min))))
1515 (when (and (eq ggtags-auto-jump-to-match 'history)
1516 (numberp ggtags-auto-jump-to-match-target)
1517 (not compilation-current-error)
1518 ;; `ggtags-global-output-lines' is imprecise but use it
1519 ;; as first approximation.
1520 (> (+ 10 ggtags-global-output-lines) ggtags-auto-jump-to-match-target)
1521 (> (line-number-at-pos (point-max))
1522 ggtags-auto-jump-to-match-target))
1523 (ggtags-forward-to-line ggtags-auto-jump-to-match-target)
1524 (setq-local ggtags-auto-jump-to-match-target nil)
1525 ;;
1526 ;; Can't call `compile-goto-error' here becuase
1527 ;; `compilation-filter' restores point and as a result commands
1528 ;; dependent on point such as `ggtags-navigation-next-file' and
1529 ;; `ggtags-navigation-previous-file' fail to work.
1530 (run-with-idle-timer 0 nil (lambda (buf pt)
1531 (and (buffer-live-p buf)
1532 (with-current-buffer buf
1533 (ggtags-delay-finish-functions
1534 (let ((compilation-auto-jump-to-first-error t))
1535 (with-display-buffer-no-window
1536 (compilation-auto-jump buf pt)))))))
1537 (current-buffer) (point)))
1538 (make-local-variable 'ggtags-global-large-output)
1539 (when (> ggtags-global-output-lines ggtags-global-large-output)
1540 (cl-incf ggtags-global-large-output 500)
1541 (ggtags-echo "Output %d lines (Type `C-c C-k' to cancel)"
1542 ggtags-global-output-lines)))
1543
1544 (defun ggtags-global-handle-exit (buf how)
1545 "A function for `compilation-finish-functions' (which see)."
1546 (cond
1547 (ggtags-global-continuation
1548 (let ((cont (prog1 ggtags-global-continuation
1549 (setq ggtags-global-continuation nil))))
1550 (funcall cont buf how)))
1551 ((string-prefix-p "exited abnormally" how)
1552 ;; If exit abnormally display the buffer for inspection.
1553 (ggtags-global--display-buffer)
1554 (when (save-excursion
1555 (goto-char (point-max))
1556 (re-search-backward
1557 (eval-when-compile
1558 (format "^global: %s not found.$"
1559 (regexp-opt '("GTAGS" "GRTAGS" "GSYMS" "GPATH"))))
1560 nil t))
1561 (ggtags-echo "WARNING: Global tag files missing in `%s'"
1562 ggtags-project-root)
1563 (remhash ggtags-project-root ggtags-projects)))
1564 (ggtags-auto-jump-to-match
1565 (if (pcase (compilation-next-single-property-change
1566 (point-min) 'compilation-message)
1567 ((and pt (guard pt))
1568 (compilation-next-single-property-change
1569 (save-excursion (goto-char pt) (end-of-line) (point))
1570 'compilation-message)))
1571 ;; There are multiple matches so pop up the buffer.
1572 (and ggtags-navigation-mode (ggtags-global--display-buffer))
1573 ;; For the `compilation-auto-jump' in idle timer to run.
1574 ;; See also: http://debbugs.gnu.org/13829
1575 (sit-for 0)
1576 (ggtags-navigation-mode -1)
1577 (ggtags-navigation-mode-cleanup buf 0)))))
1578
1579 (defvar ggtags-global-mode-font-lock-keywords
1580 '(("^Global \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*"
1581 (1 'compilation-error)
1582 (2 'compilation-error nil t))
1583 ("^Global found \\([0-9]+\\)" (1 compilation-info-face))))
1584
1585 (defvar compilation-always-kill) ;new in 24.3
1586
1587 (define-compilation-mode ggtags-global-mode "Global"
1588 "A mode for showing outputs from gnu global."
1589 ;; Note: Place `ggtags-global-output-format' as first element for
1590 ;; `ggtags-abbreviate-files'.
1591 (setq-local compilation-error-regexp-alist (list ggtags-global-output-format))
1592 (when (markerp ggtags-global-start-marker)
1593 (setq ggtags-project-root
1594 (buffer-local-value 'ggtags-project-root
1595 (marker-buffer ggtags-global-start-marker))))
1596 (pcase ggtags-auto-jump-to-match
1597 (`history (make-local-variable 'ggtags-auto-jump-to-match-target)
1598 (setq-local compilation-auto-jump-to-first-error
1599 (not ggtags-auto-jump-to-match-target)))
1600 (`nil (setq-local compilation-auto-jump-to-first-error nil))
1601 (_ (setq-local compilation-auto-jump-to-first-error t)))
1602 (setq-local compilation-scroll-output nil)
1603 ;; See `compilation-move-to-column' for details.
1604 (setq-local compilation-first-column 0)
1605 (setq-local compilation-error-screen-columns nil)
1606 (setq-local compilation-disable-input t)
1607 (setq-local compilation-always-kill t)
1608 (setq-local compilation-error-face 'compilation-info)
1609 (setq-local compilation-exit-message-function
1610 'ggtags-global-exit-message-function)
1611 ;; See: https://github.com/leoliu/ggtags/issues/26
1612 (setq-local find-file-suppress-same-file-warnings t)
1613 (setq-local truncate-lines t)
1614 (jit-lock-register #'ggtags-abbreviate-files)
1615 (add-hook 'compilation-filter-hook 'ggtags-global-filter nil 'local)
1616 (add-hook 'compilation-finish-functions 'ggtags-global-handle-exit nil t)
1617 (setq-local bookmark-make-record-function #'ggtags-make-bookmark-record)
1618 (setq-local ggtags-enable-navigation-keys nil)
1619 (add-hook 'kill-buffer-hook (lambda () (ggtags-navigation-mode -1)) nil t))
1620
1621 ;; NOTE: Need this to avoid putting menu items in
1622 ;; `emulation-mode-map-alists', which creates double entries. See
1623 ;; http://i.imgur.com/VJJTzVc.png
1624 (defvar ggtags-navigation-map
1625 (let ((map (make-sparse-keymap)))
1626 (define-key map "\M-n" 'next-error)
1627 (define-key map "\M-p" 'previous-error)
1628 (define-key map "\M-}" 'ggtags-navigation-next-file)
1629 (define-key map "\M-{" 'ggtags-navigation-previous-file)
1630 (define-key map "\M->" 'ggtags-navigation-last-error)
1631 (define-key map "\M-<" 'first-error)
1632 ;; Note: shadows `isearch-forward-regexp' but it can still be
1633 ;; invoked with `C-u C-s'.
1634 (define-key map "\C-\M-s" 'ggtags-navigation-isearch-forward)
1635 ;; Add an alternative binding because C-M-s is reported not
1636 ;; working on some systems.
1637 (define-key map "\M-ss" 'ggtags-navigation-isearch-forward)
1638 (define-key map "\C-c\C-k"
1639 (lambda () (interactive)
1640 (ggtags-ensure-global-buffer (kill-compilation))))
1641 (define-key map "\M-o" 'ggtags-navigation-visible-mode)
1642 (define-key map [return] 'ggtags-navigation-mode-done)
1643 (define-key map "\r" 'ggtags-navigation-mode-done)
1644 (define-key map [remap pop-tag-mark] 'ggtags-navigation-mode-abort)
1645 map))
1646
1647 (defvar ggtags-mode-map-alist
1648 `((ggtags-enable-navigation-keys . ,ggtags-navigation-map)))
1649
1650 (defvar ggtags-navigation-mode-map
1651 (let ((map (make-sparse-keymap))
1652 (menu (make-sparse-keymap "GG-Navigation")))
1653 ;; Menu items: (info "(elisp)Extended Menu Items")
1654 (define-key map [menu-bar ggtags-navigation] (cons "GG-Navigation" menu))
1655 ;; Ordered backwards
1656 (define-key menu [visible-mode]
1657 '(menu-item "Visible mode" ggtags-navigation-visible-mode
1658 :button (:toggle . (ignore-errors
1659 (ggtags-ensure-global-buffer
1660 visible-mode)))))
1661 (define-key menu [done]
1662 '(menu-item "Finish navigation" ggtags-navigation-mode-done))
1663 (define-key menu [abort]
1664 '(menu-item "Abort" ggtags-navigation-mode-abort))
1665 (define-key menu [last-match]
1666 '(menu-item "Last match" ggtags-navigation-last-error))
1667 (define-key menu [first-match] '(menu-item "First match" first-error))
1668 (define-key menu [previous-file]
1669 '(menu-item "Previous file" ggtags-navigation-previous-file))
1670 (define-key menu [next-file]
1671 '(menu-item "Next file" ggtags-navigation-next-file))
1672 (define-key menu [isearch-forward]
1673 '(menu-item "Find match with isearch" ggtags-navigation-isearch-forward))
1674 (define-key menu [previous]
1675 '(menu-item "Previous match" previous-error))
1676 (define-key menu [next]
1677 '(menu-item "Next match" next-error))
1678 map))
1679
1680 (defun ggtags-move-to-tag (&optional name)
1681 "Move to NAME tag in current line."
1682 (let ((tag (or name ggtags-current-tag-name)))
1683 ;; Do nothing if on the tag already i.e. by `ggtags-global-column'.
1684 (unless (or (not tag) (looking-at (concat (regexp-quote tag) "\\_>")))
1685 (let ((orig (point))
1686 (regexps (mapcar (lambda (fmtstr)
1687 (format fmtstr (regexp-quote tag)))
1688 '("\\_<%s\\_>" "%s\\_>" "%s"))))
1689 (beginning-of-line)
1690 (if (cl-loop for re in regexps
1691 ;; Note: tag might not agree with current
1692 ;; major-mode's symbol, so try harder. For
1693 ;; example, in `php-mode' $cacheBackend is a
1694 ;; symbol, but cacheBackend is a tag.
1695 thereis (re-search-forward re (line-end-position) t))
1696 (goto-char (match-beginning 0))
1697 (goto-char orig))))))
1698
1699 (defun ggtags-navigation-mode-cleanup (&optional buf time)
1700 (let ((buf (or buf ggtags-global-last-buffer)))
1701 (and (buffer-live-p buf)
1702 (with-current-buffer buf
1703 (when (get-buffer-process (current-buffer))
1704 (kill-compilation))
1705 (when (and (derived-mode-p 'ggtags-global-mode)
1706 (get-buffer-window))
1707 (quit-windows-on (current-buffer)))
1708 (and time (run-with-idle-timer time nil #'kill-buffer buf))))))
1709
1710 (defun ggtags-navigation-mode-done ()
1711 (interactive)
1712 (ggtags-navigation-mode -1)
1713 (setq tags-loop-scan t
1714 tags-loop-operate '(ggtags-find-tag-continue))
1715 (ggtags-navigation-mode-cleanup))
1716
1717 (defun ggtags-navigation-mode-abort ()
1718 "Abort navigation and return to where the search was started."
1719 (interactive)
1720 (ggtags-navigation-mode -1)
1721 (ggtags-navigation-mode-cleanup nil 0)
1722 ;; Run after (ggtags-navigation-mode -1) or
1723 ;; ggtags-global-start-marker might not have been saved.
1724 (when (and ggtags-global-start-marker
1725 (not (markerp ggtags-global-start-marker)))
1726 (setq ggtags-global-start-marker nil)
1727 (pop-tag-mark)))
1728
1729 (defun ggtags-navigation-next-file (n)
1730 (interactive "p")
1731 (ggtags-ensure-global-buffer
1732 (compilation-next-file n)
1733 (compile-goto-error)))
1734
1735 (defun ggtags-navigation-previous-file (n)
1736 (interactive "p")
1737 (ggtags-navigation-next-file (- n)))
1738
1739 (defun ggtags-navigation-last-error ()
1740 (interactive)
1741 (ggtags-ensure-global-buffer
1742 (goto-char (point-max))
1743 (compilation-previous-error 1)
1744 (compile-goto-error)))
1745
1746 (defun ggtags-navigation-isearch-forward (&optional regexp-p)
1747 (interactive "P")
1748 (ggtags-ensure-global-buffer
1749 (let ((saved (if visible-mode 1 -1)))
1750 (visible-mode 1)
1751 (with-selected-window (get-buffer-window (current-buffer))
1752 (isearch-forward regexp-p)
1753 (beginning-of-line)
1754 (visible-mode saved)
1755 (compile-goto-error)))))
1756
1757 (defun ggtags-navigation-visible-mode (&optional arg)
1758 (interactive (list (or current-prefix-arg 'toggle)))
1759 (ggtags-ensure-global-buffer
1760 (visible-mode arg)))
1761
1762 (defvar ggtags-global-line-overlay nil)
1763
1764 (defun ggtags-global-next-error-function ()
1765 (when (eq next-error-last-buffer ggtags-global-last-buffer)
1766 (ggtags-move-to-tag)
1767 (ggtags-global-save-start-marker)
1768 (and (ggtags-project-update-mtime-maybe)
1769 (message "File `%s' is newer than GTAGS"
1770 (file-name-nondirectory buffer-file-name)))
1771 (and ggtags-mode-sticky (ggtags-mode 1))
1772 (ignore-errors
1773 (ggtags-ensure-global-buffer
1774 (unless (overlayp ggtags-global-line-overlay)
1775 (setq ggtags-global-line-overlay (make-overlay (point) (point)))
1776 (overlay-put ggtags-global-line-overlay 'face 'ggtags-global-line))
1777 (move-overlay ggtags-global-line-overlay
1778 (line-beginning-position) (line-end-position)
1779 (current-buffer))
1780 ;; Update search history
1781 (let ((id (ggtags-global-search-id (car compilation-arguments)
1782 default-directory)))
1783 (setq ggtags-global-search-history
1784 (cl-remove id ggtags-global-search-history :test #'equal :key #'car))
1785 (add-to-history 'ggtags-global-search-history
1786 (cons id (ggtags-global-current-search))
1787 ggtags-global-history-length))))
1788 (run-hooks 'ggtags-find-tag-hook)))
1789
1790 (put 'ggtags-navigation-mode-lighter 'risky-local-variable t)
1791
1792 (defvar ggtags-navigation-mode-lighter
1793 '(" GG["
1794 (:eval
1795 (if (not (buffer-live-p ggtags-global-last-buffer))
1796 '(:propertize "??" face error help-echo "No Global buffer")
1797 (with-current-buffer ggtags-global-last-buffer
1798 (pcase (or ggtags-global-exit-info '(0 0 ""))
1799 (`(,exit ,count ,db)
1800 `((:propertize ,(pcase db
1801 (`"GTAGS" "D")
1802 (`"GRTAGS" "R")
1803 (`"GSYMS" "S")
1804 (`"GPATH" "F")
1805 (`"ID" "I"))
1806 face success)
1807 (:propertize
1808 ,(pcase (get-text-property (line-beginning-position)
1809 'compilation-message)
1810 (`nil "?")
1811 ;; Assume the first match appears at line 5
1812 (_ (number-to-string (- (line-number-at-pos) 4))))
1813 face success)
1814 "/"
1815 (:propertize ,(number-to-string count) face success)
1816 ,(unless (zerop exit)
1817 `(":" (:propertize ,(number-to-string exit) face error)))))))))
1818 "]")
1819 "Ligher for `ggtags-navigation-mode'; set to nil to disable it.")
1820
1821 (define-minor-mode ggtags-navigation-mode nil
1822 :lighter ggtags-navigation-mode-lighter
1823 :global t
1824 (if ggtags-navigation-mode
1825 (progn
1826 ;; Higher priority for `ggtags-navigation-mode' to avoid being
1827 ;; hijacked by modes such as `view-mode'.
1828 (add-to-list 'emulation-mode-map-alists 'ggtags-mode-map-alist)
1829 (add-hook 'next-error-hook 'ggtags-global-next-error-function)
1830 (add-hook 'minibuffer-setup-hook 'ggtags-minibuffer-setup-function))
1831 (setq emulation-mode-map-alists
1832 (delq 'ggtags-mode-map-alist emulation-mode-map-alists))
1833 (remove-hook 'next-error-hook 'ggtags-global-next-error-function)
1834 (remove-hook 'minibuffer-setup-hook 'ggtags-minibuffer-setup-function)))
1835
1836 (defun ggtags-minibuffer-setup-function ()
1837 ;; Disable ggtags-navigation-mode in minibuffer.
1838 (setq-local ggtags-enable-navigation-keys nil))
1839
1840 (defun ggtags-kill-file-buffers (&optional interactive)
1841 "Kill all buffers visiting files in current project."
1842 (interactive "p")
1843 (ggtags-check-project)
1844 (let ((directories (cons (ggtags-current-project-root) (ggtags-get-libpath)))
1845 (count 0))
1846 (dolist (buf (buffer-list))
1847 (let ((file (and (buffer-live-p buf)
1848 (not (eq buf (current-buffer)))
1849 (buffer-file-name buf))))
1850 (when (and file (cl-some (lambda (dir)
1851 ;; Don't use `file-in-directory-p'
1852 ;; to allow symbolic links.
1853 (string-prefix-p dir file))
1854 directories))
1855 (and (kill-buffer buf) (cl-incf count)))))
1856 (and interactive
1857 (message "%d %s killed" count (if (= count 1) "buffer" "buffers")))))
1858
1859 (defun ggtags-after-save-function ()
1860 (when (ggtags-find-project)
1861 (ggtags-project-update-mtime-maybe)
1862 (and buffer-file-name
1863 (ggtags-update-tags-single buffer-file-name 'nowait))))
1864
1865 (defun ggtags-global-output (buffer cmds callback &optional cutoff)
1866 "Asynchronously pipe the output of running CMDS to BUFFER.
1867 When finished invoke CALLBACK in BUFFER with process exit status."
1868 (or buffer (error "Output buffer required"))
1869 (when (get-buffer-process (get-buffer buffer))
1870 ;; Notice running multiple processes in the same buffer so that we
1871 ;; can fix the caller. See for example `ggtags-eldoc-function'.
1872 (message "Warning: detected %S already running in %S; interrupting..."
1873 (get-buffer-process buffer) buffer)
1874 (interrupt-process (get-buffer-process buffer)))
1875 (let* ((program (car cmds))
1876 (args (cdr cmds))
1877 (cutoff (and cutoff (+ cutoff (if (get-buffer buffer)
1878 (with-current-buffer buffer
1879 (line-number-at-pos (point-max)))
1880 0))))
1881 (proc (apply #'start-file-process program buffer program args))
1882 (filter (lambda (proc string)
1883 (and (buffer-live-p (process-buffer proc))
1884 (with-current-buffer (process-buffer proc)
1885 (goto-char (process-mark proc))
1886 (insert string)
1887 (when (and (> (line-number-at-pos (point-max)) cutoff)
1888 (process-live-p proc))
1889 (interrupt-process (current-buffer)))))))
1890 (sentinel (lambda (proc _msg)
1891 (when (memq (process-status proc) '(exit signal))
1892 (with-current-buffer (process-buffer proc)
1893 (set-process-buffer proc nil)
1894 (funcall callback (process-exit-status proc)))))))
1895 (set-process-query-on-exit-flag proc nil)
1896 (and cutoff (set-process-filter proc filter))
1897 (set-process-sentinel proc sentinel)
1898 proc))
1899
1900 (cl-defun ggtags-fontify-code (code &optional (mode major-mode))
1901 (cl-check-type mode function)
1902 (cl-typecase code
1903 ((not string) code)
1904 (string (cl-labels ((prepare-buffer ()
1905 (with-current-buffer
1906 (get-buffer-create " *Code-Fontify*")
1907 (delay-mode-hooks (funcall mode))
1908 (setq font-lock-mode t)
1909 (funcall font-lock-function font-lock-mode)
1910 (current-buffer))))
1911 (with-current-buffer (prepare-buffer)
1912 (let ((inhibit-read-only t))
1913 (erase-buffer)
1914 (insert code)
1915 (font-lock-default-fontify-region
1916 (point-min) (point-max) nil))
1917 (buffer-string))))))
1918
1919 (defun ggtags-get-definition-default (defs)
1920 (and (caar defs)
1921 (concat (ggtags-fontify-code (caar defs))
1922 (and (cdr defs) " [guess]"))))
1923
1924 (defun ggtags-show-definition (name)
1925 (interactive (list (ggtags-read-tag 'definition current-prefix-arg)))
1926 (ggtags-check-project)
1927 (let* ((re (cadr (assq 'grep ggtags-global-error-regexp-alist-alist)))
1928 (current (current-buffer))
1929 (buffer (get-buffer-create " *ggtags-definition*"))
1930 ;; Need these bindings so that let-binding
1931 ;; `ggtags-print-definition-function' can work see
1932 ;; `ggtags-eldoc-function'.
1933 (get-fn ggtags-get-definition-function)
1934 (print-fn ggtags-print-definition-function)
1935 (show (lambda (_status)
1936 (goto-char (point-min))
1937 (let ((defs (cl-loop while (re-search-forward re nil t)
1938 collect (list (buffer-substring (1+ (match-end 2))
1939 (line-end-position))
1940 name
1941 (match-string 1)
1942 (string-to-number (match-string 2))))))
1943 (kill-buffer buffer)
1944 (with-current-buffer current
1945 (funcall print-fn (funcall get-fn defs)))))))
1946 (ggtags-with-current-project
1947 (ggtags-global-output
1948 buffer
1949 (list (ggtags-program-path "global")
1950 "--result=grep" "--path-style=absolute" name)
1951 show 100))))
1952
1953 (defvar ggtags-mode-prefix-map
1954 (let ((m (make-sparse-keymap)))
1955 ;; Globally bound to `M-g p'.
1956 ;; (define-key m "\M-'" 'previous-error)
1957 (define-key m (kbd "M-DEL") 'ggtags-delete-tags)
1958 (define-key m "\M-p" 'ggtags-prev-mark)
1959 (define-key m "\M-n" 'ggtags-next-mark)
1960 (define-key m "\M-f" 'ggtags-find-file)
1961 (define-key m "\M-o" 'ggtags-find-other-symbol)
1962 (define-key m "\M-g" 'ggtags-grep)
1963 (define-key m "\M-i" 'ggtags-idutils-query)
1964 (define-key m "\M-b" 'ggtags-browse-file-as-hypertext)
1965 (define-key m "\M-k" 'ggtags-kill-file-buffers)
1966 (define-key m "\M-h" 'ggtags-view-tag-history)
1967 (define-key m "\M-j" 'ggtags-visit-project-root)
1968 (define-key m "\M-/" 'ggtags-view-search-history)
1969 (define-key m (kbd "M-SPC") 'ggtags-save-to-register)
1970 (define-key m (kbd "M-%") 'ggtags-query-replace)
1971 (define-key m "\M-?" 'ggtags-show-definition)
1972 m))
1973
1974 (defvar ggtags-mode-map
1975 (let ((map (make-sparse-keymap))
1976 (menu (make-sparse-keymap "Ggtags")))
1977 (define-key map "\M-." 'ggtags-find-tag-dwim)
1978 (define-key map (kbd "M-]") 'ggtags-find-reference)
1979 (define-key map (kbd "C-M-.") 'ggtags-find-tag-regexp)
1980 (define-key map ggtags-mode-prefix-key ggtags-mode-prefix-map)
1981 ;; Menu items
1982 (define-key map [menu-bar ggtags] (cons "Ggtags" menu))
1983 ;; Ordered backwards
1984 (define-key menu [report-bugs]
1985 `(menu-item "Report bugs"
1986 (lambda () (interactive)
1987 (browse-url ggtags-bug-url)
1988 (message "Please visit %s" ggtags-bug-url))
1989 :help ,(format "Visit %s" ggtags-bug-url)))
1990 (define-key menu [custom-ggtags]
1991 '(menu-item "Customize Ggtags"
1992 (lambda () (interactive) (customize-group 'ggtags))))
1993 (define-key menu [eldoc-mode]
1994 '(menu-item "Toggle eldoc mode" eldoc-mode :button (:toggle . eldoc-mode)))
1995 (define-key menu [save-project]
1996 '(menu-item "Save project settings" ggtags-save-project-settings))
1997 (define-key menu [toggle-read-only]
1998 '(menu-item "Toggle project read-only" ggtags-toggle-project-read-only
1999 :button (:toggle . buffer-read-only)))
2000 (define-key menu [visit-project-root]
2001 '(menu-item "Visit project root" ggtags-visit-project-root))
2002 (define-key menu [sep2] menu-bar-separator)
2003 (define-key menu [browse-hypertext]
2004 '(menu-item "Browse as hypertext" ggtags-browse-file-as-hypertext
2005 :enable (ggtags-find-project)))
2006 (define-key menu [delete-tags]
2007 '(menu-item "Delete tags" ggtags-delete-tags
2008 :enable (ggtags-find-project)
2009 :help "Delete file GTAGS, GRTAGS, GPATH, ID etc."))
2010 (define-key menu [kill-buffers]
2011 '(menu-item "Kill project file buffers" ggtags-kill-file-buffers
2012 :enable (ggtags-find-project)))
2013 (define-key menu [view-tag]
2014 '(menu-item "View tag history" ggtags-view-tag-history))
2015 (define-key menu [pop-mark]
2016 '(menu-item "Pop mark" pop-tag-mark
2017 :help "Pop to previous mark and destroy it"))
2018 (define-key menu [next-mark]
2019 '(menu-item "Next mark" ggtags-next-mark))
2020 (define-key menu [prev-mark]
2021 '(menu-item "Previous mark" ggtags-prev-mark))
2022 (define-key menu [sep1] menu-bar-separator)
2023 (define-key menu [previous-error]
2024 '(menu-item "Previous match" previous-error))
2025 (define-key menu [next-error]
2026 '(menu-item "Next match" next-error))
2027 (define-key menu [rerun-search]
2028 '(menu-item "View past searches" ggtags-view-search-history))
2029 (define-key menu [save-to-register]
2030 '(menu-item "Save search to register" ggtags-save-to-register))
2031 (define-key menu [find-file]
2032 '(menu-item "Find files" ggtags-find-file))
2033 (define-key menu [query-replace]
2034 '(menu-item "Query replace" ggtags-query-replace))
2035 (define-key menu [idutils]
2036 '(menu-item "Query idutils DB" ggtags-idutils-query))
2037 (define-key menu [grep]
2038 '(menu-item "Grep" ggtags-grep))
2039 (define-key menu [find-symbol]
2040 '(menu-item "Find other symbol" ggtags-find-other-symbol
2041 :help "Find references without definition"))
2042 (define-key menu [find-tag-regexp]
2043 '(menu-item "Find tag matching regexp" ggtags-find-tag-regexp))
2044 (define-key menu [show-definition]
2045 '(menu-item "Show definition" ggtags-show-definition))
2046 (define-key menu [find-reference]
2047 '(menu-item "Find reference" ggtags-find-reference))
2048 (define-key menu [find-tag-continue]
2049 '(menu-item "Continue find tag" tags-loop-continue))
2050 (define-key menu [find-tag]
2051 '(menu-item "Find tag" ggtags-find-tag-dwim))
2052 (define-key menu [update-tags]
2053 '(menu-item "Update tag files" ggtags-update-tags
2054 :visible (ggtags-find-project)))
2055 (define-key menu [run-gtags]
2056 '(menu-item "Run gtags" ggtags-create-tags
2057 :visible (not (ggtags-find-project))))
2058 map))
2059
2060 (defvar ggtags-mode-line-project-keymap
2061 (let ((map (make-sparse-keymap)))
2062 (define-key map [mode-line mouse-1] 'ggtags-visit-project-root)
2063 map))
2064
2065 (put 'ggtags-mode-line-project-name 'risky-local-variable t)
2066 (defvar ggtags-mode-line-project-name
2067 '("[" (:eval (let ((name (if (stringp ggtags-project-root)
2068 (file-name-nondirectory
2069 (directory-file-name ggtags-project-root))
2070 "?")))
2071 (propertize
2072 name 'face compilation-info-face
2073 'help-echo (if (stringp ggtags-project-root)
2074 (concat "mouse-1 to visit " ggtags-project-root)
2075 "mouse-1 to set project")
2076 'mouse-face 'mode-line-highlight
2077 'keymap ggtags-mode-line-project-keymap)))
2078 "]")
2079 "Mode line construct for displaying current project name.
2080 The value is the name of the project root directory. Setting it
2081 to nil disables displaying this information.")
2082
2083 ;;;###autoload
2084 (define-minor-mode ggtags-mode nil
2085 :lighter (:eval (if ggtags-navigation-mode "" " GG"))
2086 (unless (timerp ggtags-highlight-tag-timer)
2087 (setq ggtags-highlight-tag-timer
2088 (run-with-idle-timer
2089 ggtags-highlight-tag-delay t #'ggtags-highlight-tag-at-point)))
2090 (if ggtags-mode
2091 (progn
2092 (add-hook 'after-save-hook 'ggtags-after-save-function nil t)
2093 ;; Append to serve as a fallback method.
2094 (add-hook 'completion-at-point-functions
2095 #'ggtags-completion-at-point t t)
2096 (unless (memq 'ggtags-mode-line-project-name
2097 mode-line-buffer-identification)
2098 (setq mode-line-buffer-identification
2099 (append mode-line-buffer-identification
2100 '(ggtags-mode-line-project-name)))))
2101 (remove-hook 'after-save-hook 'ggtags-after-save-function t)
2102 (remove-hook 'completion-at-point-functions #'ggtags-completion-at-point t)
2103 (setq mode-line-buffer-identification
2104 (delq 'ggtags-mode-line-project-name mode-line-buffer-identification))
2105 (and (overlayp ggtags-highlight-tag-overlay)
2106 (delete-overlay ggtags-highlight-tag-overlay))
2107 (setq ggtags-highlight-tag-overlay nil)))
2108
2109 (defvar ggtags-highlight-tag-map
2110 (let ((map (make-sparse-keymap)))
2111 ;; Bind down- events so that the global keymap won't ``shine
2112 ;; through''. See `mode-line-buffer-identification-keymap' for
2113 ;; similar workaround.
2114 (define-key map [S-mouse-1] 'ggtags-find-tag-dwim)
2115 (define-key map [S-down-mouse-1] 'ignore)
2116 (define-key map [S-mouse-3] 'ggtags-find-reference)
2117 (define-key map [S-down-mouse-3] 'ignore)
2118 map)
2119 "Keymap used for valid tag at point.")
2120
2121 (put 'ggtags-active-tag 'face 'ggtags-highlight)
2122 (put 'ggtags-active-tag 'keymap ggtags-highlight-tag-map)
2123 ;; (put 'ggtags-active-tag 'mouse-face 'match)
2124 (put 'ggtags-active-tag 'help-echo
2125 "S-mouse-1 for definitions\nS-mouse-3 for references")
2126
2127 (defun ggtags-highlight-tag-at-point ()
2128 (when (and ggtags-mode ggtags-project-root (ggtags-find-project))
2129 (unless (overlayp ggtags-highlight-tag-overlay)
2130 (setq ggtags-highlight-tag-overlay (make-overlay (point) (point) nil t))
2131 (overlay-put ggtags-highlight-tag-overlay 'modification-hooks
2132 (list (lambda (o after &rest _args)
2133 (and (not after) (delete-overlay o))))))
2134 (let ((bounds (funcall ggtags-bounds-of-tag-function))
2135 (o ggtags-highlight-tag-overlay))
2136 (cond
2137 ((and bounds
2138 (eq (overlay-buffer o) (current-buffer))
2139 (= (overlay-start o) (car bounds))
2140 (= (overlay-end o) (cdr bounds)))
2141 ;; Overlay matches current tag so do nothing.
2142 nil)
2143 ((and bounds (let ((completion-ignore-case nil))
2144 (test-completion
2145 (buffer-substring (car bounds) (cdr bounds))
2146 ggtags-completion-table)))
2147 (move-overlay o (car bounds) (cdr bounds) (current-buffer))
2148 (overlay-put o 'category 'ggtags-active-tag))
2149 (t (move-overlay o
2150 (or (car bounds) (point))
2151 (or (cdr bounds) (point))
2152 (current-buffer))
2153 (overlay-put o 'category nil))))))
2154
2155 ;;; eldoc
2156
2157 (defvar-local ggtags-eldoc-cache nil)
2158
2159 (declare-function eldoc-message "eldoc")
2160 (defun ggtags-eldoc-function ()
2161 "A function suitable for `eldoc-documentation-function' (which see)."
2162 (pcase (ggtags-tag-at-point)
2163 (`nil nil)
2164 (tag (if (equal tag (car ggtags-eldoc-cache))
2165 (cadr ggtags-eldoc-cache)
2166 (and ggtags-project-root (ggtags-find-project)
2167 (let* ((ggtags-print-definition-function
2168 (lambda (s)
2169 (setq ggtags-eldoc-cache (list tag s))
2170 (eldoc-message s))))
2171 ;; Prevent multiple runs of ggtags-show-definition
2172 ;; for the same tag.
2173 (setq ggtags-eldoc-cache (list tag))
2174 (ggtags-show-definition tag)
2175 nil))))))
2176
2177 ;;; imenu
2178
2179 (defun ggtags-goto-imenu-index (name line &rest _args)
2180 (ggtags-forward-to-line line)
2181 (ggtags-move-to-tag name))
2182
2183 ;;;###autoload
2184 (defun ggtags-build-imenu-index ()
2185 "A function suitable for `imenu-create-index-function'."
2186 (let ((file (and buffer-file-name (file-relative-name buffer-file-name))))
2187 (and file (with-temp-buffer
2188 (when (with-demoted-errors "ggtags-build-imenu-index: %S"
2189 (zerop (ggtags-with-current-project
2190 (process-file (ggtags-program-path "global")
2191 nil t nil "-x" "-f" file))))
2192 (goto-char (point-min))
2193 (cl-loop while (re-search-forward
2194 "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)" nil t)
2195 collect (list (match-string 1)
2196 (string-to-number (match-string 2))
2197 'ggtags-goto-imenu-index)))))))
2198
2199 ;;; hippie-expand
2200
2201 ;;;###autoload
2202 (defun ggtags-try-complete-tag (old)
2203 "A function suitable for `hippie-expand-try-functions-list'."
2204 (eval-and-compile (require 'hippie-exp))
2205 (unless old
2206 (he-init-string (or (car (funcall ggtags-bounds-of-tag-function)) (point))
2207 (point))
2208 (setq he-expand-list
2209 (and (not (equal he-search-string ""))
2210 (ggtags-find-project)
2211 (sort (all-completions he-search-string
2212 ggtags-completion-table)
2213 #'string-lessp))))
2214 (if (null he-expand-list)
2215 (progn
2216 (if old (he-reset-string))
2217 nil)
2218 (he-substitute-string (car he-expand-list))
2219 (setq he-expand-list (cdr he-expand-list))
2220 t))
2221
2222 (defun ggtags-reload (&optional force)
2223 (interactive "P")
2224 (unload-feature 'ggtags force)
2225 (require 'ggtags))
2226
2227 (provide 'ggtags)
2228 ;;; ggtags.el ends here