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