avrae-ls 0.6.4__py3-none-any.whl → 0.7.1__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.
- avrae_ls/__main__.py +54 -5
- avrae_ls/alias_preview.py +66 -6
- avrae_ls/alias_tests.py +13 -1
- avrae_ls/ast_utils.py +14 -0
- avrae_ls/code_actions.py +6 -2
- avrae_ls/completions.py +191 -1156
- avrae_ls/config.py +1 -0
- avrae_ls/context.py +62 -32
- avrae_ls/diagnostics.py +33 -60
- avrae_ls/lsp_utils.py +41 -0
- avrae_ls/parser.py +30 -3
- avrae_ls/server.py +85 -47
- avrae_ls/source_context.py +30 -0
- avrae_ls/symbols.py +27 -60
- avrae_ls/type_inference.py +470 -0
- avrae_ls/type_system.py +729 -0
- {avrae_ls-0.6.4.dist-info → avrae_ls-0.7.1.dist-info}/METADATA +6 -1
- avrae_ls-0.7.1.dist-info/RECORD +39 -0
- avrae_ls-0.6.4.dist-info/RECORD +0 -34
- {avrae_ls-0.6.4.dist-info → avrae_ls-0.7.1.dist-info}/WHEEL +0 -0
- {avrae_ls-0.6.4.dist-info → avrae_ls-0.7.1.dist-info}/entry_points.txt +0 -0
- {avrae_ls-0.6.4.dist-info → avrae_ls-0.7.1.dist-info}/licenses/LICENSE +0 -0
avrae_ls/config.py
CHANGED
|
@@ -82,6 +82,7 @@ class ContextProfile:
|
|
|
82
82
|
class AvraeLSConfig:
|
|
83
83
|
workspace_root: Path
|
|
84
84
|
enable_gvar_fetch: bool = False
|
|
85
|
+
silent_gvar_fetch: bool = False
|
|
85
86
|
service: AvraeServiceConfig = field(default_factory=AvraeServiceConfig)
|
|
86
87
|
var_files: Tuple[Path, ...] = field(default_factory=tuple)
|
|
87
88
|
default_profile: str = "default"
|
avrae_ls/context.py
CHANGED
|
@@ -145,6 +145,19 @@ class GVarResolver:
|
|
|
145
145
|
self._config = config
|
|
146
146
|
self._cache: Dict[str, Any] = {}
|
|
147
147
|
|
|
148
|
+
def _silent_failure(self, key: str) -> bool:
|
|
149
|
+
if not self._config.silent_gvar_fetch:
|
|
150
|
+
return False
|
|
151
|
+
self._cache[str(key)] = None
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
def _silent_failure_many(self, keys: Iterable[str]) -> bool:
|
|
155
|
+
if not self._config.silent_gvar_fetch:
|
|
156
|
+
return False
|
|
157
|
+
for key in keys:
|
|
158
|
+
self._cache[str(key)] = None
|
|
159
|
+
return True
|
|
160
|
+
|
|
148
161
|
def reset(self, gvars: Dict[str, Any] | None = None) -> None:
|
|
149
162
|
self._cache = {}
|
|
150
163
|
if gvars:
|
|
@@ -176,10 +189,18 @@ class GVarResolver:
|
|
|
176
189
|
if not missing:
|
|
177
190
|
return results
|
|
178
191
|
if not self._config.enable_gvar_fetch:
|
|
179
|
-
|
|
192
|
+
if not self._config.silent_gvar_fetch:
|
|
193
|
+
log.warning("GVAR fetch disabled; skipping %s", missing)
|
|
194
|
+
if self._silent_failure_many(missing):
|
|
195
|
+
for key in missing:
|
|
196
|
+
results[key] = True
|
|
180
197
|
return results
|
|
181
198
|
if not self._config.service.token:
|
|
182
|
-
|
|
199
|
+
if not self._config.silent_gvar_fetch:
|
|
200
|
+
log.debug("GVAR fetch skipped for %s: no token configured", missing)
|
|
201
|
+
if self._silent_failure_many(missing):
|
|
202
|
+
for key in missing:
|
|
203
|
+
results[key] = True
|
|
183
204
|
return results
|
|
184
205
|
|
|
185
206
|
sem = asyncio.Semaphore(self._CONCURRENCY)
|
|
@@ -191,8 +212,9 @@ class GVarResolver:
|
|
|
191
212
|
try:
|
|
192
213
|
ensured = await self._fetch_remote(key, client=client, sem=sem)
|
|
193
214
|
except Exception as exc: # pragma: no cover - defensive
|
|
194
|
-
|
|
195
|
-
|
|
215
|
+
if not self._config.silent_gvar_fetch:
|
|
216
|
+
log.error("GVAR fetch failed for %s: %s", key, exc)
|
|
217
|
+
ensured = self._silent_failure(key)
|
|
196
218
|
results[key] = ensured
|
|
197
219
|
|
|
198
220
|
async with httpx.AsyncClient(timeout=5) as client:
|
|
@@ -205,11 +227,13 @@ class GVarResolver:
|
|
|
205
227
|
log.debug("GVAR ensure_blocking cache hit for %s", key)
|
|
206
228
|
return True
|
|
207
229
|
if not self._config.enable_gvar_fetch:
|
|
208
|
-
|
|
209
|
-
|
|
230
|
+
if not self._config.silent_gvar_fetch:
|
|
231
|
+
log.warning("GVAR fetch disabled; skipping %s", key)
|
|
232
|
+
return self._silent_failure(key)
|
|
210
233
|
if not self._config.service.token:
|
|
211
|
-
|
|
212
|
-
|
|
234
|
+
if not self._config.silent_gvar_fetch:
|
|
235
|
+
log.debug("GVAR fetch skipped for %s: no token configured", key)
|
|
236
|
+
return self._silent_failure(key)
|
|
213
237
|
|
|
214
238
|
base_url = self._config.service.base_url.rstrip("/")
|
|
215
239
|
url = f"{base_url}/customizations/gvars/{key}"
|
|
@@ -219,17 +243,19 @@ class GVarResolver:
|
|
|
219
243
|
with httpx.Client(timeout=5) as client:
|
|
220
244
|
resp = client.get(url, headers=headers)
|
|
221
245
|
except Exception as exc:
|
|
222
|
-
|
|
223
|
-
|
|
246
|
+
if not self._config.silent_gvar_fetch:
|
|
247
|
+
log.error("GVAR blocking fetch failed for %s: %s", key, exc)
|
|
248
|
+
return self._silent_failure(key)
|
|
224
249
|
|
|
225
250
|
if resp.status_code != 200:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
251
|
+
if not self._config.silent_gvar_fetch:
|
|
252
|
+
log.warning(
|
|
253
|
+
"GVAR blocking fetch returned %s for %s (body: %s)",
|
|
254
|
+
resp.status_code,
|
|
255
|
+
key,
|
|
256
|
+
(resp.text or "").strip(),
|
|
257
|
+
)
|
|
258
|
+
return self._silent_failure(key)
|
|
233
259
|
|
|
234
260
|
value: Any = None
|
|
235
261
|
try:
|
|
@@ -241,8 +267,9 @@ class GVarResolver:
|
|
|
241
267
|
value = payload["value"]
|
|
242
268
|
|
|
243
269
|
if value is None:
|
|
244
|
-
|
|
245
|
-
|
|
270
|
+
if not self._config.silent_gvar_fetch:
|
|
271
|
+
log.error("GVAR %s payload missing value", key)
|
|
272
|
+
return self._silent_failure(key)
|
|
246
273
|
self._cache[key] = value
|
|
247
274
|
return True
|
|
248
275
|
|
|
@@ -262,9 +289,9 @@ class GVarResolver:
|
|
|
262
289
|
if key in self._cache:
|
|
263
290
|
return True
|
|
264
291
|
if not self._config.enable_gvar_fetch:
|
|
265
|
-
return
|
|
292
|
+
return self._silent_failure(key)
|
|
266
293
|
if not self._config.service.token:
|
|
267
|
-
return
|
|
294
|
+
return self._silent_failure(key)
|
|
268
295
|
|
|
269
296
|
base_url = self._config.service.base_url.rstrip("/")
|
|
270
297
|
url = f"{base_url}/customizations/gvars/{key}"
|
|
@@ -286,21 +313,23 @@ class GVarResolver:
|
|
|
286
313
|
log.debug("GVAR fetching %s from %s", key, url)
|
|
287
314
|
resp = await _do_request(session)
|
|
288
315
|
except Exception as exc:
|
|
289
|
-
|
|
316
|
+
if not self._config.silent_gvar_fetch:
|
|
317
|
+
log.error("GVAR fetch failed for %s: %s", key, exc)
|
|
290
318
|
if close_client:
|
|
291
319
|
await session.aclose()
|
|
292
|
-
return
|
|
320
|
+
return self._silent_failure(key)
|
|
293
321
|
if close_client:
|
|
294
322
|
await session.aclose()
|
|
295
323
|
|
|
296
324
|
if resp.status_code != 200:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
325
|
+
if not self._config.silent_gvar_fetch:
|
|
326
|
+
log.warning(
|
|
327
|
+
"GVAR fetch returned %s for %s (body: %s)",
|
|
328
|
+
resp.status_code,
|
|
329
|
+
key,
|
|
330
|
+
(resp.text or "").strip(),
|
|
331
|
+
)
|
|
332
|
+
return self._silent_failure(key)
|
|
304
333
|
|
|
305
334
|
value: Any = None
|
|
306
335
|
try:
|
|
@@ -314,8 +343,9 @@ class GVarResolver:
|
|
|
314
343
|
log.debug("GVAR fetch parsed value for %s (type=%s)", key, type(value).__name__)
|
|
315
344
|
|
|
316
345
|
if value is None:
|
|
317
|
-
|
|
318
|
-
|
|
346
|
+
if not self._config.silent_gvar_fetch:
|
|
347
|
+
log.error("GVAR %s payload missing value", key)
|
|
348
|
+
return self._silent_failure(key)
|
|
319
349
|
self._cache[key] = value
|
|
320
350
|
return True
|
|
321
351
|
|
avrae_ls/diagnostics.py
CHANGED
|
@@ -10,11 +10,14 @@ from lsprotocol import types
|
|
|
10
10
|
|
|
11
11
|
from .alias_preview import simulate_command
|
|
12
12
|
from .codes import MISSING_GVAR_CODE, UNDEFINED_NAME_CODE, UNSUPPORTED_IMPORT_CODE
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
13
|
+
from .source_context import build_source_context
|
|
14
|
+
from .type_inference import infer_type_map, resolve_type_name
|
|
15
|
+
from .type_system import type_meta
|
|
15
16
|
from .config import DiagnosticSettings
|
|
16
17
|
from .context import ContextData, GVarResolver
|
|
17
|
-
from .parser import
|
|
18
|
+
from .parser import wrap_draconic
|
|
19
|
+
from .lsp_utils import range_from_positions, shift_range
|
|
20
|
+
from .ast_utils import collect_target_names
|
|
18
21
|
from .runtime import MockExecutor, _default_builtins
|
|
19
22
|
|
|
20
23
|
log = logging.getLogger(__name__)
|
|
@@ -37,17 +40,19 @@ class DiagnosticProvider:
|
|
|
37
40
|
source: str,
|
|
38
41
|
ctx_data: ContextData,
|
|
39
42
|
gvar_resolver: GVarResolver,
|
|
43
|
+
*,
|
|
44
|
+
treat_as_module: bool = False,
|
|
40
45
|
) -> List[types.Diagnostic]:
|
|
41
46
|
diagnostics: list[types.Diagnostic] = []
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
blocks =
|
|
48
|
+
source_ctx = build_source_context(source, treat_as_module)
|
|
49
|
+
blocks = source_ctx.blocks
|
|
45
50
|
if not blocks:
|
|
46
|
-
plain = _plain_command_diagnostics(
|
|
51
|
+
plain = _plain_command_diagnostics(source_ctx.prepared)
|
|
47
52
|
if plain is not None:
|
|
48
53
|
diagnostics.extend(plain)
|
|
49
54
|
return diagnostics
|
|
50
|
-
diagnostics.extend(await self._analyze_code(
|
|
55
|
+
diagnostics.extend(await self._analyze_code(source_ctx.prepared, ctx_data, gvar_resolver))
|
|
51
56
|
return diagnostics
|
|
52
57
|
|
|
53
58
|
for block in blocks:
|
|
@@ -67,7 +72,7 @@ class DiagnosticProvider:
|
|
|
67
72
|
try:
|
|
68
73
|
body = parser.parse(code)
|
|
69
74
|
except draconic.DraconicSyntaxError as exc:
|
|
70
|
-
wrapped, added =
|
|
75
|
+
wrapped, added = wrap_draconic(code)
|
|
71
76
|
try:
|
|
72
77
|
body = parser.parse(wrapped)
|
|
73
78
|
line_shift = -added
|
|
@@ -252,11 +257,12 @@ class DiagnosticProvider:
|
|
|
252
257
|
|
|
253
258
|
|
|
254
259
|
def _syntax_diagnostic(exc: draconic.DraconicSyntaxError) -> types.Diagnostic:
|
|
255
|
-
rng =
|
|
260
|
+
rng = range_from_positions(
|
|
256
261
|
exc.lineno,
|
|
257
262
|
exc.offset,
|
|
258
263
|
exc.end_lineno,
|
|
259
264
|
exc.end_offset,
|
|
265
|
+
one_based=True,
|
|
260
266
|
)
|
|
261
267
|
return types.Diagnostic(
|
|
262
268
|
message=exc.msg,
|
|
@@ -268,7 +274,13 @@ def _syntax_diagnostic(exc: draconic.DraconicSyntaxError) -> types.Diagnostic:
|
|
|
268
274
|
|
|
269
275
|
def _syntax_from_std(exc: SyntaxError) -> types.Diagnostic:
|
|
270
276
|
lineno, offset = exc.lineno, exc.offset
|
|
271
|
-
rng =
|
|
277
|
+
rng = range_from_positions(
|
|
278
|
+
lineno,
|
|
279
|
+
offset,
|
|
280
|
+
getattr(exc, "end_lineno", None),
|
|
281
|
+
getattr(exc, "end_offset", None),
|
|
282
|
+
one_based=True,
|
|
283
|
+
)
|
|
272
284
|
return types.Diagnostic(
|
|
273
285
|
message=exc.msg,
|
|
274
286
|
range=rng,
|
|
@@ -278,16 +290,7 @@ def _syntax_from_std(exc: SyntaxError) -> types.Diagnostic:
|
|
|
278
290
|
|
|
279
291
|
|
|
280
292
|
def _names_in_target(target: ast.AST) -> Set[str]:
|
|
281
|
-
|
|
282
|
-
if isinstance(target, ast.Name):
|
|
283
|
-
names.add(target.id)
|
|
284
|
-
elif isinstance(target, ast.Tuple):
|
|
285
|
-
for elt in target.elts:
|
|
286
|
-
names.update(_names_in_target(elt))
|
|
287
|
-
elif isinstance(target, ast.List):
|
|
288
|
-
for elt in target.elts:
|
|
289
|
-
names.update(_names_in_target(elt))
|
|
290
|
-
return names
|
|
293
|
+
return set(collect_target_names([target]))
|
|
291
294
|
|
|
292
295
|
|
|
293
296
|
async def _check_gvars(
|
|
@@ -437,7 +440,7 @@ def _property_call_diagnostics(
|
|
|
437
440
|
base_type = _resolve_expr_type(node.func.value, type_map, code)
|
|
438
441
|
if not base_type:
|
|
439
442
|
return []
|
|
440
|
-
meta =
|
|
443
|
+
meta = type_meta(base_type)
|
|
441
444
|
attr = node.func.attr
|
|
442
445
|
if attr in meta.methods or attr not in meta.attrs:
|
|
443
446
|
return []
|
|
@@ -484,7 +487,7 @@ def _iterable_attr_diagnostics(
|
|
|
484
487
|
base_type = _resolve_expr_type(node.value, type_map, code)
|
|
485
488
|
if not base_type:
|
|
486
489
|
return []
|
|
487
|
-
meta =
|
|
490
|
+
meta = type_meta(base_type)
|
|
488
491
|
attr_meta = meta.attrs.get(node.attr)
|
|
489
492
|
if not attr_meta:
|
|
490
493
|
return []
|
|
@@ -510,18 +513,18 @@ def _iterable_attr_diagnostics(
|
|
|
510
513
|
|
|
511
514
|
|
|
512
515
|
def _diagnostic_type_map(code: str) -> Dict[str, str]:
|
|
513
|
-
mapping =
|
|
516
|
+
mapping = infer_type_map(code)
|
|
514
517
|
if mapping:
|
|
515
518
|
return mapping
|
|
516
|
-
wrapped, _ =
|
|
517
|
-
return
|
|
519
|
+
wrapped, _ = wrap_draconic(code)
|
|
520
|
+
return infer_type_map(wrapped)
|
|
518
521
|
|
|
519
522
|
|
|
520
523
|
def _resolve_expr_type(expr: ast.AST, type_map: Dict[str, str], code: str) -> str:
|
|
521
524
|
expr_text = _expr_to_str(expr)
|
|
522
525
|
if not expr_text:
|
|
523
526
|
return ""
|
|
524
|
-
return
|
|
527
|
+
return resolve_type_name(expr_text, code, type_map)
|
|
525
528
|
|
|
526
529
|
|
|
527
530
|
def _expr_to_str(expr: ast.AST) -> str:
|
|
@@ -584,11 +587,12 @@ def _make_diagnostic(
|
|
|
584
587
|
) -> types.Diagnostic:
|
|
585
588
|
severity = SEVERITY.get(level, types.DiagnosticSeverity.Warning)
|
|
586
589
|
if hasattr(node, "lineno"):
|
|
587
|
-
rng =
|
|
590
|
+
rng = range_from_positions(
|
|
588
591
|
getattr(node, "lineno", 1),
|
|
589
592
|
getattr(node, "col_offset", 0) + 1,
|
|
590
593
|
getattr(node, "end_lineno", None),
|
|
591
594
|
getattr(node, "end_col_offset", None),
|
|
595
|
+
one_based=True,
|
|
592
596
|
)
|
|
593
597
|
else:
|
|
594
598
|
rng = types.Range(
|
|
@@ -611,7 +615,7 @@ def _shift_diagnostics(diags: List[types.Diagnostic], line_offset: int, char_off
|
|
|
611
615
|
shifted.append(
|
|
612
616
|
types.Diagnostic(
|
|
613
617
|
message=diag.message,
|
|
614
|
-
range=
|
|
618
|
+
range=shift_range(diag.range, line_offset, char_offset),
|
|
615
619
|
severity=diag.severity,
|
|
616
620
|
source=diag.source,
|
|
617
621
|
code=diag.code,
|
|
@@ -624,20 +628,6 @@ def _shift_diagnostics(diags: List[types.Diagnostic], line_offset: int, char_off
|
|
|
624
628
|
return shifted
|
|
625
629
|
|
|
626
630
|
|
|
627
|
-
def _shift_range(rng: types.Range, line_offset: int, char_offset: int) -> types.Range:
|
|
628
|
-
def _shift_pos(pos: types.Position) -> types.Position:
|
|
629
|
-
return types.Position(
|
|
630
|
-
line=max(pos.line + line_offset, 0),
|
|
631
|
-
character=max(pos.character + (char_offset if pos.line == 0 else 0), 0),
|
|
632
|
-
)
|
|
633
|
-
|
|
634
|
-
return types.Range(start=_shift_pos(rng.start), end=_shift_pos(rng.end))
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
def _wrap_draconic(code: str) -> tuple[str, int]:
|
|
638
|
-
indented = "\n".join(f" {line}" for line in code.splitlines())
|
|
639
|
-
wrapped = f"def __alias_main__():\n{indented}\n__alias_main__()"
|
|
640
|
-
return wrapped, 1
|
|
641
631
|
|
|
642
632
|
|
|
643
633
|
def _build_builtin_signatures() -> dict[str, inspect.Signature]:
|
|
@@ -788,23 +778,6 @@ def _check_imports(body: Sequence[ast.AST], severity_level: str) -> List[types.D
|
|
|
788
778
|
return diagnostics
|
|
789
779
|
|
|
790
780
|
|
|
791
|
-
def _range_from_positions(
|
|
792
|
-
lineno: int | None,
|
|
793
|
-
col_offset: int | None,
|
|
794
|
-
end_lineno: int | None,
|
|
795
|
-
end_col_offset: int | None,
|
|
796
|
-
) -> types.Range:
|
|
797
|
-
start = types.Position(
|
|
798
|
-
line=max((lineno or 1) - 1, 0),
|
|
799
|
-
character=max((col_offset or 1) - 1, 0),
|
|
800
|
-
)
|
|
801
|
-
end = types.Position(
|
|
802
|
-
line=max(((end_lineno or lineno or 1) - 1), 0),
|
|
803
|
-
character=max(((end_col_offset or col_offset or 1) - 1), 0),
|
|
804
|
-
)
|
|
805
|
-
return types.Range(start=start, end=end)
|
|
806
|
-
|
|
807
|
-
|
|
808
781
|
def _plain_command_diagnostics(source: str) -> list[types.Diagnostic] | None:
|
|
809
782
|
"""Handle simple commands (embed/echo) without draconic blocks."""
|
|
810
783
|
simulated = simulate_command(source)
|
|
@@ -815,7 +788,7 @@ def _plain_command_diagnostics(source: str) -> list[types.Diagnostic] | None:
|
|
|
815
788
|
return [
|
|
816
789
|
types.Diagnostic(
|
|
817
790
|
message=simulated.validation_error,
|
|
818
|
-
range=
|
|
791
|
+
range=range_from_positions(1, 1, 1, 1, one_based=True),
|
|
819
792
|
severity=SEVERITY["warning"],
|
|
820
793
|
source="avrae-ls",
|
|
821
794
|
)
|
avrae_ls/lsp_utils.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from lsprotocol import types
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def range_from_positions(
|
|
7
|
+
lineno: int | None,
|
|
8
|
+
col_offset: int | None,
|
|
9
|
+
end_lineno: int | None,
|
|
10
|
+
end_col_offset: int | None,
|
|
11
|
+
*,
|
|
12
|
+
one_based: bool = False,
|
|
13
|
+
ensure_nonempty: bool = False,
|
|
14
|
+
) -> types.Range:
|
|
15
|
+
start_line = max((lineno or 1) - 1, 0)
|
|
16
|
+
if one_based:
|
|
17
|
+
start_char = max((col_offset or 1) - 1, 0)
|
|
18
|
+
else:
|
|
19
|
+
start_char = max(col_offset or 0, 0)
|
|
20
|
+
end_line = max(((end_lineno or lineno or 1) - 1), 0)
|
|
21
|
+
if one_based:
|
|
22
|
+
end_char = max(((end_col_offset or col_offset or 1) - 1), 0)
|
|
23
|
+
else:
|
|
24
|
+
raw_end_char = end_col_offset if end_col_offset is not None else col_offset
|
|
25
|
+
end_char = max(raw_end_char if raw_end_char is not None else start_char, start_char)
|
|
26
|
+
if ensure_nonempty and end_char <= start_char:
|
|
27
|
+
end_char = start_char + 1
|
|
28
|
+
return types.Range(
|
|
29
|
+
start=types.Position(line=start_line, character=start_char),
|
|
30
|
+
end=types.Position(line=end_line, character=end_char),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def shift_range(rng: types.Range, line_offset: int, char_offset: int) -> types.Range:
|
|
35
|
+
def _shift_pos(pos: types.Position) -> types.Position:
|
|
36
|
+
return types.Position(
|
|
37
|
+
line=max(pos.line + line_offset, 0),
|
|
38
|
+
character=max(pos.character + (char_offset if pos.line == 0 else 0), 0),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return types.Range(start=_shift_pos(rng.start), end=_shift_pos(rng.end))
|
avrae_ls/parser.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import List
|
|
6
7
|
|
|
7
8
|
|
|
@@ -17,9 +18,29 @@ class DraconicBlock:
|
|
|
17
18
|
DRACONIC_RE = re.compile(r"<drac2>([\s\S]*?)</drac2>", re.IGNORECASE)
|
|
18
19
|
INLINE_DRACONIC_RE = re.compile(r"\{\{([\s\S]*?)\}\}", re.DOTALL)
|
|
19
20
|
INLINE_ROLL_RE = re.compile(r"(?<!\{)\{(?!\{)([\s\S]*?)(?<!\})\}(?!\})", re.DOTALL)
|
|
21
|
+
ALIAS_MODULE_SUFFIX = ".alias-module"
|
|
20
22
|
|
|
21
23
|
|
|
22
|
-
def
|
|
24
|
+
def is_alias_module_path(path: str | Path | None) -> bool:
|
|
25
|
+
if path is None:
|
|
26
|
+
return False
|
|
27
|
+
return str(path).endswith(ALIAS_MODULE_SUFFIX)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _full_source_block(source: str) -> DraconicBlock:
|
|
31
|
+
line_count = source.count("\n") + 1 if source else 1
|
|
32
|
+
return DraconicBlock(
|
|
33
|
+
code=source,
|
|
34
|
+
line_offset=0,
|
|
35
|
+
char_offset=0,
|
|
36
|
+
line_count=line_count,
|
|
37
|
+
inline=False,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def find_draconic_blocks(source: str, *, treat_as_module: bool = False) -> List[DraconicBlock]:
|
|
42
|
+
if treat_as_module:
|
|
43
|
+
return [_full_source_block(source)]
|
|
23
44
|
matches: list[tuple[int, DraconicBlock]] = []
|
|
24
45
|
|
|
25
46
|
def _block_from_match(match: re.Match[str], inline: bool = False) -> tuple[int, int, DraconicBlock]:
|
|
@@ -60,9 +81,15 @@ def find_draconic_blocks(source: str) -> List[DraconicBlock]:
|
|
|
60
81
|
return blocks
|
|
61
82
|
|
|
62
83
|
|
|
63
|
-
def primary_block_or_source(source: str) -> tuple[str, int, int]:
|
|
64
|
-
blocks = find_draconic_blocks(source)
|
|
84
|
+
def primary_block_or_source(source: str, *, treat_as_module: bool = False) -> tuple[str, int, int]:
|
|
85
|
+
blocks = find_draconic_blocks(source, treat_as_module=treat_as_module)
|
|
65
86
|
if not blocks:
|
|
66
87
|
return source, 0, 0
|
|
67
88
|
block = blocks[0]
|
|
68
89
|
return block.code, block.line_offset, block.char_offset
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def wrap_draconic(code: str) -> tuple[str, int]:
|
|
93
|
+
indented = "\n".join(f" {line}" for line in code.splitlines())
|
|
94
|
+
wrapped = f"def __alias_main__():\n{indented}\n__alias_main__()"
|
|
95
|
+
return wrapped, 1
|