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