-
Notifications
You must be signed in to change notification settings - Fork 26
/
citre-ctags.el
697 lines (605 loc) · 26 KB
/
citre-ctags.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
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
;;; citre-ctags.el --- Generate & update tags files -*- lexical-binding: t -*-
;; Copyright (C) 2021 Hao Wang
;; Author: Hao Wang <[email protected]>
;; Maintainer: Hao Wang <[email protected]>
;; Created: 22 Aug 2020
;; 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:
;; Tool for finding, generating and updating tags files.
;;; Code:
;;;; Libraries
(require 'citre-readtags)
(require 'cl-lib)
;;;; User options
;;;;; Find tags file
(defcustom citre-tags-file-names '(".tags" "tags")
"List of tags file names.
These are searched up directory hierarchy from the file of
current buffer, or from `default-directory' in current buffer, to
decide which tags file to use.
This list is in descending order of priority (i.e., if we find
one, then the rest will be ignored)."
:type '(repeat string)
:group 'citre)
(make-obsolete 'citre-tags-files 'citre-tags-file-names "0.3")
(defcustom citre-tags-file-global-cache-dir "~/.cache/tags/"
"An absolute directory where you can save all your tags files.
Tags files in it are named using the path to the directory in
which you want to use the tags file.
If you work on a remote machine, this points to directory on the
remote machine."
:type 'directory
:group 'citre)
(defvar-local citre-tags-file nil
"A buffer-local variable of the absolute path of tags file.
Set this as a directory-local or file-local variable to specify
the tags file used.")
;;;###autoload
(put 'citre-tags-file 'safe-local-variable #'stringp)
(make-obsolete 'citre-tags-file-per-project-cache-dir
"Put the tags file directly in the tagged dir, or the global \
cache dir instead."
"0.4")
(make-obsolete 'citre-tags-file-alist
"Set `citre-tags-file' as a directory variable instead."
"0.4")
;;;;; Create tags file
(defcustom citre-ctags-program nil
"The name or path of the ctags program.
Citre requires ctags program provided by Universal Ctags. Set
this if ctags is not in your PATH, or its name is not \"ctags\""
:type '(choice (string :tag "Path or name of the ctags program")
(const :tag "readtags" nil))
:group 'citre)
(defcustom citre-ctags-default-options
"-o
%TAGSFILE%
-L
%LISTFILE%
# Programming languages to be scanned, or \"all\" for all supported languages
--languages=%LANGUAGES%
--kinds-all=*
--fields=*
--extras=*
--recurse
"
"Default content of the option file, the \"recipe\" for creating a tags file.
It needs to contain \"%TAGSFILE%\", \"%LISTFILE%\" and
\"%LANGUAGES%\" as placeholders."
:type 'string
:group 'citre)
(make-obsolete 'citre-ctags-cmd-buf-default-cmd
'citre-ctags-default-options
"0.4")
(make-obsolete 'citre-edit-cmd-buf-default-cmd
'citre-ctags-default-options
"0.3")
(defcustom citre-ctags-cmd-buf-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c l") 'citre-ctags-cmd-buf-add-langs)
(define-key map (kbd "C-c f") 'citre-ctags-cmd-buf-add-dir-or-file)
(define-key map (kbd "C-c C-c") 'citre-ctags-cmd-buf-commit)
(define-key map (kbd "C-c C-k") 'citre-ctags-cmd-buf-cancel)
map)
"Keymap used when editing tags recipe."
:type 'keymap
:group 'citre)
(make-obsolete 'citre-edit-cmd-buf-map
'citre-ctags-cmd-buf-map
"0.3")
(defcustom citre-default-create-tags-file-location nil
"Default location to create a tags file. Can be:
- nil: Ask me to select one every time.
- `in-dir': In the directory to use it.
- `global-cache': In `citre-tags-file-global-cache-dir'."
:type '(choice (const :tag "Ask me to select a scheme every time" nil)
(const :tag "In the directory to use it" in-dir)
(const :tag "In global cache dir" global-cache))
:group 'citre)
(defcustom citre-edit-ctags-options-manually t
"When non-nil, edit the ctags options manually when creating a tags file.
Otherwise, prompt the user to choose the languages to scan, and
generate a option file using `citre-ctags-default-options'."
:type 'boolean
:group 'citre)
(make-obsolete 'citre-use-project-root-when-creating-tags
"Please pick a directory manually when creating a tags file."
"0.4")
(make-obsolete 'citre-prompt-language-for-ctags-command
'citre-edit-ctags-options-manually
"0.4")
;;;; Helper functions
(defun citre--ctags-language-list ()
"Return a list of language names supported by Universal Ctags."
(let ((lines (with-temp-buffer
(process-file (or citre-ctags-program "ctags")
nil (current-buffer) nil
"--list-languages")
(split-string (buffer-string) "\n" t)))
result)
(dolist (l lines)
;; There may be a " [disabled]" following languages that are not enabled
;; in the current directory, due to the option file in use.
(push (car (split-string l)) result))
(nreverse result)))
(defun citre--ctags-read-languages (prompt)
"Read languages from Universal Ctags supported languages.
PROMPT is the prompt shown to the user."
(completing-read-multiple prompt (citre--ctags-language-list)))
(defun citre--encode-path-to-filename (path)
"Encode local absolute PATH into a file name."
(let ((segs (split-string
;; expand the "~" so Unix paths will start with a slash. This
;; also collapses consecutive slashes.
(expand-file-name path)
;; Use different separators for Windows/Unix style paths.
(if (eq (aref path 0) ?/) (rx "/") (rx (or "/" "\\")))))
result)
(dolist (seg segs)
(setq seg (replace-regexp-in-string (rx (group (or "%" "!")))
"%\\1" seg 'fixedcase))
;; Also replace ":" to make it legal in Windows.
(setq seg (replace-regexp-in-string ":" "%;" seg 'fixedcase 'literal))
(push seg result))
(string-join (nreverse result) "!")))
(defun citre--decode-filename-to-path (filename)
"Decode FILENAME encoded by `citre--encode-path-to-filename'."
(setq filename (replace-regexp-in-string
;; Find non-escaped "!".
(rx (group (or line-start (not (any "%"))) (* "%%")) "!")
"\\1/"
filename 'fixedcase))
(setq filename (replace-regexp-in-string
;; Find escaped ";"
(rx (group (or line-start (not (any "%")))) (* "%%") "%;")
"\\1:"
filename 'fixedcase))
(setq filename (replace-regexp-in-string
(rx "%" (group (or "%" "!"))) "\\1" filename
'fixedcase))
filename)
;;;; Find tags file: Internals
(defvar-local citre--tags-file nil
"Buffer-local cache for tags file path.
See `citre-tags-file-path' to know how this is used.")
;;;;; By `citre-tags-file-global-cache-dir'
(defun citre--tags-global-cache-dir ()
"Return the global cache dir of tagsfile.
This also works on remote machine."
(when citre-tags-file-global-cache-dir
(concat (or (file-remote-p default-directory) "")
citre-tags-file-global-cache-dir)))
(defun citre--tags-file-in-global-cache (dir)
"Return the tags file name of DIR in global cache dir.
DIR is absolute. The full path of the tags file is returned."
(expand-file-name
(concat (citre--encode-path-to-filename
(file-local-name (file-truename dir)))
".tags")
(citre--tags-global-cache-dir)))
(defun citre--find-tags-file-in-global-cache (dir)
"Find the tags file of DIR in global cache dir.
DIR is absolute. The full path of the tags file is returned."
(when citre-tags-file-global-cache-dir
(let ((tagsfile (citre--tags-file-in-global-cache dir)))
(when (citre-non-dir-file-exists-p tagsfile)
tagsfile))))
;;;;; By `citre-tags-file-names'
(defun citre--find-tags-file-in-dir (dir)
"Find the tags file of DIR by `citre-tags-file-names' in DIR.
DIR is an absolute path."
(cl-dolist (file citre-tags-file-names)
(let ((tags (expand-file-name file dir)))
(when (and (citre-non-dir-file-exists-p tags)
(not (file-directory-p tags)))
(cl-return tags)))))
;;;; Find tags file: APIs
(defun citre-tags-file-path ()
"Return the canonical path of tags file for current buffer.
This finds the tags file up directory hierarchy, and for each
directory, it tries the following methods in turn:
- Use `citre-tags-file-alist'.
- Find in `citre-tags-file-cache-dirs'.
- See if one name in `citre-tags-file-names' exists in this dir.
The result is cached, and can be cleared by
`citre-clear-tags-file-cache'. It also sets
`citre-readtags--tags-file-cwd-guess-table', so for tags file
without the TAG_PROC_CWD pseudo tag, we can better guess its root
dir."
(pcase citre--tags-file
('none nil)
((and val (pred stringp) (pred citre-non-dir-file-exists-p)) val)
(_ (let* ((current-dir (file-truename (citre-current-dir)))
(tagsfile nil))
;; See if `citre-tags-file' is set by the user.
(setq tagsfile citre-tags-file)
;; Go up directory hierarchy and see if the tags file is in global
;; cache dir, or the directory itself.
(while (and (null tagsfile) current-dir)
(setq tagsfile
(or (citre--find-tags-file-in-global-cache current-dir)
(citre--find-tags-file-in-dir current-dir)))
(unless tagsfile
(setq current-dir (citre-directory-of current-dir))))
(if tagsfile
(progn
(setq tagsfile (file-truename tagsfile))
(puthash tagsfile current-dir
citre-readtags--tags-file-cwd-guess-table)
;; Only cache the result for file buffers, since non-file
;; buffers may change their own default directories, e.g., when
;; cd to another project.
(when buffer-file-name
(setq citre--tags-file tagsfile))
tagsfile)
(when buffer-file-name
(setq citre--tags-file 'none)
nil))))))
(defun citre-read-tags-file-name ()
"Prompt the user for an existing file.
This should be used for selecting a tags file. When the current
buffer has a related tags file, it's used as the initial input."
(let* ((current-tags-file (citre-tags-file-path))
(dir (when current-tags-file
(file-name-directory current-tags-file)))
(initial (when current-tags-file
(file-name-nondirectory
current-tags-file))))
(read-file-name "Tags file: " dir nil t initial)))
(defun citre-clear-tags-file-cache ()
"Clear the cache of buffer -> tagsfile.
Use this when a new tags file is created."
(dolist (b (buffer-list))
(with-current-buffer b
(kill-local-variable 'citre--tags-file))))
;;;; Create tags file: Internals
(defvar citre-ctags-cmd-buf-help-msg
"# This block is the help message.
#
# The next block is the Universal Ctags option file. Put one
# command line argument per line. Comments start with \"#\"
#
# The last block contains the files to scan, one file/dir per
# line. You can use paths relative to %ROOT%.
#
# Commands:
#
# - \\[citre-ctags-cmd-buf-add-langs]: Insert a language
# - \\[citre-ctags-cmd-buf-add-dir-or-file]: Insert a dir or file
# - \\[citre-ctags-cmd-buf-commit]: Save, close and update the tags file
# - \\[citre-ctags-cmd-buf-cancel]: Cancel
"
"Help message in the command line editing buffer.")
(defvar citre--ctags-cmd-buf-divider
"---------------8<---------------
"
"The divider in the command line editing buffer.")
(defvar citre--ctags-cmd-buf-default-file-list
".
"
"The default files to scan.")
;;;;; Create recipe files
(defun citre--generate-recipe-file-path (tagsfile &optional listfile)
"Generate a recipe file path for TAGSFILE.
If LISTFILE is nil, it returns an option file path. Otherwise, a
list file path, which contains the files to scan."
(let ((cache-dir (citre--tags-global-cache-dir))
(ext (if listfile "list" "ctags")))
(if (file-in-directory-p tagsfile cache-dir)
(concat (file-name-sans-extension tagsfile) "." ext)
(expand-file-name (concat ".ctags.d/0." ext)
(file-name-directory tagsfile)))))
(defun citre--default-tags-option-file-content (tagsfile &optional languages)
"Create default option file content for TAGSFILE.
LANGUAGES are the languages to scan, `all' means to scan all
supported languages. It can also be nil but then the returned
content needs to be edited manually to add languages."
(let ((in-global (file-in-directory-p tagsfile
(citre--tags-global-cache-dir)))
(tagsfile (expand-file-name tagsfile)))
(let ((cmd citre-ctags-default-options))
(setq cmd (replace-regexp-in-string
"%TAGSFILE%"
(if in-global
(file-local-name tagsfile)
(file-name-nondirectory tagsfile))
cmd 'fixedcase 'literal))
(setq cmd (replace-regexp-in-string
"%LISTFILE%"
(if in-global
(file-local-name
(concat (file-name-sans-extension tagsfile) ".list"))
".ctags.d/0.list")
cmd 'fixedcase 'literal))
(setq cmd (replace-regexp-in-string
"%LANGUAGES%"
(if (eq languages 'all) "all" (string-join languages ","))
cmd 'fixedcase 'literal))
cmd)))
;;;;; Find recipe files
(defun citre--find-tags-file-recipe (tagsfile &optional listfile)
"Find the recipe files of TAGSFILE.
If LISTFILE is nil, find the option file. Otherwise find the
list file."
(let ((file (citre--generate-recipe-file-path tagsfile listfile)))
(when (citre-non-dir-file-exists-p file)
file)))
;;;;; Generate/update tags file
(defun citre--ctags-cwd-of-tags-file (tagsfile)
"Return the cwd of ctags program to generate TAGSFILE."
(if (file-in-directory-p tagsfile (citre--tags-global-cache-dir))
(concat (or (file-remote-p tagsfile) "")
(citre--decode-filename-to-path (file-name-base tagsfile)))
(file-name-directory tagsfile)))
(defun citre--ctags-cmd-option-file-to-read (tagsfile)
"Return an option file to read to update TAGSFILE.
Nil if an option file is not found, or ctags doesn't need to read
an option file."
(when (file-in-directory-p tagsfile (citre--tags-global-cache-dir))
(file-local-name (citre--find-tags-file-recipe tagsfile))))
(defun citre--update-updatable-tags-file (tagsfile &optional callback sync)
"Update TAGSFILE if it has a recipe.
If CALLBACK is non-nil, call it when finish updating. If SYNC is
non-nil, update synchronously.
If the tags file is updated or the process is started, return t,
otherwise return nil."
(when (and (citre--find-tags-file-recipe tagsfile)
(citre--find-tags-file-recipe tagsfile 'listfile))
(let* ((option-file (citre--ctags-cmd-option-file-to-read tagsfile))
(cwd (citre--ctags-cwd-of-tags-file tagsfile))
(cmd (list (or citre-ctags-program "ctags")))
(default-directory cwd))
(when option-file (push (format "--options=%s" option-file) cmd))
(setq cmd (nreverse cmd))
(if sync
(progn (apply #'process-file (car cmd)
nil (get-buffer-create "*citre-ctags*") nil
(cdr cmd))
(when callback (funcall callback)))
(make-process
:name "ctags"
:buffer (get-buffer-create "*citre-ctags*")
:command cmd
:connection-type 'pipe
:stderr nil
:sentinel
(lambda (proc _msg)
(pcase (process-status proc)
('exit
(pcase (process-exit-status proc)
(0 (when callback (funcall callback))
(message "Finished updating %s" tagsfile))
(s (user-error "Ctags exits %s. See *citre-ctags* buffer"
s))))
(s (user-error "Abnormal status of ctags: %s. \
See *citre-ctags* buffer" s))))
:file-handler t)
(message "Updating %s..." tagsfile))
t)))
;;;;; Edit option files
(defvar-local citre--ctags-cmd-buf-tagsfile nil
"The tags file that'll be generated.")
(defvar-local citre--ctags-cmd-buf-optionfile nil
"The option file that'll be written.")
(defvar-local citre--ctags-cmd-buf-listfile nil
"The list file that'll be written.")
(defvar-local citre--ctags-cmd-buf-cwd nil
"The cwd of ctags program.")
(defvar-local citre--ctags-cmd-buf-create-new-p nil
"Whether a new tags file will be created.")
(defun citre--ctags-edit-recipe (tagsfile &optional new)
"Edit the recipe of TAGSFILE.
If NEW is non-nil, it means this will create a new tags file, and
Citre will clean the buffer -> tags file cache."
(let* ((tagsfile (expand-file-name tagsfile))
(optionfile (citre--find-tags-file-recipe tagsfile))
(listfile (citre--find-tags-file-recipe tagsfile 'listfile))
(cwd (citre--ctags-cwd-of-tags-file tagsfile))
help-msg)
(unless optionfile
(user-error "No option file found for tags file %s. Please recreate \
the tags file" tagsfile))
(unless listfile
(user-error "No list file found for tags file %s. Please recreate \
the tags file" tagsfile))
(pop-to-buffer (generate-new-buffer "*ctags-command-line*")
'(display-buffer-same-window))
(conf-mode)
(setq citre--ctags-cmd-buf-tagsfile tagsfile)
(setq citre--ctags-cmd-buf-optionfile optionfile)
(setq citre--ctags-cmd-buf-listfile listfile)
(setq citre--ctags-cmd-buf-cwd cwd)
(setq citre--ctags-cmd-buf-create-new-p new)
(let ((map (copy-keymap citre-ctags-cmd-buf-map)))
(set-keymap-parent map (current-local-map))
(use-local-map map))
(setq help-msg (replace-regexp-in-string
"%ROOT%" cwd citre-ctags-cmd-buf-help-msg
'fixedcase 'literal))
(setq help-msg (substitute-command-keys help-msg))
(save-excursion
(insert help-msg)
(insert citre--ctags-cmd-buf-divider)
(forward-char (nth 1 (insert-file-contents optionfile)))
(insert citre--ctags-cmd-buf-divider)
(forward-char (nth 1 (insert-file-contents listfile))))))
(defun citre-ctags-cmd-buf-add-dir-or-file ()
"Insert a directory or file in the recipe editing buffer.
When it's in the working directory that ctags will run, it's
converted to relative path."
(interactive)
(let ((dir (read-file-name "Dir: " citre--ctags-cmd-buf-cwd)))
(if (file-in-directory-p dir citre--ctags-cmd-buf-cwd)
(progn
(setq dir (file-relative-name dir citre--ctags-cmd-buf-cwd))
;; For cwd itself, if we use "./" in ctags command, a file named
;; "file" under cwd will be "./file" in the input field. But if we
;; use ".", it will be "file", which saves some space.
(when (equal dir "./") (setq dir ".")))
;; Expand "~" in the file name as ctags program doesn't expand it.
(setq dir (file-local-name (expand-file-name dir))))
(insert dir "\n")))
(defun citre-ctags-cmd-buf-add-langs ()
"Insert languages in the option file.
This command requires the ctags program from Universal Ctags."
(interactive)
(insert (string-join (citre--ctags-read-languages "Pick languages: ") ",")))
(defun citre-ctags-cmd-buf-commit ()
"Save and close the recipe, and update the tags file."
(interactive)
(let ((blocks (save-excursion
(split-string
(buffer-string)
(regexp-quote citre--ctags-cmd-buf-divider)))))
(unless (eq (length blocks) 3)
(user-error "More then 3 blocks found in the buffer"))
(with-temp-file citre--ctags-cmd-buf-optionfile
(insert (nth 1 blocks)))
(with-temp-file citre--ctags-cmd-buf-listfile
(insert (nth 2 blocks))))
(citre--update-updatable-tags-file
citre--ctags-cmd-buf-tagsfile
(when citre--ctags-cmd-buf-create-new-p #'citre-clear-tags-file-cache))
(kill-buffer))
(defun citre-ctags-cmd-buf-cancel ()
"Quit the command editing."
(interactive)
(kill-buffer))
;;;; Command
;;;###autoload
(defun citre-update-tags-file (&optional tagsfile sync)
"Update TAGSFILE.
When called interactively, ask the user to pick a tags file.
If Citre can't find an option file for the tagsfile, an error is
signaled. When SYNC is non-nil, update TAGSFILE synchronously."
(interactive)
(setq tagsfile (or tagsfile (citre-read-tags-file-name)))
(or (citre--update-updatable-tags-file tagsfile nil sync)
(user-error "Recipe for %s not found, please regenerate one using \
`citre-create-tags-file'"
tagsfile)))
;;;###autoload
(defun citre-update-this-tags-file (&optional sync)
"Update the currently used tags file.
When no such tags file is found, ask the user to create one.
When a tags file is found, but Citre can't find an updating
recipe in the tagsfile, ask the user to edit one and save it to
the tags file.
When SYNC is non-nil, update the tags file synchronously."
(interactive)
(if-let* ((tagsfile (citre-tags-file-path)))
(citre-update-tags-file tagsfile sync)
(when (y-or-n-p "Can't find tags file for this buffer. Create one? ")
(citre-create-tags-file))))
;;;###autoload
(defun citre-edit-tags-file-recipe (&optional tagsfile)
"Edit the recipe of TAGSFILE.
When called interactively, ask the user to select a tags file.
When CMD-PTAG is non-nil, don't use a command-editing buffer, but
write it to CITRE_CMD ptag directly.
When CWD is non-nil, don't ask the user to pick a root dir to run Ctags.
When NOCONFIRM is non-nil, don't ask the user whether to update
the tags file now (update it directly instead)."
(interactive)
(let ((tagsfile (or tagsfile (citre-read-tags-file-name))))
(citre--ctags-edit-recipe tagsfile)))
;;;###autoload
(defun citre-create-tags-file ()
"Create a new tags file."
(interactive)
(let* ((project (funcall citre-project-root-function))
(dir (read-directory-name
"I want to use the tags file when in this dir: " project))
location
(read-location
(lambda ()
(setq location
(pcase (read-char-choice (format "Save tags file to ...
[1] The directory where I want to use it.
[2] Global cache directory %s.
==> Please type a number (1-2) to choose: " citre-tags-file-global-cache-dir)
'(?1 ?2))
(?1 'in-dir)
(?2 'global-cache)))))
(warning
(lambda (msg)
(read-char
(concat msg "\n==> Press any key to pick another location."))))
tagsfile optionfile listfile)
(setq location citre-default-create-tags-file-location)
(while (null tagsfile)
(unless location (funcall read-location))
(pcase location
('in-dir
(if (null citre-tags-file-names)
(funcall warning "`citre-tags-file-names' \
should be non-nil to use this location.")
(setq tagsfile
(expand-file-name
(completing-read
"Tags file name: " citre-tags-file-names nil t
nil nil (car citre-tags-file-names))
dir))))
('global-cache
(if (null citre-tags-file-global-cache-dir)
(funcall warning "`citre-tags-file-global-cache-dir' \
should be non-nil to use this location.")
(setq tagsfile (citre--tags-file-in-global-cache dir)))))
(setq location nil))
(when (citre-dir-exists-p tagsfile)
(user-error "%s already exists, and is a directory" tagsfile))
(when (or (not (file-exists-p tagsfile))
(and (citre-non-dir-file-exists-p tagsfile)
(y-or-n-p (format "%s already exists. Overwrite it? "
tagsfile))))
(setq optionfile (citre--generate-recipe-file-path tagsfile))
(setq listfile (citre--generate-recipe-file-path tagsfile 'listfile))
(dolist (f (list optionfile tagsfile))
(unless (file-exists-p (file-name-directory f))
(make-directory (file-name-directory f) 'parents)))
(with-temp-buffer
(insert (citre--default-tags-option-file-content
tagsfile
(unless citre-edit-ctags-options-manually
(or (citre--ctags-read-languages
"Pick languages to scan, or pick none to scan all \
supported languages: ")
'all))))
(write-file optionfile))
(with-temp-buffer
(insert citre--ctags-cmd-buf-default-file-list)
(write-file listfile))
(if citre-edit-ctags-options-manually
(citre--ctags-edit-recipe tagsfile 'new)
(citre--update-updatable-tags-file tagsfile
#'citre-clear-tags-file-cache)))))
;;;###autoload
(defun citre-relocate-tags-files ()
"Notify tags backend that it needs to relocate tags files.
Call this after creating a tags file manually. This is not
needed if you use Citre commands to generate a tags file."
(interactive)
(citre-clear-tags-file-cache))
(provide 'citre-ctags)
;; Local Variables:
;; indent-tabs-mode: nil
;; outline-regexp: ";;;;* "
;; fill-column: 79
;; emacs-lisp-docstring-fill-column: 65
;; sentence-end-double-space: t
;; End:
;;; citre-ctags.el ends here