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