-
Notifications
You must be signed in to change notification settings - Fork 27
/
citre-tags.el
646 lines (546 loc) · 25.9 KB
/
citre-tags.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
;;; citre-tags.el --- Tags file backend -*- lexical-binding: t -*-
;; Copyright (C) 2022 Hao Wang
;; Author: Hao Wang <[email protected]>
;; Maintainer: Hao Wang <[email protected]>
;; Created: 25 May 2022
;; Keywords: convenience, tools
;; Homepage: https://github.com/universal-ctags/citre
;; Version: 0.4.1
;; Package-Requires: ((emacs "26.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3
;; of the License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Tags file backend.
;;; Code:
;; To see the outline of this file, run M-x outline-minor-mode and
;; then press C-c @ C-t. To also show the top-level functions and
;; variable declarations in each section, run M-x occur with the
;; following query: ^;;;;* \|^(
;;;; Libraries
(require 'citre-backend-interface)
(require 'citre-ctags)
(require 'citre-readtags)
(require 'cl-lib)
(require 'rx)
(require 'subr-x)
;;;; User Options
;;;;; Auto-completion
(defcustom citre-tags-substr-completion nil
"Whether do substring completion for the tags backend.
Non-nil means to match tags *containing* the symbol to be
completed, Otherwise match tags *start with* the symbol to be
completed.
Notice that when listing the candidates, Emacs itself will
further filter the completions we supply, and its behavior is
controlled by `completion-styles'. If you want substring
completion, you need to set `citre-tags-substr-completion' to
non-nil, *and* add `substring' to `completion-styles' (for Emacs
27, there is also a `flex' style that will work)."
:type 'boolean
:group 'citre)
(make-obsolete 'citre-capf-substr-completion
'citre-tags-substr-completion
"0.3")
(defcustom citre-tags-completion-case-sensitive t
"Case sensitivity of auto-completion using tags backend.
Note for developers: Actually this doesn't affect auto-completion
directly. This option controls the behavior of
`citre-tags-get-tags' when its argument MATCH is not nil or
`exact', and when this is the case, it's likely that the user is
getting tags for auto-completion."
:type 'boolean
:group 'citre)
(make-obsolete 'citre-capf-completion-case-sensitive
'citre-tags-completion-case-sensitive
"0.3")
;;;;; Imenu
(defcustom citre-tags-imenu-create-tags-file-threshold (* 50 1024 1024)
"The threshold (in bytes) to create a tags file for imenu.
When the tags file used is bigger than this threshold, searching
for tags in current file in it could be slow. So, Citre let
Ctags scan the current file, and create a temporary tags file (in
variable `temporary-file-directory'), which is faster.
When the tags file in use contains a recipe, the command line in
it is used, just the dir/files to scan are substituted by the
current file. If not, a command for Universal Ctags is used.
When this is nil, always use the existing tags file and never
create one for imenu. When this is 0, always create a new tags
file for imenu."
:type '(choice (integer :tag "Threshold in bytes")
(const :tag "Never create a tagsfile"))
:group 'citre)
(make-obsolete 'citre-imenu-create-tags-file-threshold
'citre-tags-imenu-create-tags-file-threshold
"0.3")
;;;; APIs
;;;;; Readtags API wrapper
(cl-defun citre-tags-get-tags
(&optional tagsfile name match
&key filter sorter
require optional exclude parse-all-fields)
"Get tags in tags file TAGSFILE that match NAME.
This is like `citre-readtags-get-tags', except that:
- TAGSFILE could be nil, and it will be find automatically.
- When MATCH is nil or `exact', CASE-FOLD is always nil,
otherwise it's decided by `citre-tags-completion-case-sensitive'.
TAGSFILE is the absolute path of the tags file. For FILTER,
SORTER, REQUIRE, OPTIONAL, EXCLUDE, and PARSE-ALL-FIELDS, see
`citre-readtags-get-tags'.
Each element in the returned value is a list containing the tag
and some of its fields, which can be utilized by
`citre-get-tag-field'."
(when (citre-executable-find (or citre-readtags-program "readtags") t)
(when-let* ((tagsfile (or tagsfile (citre-tags-file-path))))
(citre-readtags-get-tags tagsfile name match
(unless (or (null match) (eq match 'exact))
(not citre-tags-completion-case-sensitive))
:filter filter :sorter sorter
:require require :optional optional
:exclude exclude
:parse-all-fields parse-all-fields))))
;;;;; Common filter/sorter snippets
(defun citre-tags-filter-extra-tags (extras)
"Filter that matches extra tags in list EXTRAS."
(citre-readtags-filter 'extras extras 'csv-contain))
(defvar citre-tags-filter-file-tags
`(or
,(citre-readtags-filter 'extras '("inputFile") 'csv-contain)
,(citre-readtags-filter-kind "file"))
"Filter that matches file tags.")
(defun citre-tags-filter-local-symbol-in-other-file (file tagsfile)
"Filter that matches tags with \"file\" scope, but not in FILE.
TAGSFILE is the absolute path of the tags file to use this filter
on."
`(and (not ,(citre-readtags-filter-input file tagsfile))
(or ,(citre-readtags-filter-field-exist 'file)
,(citre-readtags-filter 'extras "fileScope" 'csv-contain))))
(defvar citre-tags-sorter-arg-size-order
'(expr (if (and $line $end &line &end)
(<> (- &end &line) (- $end $line))
0))
"For tags with `line' and `end' field, sort them by size.
The \"size\" is the difference between its `end' and `line'
field. A \"smaller\" definition may be a prototype or forward
declaration, while the \"bigger\" one is the actual definition.
This can be used as an arg for `citre-readtags-sorter'.")
(defvar citre-tags-sorter-arg-put-references-below
`(filter ,(citre-readtags-filter 'extras "reference" 'csv-contain) -)
"Put reference tags below others.
This can be used as an arg for `citre-readtags-sorter'.")
(defun citre-tags-sorter-arg-put-kinds-above (kinds)
"Put tags with kind field in list KINDS above others.
This can be used as an arg for `citre-readtags-sorter'."
(let ((filters (mapcar (lambda (k) (citre-readtags-filter-kind k))
kinds)))
(if (eq (length filters) 1)
(setq filters `(filter ,(car filters) +))
(setq filters `(filter (or ,@filters) +)))))
;;;;; Language support framework
;;;;;; The lookup table
(defvar citre-tags-language-support-alist nil
"The lookup table for language-specific support of tags backend.
A key of it is the language's major mode (a symbol).
A value of it is a plist. Its props and values are:
- `:get-symbol': The function to get the symbol at point.
It's a function with no arguments. The returned value is a
string of the symbol name. To support auto-completion, Citre
requires a `citre-bounds' property, which is a cons pair of the
beginning/end positions of the symbol.
You can use other properties to record the information you need
for filtering/sorting the tags, see the props below. Citre
automatically attach 2 more props to the returned value:
`citre-file-path' for the full path of current file (when in a
file buffer), and `citre-tags-file' for the canonical path of
tags file, so filters/sorters can make use of them.
If you don't specify this prop, `citre-tags-get-symbol-default' is
used as fallback. You can also use it internally, and add more
properties you need.
When there's an active region, it's recommended to get the text
in it as a symbol, so when your function doesn't work well for
the user, they can manually specify which part to get.
`citre-get-marked-symbol' implements this, and is also used by
`citre-tags-get-symbol-default'.
- `:completion-filter': The filter for auto-completion.
It can be a filter expression, a symbol whose value is a filter
expression, or a function that takes the string returned by the
`:get-symbol' function, and returns the filter expression. The
fallback is `citre-tags-completion-default-filter'.
- `:completion-sorter': The sorter for auto-completion.
It can be a sorter expression, a symbol whose value is a sorter
expression, or a function that takes the string returned by the
`:get-symbol' function, and returns the sorter expression.The
fallback is `citre-tags-completion-default-sorter'.
- `:definition-filter' and `:definition-sorter': The same as
`:completion-filter' and `:completion-sorter', but used for
finding definitions. Their fallback values are
`citre-tags-definition-default-filter' and
`citre-tags-definition-default-sorter'.
The filter/sorter functions should be pure, i.e., should only use
information provided by the symbol, and not fetch information
from the environment.")
(defun citre-tags--get-value-in-language-alist (prop &optional symbol)
"A helper for lookup PROP in `citre-tags-language-support-alist'.
Returns the value in it for the language in current buffer, and
PROP.
If SYMBOL is non-nil, and the value we get is a function, call
the function on SYMBOL and return its value."
(when-let* ((value (plist-get (alist-get major-mode
citre-tags-language-support-alist)
prop)))
(cond
((and (symbolp value) (boundp value))
(symbol-value value))
((and symbol (functionp value))
(funcall value symbol))
(t value))))
;;;;;; APIs
(defun citre-tags-get-marked-symbol ()
"Get the text in activate region as a symbol."
(when (use-region-p)
(let ((bounds (cons (region-beginning) (region-end))))
(citre-put-property
(buffer-substring-no-properties (car bounds) (cdr bounds))
'bounds bounds))))
(defun citre-tags-get-symbol-at-point ()
"Get the symbol at point."
(when-let* ((bounds (bounds-of-thing-at-point 'symbol)))
(citre-put-property
(buffer-substring-no-properties (car bounds) (cdr bounds))
'bounds bounds)))
(defun citre-tags-get-symbol-default ()
"Get the symbol at point.
If there's an active region, the text in it is returned as a
symbol. Otherwise, the symbol at point is returned. If both
fails, nil is returned.
The returned symbol is a string with a `citre-bounds' property,
recording the beginning/end positions of the symbol."
(or (citre-tags-get-marked-symbol)
(citre-tags-get-symbol-at-point)))
(defun citre-tags-get-symbol (&optional tagsfile)
"Get the symbol at point.
Set `citre-tags-language-support-alist' to control the behavior
of this function for different languages. `citre-file-path' and
`citre-tags-file' properties are attached to the symbol string so
filters/sorters can make use of them.
When TAGSFILE is non-nil, write it (rather than the tags file
associated with current buffer) to the `citre-tags-file' property
in the returned string. This is needed when getting
definitions/completions of the returned symbol from a specified
tags file."
(let ((sym (funcall (or (citre-tags--get-value-in-language-alist :get-symbol)
#'citre-tags-get-symbol-default))))
(citre-put-property sym 'file-path (buffer-file-name))
(citre-put-property sym 'tags-file (or tagsfile (citre-tags-file-path)))
sym))
(defun citre-tags-register-language-support (mode plist)
"Register language support for the tags backend.
MODE is a symbol of the major mode, PLIST is a plist described in
`citre-tags-language-support-alist'."
(setf (alist-get mode citre-tags-language-support-alist)
plist))
(defun citre-tags-symbol-at-point ()
"Get the symbol at point using tags backend.
This is for display purpose only and the returned string doesn't
contain some properties as returned by `citre-tags-get-symbol'."
(funcall (or (citre-tags--get-value-in-language-alist :get-symbol)
#'citre-tags-get-symbol-default)))
;;;;; Auto-completion related
(defun citre-tags-completion-default-filter (symbol)
"Default completion filter for SYMBOL."
(let ((tags-file (citre-get-property 'tags-file symbol))
(file-path (citre-get-property 'file-path symbol)))
`(not
(or
,(citre-tags-filter-extra-tags '("anonymous" "reference"))
,citre-tags-filter-file-tags
,(if file-path
(citre-tags-filter-local-symbol-in-other-file file-path tags-file)
'false)))))
(defvar citre-tags-completion-default-sorter
(citre-readtags-sorter
'(length name +) 'name)
"The default sorter expression for auto-completion.
This sorts the candidates by their length, then the alphabetical
order of their name.")
(defun citre-tags-get-completions (&optional symbol tagsfile substr-completion)
"Get completions from TAGSFILE of symbol at point.
TAGSFILE is the absolute path of the tags file. If SYMBOL is
non-nil, use that symbol instead. If TAGSFILE is not specified,
fint it automatically. If SUBSTR-COMPLETION is non-nil, get tags
that contains SYMBOL, or get tags that starts with SYMBOL. The
case sensitivity is controlled by
`citre-tags-completion-case-sensitive'.
The returned value is a list of tags. Nil is returned when the
completion can't be done."
(when-let* ((symbol (or symbol (citre-tags-get-symbol tagsfile)))
(tagsfile (or tagsfile (citre-tags-file-path)))
(match (if substr-completion 'substr 'prefix)))
(citre-tags-get-tags
tagsfile symbol match
:filter (or (citre-tags--get-value-in-language-alist
:completion-filter symbol)
(citre-tags-completion-default-filter symbol))
:sorter (or (citre-tags--get-value-in-language-alist
:completion-sorter symbol)
citre-tags-completion-default-sorter)
:require '(name)
:optional '(ext-kind-full signature pattern scope typeref))))
;;;;; Finding definitions
(defun citre-tags-definition-default-filter (symbol)
"Default definition filter for SYMBOL."
(let ((tags-file (citre-get-property 'tags-file symbol))
(file-path (citre-get-property 'file-path symbol)))
`(not
(or
;; Don't excluded "anonymous" here as such symbols can appear in typeref
;; or scope fields of other tags, which may be shown in an xref buffer,
;; so we should be able to find their definitions.
,(if file-path
(citre-tags-filter-local-symbol-in-other-file file-path tags-file)
'false)))))
(defvar citre-tags-definition-default-sorter
(citre-readtags-sorter
citre-tags-sorter-arg-put-references-below
'input '(length name +) 'name
citre-tags-sorter-arg-size-order)
"The default sorter expression for finding definitions.
This sorts the file name by their alphabetical order, then the
length and alphabetical order of the tag names.")
(defun citre-tags-get-definitions (&optional symbol tagsfile)
"Get definitions of symbol at point.
If SYMBOL is non-nil, use that symbol instead. Notice it should
be returned by `citre-tags-get-symbol'. If TAGSFILE is non-nil,
find definitions from that tags file, otherwise get the tagsfile
from SYMBOL.
The result is a list of tags. Nil is returned when no definition
is found."
(when-let* ((symbol (or symbol (citre-tags-get-symbol tagsfile)))
(tagsfile (or tagsfile (citre-get-property 'tags-file symbol))))
(citre-tags-get-tags
tagsfile symbol 'exact
:filter (or (citre-tags--get-value-in-language-alist
:definition-filter symbol)
(citre-tags-definition-default-filter symbol))
:sorter (or (citre-tags--get-value-in-language-alist
:definition-sorter symbol)
citre-tags-definition-default-sorter)
:require '(name ext-abspath pattern)
:optional '(ext-kind-full line typeref scope extras))))
;;;; Completion
(defvar citre-tags--completion-cache
'(:file nil :symbol nil :bounds nil :substr nil :cands nil)
"A plist for completion cache of the tags backend.
Its props are:
- `:file': The file where the completion happens.
- `:symbol': The symbol that's been completed.
- `:bounds': The bound positions of `:symbol'.
- `:substr': Whether substring completion is used. This is
needed since in the same position, user may use popup
completion that does prefix completion, and use their own
command that let binds `citre-tags-substr-completion' to t and
call `completion-at-point'.
- `:cands': The tags of completions.")
(defun citre-tags-get-completions-at-point ()
"Get completions of symbol at point.
The result is a list (BEG END TAGS), see
`citre-register-completion-backend'."
;; Just to make sure the tags file exists.
(when-let* ((tagsfile (citre-tags-file-path))
(symbol (citre-tags-get-symbol)))
(if citre-capf-optimize-for-popup
(let* ((cache citre-tags--completion-cache)
(file (buffer-file-name))
(bounds (citre-get-property 'bounds symbol)))
(if (and (equal (plist-get cache :file) file)
(string-prefix-p (plist-get cache :symbol) symbol)
;; We also need to make sure we are in the process of
;; completing the same whole symbol, since same symbol in
;; different positions can produce different results,
;; depending on the language support implementation.
(eq (car (plist-get cache :bounds)) (car bounds))
;; Just in case the user set `citre-tags-substr-completion'
;; to something can't compare by `eq', we use `null' to make
;; sure we are comparing t or nil.
(eq (null (plist-get cache :substr))
(null citre-tags-substr-completion)))
(list (car bounds) (cdr bounds) (plist-get cache :cands))
;; Make sure we get a non-nil collection first, then setup the
;; cache, since the calculation can be interrupted by user input,
;; and we get nil, which aren't the actual completions.
(when-let* ((cands (citre-tags-get-completions
symbol nil citre-tags-substr-completion)))
;; Prevent keyboard quit when building cache.
(let ((inhibit-quit t))
(plist-put cache :file file)
(plist-put cache :symbol (substring-no-properties symbol))
(plist-put cache :bounds bounds)
(plist-put cache :substr citre-tags-substr-completion)
(plist-put cache :cands cands))
(list (car bounds) (cdr bounds) cands))))
(let ((bounds (citre-get-property 'bounds symbol)))
(list (car bounds) (cdr bounds)
(citre-tags-get-completions
symbol nil citre-tags-substr-completion))))))
;;;; Find definition
(defun citre-tags-get-definitions-at-point ()
"Get definitions of symbol at point."
(when-let* ((tagsfile (citre-tags-file-path))
(symbol (citre-tags-get-symbol)))
(citre-tags-get-definitions symbol tagsfile)))
(defvar citre-tags--find-definition-for-id-filter
`(not ,(citre-readtags-filter 'extras "anonymous" 'csv-contain))
"Filter for finding definitions when the symbol is inputted by user.")
(defvar citre-tags--id-list-cache
'(:tags-file nil :time nil :tags nil)
"Plist for caching identifier list for tags backend.
Its props and vals are:
- `:tags-file': Canonical path of tags file.
- `:time': Last modified time of tags file.
- `:tags': The tags.")
(defun citre-tags-get-definitions-of-id (symbol)
"Get definition for SYMBOL without text property.
When xref prompts for user input for the symbol, we can't get
information from the environment of the symbol at point, so we
have to bypass the whole filter/sort mechanism of Citre and use
simple tag name matching. This function is for it."
(citre-tags-get-tags
nil symbol 'exact
:filter citre-tags--find-definition-for-id-filter
:sorter citre-tags-definition-default-sorter
:require '(name ext-abspath pattern)
:optional '(ext-kind-full line typeref scope extras)))
(defun citre-tags-get-identifiers ()
"Get a list of identifiers in current project."
(when-let* ((tagsfile (citre-tags-file-path))
(update-time (gethash 'time (citre-readtags-tags-file-info
tagsfile))))
(if (and (equal tagsfile
(plist-get citre-tags--id-list-cache
:tags-file))
(equal update-time
(plist-get citre-tags--id-list-cache
:time)))
(plist-get citre-tags--id-list-cache :tags)
(let ((tags
(cl-remove-duplicates
(mapcar
(lambda (tag) (citre-get-tag-field 'name tag))
(citre-tags-get-tags
;; We don't use STR here, but return all tag names,
;; since we need to work with completion styles that
;; may not do a prefix completion.
tagsfile nil nil
:filter citre-tags--find-definition-for-id-filter
:sorter (citre-readtags-sorter '(length name +) 'name)
:require '(name)))
:test #'equal)))
(plist-put citre-tags--id-list-cache
:tags-file tagsfile)
(plist-put citre-tags--id-list-cache
:time update-time)
(plist-put citre-tags--id-list-cache
:tags tags)
tags))))
;;;; Tags in buffer
(declare-function tramp-get-remote-tmpdir "tramp" (vec))
(declare-function tramp-dissect-file-name "tramp" (name &optional nodefault))
(defun citre-tags--imenu-temp-tags-file-path ()
"Return a temporary tags file path for imenu.
This also works on a remote machine."
;; Generate a name unique to this user & Emacs instance so we don't prevent
;; other users on the same machine to generate their own temporary tags file.
(let* ((file (format "citre-%s-%s.tags" (user-login-name) (emacs-pid)))
(dir (if (file-remote-p default-directory)
(tramp-get-remote-tmpdir
(tramp-dissect-file-name default-directory))
temporary-file-directory)))
(expand-file-name file dir)))
(defun citre-tags--imenu-ctags-command-cwd ()
"Return ctags command and its cwd for tags file for imenu."
(cons `(,(or citre-ctags-program "ctags") "-o"
,(citre-tags--imenu-temp-tags-file-path)
"--kinds-all=*" "--fields=*" "--extras=*"
,(file-local-name (buffer-file-name)))
(file-name-directory (buffer-file-name))))
(defun citre-tags--imenu-tags-from-tags-file ()
"Get tags for imenu from the tags file being used."
(when-let* ((tagsfile (citre-tags-file-path)))
(citre-tags-get-tags
tagsfile nil nil
:filter
`(and ,(citre-readtags-filter-input (buffer-file-name)
tagsfile)
(not (or ,(citre-readtags-filter
'extras
'("anonymous" "inputFile")
'csv-contain)
,(citre-readtags-filter-kind "file"))))
:sorter (citre-readtags-sorter 'line)
:require '(name pattern)
:optional '(ext-kind-full line typeref scope extras))))
(defun citre-tags--imenu-tags-from-temp-tags-file ()
"Get tags for imenu from a new temporary tags file.
If the ctags program is not found, this returns nil."
(when (citre-executable-find (or citre-ctags-program "ctags"))
(pcase-let* ((`(,cmd . ,cwd) (citre-tags--imenu-ctags-command-cwd))
(tags-file (citre-tags--imenu-temp-tags-file-path)))
(make-directory (file-name-directory tags-file) 'parents)
(let ((default-directory cwd))
(apply #'process-file (car cmd)
nil (get-buffer-create "*ctags*") nil
(cdr cmd)))
;; WORKAROUND: If we don't sit for a while, the readtags process will
;; freeze. TOOD: Fix this when uctags offers "edittags" command.
(sit-for 0.001)
(let ((tags (citre-tags-get-tags
tags-file nil nil
:filter
`(not (or ,(citre-readtags-filter
'extras
'("anonymous" "inputFile")
'csv-contain)
,(citre-readtags-filter-kind "file")))
:sorter (citre-readtags-sorter 'line)
:require '(name pattern)
:optional '(ext-kind-full line typeref scope extras))))
(delete-file tags-file)
tags))))
(defun citre-tags-get-tags-in-buffer ()
"Get tags in buffer."
(let* ((tagsfile (citre-tags-file-path)))
(if (or (null citre-tags-imenu-create-tags-file-threshold)
(and tagsfile
(< (file-attribute-size (file-attributes tagsfile))
citre-tags-imenu-create-tags-file-threshold)))
(citre-tags--imenu-tags-from-tags-file)
(citre-tags--imenu-tags-from-temp-tags-file))))
;;;; Backend definition
(defvar citre-tags-backend
(citre-backend-create
:usable-probe #'citre-tags-file-path
:symbol-at-point-fn #'citre-tags-symbol-at-point
:completions-fn #'citre-tags-get-completions-at-point
:id-list-fn #'citre-tags-get-identifiers
:defs-fn #'citre-tags-get-definitions-at-point
:defs-of-id-fn #'citre-tags-get-definitions-of-id
:tags-in-buffer-fn #'citre-tags-get-tags-in-buffer)
"Tags backend.")
(citre-register-backend 'tags citre-tags-backend)
(provide 'citre-tags)
;; Local Variables:
;; indent-tabs-mode: nil
;; outline-regexp: ";;;;* "
;; fill-column: 79
;; emacs-lisp-docstring-fill-column: 65
;; sentence-end-double-space: t
;; End:
;;; citre-tags.el ends here