]> code.delx.au - gnu-emacs-elpa/blob - ggtags.el
Fix false positive error regexp matches
[gnu-emacs-elpa] / ggtags.el
1 ;;; ggtags.el --- GNU Global source code tagging system -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2013 Free Software Foundation, Inc.
4
5 ;; Author: Leo Liu <sdl.web@gmail.com>
6 ;; Version: 0.6.4
7 ;; Keywords: tools, convenience
8 ;; Created: 2013-01-29
9 ;; URL: https://github.com/leoliu/ggtags
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; A package to integrate GNU Global source code tagging system
27 ;; (http://www.gnu.org/software/global) with Emacs.
28 ;;
29 ;; Usage:
30 ;;
31 ;; Type `M-x ggtags-mode' to enable the minor mode, or as usual enable
32 ;; it in your desired major mode hooks. When the mode is on the symbol
33 ;; at point is underlined if it is a valid (definition) tag.
34 ;;
35 ;; `M-.' finds definition or references according to the context at
36 ;; point, i.e. if point is at a definition tag find references and
37 ;; vice versa. `C-u M-.' is verbose and will ask you the name - with
38 ;; completion - and the type of tag to search.
39 ;;
40 ;; If multiple matches are found, navigation mode is entered. In this
41 ;; mode, `M-n' and `M-p' moves to next and previous match, `M-}' and
42 ;; `M-{' to next and previous file respectively. `M-o' toggles between
43 ;; full and abbreviated displays of file names in the auxiliary popup
44 ;; window. When you locate the right match, press RET to finish which
45 ;; hides the auxiliary window and exits navigation mode. You can
46 ;; resume the search using `M-,'. To abort the search press `M-*'.
47 ;;
48 ;; Normally after a few searches a dozen buffers are created visiting
49 ;; files tracked by GNU Global. `C-c M-k' helps clean them up.
50
51 ;;; Code:
52
53 (eval-when-compile (require 'cl))
54 (require 'compile)
55
56 (if (not (fboundp 'comment-string-strip))
57 (autoload 'comment-string-strip "newcomment"))
58
59 (eval-when-compile
60 (unless (fboundp 'setq-local)
61 (defmacro setq-local (var val)
62 (list 'set (list 'make-local-variable (list 'quote var)) val)))
63
64 (unless (fboundp 'defvar-local)
65 (defmacro defvar-local (var val &optional docstring)
66 (declare (debug defvar) (doc-string 3))
67 (list 'progn (list 'defvar var val docstring)
68 (list 'make-variable-buffer-local (list 'quote var))))))
69
70 (defgroup ggtags nil
71 "GNU Global source code tagging system."
72 :group 'tools)
73
74 (defface ggtags-highlight '((t (:underline t)))
75 "Face used to highlight a valid tag at point.")
76
77 (defcustom ggtags-auto-jump-to-first-match t
78 "Non-nil to automatically jump to the first match."
79 :type 'boolean
80 :group 'ggtags)
81
82 (defcustom ggtags-global-window-height 8 ; ggtags-global-mode
83 "Number of lines for the 'global' popup window.
84 If nil, use Emacs default."
85 :type '(choice (const :tag "Default" nil) integer)
86 :group 'ggtags)
87
88 (defcustom ggtags-global-abbreviate-filename 35
89 "Non-nil to display file names abbreviated such as '/u/b/env'."
90 :type '(choice (const :tag "No" nil)
91 (const :tag "Always" t)
92 integer)
93 :group 'ggtags)
94
95 (defcustom ggtags-split-window-function split-window-preferred-function
96 "A function to control how ggtags pops up the auxiliary window."
97 :type 'function
98 :group 'ggtags)
99
100 (defcustom ggtags-global-output-format 'grep
101 "The output format for the 'global' command."
102 :type '(choice (const path)
103 (const ctags)
104 (const ctags-x)
105 (const grep)
106 (const cscope))
107 :group 'ggtags)
108
109 (defvar ggtags-cache nil) ; (ROOT TABLE DIRTY TIMESTAMP)
110
111 (defvar ggtags-current-tag-name nil)
112
113 ;; Used by ggtags-global-mode
114 (defvar ggtags-global-error "match"
115 "Stem of message to print when no matches are found.")
116
117 ;; http://thread.gmane.org/gmane.comp.gnu.global.bugs/1518
118 (defvar ggtags-global-has-path-style ; introduced in global 6.2.8
119 (with-demoted-errors ; in case `global' not found
120 (and (string-match-p "^--path-style "
121 (shell-command-to-string "global --help"))
122 t))
123 "Non-nil if `global' supports --path-style switch.")
124
125 (defmacro ggtags-ensure-global-buffer (&rest body)
126 (declare (indent 0))
127 `(progn
128 (or (and (buffer-live-p compilation-last-buffer)
129 (with-current-buffer compilation-last-buffer
130 (derived-mode-p 'ggtags-global-mode)))
131 (error "No global buffer found"))
132 (with-current-buffer compilation-last-buffer ,@body)))
133
134 (defun ggtags-get-timestamp (root)
135 "Get the timestamp (float) of file GTAGS in ROOT directory.
136 Return -1 if it does not exist."
137 (let ((file (expand-file-name "GTAGS" root)))
138 (if (file-exists-p file)
139 (float-time (nth 5 (file-attributes file)))
140 -1)))
141
142 (defun ggtags-get-libpath ()
143 (split-string (or (getenv "GTAGSLIBPATH") "")
144 (regexp-quote path-separator) t))
145
146 (defun ggtags-cache-get (key)
147 (assoc key ggtags-cache))
148
149 (defun ggtags-cache-set (key val &optional dirty)
150 (let ((c (ggtags-cache-get key)))
151 (if c
152 (setcdr c (list val dirty (float-time)))
153 (push (list key val dirty (float-time)) ggtags-cache))))
154
155 (defun ggtags-cache-mark-dirty (key flag)
156 "Return non-nil if operation is successful."
157 (let ((cache (ggtags-cache-get key)))
158 (when cache
159 (setcar (cddr cache) flag))))
160
161 (defun ggtags-cache-dirty-p (key)
162 "Value is non-nil if 'global -u' is needed."
163 (third (ggtags-cache-get key)))
164
165 (defun ggtags-cache-stale-p (key)
166 "Value is non-nil if tags in cache needs to be rebuilt."
167 (> (ggtags-get-timestamp key)
168 (or (fourth (ggtags-cache-get key)) 0)))
169
170 (defvar-local ggtags-root-directory 'unset
171 "Internal; use function `ggtags-root-directory' instead.")
172
173 ;;;###autoload
174 (defun ggtags-root-directory ()
175 (if (string-or-null-p ggtags-root-directory)
176 ggtags-root-directory
177 (setq ggtags-root-directory
178 (with-temp-buffer
179 (when (zerop (call-process "global" nil (list t nil) nil "-pr"))
180 (file-name-as-directory
181 (comment-string-strip (buffer-string) t t)))))))
182
183 (defun ggtags-check-root-directory ()
184 (or (ggtags-root-directory) (error "File GTAGS not found")))
185
186 (defun ggtags-ensure-root-directory ()
187 (or (ggtags-root-directory)
188 (if (yes-or-no-p "File GTAGS not found; run gtags? ")
189 (let ((root (read-directory-name "Directory: " nil nil t)))
190 (and (= (length root) 0) (error "No directory chosen"))
191 (with-temp-buffer
192 (if (zerop (let ((default-directory
193 (file-name-as-directory root)))
194 (call-process "gtags" nil t)))
195 (message "File GTAGS generated in `%s'"
196 (ggtags-root-directory))
197 (error "%s" (comment-string-strip (buffer-string) t t)))))
198 (error "Aborted"))))
199
200 (defun ggtags-tag-names-1 (root &optional prefix)
201 (when root
202 (if (ggtags-cache-stale-p root)
203 (let* ((default-directory (file-name-as-directory root))
204 (tags (with-demoted-errors
205 (split-string
206 (with-output-to-string
207 (call-process "global" nil (list standard-output nil)
208 nil "-c" (or prefix "")))))))
209 (and tags (ggtags-cache-set root tags))
210 tags)
211 (cadr (ggtags-cache-get root)))))
212
213 ;;;###autoload
214 (defun ggtags-tag-names (&optional prefix)
215 "Get a list of tag names starting with PREFIX."
216 (let ((root (ggtags-root-directory)))
217 (when (and root (ggtags-cache-dirty-p root))
218 (if (zerop (call-process "global" nil nil nil "-u"))
219 (ggtags-cache-mark-dirty root nil)
220 (message "ggtags: error running 'global -u'")))
221 (apply 'append (mapcar (lambda (r)
222 (ggtags-tag-names-1 r prefix))
223 (cons root (ggtags-get-libpath))))))
224
225 (defun ggtags-read-tag (quick)
226 (ggtags-ensure-root-directory)
227 (let* ((tags (ggtags-tag-names))
228 (sym (thing-at-point 'symbol))
229 (default (and (member sym tags) sym)))
230 (setq ggtags-current-tag-name
231 (if quick (or default (error "No valid tag at point"))
232 (completing-read
233 (format (if default "Tag (default %s): " "Tag: ") default)
234 tags nil t nil nil default)))))
235
236 (defun ggtags-global-options ()
237 (concat "-v --result="
238 (symbol-name ggtags-global-output-format)
239 (and ggtags-global-has-path-style " --path-style=shorter")))
240
241 ;;;###autoload
242 (defun ggtags-find-tag (name &optional verbose)
243 "Find definitions or references to tag NAME by context.
244 If point is at a definition tag, find references, and vice versa.
245 When called with prefix, ask the name and kind of tag."
246 (interactive (list (ggtags-read-tag (not current-prefix-arg))
247 current-prefix-arg))
248 (eval-and-compile (require 'etags))
249 (ggtags-check-root-directory)
250 (ggtags-navigation-mode +1)
251 (ring-insert find-tag-marker-ring (point-marker))
252 (let ((split-window-preferred-function ggtags-split-window-function)
253 (default-directory (ggtags-root-directory)))
254 (compilation-start
255 (if (or verbose (not buffer-file-name))
256 (format "global %s %s \"%s\""
257 (ggtags-global-options)
258 (if (y-or-n-p "Find definition (n for reference)? ")
259 "" "-r")
260 name)
261 (format "global %s --from-here=%d:%s \"%s\""
262 (ggtags-global-options)
263 (line-number-at-pos)
264 (expand-file-name (file-truename buffer-file-name))
265 name))
266 'ggtags-global-mode)))
267
268 (defun ggtags-find-tag-resume ()
269 (interactive)
270 (ggtags-ensure-global-buffer
271 (ggtags-navigation-mode +1)
272 (let ((split-window-preferred-function ggtags-split-window-function))
273 (compile-goto-error))))
274
275 (defun ggtags-global-exit-message-function (_process-status exit-status msg)
276 (let ((count (save-excursion
277 (goto-char (point-max))
278 (if (re-search-backward "^\\([0-9]+\\) \\w+ located" nil t)
279 (string-to-number (match-string 1))
280 0))))
281 (cons (if (> exit-status 0)
282 msg
283 (format "found %d %s" count (if (= count 1) "match" "matches")))
284 exit-status)))
285
286 ;;; NOTE: Must not match the 'Global started at Mon Jun 3 10:24:13'
287 ;;; line or `compilation-auto-jump' will jump there and fail. See
288 ;;; comments before the 'gnu' entry in
289 ;;; `compilation-error-regexp-alist-alist'.
290 (defvar ggtags-global-error-regexp-alist-alist
291 (append
292 '((path "^\\(?:[^/\n]*/\\)?[^ )\t\n]+$" 0)
293 ;; ACTIVE_ESCAPE src/dialog.cc 172
294 (ctags "^\\([^ \t\n]+\\)[ \t]+\\(.*?\\)[ \t]+\\([0-9]+\\)$"
295 2 3 nil nil 2 (1 font-lock-function-name-face))
296 ;; ACTIVE_ESCAPE 172 src/dialog.cc #undef ACTIVE_ESCAPE
297 (ctags-x "^\\([^ \t\n]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\(\\(?:[^/\n]*/\\)?[^ \t\n]+\\)"
298 3 2 nil nil 3 (1 font-lock-function-name-face))
299 ;; src/dialog.cc:172:#undef ACTIVE_ESCAPE
300 (grep "^\\(.+?\\):\\([0-9]+\\):\\(?:[^0-9\n]\\|[0-9][^0-9\n]\\|[0-9][0-9].\\)"
301 1 2 nil nil 1)
302 ;; src/dialog.cc ACTIVE_ESCAPE 172 #undef ACTIVE_ESCAPE
303 (cscope "^\\(.+?\\)[ \t]+\\([^ \t\n]+\\)[ \t]+\\([0-9]+\\).*\\(?:[^0-9\n]\\|[^0-9\n][0-9]\\|[^:\n][0-9][0-9]\\)$"
304 1 3 nil nil 1 (2 font-lock-function-name-face)))
305 compilation-error-regexp-alist-alist))
306
307 (defun ggtags-abbreviate-file (start end)
308 (let ((inhibit-read-only t)
309 (amount (if (numberp ggtags-global-abbreviate-filename)
310 (- (- end start) ggtags-global-abbreviate-filename)
311 999))
312 (advance-word (lambda ()
313 "Return the length of the text made invisible."
314 (let ((wend (min end (progn (forward-word 1) (point))))
315 (wbeg (max start (progn (backward-word 1) (point)))))
316 (goto-char wend)
317 (if (<= (- wend wbeg) 1)
318 0
319 (put-text-property (1+ wbeg) wend 'invisible t)
320 (1- (- wend wbeg)))))))
321 (goto-char start)
322 (while (and (> amount 0) (> end (point)))
323 (decf amount (funcall advance-word)))))
324
325 (defun ggtags-abbreviate-files (start end)
326 (goto-char start)
327 (let* ((error-re (cdr (assq ggtags-global-output-format
328 ggtags-global-error-regexp-alist-alist)))
329 (sub (cadr error-re)))
330 (when (and ggtags-global-abbreviate-filename error-re)
331 (while (re-search-forward (car error-re) end t)
332 (when (and (or (not (numberp ggtags-global-abbreviate-filename))
333 (> (length (match-string sub))
334 ggtags-global-abbreviate-filename))
335 ;; Ignore bogus file lines such as:
336 ;; Global found 2 matches at Thu Jan 31 13:45:19
337 (get-text-property (match-beginning sub) 'compilation-message))
338 (ggtags-abbreviate-file (match-beginning sub) (match-end sub)))))))
339
340 (defun ggtags-handle-single-match (buf _how)
341 (unless (or (not ggtags-auto-jump-to-first-match)
342 (save-excursion
343 (goto-char (point-min))
344 (ignore-errors
345 (goto-char (compilation-next-single-property-change
346 (point) 'compilation-message))
347 (end-of-line)
348 (compilation-next-single-property-change
349 (point) 'compilation-message))))
350 (ggtags-navigation-mode -1)
351 ;; 0.5s delay for `ggtags-auto-jump-to-first-match'
352 (sit-for 0) ; See: http://debbugs.gnu.org/13829
353 (ggtags-navigation-mode-cleanup buf 0.5)))
354
355 (defvar ggtags-global-mode-font-lock-keywords
356 '(("^-[*]-.*-[*]-$"
357 (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t))
358 ("^\\w+ not found.*\\|^[0-9]+ \\w+ located.*"
359 (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t))
360 ("^Global \\(exited abnormally\\|interrupt\\|killed\\|terminated\\)\\(?:.*with code \\([0-9]+\\)\\)?.*"
361 (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t)
362 (1 compilation-error-face)
363 (2 compilation-error-face nil t))))
364
365 (define-compilation-mode ggtags-global-mode "Global"
366 "A mode for showing outputs from gnu global."
367 (setq-local compilation-error-regexp-alist
368 (list ggtags-global-output-format))
369 (setq-local compilation-auto-jump-to-first-error
370 ggtags-auto-jump-to-first-match)
371 (setq-local compilation-scroll-output 'first-error)
372 (setq-local compilation-disable-input t)
373 (setq-local compilation-always-kill t)
374 (setq-local compilation-error-face 'compilation-info)
375 (setq-local compilation-exit-message-function
376 'ggtags-global-exit-message-function)
377 (setq-local truncate-lines t)
378 (jit-lock-register #'ggtags-abbreviate-files)
379 (add-hook 'compilation-finish-functions 'ggtags-handle-single-match nil t)
380 (define-key ggtags-global-mode-map "o" 'visible-mode))
381
382 (defvar ggtags-navigation-mode-map
383 (let ((map (make-sparse-keymap)))
384 (define-key map "\M-n" 'next-error)
385 (define-key map "\M-p" 'previous-error)
386 (define-key map "\M-}" 'ggtags-navigation-next-file)
387 (define-key map "\M-{" 'ggtags-navigation-previous-file)
388 (define-key map "\M-o" 'ggtags-navigation-visible-mode)
389 (define-key map "\r" 'ggtags-navigation-mode-done)
390 ;; Intercept M-. and M-* keys
391 (define-key map [remap pop-tag-mark] 'ggtags-navigation-mode-abort)
392 (define-key map [remap ggtags-find-tag] 'undefined)
393 map))
394
395 (defun ggtags-move-to-tag (&optional name)
396 "Move to NAME tag in current line."
397 (let ((orig (point))
398 (tag (or name ggtags-current-tag-name)))
399 (beginning-of-line)
400 (if (and tag (re-search-forward
401 (concat "\\_<" (regexp-quote tag) "\\_>")
402 (line-end-position)
403 t))
404 (goto-char (match-beginning 0))
405 (goto-char orig))))
406
407 (defun ggtags-navigation-mode-cleanup (&optional buf time)
408 (let ((buf (or buf compilation-last-buffer)))
409 (and (buffer-live-p buf)
410 (with-current-buffer buf
411 (when (get-buffer-process (current-buffer))
412 (kill-compilation))
413 (when (and (derived-mode-p 'ggtags-global-mode)
414 (get-buffer-window))
415 (quit-window nil (get-buffer-window)))
416 (and time (run-with-idle-timer time nil 'kill-buffer buf))))))
417
418 (defun ggtags-navigation-mode-done ()
419 (interactive)
420 (ggtags-navigation-mode -1)
421 (ggtags-navigation-mode-cleanup))
422
423 (defun ggtags-navigation-mode-abort ()
424 (interactive)
425 (pop-tag-mark)
426 (ggtags-navigation-mode -1)
427 (ggtags-navigation-mode-cleanup nil 0))
428
429 (defun ggtags-navigation-next-file (n)
430 (interactive "p")
431 (ggtags-ensure-global-buffer
432 (compilation-next-file n)
433 (compile-goto-error)))
434
435 (defun ggtags-navigation-previous-file (n)
436 (interactive "p")
437 (ggtags-navigation-next-file (- n)))
438
439 (defun ggtags-navigation-visible-mode (&optional arg)
440 (interactive (list (or current-prefix-arg 'toggle)))
441 (ggtags-ensure-global-buffer
442 (visible-mode arg)))
443
444 (define-minor-mode ggtags-navigation-mode nil
445 :lighter (" GG[" (:propertize "n" face error) "]")
446 :global t
447 (if ggtags-navigation-mode
448 (progn
449 (add-hook 'next-error-hook 'ggtags-move-to-tag)
450 (add-hook 'minibuffer-setup-hook 'ggtags-minibuffer-setup-function))
451 (remove-hook 'next-error-hook 'ggtags-move-to-tag)
452 (remove-hook 'minibuffer-setup-hook 'ggtags-minibuffer-setup-function)))
453
454 (defun ggtags-minibuffer-setup-function ()
455 ;; Disable ggtags-navigation-mode in minibuffer.
456 (setq-local ggtags-navigation-mode nil))
457
458 (defun ggtags-kill-file-buffers (&optional interactive)
459 "Kill all buffers visiting files in the root directory."
460 (interactive "p")
461 (ggtags-check-root-directory)
462 (let ((root (ggtags-root-directory))
463 (count 0)
464 (some (lambda (pred list)
465 (loop for x in list when (funcall pred x) return it))))
466 (dolist (buf (buffer-list))
467 (let ((file (and (buffer-live-p buf)
468 (not (eq buf (current-buffer)))
469 (buffer-file-name buf))))
470 (when (and file (funcall some (apply-partially #'file-in-directory-p
471 (file-truename file))
472 (cons root (ggtags-get-libpath))))
473 (and (kill-buffer buf)
474 (incf count)))))
475 (and interactive
476 (message "%d %s killed" count (if (= count 1) "buffer" "buffers")))))
477
478 (defun ggtags-after-save-function ()
479 (let ((root (with-demoted-errors (ggtags-root-directory))))
480 (and root (ggtags-cache-mark-dirty root t))))
481
482 (defvar ggtags-tag-overlay nil)
483 (defvar ggtags-highlight-tag-timer nil)
484
485 (defvar ggtags-mode-map
486 (let ((map (make-sparse-keymap)))
487 (define-key map "\M-." 'ggtags-find-tag)
488 (define-key map "\M-," 'ggtags-find-tag-resume)
489 (define-key map "\C-c\M-k" 'ggtags-kill-file-buffers)
490 map))
491
492 ;;;###autoload
493 (define-minor-mode ggtags-mode nil
494 :lighter (:eval (if ggtags-navigation-mode "" " GG"))
495 (if ggtags-mode
496 (progn
497 (add-hook 'after-save-hook 'ggtags-after-save-function nil t)
498 (or (executable-find "global")
499 (message "Failed to find GNU Global")))
500 (remove-hook 'after-save-hook 'ggtags-after-save-function t)
501 (and (overlayp ggtags-tag-overlay)
502 (delete-overlay ggtags-tag-overlay))
503 (setq ggtags-tag-overlay nil)))
504
505 (defun ggtags-highlight-tag-at-point ()
506 (when ggtags-mode
507 (unless (overlayp ggtags-tag-overlay)
508 (setq ggtags-tag-overlay (make-overlay (point) (point)))
509 (overlay-put ggtags-tag-overlay 'ggtags t))
510 (let* ((bounds (bounds-of-thing-at-point 'symbol))
511 (valid-tag (when bounds
512 (member (buffer-substring (car bounds) (cdr bounds))
513 (ggtags-tag-names))))
514 (o ggtags-tag-overlay)
515 (done-p (lambda ()
516 (and (memq o (overlays-at (car bounds)))
517 (= (overlay-start o) (car bounds))
518 (= (overlay-end o) (cdr bounds))
519 (or (and valid-tag (overlay-get o 'face))
520 (and (not valid-tag) (not (overlay-get o 'face))))))))
521 (cond
522 ((not bounds)
523 (overlay-put ggtags-tag-overlay 'face nil)
524 (move-overlay ggtags-tag-overlay (point) (point) (current-buffer)))
525 ((not (funcall done-p))
526 (move-overlay o (car bounds) (cdr bounds) (current-buffer))
527 (overlay-put o 'face (and valid-tag 'ggtags-highlight)))))))
528
529 ;;; imenu
530
531 (defun ggtags-goto-imenu-index (name line &rest _args)
532 (save-restriction
533 (widen)
534 (goto-char (point-min))
535 (forward-line (1- line))
536 (ggtags-move-to-tag name)))
537
538 ;;;###autoload
539 (defun ggtags-build-imenu-index ()
540 "A function suitable for `imenu-create-index-function'."
541 (when buffer-file-name
542 (let ((file (file-truename buffer-file-name)))
543 (with-temp-buffer
544 (when (with-demoted-errors
545 (zerop (call-process "global" nil t nil "-f" file)))
546 (goto-char (point-min))
547 (loop while (re-search-forward
548 "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)" nil t)
549 collect (list (match-string 1)
550 (string-to-number (match-string 2))
551 'ggtags-goto-imenu-index)))))))
552
553 ;;; hippie-expand
554
555 ;;;###autoload
556 (defun try-complete-ggtags-tag (old)
557 "A function suitable for `hippie-expand-try-functions-list'."
558 (with-no-warnings ; to avoid loading hippie-exp
559 (unless old
560 (he-init-string (if (looking-back "\\_<.*" (line-beginning-position))
561 (match-beginning 0)
562 (point))
563 (point))
564 (setq he-expand-list
565 (and (not (equal he-search-string ""))
566 (with-demoted-errors (ggtags-root-directory))
567 (sort (all-completions he-search-string
568 (ggtags-tag-names))
569 'string-lessp))))
570 (if (null he-expand-list)
571 (progn
572 (if old (he-reset-string))
573 nil)
574 (he-substitute-string (car he-expand-list))
575 (setq he-expand-list (cdr he-expand-list))
576 t)))
577
578 ;;; Finish up
579
580 (when ggtags-highlight-tag-timer
581 (cancel-timer ggtags-highlight-tag-timer))
582
583 (setq ggtags-highlight-tag-timer
584 (run-with-idle-timer 0.2 t 'ggtags-highlight-tag-at-point))
585
586 ;; Higher priority for `ggtags-navigation-mode' to avoid being
587 ;; hijacked by modes such as `view-mode'.
588 (defvar ggtags-mode-map-alist
589 `((ggtags-navigation-mode . ,ggtags-navigation-mode-map)))
590
591 (add-to-list 'emulation-mode-map-alists 'ggtags-mode-map-alist)
592
593 (provide 'ggtags)
594 ;;; ggtags.el ends here