sphinx-lua-ls 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sphinx_lua_ls/__init__.py +166 -0
- sphinx_lua_ls/_version.py +21 -0
- sphinx_lua_ls/autodoc.py +632 -0
- sphinx_lua_ls/doctree.py +477 -0
- sphinx_lua_ls/domain.py +1156 -0
- sphinx_lua_ls/intersphinx.py +204 -0
- sphinx_lua_ls/lua_ls.py +694 -0
- sphinx_lua_ls/py.typed +0 -0
- sphinx_lua_ls-0.0.4.dist-info/LICENSE +21 -0
- sphinx_lua_ls-0.0.4.dist-info/METADATA +92 -0
- sphinx_lua_ls-0.0.4.dist-info/RECORD +13 -0
- sphinx_lua_ls-0.0.4.dist-info/WHEEL +5 -0
- sphinx_lua_ls-0.0.4.dist-info/top_level.txt +1 -0
sphinx_lua_ls/domain.py
ADDED
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lua domain.
|
|
3
|
+
|
|
4
|
+
This code is based on ``sphinxcontrib.luadomain`` by Eliott Dumeix.
|
|
5
|
+
|
|
6
|
+
See the original code here: https://github.com/boolangery/sphinx-luadomain
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Set
|
|
13
|
+
from typing import Any, Callable, ClassVar, Generic, Iterable, Iterator, TypeVar
|
|
14
|
+
|
|
15
|
+
from docutils import nodes
|
|
16
|
+
from docutils.parsers.rst import directives
|
|
17
|
+
from docutils.parsers.rst.states import Inliner
|
|
18
|
+
from sphinx import addnodes
|
|
19
|
+
from sphinx.builders import Builder
|
|
20
|
+
from sphinx.directives import ObjectDescription
|
|
21
|
+
from sphinx.domains import Domain, Index, IndexEntry, ObjType
|
|
22
|
+
from sphinx.environment import BuildEnvironment
|
|
23
|
+
from sphinx.locale import get_translation
|
|
24
|
+
from sphinx.roles import XRefRole
|
|
25
|
+
from sphinx.util import logging
|
|
26
|
+
from sphinx.util.docfields import TypedField
|
|
27
|
+
from sphinx.util.docutils import SphinxDirective
|
|
28
|
+
from sphinx.util.nodes import make_refnode
|
|
29
|
+
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
|
|
32
|
+
MESSAGE_CATALOG_NAME = "sphinx-lua-ls"
|
|
33
|
+
_ = get_translation(MESSAGE_CATALOG_NAME)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger("sphinx_lua_ls")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
#: Regexp for parsing a single Lua identifier.
|
|
40
|
+
_OBJECT_NAME_RE = re.compile(r"^\s*[\w-]+(\s*\.\s*[\w-]+)*\s*")
|
|
41
|
+
|
|
42
|
+
#: A single function parameter name.
|
|
43
|
+
_PARAM_NAME_RE = re.compile(r"^\s*[\w-]+\s*$")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _handle_signature_errors(handler):
|
|
47
|
+
@functools.wraps(handler)
|
|
48
|
+
def fn(self, sig: str, signode: addnodes.desc_signature):
|
|
49
|
+
try:
|
|
50
|
+
return handler(self, sig, signode)
|
|
51
|
+
except ValueError as e:
|
|
52
|
+
logger.warning(
|
|
53
|
+
"incorrect %s signature %r: %s",
|
|
54
|
+
self.objtype,
|
|
55
|
+
sig,
|
|
56
|
+
e,
|
|
57
|
+
type="lua-ls",
|
|
58
|
+
location=(signode.source, signode.line),
|
|
59
|
+
)
|
|
60
|
+
raise
|
|
61
|
+
|
|
62
|
+
return fn
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _separate_paren_prefix(sig: str) -> tuple[str, str]:
|
|
66
|
+
"""
|
|
67
|
+
If string starts with a brace sequence, separate it out from the string.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if not sig.startswith("("):
|
|
72
|
+
return "", sig.strip()
|
|
73
|
+
else:
|
|
74
|
+
sig = sig[1:]
|
|
75
|
+
|
|
76
|
+
depth = 0
|
|
77
|
+
in_str = False
|
|
78
|
+
str_c = ""
|
|
79
|
+
esc = False
|
|
80
|
+
for i, c in enumerate(sig):
|
|
81
|
+
if in_str:
|
|
82
|
+
if esc:
|
|
83
|
+
esc = False
|
|
84
|
+
elif c == str_c:
|
|
85
|
+
in_str = False
|
|
86
|
+
elif c == "\\":
|
|
87
|
+
esc = True
|
|
88
|
+
elif c in "([{<":
|
|
89
|
+
depth += 1
|
|
90
|
+
elif depth == 0 and c == ")":
|
|
91
|
+
return sig[:i].strip(), sig[i + 1 :].strip()
|
|
92
|
+
elif c in ")]}>":
|
|
93
|
+
depth = max(depth - 1, 0)
|
|
94
|
+
elif c in "'\"`":
|
|
95
|
+
in_str = True
|
|
96
|
+
str_c = c
|
|
97
|
+
|
|
98
|
+
return sig.strip(), ""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _separate_sig(sig: str, sep: str = ",", strip: bool = True) -> list[str]:
|
|
102
|
+
"""
|
|
103
|
+
Separate a string by comas, ignoring comas within parens and string literals.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
assert len(sep) == 1
|
|
108
|
+
|
|
109
|
+
res = []
|
|
110
|
+
|
|
111
|
+
pos = 0
|
|
112
|
+
depth = 0
|
|
113
|
+
in_str = False
|
|
114
|
+
str_c = ""
|
|
115
|
+
esc = False
|
|
116
|
+
for i, c in enumerate(sig):
|
|
117
|
+
if in_str:
|
|
118
|
+
if esc:
|
|
119
|
+
esc = False
|
|
120
|
+
elif c == str_c:
|
|
121
|
+
in_str = False
|
|
122
|
+
elif c == "\\":
|
|
123
|
+
esc = True
|
|
124
|
+
elif c in "([{<":
|
|
125
|
+
depth += 1
|
|
126
|
+
elif c in ")]}>":
|
|
127
|
+
depth = max(depth - 1, 0)
|
|
128
|
+
elif c in "'\"`":
|
|
129
|
+
in_str = True
|
|
130
|
+
str_c = c
|
|
131
|
+
elif depth == 0 and c == sep:
|
|
132
|
+
elem = sig[pos:i]
|
|
133
|
+
if strip:
|
|
134
|
+
elem = elem.strip()
|
|
135
|
+
if elem and not elem.isspace():
|
|
136
|
+
res.append(elem)
|
|
137
|
+
pos = i + 1
|
|
138
|
+
|
|
139
|
+
if pos < len(sig):
|
|
140
|
+
elem = sig[pos:]
|
|
141
|
+
if strip:
|
|
142
|
+
elem = elem.strip()
|
|
143
|
+
if elem and not elem.isspace():
|
|
144
|
+
res.append(elem)
|
|
145
|
+
|
|
146
|
+
return res
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _parse_types(
|
|
150
|
+
sig: str, parsingFunctionParams: bool = False
|
|
151
|
+
) -> list[tuple[str, str]]:
|
|
152
|
+
"""
|
|
153
|
+
Parse sequence of type annotations separated by comas.
|
|
154
|
+
|
|
155
|
+
Each type annotation might consist of a single type or a name-type pair.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
res = []
|
|
160
|
+
for elem in _separate_sig(sig):
|
|
161
|
+
elems = _separate_sig(elem, ":", strip=False)
|
|
162
|
+
if not elems:
|
|
163
|
+
continue
|
|
164
|
+
elif (
|
|
165
|
+
len(elems) == 1 and not parsingFunctionParams
|
|
166
|
+
) or not _PARAM_NAME_RE.match(elems[0]):
|
|
167
|
+
# A single type annotation.
|
|
168
|
+
res.append(("", ":".join(elems).strip()))
|
|
169
|
+
else:
|
|
170
|
+
# A name and a type annotation.
|
|
171
|
+
res.append((elems[0].strip(), ":".join(elems[1:]).strip()))
|
|
172
|
+
return res
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _type_to_nodes(typ: str, inliner) -> list[nodes.Node]:
|
|
176
|
+
"""
|
|
177
|
+
Loosely parse a type definition, and return a list of nodes and xrefs.
|
|
178
|
+
|
|
179
|
+
:param typ:
|
|
180
|
+
string with lua type declaration.
|
|
181
|
+
:param inliner:
|
|
182
|
+
inliner for xrefs (available in directives as ``self.state.inliner``).
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
res = []
|
|
187
|
+
|
|
188
|
+
for match in re.finditer(
|
|
189
|
+
r"""
|
|
190
|
+
# Skip spaces, they're not meaningful in this context.
|
|
191
|
+
\s+
|
|
192
|
+
|
|
|
193
|
+
(?P<dots>[.]{3})
|
|
194
|
+
|
|
|
195
|
+
# Literal string with escapes.
|
|
196
|
+
# Example: `"foo"`, `"foo-\"-bar"`.
|
|
197
|
+
(?P<string>(?P<string_q>['"`])(?:\\.|[^\\])*(?P=string_q))
|
|
198
|
+
|
|
|
199
|
+
# Number with optional exponent.
|
|
200
|
+
# Example: `1.0`, `.1`, `1.`, `1e+5`.
|
|
201
|
+
(?P<number>(?:\d+(?:\.\d*)|\.\d+)(?:[eE][+-]?\d+)?)
|
|
202
|
+
|
|
|
203
|
+
# Function type followed by an opening brace.
|
|
204
|
+
# Example: `fun( ...`.
|
|
205
|
+
(?P<kwd>fun)\s*(?=\()
|
|
206
|
+
|
|
|
207
|
+
# Ident not followed by an open brace, semicolon, etc.
|
|
208
|
+
# Example: `module.Type`.
|
|
209
|
+
# Doesn't match: `name?: ...`, `name( ...`, etc.
|
|
210
|
+
(?P<ident>[\w-]+(?:\.[\w-]+)*)
|
|
211
|
+
\s*(?P<ident_qm>\??)\s*
|
|
212
|
+
(?![:(\w.?-])
|
|
213
|
+
|
|
|
214
|
+
# Built-in type not followed by an open brace, semicolon, etc.
|
|
215
|
+
# Example: `string`, `string?`.
|
|
216
|
+
# Doesn't match: `string?: ...`, `string( ...`, etc.
|
|
217
|
+
(?P<type>nil|any|boolean|string|number|integer|function|table|thread|userdata|lightuserdata)
|
|
218
|
+
\s*(?P<type_qm>\??)\s*
|
|
219
|
+
(?![:(\w.?-])
|
|
220
|
+
|
|
|
221
|
+
# Name component, only matches when `ident` and `type` didn't match.
|
|
222
|
+
# Example: `string: ...`.
|
|
223
|
+
(?P<name>[\w.-]+)
|
|
224
|
+
|
|
|
225
|
+
# Punctuation that we separate with spaces.
|
|
226
|
+
(?P<punct>[=:,|])
|
|
227
|
+
|
|
|
228
|
+
# Punctuation that we copy as-is, without adding spaces.
|
|
229
|
+
(?P<other_punct>[-!"#$%&'()*+/;<>?@[\]^_`{}~]+)
|
|
230
|
+
|
|
|
231
|
+
# Anything else is copied as-is.
|
|
232
|
+
(?P<other>.)
|
|
233
|
+
""",
|
|
234
|
+
typ,
|
|
235
|
+
re.VERBOSE,
|
|
236
|
+
):
|
|
237
|
+
if text := match.group("dots"):
|
|
238
|
+
res.append(addnodes.desc_sig_name(text, text))
|
|
239
|
+
elif text := match.group("kwd"):
|
|
240
|
+
res.append(addnodes.desc_sig_keyword(text, text))
|
|
241
|
+
elif text := match.group("type"):
|
|
242
|
+
res.append(addnodes.desc_sig_keyword_type(text, text))
|
|
243
|
+
if qm := match.group("type_qm"):
|
|
244
|
+
res.append(addnodes.desc_sig_punctuation(qm, qm))
|
|
245
|
+
elif text := match.group("string"):
|
|
246
|
+
res.append(addnodes.desc_sig_literal_string(text, text))
|
|
247
|
+
elif text := match.group("number"):
|
|
248
|
+
res.append(addnodes.desc_sig_literal_number(text, text))
|
|
249
|
+
elif text := match.group("ident"):
|
|
250
|
+
ref_nodes, warn_nodes = LuaXRefRole()("lua:obj", text, text, 0, inliner)
|
|
251
|
+
res.extend(ref_nodes)
|
|
252
|
+
res.extend(warn_nodes)
|
|
253
|
+
if qm := match.group("ident_qm"):
|
|
254
|
+
res.append(addnodes.desc_sig_punctuation(qm, qm))
|
|
255
|
+
elif text := match.group("name"):
|
|
256
|
+
res.append(addnodes.desc_sig_name(text, text))
|
|
257
|
+
elif text := match.group("punct"):
|
|
258
|
+
if text in "=|":
|
|
259
|
+
res.append(addnodes.desc_sig_space())
|
|
260
|
+
res.append(addnodes.desc_sig_punctuation(text, text))
|
|
261
|
+
res.append(addnodes.desc_sig_space())
|
|
262
|
+
elif text := match.group("other_punct"):
|
|
263
|
+
res.append(addnodes.desc_sig_punctuation(text, text))
|
|
264
|
+
elif text := match.group("other"):
|
|
265
|
+
res.append(nodes.Text(text))
|
|
266
|
+
|
|
267
|
+
return res
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class LuaTypedField(TypedField):
|
|
271
|
+
def make_field(
|
|
272
|
+
self,
|
|
273
|
+
types: dict[str, list[nodes.Node]],
|
|
274
|
+
domain: str,
|
|
275
|
+
items: list[tuple[str, list[nodes.Node]]],
|
|
276
|
+
env: BuildEnvironment | None = None,
|
|
277
|
+
inliner: Inliner | None = None,
|
|
278
|
+
location: nodes.Element | None = None,
|
|
279
|
+
) -> nodes.field:
|
|
280
|
+
# Process names and types in :param: and :return: flags.
|
|
281
|
+
for i, (name, content) in enumerate(items):
|
|
282
|
+
if name in types:
|
|
283
|
+
fieldtype = types[name]
|
|
284
|
+
if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text):
|
|
285
|
+
typename = fieldtype[0].astext()
|
|
286
|
+
|
|
287
|
+
if typename.endswith("?"):
|
|
288
|
+
new_name, new_typename = name + "?", typename[:-1]
|
|
289
|
+
if new_typename.startswith("(") and new_typename.endswith(")"):
|
|
290
|
+
new_typename = new_typename[1:-1]
|
|
291
|
+
items[i] = (new_name, content)
|
|
292
|
+
types.pop(name)
|
|
293
|
+
name, typename = new_name, new_typename
|
|
294
|
+
|
|
295
|
+
if inliner is None:
|
|
296
|
+
type_body: list[nodes.Node] = [nodes.Text(typename)]
|
|
297
|
+
else:
|
|
298
|
+
type_body = _type_to_nodes(typename, inliner)
|
|
299
|
+
|
|
300
|
+
types[name] = type_body
|
|
301
|
+
|
|
302
|
+
return super().make_field(types, domain, items, env, inliner, location)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class LuaObject(ObjectDescription[tuple[str, str, str, str]], Generic[T]):
|
|
306
|
+
"""
|
|
307
|
+
Description of a general Lua object.
|
|
308
|
+
|
|
309
|
+
Full object path consists of three parts:
|
|
310
|
+
|
|
311
|
+
1. current module,
|
|
312
|
+
2. current class,
|
|
313
|
+
3. object name.
|
|
314
|
+
|
|
315
|
+
For example, if there's a module ``app.log``, a class ``Logger`` within,
|
|
316
|
+
and then ``LogLevel`` within ``Logger``, then a full name for ``LogLevel``
|
|
317
|
+
is ``app.log.Logger.LogLevel``.
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
option_spec: ClassVar[dict[str, Callable[[str], Any]]] = {
|
|
322
|
+
"no-index": directives.flag,
|
|
323
|
+
"module": directives.unchanged,
|
|
324
|
+
"annotation": directives.unchanged,
|
|
325
|
+
"virtual": directives.flag,
|
|
326
|
+
"private": directives.flag,
|
|
327
|
+
"protected": directives.flag,
|
|
328
|
+
"package": directives.flag,
|
|
329
|
+
"abstract": directives.flag,
|
|
330
|
+
"async": directives.flag,
|
|
331
|
+
"global": directives.flag,
|
|
332
|
+
"deprecated": directives.flag,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
doc_field_types = [
|
|
336
|
+
LuaTypedField(
|
|
337
|
+
"parameter",
|
|
338
|
+
label=_("Parameters"),
|
|
339
|
+
names=(
|
|
340
|
+
"param",
|
|
341
|
+
"parameter",
|
|
342
|
+
"arg",
|
|
343
|
+
"argument",
|
|
344
|
+
),
|
|
345
|
+
rolename="",
|
|
346
|
+
typerolename="obj",
|
|
347
|
+
typenames=("paramtype", "type"),
|
|
348
|
+
can_collapse=True,
|
|
349
|
+
),
|
|
350
|
+
LuaTypedField(
|
|
351
|
+
"returnvalue",
|
|
352
|
+
label=_("Returns"),
|
|
353
|
+
names=("return", "returns"),
|
|
354
|
+
rolename="",
|
|
355
|
+
typerolename="obj",
|
|
356
|
+
typenames=("returntype", "rtype"),
|
|
357
|
+
can_collapse=True,
|
|
358
|
+
),
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
allow_nesting = False
|
|
362
|
+
|
|
363
|
+
def run(self) -> list[nodes.Node]:
|
|
364
|
+
for name, option in self.env.config["lua_ls_default_options"].items():
|
|
365
|
+
if name not in self.options:
|
|
366
|
+
self.options[name] = option
|
|
367
|
+
return super().run()
|
|
368
|
+
|
|
369
|
+
def parse_signature(self, sig: str) -> tuple[str, T]:
|
|
370
|
+
raise NotImplementedError()
|
|
371
|
+
|
|
372
|
+
def use_semicolon_path(self) -> bool:
|
|
373
|
+
return False
|
|
374
|
+
|
|
375
|
+
def handle_signature_prefix(
|
|
376
|
+
self, sig: str, signode: addnodes.desc_signature
|
|
377
|
+
) -> tuple[str, str, str, str, T]:
|
|
378
|
+
name, sigdata = self.parse_signature(sig)
|
|
379
|
+
|
|
380
|
+
modname = self.options.get("module", self.env.ref_context.get("lua:module"))
|
|
381
|
+
classname = self.env.ref_context.get("lua:class", None)
|
|
382
|
+
fullname = ".".join(filter(None, [modname, classname, name]))
|
|
383
|
+
|
|
384
|
+
# Only display full path if we're not inside of a class.
|
|
385
|
+
prefix = "" if classname else ".".join(filter(None, [modname, classname]))
|
|
386
|
+
if prefix:
|
|
387
|
+
prefix += ":" if self.use_semicolon_path() else "."
|
|
388
|
+
|
|
389
|
+
signode["module"] = modname
|
|
390
|
+
signode["class"] = classname
|
|
391
|
+
signode["fullname"] = fullname
|
|
392
|
+
|
|
393
|
+
sig_prefix = self.get_signature_prefix(sig)
|
|
394
|
+
if sig_prefix:
|
|
395
|
+
signode += addnodes.desc_annotation("", "", *sig_prefix)
|
|
396
|
+
|
|
397
|
+
if prefix:
|
|
398
|
+
signode += addnodes.desc_addname(prefix, prefix)
|
|
399
|
+
signode += addnodes.desc_name(name, name)
|
|
400
|
+
|
|
401
|
+
return fullname, modname, classname, name, sigdata
|
|
402
|
+
|
|
403
|
+
def get_signature_prefix(self, signature: str) -> list[nodes.Node]:
|
|
404
|
+
prefix = []
|
|
405
|
+
|
|
406
|
+
annotation = self.options.get("annotation")
|
|
407
|
+
if annotation:
|
|
408
|
+
prefix.extend(
|
|
409
|
+
[
|
|
410
|
+
addnodes.desc_sig_keyword(annotation, annotation),
|
|
411
|
+
addnodes.desc_sig_space(),
|
|
412
|
+
]
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
for option in [
|
|
416
|
+
"global",
|
|
417
|
+
"private",
|
|
418
|
+
"protected",
|
|
419
|
+
"package",
|
|
420
|
+
"abstract",
|
|
421
|
+
"virtual",
|
|
422
|
+
"async",
|
|
423
|
+
]:
|
|
424
|
+
if option in self.options:
|
|
425
|
+
prefix.extend(
|
|
426
|
+
[
|
|
427
|
+
addnodes.desc_sig_keyword(option, option),
|
|
428
|
+
addnodes.desc_sig_space(),
|
|
429
|
+
]
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
return prefix
|
|
433
|
+
|
|
434
|
+
def needs_arg_list(self) -> bool:
|
|
435
|
+
"""May return true if an empty argument list is to be generated even if
|
|
436
|
+
the document contains none.
|
|
437
|
+
"""
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
def get_index_text(
|
|
441
|
+
self, fullname: str, modname: str, classname: str, name: str
|
|
442
|
+
) -> str:
|
|
443
|
+
*prefix_parts, _ = fullname.split(".")
|
|
444
|
+
prefix = ".".join(prefix_parts)
|
|
445
|
+
return f"{name} ({self.objtype} in {prefix})"
|
|
446
|
+
|
|
447
|
+
def add_target_and_index(
|
|
448
|
+
self,
|
|
449
|
+
name: tuple[str, str, str, str],
|
|
450
|
+
sig: str,
|
|
451
|
+
signode: addnodes.desc_signature,
|
|
452
|
+
) -> None:
|
|
453
|
+
fullname, modname, classname, objname = name
|
|
454
|
+
anchor = "lua:" + fullname
|
|
455
|
+
if anchor not in self.state.document.ids:
|
|
456
|
+
signode["names"].append(anchor)
|
|
457
|
+
signode["ids"].append(anchor)
|
|
458
|
+
signode["first"] = not self.names
|
|
459
|
+
self.state.document.note_explicit_target(signode)
|
|
460
|
+
objects = self.env.domaindata["lua"]["objects"]
|
|
461
|
+
if fullname in objects:
|
|
462
|
+
self.state_machine.reporter.warning(
|
|
463
|
+
"duplicate object description of %s, " % fullname
|
|
464
|
+
+ "other instance in "
|
|
465
|
+
+ self.env.doc2path(objects[fullname][0])
|
|
466
|
+
+ ", use :no-index: for one of them",
|
|
467
|
+
line=self.lineno,
|
|
468
|
+
)
|
|
469
|
+
objects[fullname] = (self.env.docname, self.objtype)
|
|
470
|
+
|
|
471
|
+
indextext = self.get_index_text(fullname, modname, classname, objname)
|
|
472
|
+
if indextext:
|
|
473
|
+
self.indexnode["entries"].append(("single", indextext, anchor, "", None))
|
|
474
|
+
|
|
475
|
+
def before_content(self) -> None:
|
|
476
|
+
if self.names and self.allow_nesting:
|
|
477
|
+
_, _, classname, name = self.names[-1]
|
|
478
|
+
# Add name of the current object to the current classname,
|
|
479
|
+
# thus getting a new classname.
|
|
480
|
+
new_classname = ".".join(filter(None, [classname, name]))
|
|
481
|
+
classes = self.env.ref_context.setdefault("lua:classes", [])
|
|
482
|
+
classes.append(self.env.ref_context.get("lua:class"))
|
|
483
|
+
self.env.ref_context["lua:class"] = new_classname
|
|
484
|
+
|
|
485
|
+
if "module" in self.options:
|
|
486
|
+
modules = self.env.ref_context.setdefault("lua:modules", [])
|
|
487
|
+
modules.append(self.env.ref_context.get("lua:module"))
|
|
488
|
+
self.env.ref_context["lua:module"] = self.options["module"]
|
|
489
|
+
|
|
490
|
+
def after_content(self) -> None:
|
|
491
|
+
if self.names and self.allow_nesting:
|
|
492
|
+
classes = self.env.ref_context.setdefault("lua:classes", [])
|
|
493
|
+
self.env.ref_context["lua:class"] = classes.pop() if classes else None
|
|
494
|
+
|
|
495
|
+
if "module" in self.options:
|
|
496
|
+
modules = self.env.ref_context.setdefault("lua:modules", [])
|
|
497
|
+
self.env.ref_context["lua:module"] = modules.pop() if modules else None
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class LuaFunction(LuaObject[tuple[list[tuple[str, str]], list[tuple[str, str]]]]):
|
|
501
|
+
"""
|
|
502
|
+
Everything that looks like a function: functions, methods, static and class methods.
|
|
503
|
+
|
|
504
|
+
I.e. everything with signature ``name(params) -> returns``.
|
|
505
|
+
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
def parse_signature(self, sig):
|
|
509
|
+
if match := _OBJECT_NAME_RE.match(sig):
|
|
510
|
+
name = re.sub(r"\s", "", match.group())
|
|
511
|
+
sig = sig[match.span()[1] :]
|
|
512
|
+
else:
|
|
513
|
+
raise ValueError("Incorrect function name")
|
|
514
|
+
|
|
515
|
+
params, returns = _separate_paren_prefix(sig)
|
|
516
|
+
|
|
517
|
+
if returns and returns.startswith("->"):
|
|
518
|
+
returns = returns[2:].lstrip()
|
|
519
|
+
elif returns:
|
|
520
|
+
raise ValueError("Incorrect function return type")
|
|
521
|
+
|
|
522
|
+
if returns.startswith("(") and returns.endswith(")"):
|
|
523
|
+
returns = returns[1:-1]
|
|
524
|
+
|
|
525
|
+
return name, (
|
|
526
|
+
_parse_types(params, parsingFunctionParams=True),
|
|
527
|
+
_parse_types(returns),
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
@_handle_signature_errors
|
|
531
|
+
def handle_signature(
|
|
532
|
+
self, sig: str, signode: addnodes.desc_signature
|
|
533
|
+
) -> tuple[str, str, str, str]:
|
|
534
|
+
(
|
|
535
|
+
fullname,
|
|
536
|
+
modname,
|
|
537
|
+
classname,
|
|
538
|
+
name,
|
|
539
|
+
(args, returns),
|
|
540
|
+
) = self.handle_signature_prefix(sig, signode)
|
|
541
|
+
|
|
542
|
+
if not args:
|
|
543
|
+
if self.needs_arg_list():
|
|
544
|
+
signode += addnodes.desc_parameterlist()
|
|
545
|
+
else:
|
|
546
|
+
parameterslist = addnodes.desc_parameterlist()
|
|
547
|
+
signode += parameterslist
|
|
548
|
+
for arg, typ in args:
|
|
549
|
+
if arg and typ and typ.endswith("?"):
|
|
550
|
+
arg, typ = arg + "?", typ[:-1]
|
|
551
|
+
if typ.startswith("(") and typ.endswith(")"):
|
|
552
|
+
typ = typ[1:-1]
|
|
553
|
+
|
|
554
|
+
parameter = addnodes.desc_parameter()
|
|
555
|
+
|
|
556
|
+
parameter += addnodes.desc_sig_name(arg, arg)
|
|
557
|
+
if typ:
|
|
558
|
+
parameter += addnodes.desc_sig_punctuation(":", ":")
|
|
559
|
+
parameter += addnodes.desc_sig_space()
|
|
560
|
+
parameter += _type_to_nodes(typ, self.state.inliner)
|
|
561
|
+
|
|
562
|
+
parameterslist += parameter
|
|
563
|
+
|
|
564
|
+
if returns:
|
|
565
|
+
retnode = addnodes.desc_returns()
|
|
566
|
+
signode += retnode
|
|
567
|
+
|
|
568
|
+
for i, (arg, typ) in enumerate(returns):
|
|
569
|
+
if arg and typ and typ.endswith("?"):
|
|
570
|
+
arg, typ = arg + "?", typ[:-1]
|
|
571
|
+
if typ.startswith("(") and typ.endswith(")"):
|
|
572
|
+
typ = typ[1:-1]
|
|
573
|
+
|
|
574
|
+
if arg:
|
|
575
|
+
retnode += addnodes.desc_sig_name("", arg or "_")
|
|
576
|
+
retnode += addnodes.desc_sig_punctuation(":", ":")
|
|
577
|
+
retnode += addnodes.desc_sig_space()
|
|
578
|
+
retnode += _type_to_nodes(typ, self.state.inliner)
|
|
579
|
+
if i + 1 < len(returns):
|
|
580
|
+
retnode += addnodes.desc_sig_punctuation(",", ",")
|
|
581
|
+
retnode += addnodes.desc_sig_space()
|
|
582
|
+
|
|
583
|
+
return fullname, modname, classname, name
|
|
584
|
+
|
|
585
|
+
def needs_arg_list(self) -> bool:
|
|
586
|
+
return True
|
|
587
|
+
|
|
588
|
+
def use_semicolon_path(self) -> bool:
|
|
589
|
+
return self.objtype in ("method", "classmethod")
|
|
590
|
+
|
|
591
|
+
def get_signature_prefix(self, signature: str) -> list[nodes.Node]:
|
|
592
|
+
prefix = super().get_signature_prefix(signature)
|
|
593
|
+
if self.objtype not in ("function", "method"):
|
|
594
|
+
prefix.extend(
|
|
595
|
+
[
|
|
596
|
+
addnodes.desc_sig_keyword("", self.objtype),
|
|
597
|
+
addnodes.desc_sig_space(),
|
|
598
|
+
]
|
|
599
|
+
)
|
|
600
|
+
return prefix
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
class LuaData(LuaObject[str]):
|
|
604
|
+
"""
|
|
605
|
+
Variables and other things that have type annotations in their signature.
|
|
606
|
+
|
|
607
|
+
I.e. everything with signature ``name type``.
|
|
608
|
+
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
def parse_signature(self, sig):
|
|
612
|
+
if match := _OBJECT_NAME_RE.match(sig):
|
|
613
|
+
name = re.sub(r"\s", "", match.group())
|
|
614
|
+
sig = sig[match.span()[1] :]
|
|
615
|
+
else:
|
|
616
|
+
raise ValueError("Incorrect data name")
|
|
617
|
+
|
|
618
|
+
if sig.startswith("=") or sig.startswith(":"):
|
|
619
|
+
sig = sig[1:]
|
|
620
|
+
|
|
621
|
+
return name, sig.strip()
|
|
622
|
+
|
|
623
|
+
@_handle_signature_errors
|
|
624
|
+
def handle_signature(
|
|
625
|
+
self, sig: str, signode: addnodes.desc_signature
|
|
626
|
+
) -> tuple[str, str, str, str]:
|
|
627
|
+
fullname, modname, classname, name, typ = self.handle_signature_prefix(
|
|
628
|
+
sig, signode
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
if typ:
|
|
632
|
+
signode += addnodes.desc_sig_punctuation(":", ":")
|
|
633
|
+
signode += addnodes.desc_sig_space()
|
|
634
|
+
signode += addnodes.desc_type(
|
|
635
|
+
"", "", *_type_to_nodes(typ, self.state.inliner)
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
return fullname, modname, classname, name
|
|
639
|
+
|
|
640
|
+
def get_signature_prefix(self, signature: str) -> list[nodes.Node]:
|
|
641
|
+
prefix = super().get_signature_prefix(signature)
|
|
642
|
+
if self.objtype not in ("data", "attribute"):
|
|
643
|
+
prefix.extend(
|
|
644
|
+
[
|
|
645
|
+
addnodes.desc_sig_keyword("", self.objtype),
|
|
646
|
+
addnodes.desc_sig_space(),
|
|
647
|
+
]
|
|
648
|
+
)
|
|
649
|
+
return prefix
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
class LuaAlias(LuaObject[str]):
|
|
653
|
+
"""
|
|
654
|
+
Type aliases and other things that have type assignments in their signature.
|
|
655
|
+
|
|
656
|
+
I.e. everything with signature ``name type``.
|
|
657
|
+
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
allow_nesting = True
|
|
661
|
+
|
|
662
|
+
def parse_signature(self, sig):
|
|
663
|
+
if match := _OBJECT_NAME_RE.match(sig):
|
|
664
|
+
name = re.sub(r"\s", "", match.group())
|
|
665
|
+
sig = sig[match.span()[1] :]
|
|
666
|
+
else:
|
|
667
|
+
raise ValueError("Incorrect alias name")
|
|
668
|
+
|
|
669
|
+
if sig.startswith("=") or sig.startswith(":"):
|
|
670
|
+
sig = sig[1:]
|
|
671
|
+
|
|
672
|
+
return name, sig.strip()
|
|
673
|
+
|
|
674
|
+
@_handle_signature_errors
|
|
675
|
+
def handle_signature(
|
|
676
|
+
self, sig: str, signode: addnodes.desc_signature
|
|
677
|
+
) -> tuple[str, str, str, str]:
|
|
678
|
+
fullname, modname, classname, name, typ = self.handle_signature_prefix(
|
|
679
|
+
sig, signode
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
if typ:
|
|
683
|
+
signode += addnodes.desc_sig_space()
|
|
684
|
+
signode += addnodes.desc_sig_punctuation("=", "=")
|
|
685
|
+
signode += addnodes.desc_sig_space()
|
|
686
|
+
signode += addnodes.desc_type(
|
|
687
|
+
"", "", *_type_to_nodes(typ, self.state.inliner)
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
return fullname, modname, classname, name
|
|
691
|
+
|
|
692
|
+
def get_signature_prefix(self, signature: str) -> list[nodes.Node]:
|
|
693
|
+
prefix = super().get_signature_prefix(signature)
|
|
694
|
+
prefix.extend(
|
|
695
|
+
[
|
|
696
|
+
addnodes.desc_sig_keyword("", self.objtype),
|
|
697
|
+
addnodes.desc_sig_space(),
|
|
698
|
+
]
|
|
699
|
+
)
|
|
700
|
+
return prefix
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class LuaClass(LuaObject[list[str]]):
|
|
704
|
+
"""
|
|
705
|
+
Classes and other things that have base types in their signature.
|
|
706
|
+
|
|
707
|
+
I.e. everything with signature ``name: base1, base2, ...``.
|
|
708
|
+
|
|
709
|
+
These are nested.
|
|
710
|
+
|
|
711
|
+
"""
|
|
712
|
+
|
|
713
|
+
allow_nesting = True
|
|
714
|
+
|
|
715
|
+
def parse_signature(self, sig):
|
|
716
|
+
if match := _OBJECT_NAME_RE.match(sig):
|
|
717
|
+
name = re.sub(r"\s", "", match.group())
|
|
718
|
+
sig = sig[match.span()[1] :]
|
|
719
|
+
else:
|
|
720
|
+
raise ValueError("Incorrect data name")
|
|
721
|
+
|
|
722
|
+
if sig.startswith("=") or sig.startswith(":"):
|
|
723
|
+
sig = sig[1:]
|
|
724
|
+
|
|
725
|
+
return name, _separate_sig(sig)
|
|
726
|
+
|
|
727
|
+
@_handle_signature_errors
|
|
728
|
+
def handle_signature(
|
|
729
|
+
self, sig: str, signode: addnodes.desc_signature
|
|
730
|
+
) -> tuple[str, str, str, str]:
|
|
731
|
+
fullname, modname, classname, name, bases = self.handle_signature_prefix(
|
|
732
|
+
sig, signode
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
if bases:
|
|
736
|
+
signode += addnodes.desc_sig_space()
|
|
737
|
+
signode += addnodes.desc_sig_punctuation(":", ":")
|
|
738
|
+
signode += addnodes.desc_sig_space()
|
|
739
|
+
|
|
740
|
+
sep = False
|
|
741
|
+
for typ in bases:
|
|
742
|
+
if sep:
|
|
743
|
+
signode += addnodes.desc_sig_punctuation(",", ",")
|
|
744
|
+
signode += addnodes.desc_sig_space()
|
|
745
|
+
signode += addnodes.desc_type(
|
|
746
|
+
"", "", *_type_to_nodes(typ, self.state.inliner)
|
|
747
|
+
)
|
|
748
|
+
sep = True
|
|
749
|
+
|
|
750
|
+
return fullname, modname, classname, name
|
|
751
|
+
|
|
752
|
+
def get_signature_prefix(self, signature: str) -> list[nodes.Node]:
|
|
753
|
+
prefix = super().get_signature_prefix(signature)
|
|
754
|
+
prefix.extend(
|
|
755
|
+
[
|
|
756
|
+
addnodes.desc_sig_keyword("", self.objtype),
|
|
757
|
+
addnodes.desc_sig_space(),
|
|
758
|
+
]
|
|
759
|
+
)
|
|
760
|
+
return prefix
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
class LuaModule(SphinxDirective):
|
|
764
|
+
"""
|
|
765
|
+
Directive to mark description of a new module.
|
|
766
|
+
"""
|
|
767
|
+
|
|
768
|
+
has_content = False
|
|
769
|
+
required_arguments = 1
|
|
770
|
+
optional_arguments = 0
|
|
771
|
+
final_argument_whitespace = False
|
|
772
|
+
option_spec = {
|
|
773
|
+
"platform": lambda x: x,
|
|
774
|
+
"synopsis": lambda x: x,
|
|
775
|
+
"no-index": directives.flag,
|
|
776
|
+
"deprecated": directives.flag,
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
def run(self) -> list[nodes.Node]:
|
|
780
|
+
for name, option in self.env.config["lua_ls_default_options"].items():
|
|
781
|
+
if name not in self.options:
|
|
782
|
+
self.options[name] = option
|
|
783
|
+
|
|
784
|
+
sig = self.arguments[0]
|
|
785
|
+
if match := _OBJECT_NAME_RE.match(sig):
|
|
786
|
+
modname = re.sub(r"\s", "", match.group())
|
|
787
|
+
sig = sig[match.span()[1] :]
|
|
788
|
+
if sig:
|
|
789
|
+
raise ValueError("Unexpected symbols after module name")
|
|
790
|
+
else:
|
|
791
|
+
raise ValueError("Incorrect module name")
|
|
792
|
+
|
|
793
|
+
env = self.state.document.settings.env
|
|
794
|
+
no_index = "no-index" in self.options
|
|
795
|
+
env.ref_context["lua:module"] = modname
|
|
796
|
+
ret = []
|
|
797
|
+
if not no_index:
|
|
798
|
+
env.domaindata["lua"]["modules"][modname] = (
|
|
799
|
+
env.docname,
|
|
800
|
+
self.options.get("synopsis", ""),
|
|
801
|
+
self.options.get("platform", ""),
|
|
802
|
+
"deprecated" in self.options,
|
|
803
|
+
)
|
|
804
|
+
# make a duplicate entry in 'objects' to facilitate searching for
|
|
805
|
+
# the module in LuaDomain.find_obj()
|
|
806
|
+
env.domaindata["lua"]["objects"][modname] = (env.docname, "module")
|
|
807
|
+
target_node = nodes.target("", "", ids=["lua:" + modname], ismod=True)
|
|
808
|
+
self.state.document.note_explicit_target(target_node)
|
|
809
|
+
# the platform and synopsis aren't printed; in fact, they are only
|
|
810
|
+
# used in the modindex currently
|
|
811
|
+
ret.append(target_node)
|
|
812
|
+
indextext = _("%s (module)") % modname
|
|
813
|
+
inode = addnodes.index(
|
|
814
|
+
entries=[("single", indextext, "lua:" + modname, "", None)]
|
|
815
|
+
)
|
|
816
|
+
ret.append(inode)
|
|
817
|
+
return ret
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
class LuaCurrentModule(SphinxDirective):
|
|
821
|
+
"""
|
|
822
|
+
This directive is just to tell Sphinx that we're documenting
|
|
823
|
+
stuff in module foo, but links to module foo won't lead here.
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
has_content = False
|
|
827
|
+
required_arguments = 1
|
|
828
|
+
optional_arguments = 0
|
|
829
|
+
final_argument_whitespace = False
|
|
830
|
+
option_spec = {}
|
|
831
|
+
|
|
832
|
+
def run(self) -> list[nodes.Node]:
|
|
833
|
+
sig = self.arguments[0]
|
|
834
|
+
if match := _OBJECT_NAME_RE.match(sig):
|
|
835
|
+
modname = re.sub(r"\s", "", match.group())
|
|
836
|
+
sig = sig[match.span()[1] :]
|
|
837
|
+
if sig:
|
|
838
|
+
raise ValueError("Unexpected symbols after module name")
|
|
839
|
+
else:
|
|
840
|
+
raise ValueError("Incorrect module name")
|
|
841
|
+
|
|
842
|
+
env = self.state.document.settings.env
|
|
843
|
+
if modname == "None":
|
|
844
|
+
env.ref_context.pop("lua:module", None)
|
|
845
|
+
else:
|
|
846
|
+
env.ref_context["lua:module"] = modname
|
|
847
|
+
return []
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
class LuaXRefRole(XRefRole):
|
|
851
|
+
def process_link(
|
|
852
|
+
self,
|
|
853
|
+
env: BuildEnvironment,
|
|
854
|
+
refnode: nodes.Element,
|
|
855
|
+
has_explicit_title: bool,
|
|
856
|
+
title: str,
|
|
857
|
+
target: str,
|
|
858
|
+
) -> tuple[str, str]:
|
|
859
|
+
refnode["lua:module"] = env.ref_context.get("lua:module")
|
|
860
|
+
refnode["lua:class"] = env.ref_context.get("lua:class")
|
|
861
|
+
if not has_explicit_title:
|
|
862
|
+
title = title.lstrip(".") # only has a meaning for the target
|
|
863
|
+
target = target.lstrip("~") # only has a meaning for the title
|
|
864
|
+
# if the first character is a tilde, don't display the module/class
|
|
865
|
+
# parts of the contents
|
|
866
|
+
if title[0:1] == "~":
|
|
867
|
+
title = title[1:]
|
|
868
|
+
dot = title.rfind(".")
|
|
869
|
+
if dot != -1:
|
|
870
|
+
title = title[dot + 1 :]
|
|
871
|
+
return title, target
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
class LuaModuleIndex(Index):
|
|
875
|
+
"""
|
|
876
|
+
Index subclass to provide the Lua module index.
|
|
877
|
+
"""
|
|
878
|
+
|
|
879
|
+
name = "modindex"
|
|
880
|
+
localname = _("Lua Module Index")
|
|
881
|
+
shortname = _("modules")
|
|
882
|
+
|
|
883
|
+
def generate(
|
|
884
|
+
self, docnames: Iterable[str] | None = None
|
|
885
|
+
) -> tuple[list[tuple[str, list[IndexEntry]]], bool]:
|
|
886
|
+
content: dict[str, list[IndexEntry]] = {}
|
|
887
|
+
# list of prefixes to ignore
|
|
888
|
+
ignores = self.domain.env.config["modindex_common_prefix"]
|
|
889
|
+
ignores = sorted(ignores, key=len, reverse=True)
|
|
890
|
+
# list of all modules, sorted by module name
|
|
891
|
+
modules: list[tuple[str, tuple[str, str, str, bool]]] = sorted(
|
|
892
|
+
self.domain.data["modules"].items(), key=lambda x: x[0].lower()
|
|
893
|
+
)
|
|
894
|
+
# sort out collapsable modules
|
|
895
|
+
prev_modname = ""
|
|
896
|
+
num_top_levels = 0
|
|
897
|
+
for modname, (docname, synopsis, platforms, deprecated) in modules:
|
|
898
|
+
if docnames and docname not in docnames:
|
|
899
|
+
continue
|
|
900
|
+
if not modname:
|
|
901
|
+
continue
|
|
902
|
+
|
|
903
|
+
for ignore in ignores:
|
|
904
|
+
if modname.startswith(ignore):
|
|
905
|
+
modname = modname[len(ignore) :]
|
|
906
|
+
stripped = ignore
|
|
907
|
+
break
|
|
908
|
+
else:
|
|
909
|
+
stripped = ""
|
|
910
|
+
|
|
911
|
+
# we stripped the whole module name?
|
|
912
|
+
if not modname:
|
|
913
|
+
modname, stripped = stripped, ""
|
|
914
|
+
|
|
915
|
+
entries = content.setdefault(modname[0].lower(), [])
|
|
916
|
+
|
|
917
|
+
package = modname.split(".")[0]
|
|
918
|
+
if package != modname:
|
|
919
|
+
# it's a submodule
|
|
920
|
+
if prev_modname == package:
|
|
921
|
+
# first submodule - make parent a group head
|
|
922
|
+
if entries:
|
|
923
|
+
name, _subtype, *rest = entries[-1]
|
|
924
|
+
entries[-1] = IndexEntry(name, 1, *rest)
|
|
925
|
+
elif not prev_modname.startswith(package):
|
|
926
|
+
# submodule without parent in list, add dummy entry
|
|
927
|
+
entries.append(
|
|
928
|
+
IndexEntry(stripped + package, 1, "", "", "", "", "")
|
|
929
|
+
)
|
|
930
|
+
subtype = 2
|
|
931
|
+
else:
|
|
932
|
+
num_top_levels += 1
|
|
933
|
+
subtype = 0
|
|
934
|
+
|
|
935
|
+
qualifier = deprecated and _("Deprecated") or ""
|
|
936
|
+
entries.append(
|
|
937
|
+
IndexEntry(
|
|
938
|
+
stripped + modname,
|
|
939
|
+
subtype,
|
|
940
|
+
docname,
|
|
941
|
+
"lua:" + stripped + modname,
|
|
942
|
+
platforms,
|
|
943
|
+
qualifier,
|
|
944
|
+
synopsis,
|
|
945
|
+
)
|
|
946
|
+
)
|
|
947
|
+
prev_modname = modname
|
|
948
|
+
|
|
949
|
+
# apply heuristics when to collapse modindex at page load:
|
|
950
|
+
# only collapse if number of toplevel modules is larger than
|
|
951
|
+
# number of submodules
|
|
952
|
+
collapse = len(modules) - num_top_levels < num_top_levels
|
|
953
|
+
|
|
954
|
+
# sort by first letter
|
|
955
|
+
sorted_content = sorted(content.items())
|
|
956
|
+
|
|
957
|
+
return sorted_content, collapse
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
class LuaDomain(Domain):
|
|
961
|
+
"""Lua language domain."""
|
|
962
|
+
|
|
963
|
+
name = "lua"
|
|
964
|
+
label = "Lua"
|
|
965
|
+
object_types: dict[str, ObjType] = {
|
|
966
|
+
"function": ObjType(_("function"), "func", "obj"),
|
|
967
|
+
"data": ObjType(_("data"), "data", "obj"),
|
|
968
|
+
"const": ObjType(_("const"), "const", "obj"),
|
|
969
|
+
"class": ObjType(_("class"), "class", "obj"),
|
|
970
|
+
"alias": ObjType(_("alias"), "alias", "obj"),
|
|
971
|
+
"method": ObjType(_("method"), "meth", "obj"),
|
|
972
|
+
"classmethod": ObjType(_("class method"), "meth", "obj"),
|
|
973
|
+
"staticmethod": ObjType(_("static method"), "meth", "obj"),
|
|
974
|
+
"attribute": ObjType(_("attribute"), "attr", "obj"),
|
|
975
|
+
"module": ObjType(_("module"), "mod", "obj"),
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
directives = {
|
|
979
|
+
"function": LuaFunction,
|
|
980
|
+
"data": LuaData,
|
|
981
|
+
"const": LuaData,
|
|
982
|
+
"class": LuaClass,
|
|
983
|
+
"alias": LuaAlias,
|
|
984
|
+
"method": LuaFunction,
|
|
985
|
+
"classmethod": LuaFunction,
|
|
986
|
+
"staticmethod": LuaFunction,
|
|
987
|
+
"attribute": LuaData,
|
|
988
|
+
"module": LuaModule,
|
|
989
|
+
"currentmodule": LuaCurrentModule,
|
|
990
|
+
}
|
|
991
|
+
roles = {
|
|
992
|
+
"func": LuaXRefRole(),
|
|
993
|
+
"data": LuaXRefRole(),
|
|
994
|
+
"const": LuaXRefRole(),
|
|
995
|
+
"class": LuaXRefRole(),
|
|
996
|
+
"alias": LuaXRefRole(),
|
|
997
|
+
"meth": LuaXRefRole(),
|
|
998
|
+
"attr": LuaXRefRole(),
|
|
999
|
+
"mod": LuaXRefRole(),
|
|
1000
|
+
"obj": LuaXRefRole(),
|
|
1001
|
+
}
|
|
1002
|
+
initial_data: dict[str, dict[str, tuple[Any]]] = {
|
|
1003
|
+
"objects": {}, # fullname -> docname, objtype
|
|
1004
|
+
"modules": {}, # modname -> docname, synopsis, platform, deprecated
|
|
1005
|
+
}
|
|
1006
|
+
indices = [
|
|
1007
|
+
LuaModuleIndex,
|
|
1008
|
+
]
|
|
1009
|
+
|
|
1010
|
+
def clear_doc(self, docname: str) -> None:
|
|
1011
|
+
for fullname, (fn, _l) in list(self.data["objects"].items()):
|
|
1012
|
+
if fn == docname:
|
|
1013
|
+
del self.data["objects"][fullname]
|
|
1014
|
+
for modname, (fn, _x, _x, _x) in list(self.data["modules"].items()):
|
|
1015
|
+
if fn == docname:
|
|
1016
|
+
del self.data["modules"][modname]
|
|
1017
|
+
|
|
1018
|
+
def merge_domaindata(self, docnames: Set[str], otherdata: dict[Any, Any]) -> None:
|
|
1019
|
+
# XXX check duplicates?
|
|
1020
|
+
for fullname, (fn, objtype) in otherdata["objects"].items():
|
|
1021
|
+
if fn in docnames:
|
|
1022
|
+
self.data["objects"][fullname] = (fn, objtype)
|
|
1023
|
+
for modname, data in otherdata["modules"].items():
|
|
1024
|
+
if data[0] in docnames:
|
|
1025
|
+
self.data["modules"][modname] = data
|
|
1026
|
+
|
|
1027
|
+
def _find_obj(
|
|
1028
|
+
self, modname: str, classname: str, name: str, typ: str | None
|
|
1029
|
+
) -> tuple[str, Any] | None:
|
|
1030
|
+
if name[-2:] == "()":
|
|
1031
|
+
name = name[:-2]
|
|
1032
|
+
|
|
1033
|
+
if not name:
|
|
1034
|
+
return None
|
|
1035
|
+
|
|
1036
|
+
objects = self.data["objects"]
|
|
1037
|
+
|
|
1038
|
+
if typ == "mod":
|
|
1039
|
+
candidates = [[name]]
|
|
1040
|
+
else:
|
|
1041
|
+
candidates = [
|
|
1042
|
+
[modname, classname, name],
|
|
1043
|
+
[modname, name],
|
|
1044
|
+
[name],
|
|
1045
|
+
]
|
|
1046
|
+
|
|
1047
|
+
if typ in ("func", "meth") and "." not in name:
|
|
1048
|
+
candidates.append(["object", name])
|
|
1049
|
+
|
|
1050
|
+
for candidate in candidates:
|
|
1051
|
+
path = ".".join(filter(None, candidate))
|
|
1052
|
+
if path in objects:
|
|
1053
|
+
return path, objects[path]
|
|
1054
|
+
|
|
1055
|
+
return None
|
|
1056
|
+
|
|
1057
|
+
def resolve_xref(
|
|
1058
|
+
self,
|
|
1059
|
+
env: BuildEnvironment,
|
|
1060
|
+
fromdocname: str,
|
|
1061
|
+
builder: Builder,
|
|
1062
|
+
typ: str,
|
|
1063
|
+
target: str,
|
|
1064
|
+
node: addnodes.pending_xref,
|
|
1065
|
+
contnode: nodes.Node,
|
|
1066
|
+
) -> nodes.reference | None:
|
|
1067
|
+
modname = node.get("lua:module")
|
|
1068
|
+
classname = node.get("lua:class")
|
|
1069
|
+
if match := self._find_obj(modname, classname, target, typ):
|
|
1070
|
+
name, (docname, objtype) = match
|
|
1071
|
+
if typ not in ("any", "obj") and typ not in objtype:
|
|
1072
|
+
logger.warning(
|
|
1073
|
+
"reference :lua:%s:`%s` resolved to an object of unexpected type %r",
|
|
1074
|
+
typ,
|
|
1075
|
+
target,
|
|
1076
|
+
objtype,
|
|
1077
|
+
type="lua-ls",
|
|
1078
|
+
location=(node.source, node.line),
|
|
1079
|
+
)
|
|
1080
|
+
if (
|
|
1081
|
+
isinstance(contnode, nodes.literal)
|
|
1082
|
+
and not node["refexplicit"]
|
|
1083
|
+
and len(contnode.children) == 1
|
|
1084
|
+
and isinstance(contnode.children[0], nodes.Text)
|
|
1085
|
+
):
|
|
1086
|
+
title = contnode.astext()
|
|
1087
|
+
new_title = title
|
|
1088
|
+
if objtype in (
|
|
1089
|
+
"function",
|
|
1090
|
+
"method",
|
|
1091
|
+
"classmethod",
|
|
1092
|
+
"staticmethod",
|
|
1093
|
+
) and not new_title.endswith("()"):
|
|
1094
|
+
new_title += "()"
|
|
1095
|
+
if objtype in ("method", "classmethod"):
|
|
1096
|
+
i = new_title.rfind(".")
|
|
1097
|
+
if i != -1:
|
|
1098
|
+
new_title = new_title[:i] + ":" + new_title[i + 1 :]
|
|
1099
|
+
if new_title != title:
|
|
1100
|
+
contnode = contnode.deepcopy()
|
|
1101
|
+
contnode.clear()
|
|
1102
|
+
contnode += nodes.Text(new_title)
|
|
1103
|
+
return make_refnode(
|
|
1104
|
+
builder, fromdocname, docname, "lua:" + name, contnode, name
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
def resolve_any_xref(
|
|
1108
|
+
self,
|
|
1109
|
+
env: BuildEnvironment,
|
|
1110
|
+
fromdocname: str,
|
|
1111
|
+
builder: Builder,
|
|
1112
|
+
target: str,
|
|
1113
|
+
node: addnodes.pending_xref,
|
|
1114
|
+
contnode: nodes.Node,
|
|
1115
|
+
) -> list[tuple[str, nodes.reference]]:
|
|
1116
|
+
modname = node.get("lua:module")
|
|
1117
|
+
classname = node.get("lua:class")
|
|
1118
|
+
if match := self._find_obj(modname, classname, target, None):
|
|
1119
|
+
name, (docname, objtype) = match
|
|
1120
|
+
role = "lua:" + (self.role_for_objtype(objtype, None) or "obj")
|
|
1121
|
+
return [
|
|
1122
|
+
(
|
|
1123
|
+
role,
|
|
1124
|
+
make_refnode(
|
|
1125
|
+
builder, fromdocname, docname, "lua:" + name, contnode, name
|
|
1126
|
+
),
|
|
1127
|
+
)
|
|
1128
|
+
]
|
|
1129
|
+
|
|
1130
|
+
return []
|
|
1131
|
+
|
|
1132
|
+
def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
|
|
1133
|
+
for modname, info in self.data["modules"].items():
|
|
1134
|
+
yield (modname, modname, "module", info[0], "lua:" + modname, 0)
|
|
1135
|
+
for refname, (docname, type) in self.data["objects"].items():
|
|
1136
|
+
if type != "module": # modules are already handled
|
|
1137
|
+
yield (refname, refname, type, docname, refname, 1)
|
|
1138
|
+
|
|
1139
|
+
def get_full_qualified_name(self, node: nodes.Element) -> str | None:
|
|
1140
|
+
modname = node.get("lua:module")
|
|
1141
|
+
classname = node.get("lua:class")
|
|
1142
|
+
target = node.get("reftarget")
|
|
1143
|
+
if target is None:
|
|
1144
|
+
return None
|
|
1145
|
+
else:
|
|
1146
|
+
return ".".join(filter(None, [modname, classname, target]))
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def setup(app):
|
|
1150
|
+
app.add_domain(LuaDomain)
|
|
1151
|
+
|
|
1152
|
+
return {
|
|
1153
|
+
"version": "builtin",
|
|
1154
|
+
"parallel_read_safe": True,
|
|
1155
|
+
"parallel_write_safe": True,
|
|
1156
|
+
}
|