-
Notifications
You must be signed in to change notification settings - Fork 26
/
citre-backend-interface.el
427 lines (358 loc) · 15.5 KB
/
citre-backend-interface.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
;;; citre-backend-interface.el --- Interface of Citre backends -*- lexical-binding: t -*-
;; Copyright (C) 2020 Hao Wang
;; Author: Hao Wang <[email protected]>
;; Maintainer: Hao Wang <[email protected]>
;; Created: 23 Nov 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:
;; This file defines the Citre backend and offers APIs to use them.
;;; 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-common-tag)
;;;; User Options
(defcustom citre-completion-backends '(tags global)
"List of completion backends.
When finding completions, these backends are tried in turn until
one succeeded."
:type '(repeat symbol)
:group 'citre)
(defcustom citre-find-definition-backends '(eglot tags global)
"List of backends for finding definitions.
The backends are tried in turn until one succeeded."
:type '(repeat symbol)
:group 'citre)
(defcustom citre-find-reference-backends '(eglot global)
"List of backends for finding references.
The backends are tried in turn until one succeeded."
:type '(repeat symbol)
:group 'citre)
(defcustom citre-identifier-list-backends '(global tags)
"List of backends for getting all identifiers in current project.
The backends are tried in turn until one succeeded. This is not
useful on its own, but provides completions for finding
definitions or references of a user inputted symbol. It's best
to put backends that gets identifiers faster before the slower
ones."
:type '(repeat symbol)
:group 'citre)
(defcustom citre-tags-in-buffer-backends '(tags global)
"List of backends for finding tags in buffer.
This is used for imenu integration. The backends are tried in
turn until one succeeded."
:type '(repeat symbol)
:group 'citre)
;; Not used in this file, but defined here so backends could check it, e.g.,
;; cache the results only when this is non-nil.
(defcustom citre-capf-optimize-for-popup t
"Non-nil means optimize for popup completion.
This allows user input to interrupt the completing calculation,
so Emacs won't freeze for popup completion. Backends may have
further optimizations depending on this variable.
`company' and `auto-complete' users should leave this as t. For
other users, set this to nil may be slightly better, since a
completion session can be interrupted when you call
`completion-at-point', and while it's calculating, you press some
key by mistake, but that doesn't happen very often."
:type 'boolean
:group 'citre)
;;;; Backend interface
(defvar citre-debug nil
"Debug mode switch.
If this is nil, errors produced by backends are demoted.")
(defmacro citre--with-demoted-errors (format &rest body)
"Demote errors produced by BODY if `citre-debug' is nil.
See `with-demoted-errors' for FORMAT."
`(if citre-debug
(with-demoted-errors ,format ,@body)
,@body))
(defvar citre--backends-table (make-hash-table :test #'eq :size 5)
"Lookup table for Citre backends.
It's keys are symbols of backend names, values are of
`citre--backend' type.")
(cl-defstruct (citre--backend
(:constructor nil)
(:constructor
citre-backend-create
(&key usable-probe symbol-at-point-fn
completions-fn id-list-fn
defs-fn defs-of-id-fn
refs-fn refs-of-id-fn
tags-in-buffer-fn after-jump-fn))
(:copier nil))
"Citre backend.
Use `citre-backend-create' to create a backend, then use
`citre-register-backend' to register one.
`citre-backend-create' accpets key arguments. For detailed
explanation, see docstrings of data members of `citre--backend'.
If a backend could check whether itself is usable for a newly
opened file, provide `:usable-probe'. This is used to
automatically enable `citre-mode'.
For auto-completion capability, provide `:completions-fn'. This
function may want to utilize `citre-capf-optimize-for-popup', see
its docstring.
For finding definition capability, provide `:symbol-at-point-fn'
and `:defs-fn'. If the backend is capable of finding definitions
of user inputted symbol, provide `:defs-of-id-fn'. If the
backend can also find all symbols in current project, provide
`:id-list-fn', and xref could uses this as identifier completion
table. Based on how the backend works, you may also want to
provide `:after-jump-fn'.
Finding references are similar to finding definitions, only the
keyword names are changed to `:refs-fn' and `refs-of-id-fn'.
For the capability of finding tags in current buffer (which is
used by imenu), provide `:tags-in-buffer-fn'."
(usable-probe
nil
:documentation
"Detect if the backend is usable in current buffer.
It is called with no arguments, and should return nil when the
backend is not usable.")
(symbol-at-point-fn
nil
:documentation
"A function that returns symbol at point.
The symbol name is shown in various Citre UIs. The function is
called with no arguments, and should return a string of the
symbol at point. When there's no symbol at point, it should
return nil.
Depending on the behavior of `defs-fn' and `refs-fn' (see their
docstrings), this may return the marked text if there's an active
region."
:type "function")
(completions-fn
nil
:documentation
"A function that returns completions of symbol at point.
It is called with no arguments, and should return a list
like (BEG END TAGS). BEG and END is the region that the
completion should replace, and TAGS is a list of tags, which are
completions of symbol at point. The tags should at least contain
`name' field, and optionally `ext-kind-full', `signature',
`pattern', `scope' and `typeref' fields, which allows the UI to
display more information. If no completion is available, it
should return nil.")
(id-list-fn
nil
:documentation
"A function that returns all symbols in the current project.
It is called in the code buffer, with no arguments. It should
return a list of strings, or nil if it can't figure out the
symbols.")
(defs-fn
nil
:documentation
"A function that returns definitions of symbol at point.
It is called with no arguments, and should return a list of tags,
which are definitions of the symbol at point. The tags should at
least contain `name' and `ext-abspath' fields. `line' and
`pattern' field are optional but could help locating the
definition more precisely. `ext-kind-full', `typeref', `scope'
and `extras' fields allows the UI to display more information.
If no definition is available, it should return nil.
If it's possible, This should use the marked text as the symbol
at point when there's an active region, so the user could specify
the symbol in case it's not grabbed correctly.")
;; TODO: For now this is only used in xref. Could Citre use this to show
;; definitions of a user inputted symbol?
(defs-of-id-fn
nil
:documentation
"A function that returns definitions of a symbol name.
It is called in the code buffer, with an symbol name (a string)
as the argument , and should return the definition tags of the
symbol.")
(refs-fn
nil
:documentation
"Like `defs-fn', but returns references.
The returned tags should contain \"reference\" in their `extras'
field so Citre knows they are references.")
(refs-of-id-fn
nil
:documentation
"Like `defs-of-id-fn', but returns references.")
(tags-in-buffer-fn
nil
:documentation
"A function that returns a list of tags in current buffer.
It is called with no arguments. `name' field is required in the
returned tags, `pattern' and/or `line' fields should also appear.
`kind' and `extras' fields helps imenu to classify the tags.
When no tags is available, it should return nil.")
(after-jump-fn
nil
:documentation
"A function that's called after jumping to a definition or reference.
Some backends requires certain initialization procedures, e.g.,
finding a database or connect to a server. If the user jumps to
a location found by some backend, it's likely that the user will
continue to find definitions or references from that location, so
the backend should be initialized.
This is called with no arguments."))
(defun citre--get-backend (name)
"Get a `citre--backend' by symbol NAME.
Returns nil if there's no such backend."
(gethash name citre--backends-table))
(defun citre--call-backend-fn (name member &rest args)
"Call function MEMBER from backend symbol NAME.
When the backend is not registered or the member is not defined,
return nil. The errors produced by the function may be demoted
based on `citre-debug'."
(when-let* ((backend (citre--get-backend name))
(fn (funcall (intern (format "citre--backend-%s" member))
backend)))
(citre--with-demoted-errors "Citre backend error: %S"
(apply fn args))))
(defun citre--try-fn-on-list (fn lst &rest args)
"Call fn on LST until one returns non-nil.
A cons cell of that element in LST and the returned value of FN
is returned. Elements in LST is used as the first argument of
FN, ARGS are the rest."
(cl-dolist (e lst)
(when-let* ((v (apply fn e args)))
(cl-return (cons e v)))))
;;;;; APIs for backends
(defun citre-register-backend (name backend)
"Register a Citre backend.
NAME is a symbol, BACKEND is a created by `citre-backend-create'.
This ties the symbol with the backend, so the symbol could be
used in `citre-completion-backends',
`citre-find-definition-backends', `citre-find-reference-backends'
and `citre-tags-in-buffer-backends', and other Citre APIs."
(puthash name backend citre--backends-table))
;;;; APIs for upper components
;;;;; APIs for tinkering with a single backend
(defun citre-backend-usable-p (backend)
"Check if BACKEND is usable."
(citre--call-backend-fn backend 'usable-probe))
(defun citre-backend-completions (backend)
"Get completions of symbol at point by BACKEND."
(citre--call-backend-fn backend 'completions-fn))
(defun citre-backend-symbol-at-point (backend)
"Get symbol at point by BACKEND."
(citre--call-backend-fn backend 'symbol-at-point-fn))
(defun citre-backend-id-list (backend)
"Get identifiers in project by BACKEND."
(citre--call-backend-fn backend 'id-list-fn))
(defun citre-backend-find-definition (backend)
"Return definitions of symbol at point by BACKEND."
(citre--call-backend-fn backend 'defs-fn))
(defun citre-backend-find-definition-of-id (backend id)
"Return definitions of ID by BACKEND."
(citre--call-backend-fn backend 'defs-of-id-fn id))
(defun citre-backend-find-reference (backend)
"Return references of symbol at point by BACKEND."
(citre--call-backend-fn backend 'refs-fn))
(defun citre-backend-find-reference-of-id (backend id)
"Return references of ID by BACKEND."
(citre--call-backend-fn backend 'refs-of-id-fn id))
(defun citre-backend-tag-in-buffer (backend)
"Return tags in buffer by BACKEND."
(citre--call-backend-fn backend 'tags-in-buffer-fn))
(defun citre-backend-after-jump (backend)
"Call after-jump action of BACKEND."
(citre--call-backend-fn backend 'after-jump-fn))
;;;;; APIs for getting completions, definitions, references and tags in buffer
(defun citre-get-backend-and-completions ()
"Get completions of symbol at point.
Backends in `citre-completion-backends' are tried in turn. The
first succeeded backend and the results are returned in a cons
pair."
(citre--try-fn-on-list #'citre-backend-completions
citre-completion-backends))
(defun citre-get-completions ()
"Get completions of symbol at point.
The result is a list (BEG END TAGS)."
(cdr (citre-get-backend-and-completions)))
(defun citre-get-backend-and-id-list ()
"Get a list of identifiers of current project.
`citre-find-definition-backends' and
`citre-find-reference-backends' are both tried. The first
succeeded backend and the results are returned in a cons pair."
(citre--try-fn-on-list #'citre-backend-id-list
citre-identifier-list-backends))
(defun citre-get-id-list ()
"Get a list of identifiers of current project."
(cdr (citre-get-backend-and-id-list)))
(defun citre-get-backend-and-definitions ()
"Get definitions of symbol at point.
Backends in `citre-find-definition-backends' are tried in turn.
The first succeeded backend and the results are returned in a
cons pair."
(citre--try-fn-on-list #'citre-backend-find-definition
citre-find-definition-backends))
(defun citre-get-definitions ()
"Get definitions of symbol at point.
The result is a list of tags."
(cdr (citre-get-backend-and-definitions)))
(defun citre-get-backend-and-definitions-of-id (id)
"Get definitions of identifier ID.
Backends in `citre-find-definition-backends' are tried in turn.
The first succeeded backend and the results are returned in a
cons pair."
(citre--try-fn-on-list #'citre-backend-find-definition-of-id
citre-find-definition-backends
id))
(defun citre-get-definitions-of-id (id)
"Get definitions of identifier ID.
The result is a list of tags."
(cdr (citre-get-backend-and-definitions-of-id id)))
(defun citre-get-backend-and-references ()
"Get references of symbol at point.
Backends in `citre-find-reference-backends' are tried in turn.
The first succeeded backend and the results are returned in a
cons pair."
(citre--try-fn-on-list #'citre-backend-find-reference
citre-find-reference-backends))
(defun citre-get-references ()
"Get references of symbol at point.
The result is a list of tags."
(cdr (citre-get-backend-and-references)))
(defun citre-get-backend-and-references-of-id (id)
"Get references of identifier ID.
Backends in `citre-find-reference-backends' are tried in turn.
The first succeeded backend and the results are returned in a
cons pair."
(citre--try-fn-on-list #'citre-backend-find-reference-of-id
citre-find-reference-backends
id))
(defun citre-get-references-of-id (id)
"Get references of identifier ID.
The result is a list of tags."
(cdr (citre-get-backend-and-references-of-id id)))
(defun citre-get-backend-and-tags-in-buffer ()
"Try getting tags in buffer using `citre-tags-in-buffer-backends'.
The first succeeded backend and the results are returned in a
cons pair."
(citre--try-fn-on-list #'citre-backend-tag-in-buffer
citre-tags-in-buffer-backends))
(defun citre-get-tags-in-buffer ()
"Get tags in buffer."
(cdr (citre-get-backend-and-tags-in-buffer)))
(provide 'citre-backend-interface)
;; Local Variables:
;; indent-tabs-mode: nil
;; outline-regexp: ";;;;* "
;; fill-column: 79
;; emacs-lisp-docstring-fill-column: 65
;; sentence-end-double-space: t
;; End:
;;; citre-backend-interface.el ends here