python-delphi-lsp 3.3.0__py3-none-any.whl → 3.4.0__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.
- delphi_lsp/_version.py +1 -1
- delphi_lsp/agent_cache.py +110 -55
- delphi_lsp/agent_cli.py +117 -23
- delphi_lsp/agent_context.py +668 -72
- delphi_lsp/agent_layers.py +218 -29
- delphi_lsp/agent_metrics.py +227 -15
- delphi_lsp/agent_relations.py +475 -50
- delphi_lsp/agent_wiki.py +16 -7
- delphi_lsp/agent_workspace.py +18 -0
- delphi_lsp/delphiast_lexer.py +156 -39
- delphi_lsp/delphiast_parser.py +336 -48
- delphi_lsp/delphiast_tokens.py +4 -4
- delphi_lsp/incremental.py +1139 -0
- delphi_lsp/lark_builder.py +6 -3
- delphi_lsp/lsp_server.py +4045 -207
- delphi_lsp/metrics.py +654 -75
- delphi_lsp/navigation_cache.py +2 -2
- delphi_lsp/nodes.py +93 -2
- delphi_lsp/parallel_outline.py +349 -44
- delphi_lsp/parse_artifacts.py +969 -0
- delphi_lsp/parser.py +21 -2
- delphi_lsp/parser_backend.py +13 -2
- delphi_lsp/preprocessor.py +28 -14
- delphi_lsp/project_config.py +22 -4
- delphi_lsp/project_indexer.py +31 -7
- delphi_lsp/response_cache.py +465 -0
- delphi_lsp/semantic.py +4 -1
- delphi_lsp/semantic_builder.py +746 -21
- {python_delphi_lsp-3.3.0.dist-info → python_delphi_lsp-3.4.0.dist-info}/METADATA +17 -2
- python_delphi_lsp-3.4.0.dist-info/RECORD +51 -0
- python_delphi_lsp-3.3.0.dist-info/RECORD +0 -48
- {python_delphi_lsp-3.3.0.dist-info → python_delphi_lsp-3.4.0.dist-info}/WHEEL +0 -0
- {python_delphi_lsp-3.3.0.dist-info → python_delphi_lsp-3.4.0.dist-info}/entry_points.txt +0 -0
- {python_delphi_lsp-3.3.0.dist-info → python_delphi_lsp-3.4.0.dist-info}/licenses/LICENSE +0 -0
- {python_delphi_lsp-3.3.0.dist-info → python_delphi_lsp-3.4.0.dist-info}/top_level.txt +0 -0
delphi_lsp/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "3.
|
|
1
|
+
__version__ = "3.4.0"
|
delphi_lsp/agent_cache.py
CHANGED
|
@@ -54,6 +54,7 @@ _WATCHER_READY_TIMEOUT_SECONDS = 1.0
|
|
|
54
54
|
_WATCHER_READY_POLL_MILLISECONDS = 50
|
|
55
55
|
_STOP_TIMEOUT_SECONDS = 15.0
|
|
56
56
|
_STOP_SIGNAL_DELAY_SECONDS = 3.0
|
|
57
|
+
_STOP_FORCE_TIMEOUT_SECONDS = 2.0
|
|
57
58
|
_WATCHED_SUFFIXES = frozenset(
|
|
58
59
|
{".pas", ".pp", ".inc", ".dpr", ".dpk", ".dproj", ".cfg"}
|
|
59
60
|
)
|
|
@@ -365,9 +366,12 @@ class _RequestGuard:
|
|
|
365
366
|
self,
|
|
366
367
|
service: _CacheService,
|
|
367
368
|
connection: socket.socket,
|
|
369
|
+
*,
|
|
370
|
+
enforce_rss_budget: bool = True,
|
|
368
371
|
) -> None:
|
|
369
372
|
self._service = service
|
|
370
373
|
self._connection = connection
|
|
374
|
+
self._enforce_rss_budget = enforce_rss_budget
|
|
371
375
|
|
|
372
376
|
def check(self) -> None:
|
|
373
377
|
if self._service.shutdown.is_set():
|
|
@@ -394,14 +398,14 @@ class _RequestGuard:
|
|
|
394
398
|
"client_disconnected",
|
|
395
399
|
"Cache client disconnected.",
|
|
396
400
|
) from error
|
|
397
|
-
rss_bytes = current_process_rss_bytes()
|
|
398
401
|
if (
|
|
399
|
-
|
|
400
|
-
and
|
|
402
|
+
self._enforce_rss_budget
|
|
403
|
+
and self._service._measure_retained_bytes()
|
|
404
|
+
> self._service.metadata.max_memory_bytes
|
|
401
405
|
):
|
|
402
406
|
raise CacheClientError(
|
|
403
407
|
"memory_budget_exceeded",
|
|
404
|
-
"Cache request exceeded the configured
|
|
408
|
+
"Cache request exceeded the configured retained-memory budget.",
|
|
405
409
|
)
|
|
406
410
|
|
|
407
411
|
|
|
@@ -854,11 +858,15 @@ class _CacheService:
|
|
|
854
858
|
context = self._initialize_context()
|
|
855
859
|
try:
|
|
856
860
|
self.last_revision = context.prewarm_navigation()
|
|
857
|
-
self.cache_state = "warm"
|
|
858
861
|
except AgentProtocolError as error:
|
|
859
862
|
if error.code != "project_required":
|
|
860
863
|
raise
|
|
861
|
-
|
|
864
|
+
prewarm_rss = current_process_rss_bytes()
|
|
865
|
+
if prewarm_rss > 0:
|
|
866
|
+
# Parsing can leave a high allocator RSS watermark even though the
|
|
867
|
+
# retained navigation data is already covered by its own estimate.
|
|
868
|
+
# Track only subsequent, otherwise unaccounted-for RSS growth.
|
|
869
|
+
self._rss_baseline_bytes = prewarm_rss
|
|
862
870
|
self._enforce_budget()
|
|
863
871
|
self.prewarm_seconds = time.monotonic() - started
|
|
864
872
|
self.stats.parallel_fallbacks = context.parallel_stats.fallbacks
|
|
@@ -921,58 +929,86 @@ class _CacheService:
|
|
|
921
929
|
self.context.invalidate_revision_cache()
|
|
922
930
|
with self._request_state_lock:
|
|
923
931
|
self._active_requests += 1
|
|
932
|
+
lock_acquired = False
|
|
924
933
|
try:
|
|
925
|
-
|
|
926
|
-
self.
|
|
927
|
-
|
|
928
|
-
|
|
934
|
+
while not self.lock.acquire(timeout=0.05):
|
|
935
|
+
if self.shutdown.is_set():
|
|
936
|
+
raise CacheClientError(
|
|
937
|
+
"request_cancelled",
|
|
938
|
+
"Cache request was cancelled.",
|
|
939
|
+
)
|
|
940
|
+
if cancel_check is not None:
|
|
941
|
+
cancel_check()
|
|
942
|
+
lock_acquired = True
|
|
943
|
+
if self.shutdown.is_set():
|
|
944
|
+
raise CacheClientError(
|
|
945
|
+
"request_cancelled",
|
|
946
|
+
"Cache request was cancelled.",
|
|
947
|
+
)
|
|
948
|
+
if cancel_check is not None:
|
|
949
|
+
cancel_check()
|
|
950
|
+
self.last_activity = time.monotonic()
|
|
951
|
+
before = self.last_revision
|
|
952
|
+
was_warm = self.context.navigation_cache_is_warm
|
|
953
|
+
request_aborted = False
|
|
954
|
+
try:
|
|
929
955
|
try:
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
cancel_check=cancel_check,
|
|
937
|
-
)
|
|
956
|
+
handled = (
|
|
957
|
+
self.context.handle(request)
|
|
958
|
+
if cancel_check is None
|
|
959
|
+
else self.context.handle(
|
|
960
|
+
request,
|
|
961
|
+
cancel_check=cancel_check,
|
|
938
962
|
)
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
raise
|
|
942
|
-
except AgentProtocolError as error:
|
|
943
|
-
raise CacheClientError(
|
|
944
|
-
error.code,
|
|
945
|
-
error.message,
|
|
946
|
-
) from None
|
|
947
|
-
except (OSError, UnicodeError):
|
|
948
|
-
raise CacheClientError(
|
|
949
|
-
"source_unavailable",
|
|
950
|
-
"Source is unavailable.",
|
|
951
|
-
) from None
|
|
952
|
-
except Exception:
|
|
963
|
+
)
|
|
964
|
+
if self.shutdown.is_set():
|
|
953
965
|
raise CacheClientError(
|
|
954
|
-
"
|
|
955
|
-
"
|
|
956
|
-
)
|
|
957
|
-
|
|
966
|
+
"request_cancelled",
|
|
967
|
+
"Cache request was cancelled.",
|
|
968
|
+
)
|
|
969
|
+
if cancel_check is not None:
|
|
970
|
+
cancel_check()
|
|
971
|
+
response = handled.to_mapping()
|
|
972
|
+
except CacheClientError:
|
|
973
|
+
request_aborted = True
|
|
974
|
+
raise
|
|
975
|
+
except AgentProtocolError as error:
|
|
976
|
+
raise CacheClientError(
|
|
977
|
+
error.code,
|
|
978
|
+
error.message,
|
|
979
|
+
) from None
|
|
980
|
+
except (OSError, UnicodeError):
|
|
981
|
+
raise CacheClientError(
|
|
982
|
+
"source_unavailable",
|
|
983
|
+
"Source is unavailable.",
|
|
984
|
+
) from None
|
|
985
|
+
except Exception:
|
|
986
|
+
raise CacheClientError(
|
|
987
|
+
"internal_error",
|
|
988
|
+
"Internal cache error.",
|
|
989
|
+
) from None
|
|
990
|
+
finally:
|
|
991
|
+
if not request_aborted and not self.shutdown.is_set():
|
|
958
992
|
self._enforce_budget()
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
)
|
|
969
|
-
if before != after:
|
|
970
|
-
self.stats.invalidations += 1
|
|
971
|
-
return CacheClientResponse(
|
|
972
|
-
response,
|
|
973
|
-
self._consume_warning(),
|
|
993
|
+
after = str(response["workspace_revision"])
|
|
994
|
+
self.last_revision = after
|
|
995
|
+
self.stats.requests += 1
|
|
996
|
+
if was_warm:
|
|
997
|
+
self.stats.warm_hits += 1
|
|
998
|
+
else:
|
|
999
|
+
self.stats.rebuilds += 1
|
|
1000
|
+
self.stats.parallel_fallbacks += (
|
|
1001
|
+
self.context.parallel_stats.fallbacks
|
|
974
1002
|
)
|
|
1003
|
+
if before != after:
|
|
1004
|
+
self.stats.invalidations += 1
|
|
1005
|
+
return CacheClientResponse(
|
|
1006
|
+
response,
|
|
1007
|
+
self._consume_warning(),
|
|
1008
|
+
)
|
|
975
1009
|
finally:
|
|
1010
|
+
if lock_acquired:
|
|
1011
|
+
self.lock.release()
|
|
976
1012
|
with self._request_state_lock:
|
|
977
1013
|
self._active_requests -= 1
|
|
978
1014
|
|
|
@@ -1142,10 +1178,11 @@ def _serve_connection(connection: socket.socket, service: _CacheService) -> None
|
|
|
1142
1178
|
response: dict[str, object] = {"error": {"code": "authentication_failed", "message": "authentication failed"}}
|
|
1143
1179
|
else:
|
|
1144
1180
|
try:
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1181
|
+
action = request.get("action")
|
|
1182
|
+
guard = None if action in {"status", "stop"} else _RequestGuard(
|
|
1183
|
+
service,
|
|
1184
|
+
connection,
|
|
1185
|
+
enforce_rss_budget=action in {"trace", "cpg"},
|
|
1149
1186
|
)
|
|
1150
1187
|
result = service.request(
|
|
1151
1188
|
request,
|
|
@@ -1476,6 +1513,24 @@ def stop_cache(root: str | Path) -> None:
|
|
|
1476
1513
|
os.kill(metadata.pid, signal.SIGTERM)
|
|
1477
1514
|
signal_sent = True
|
|
1478
1515
|
time.sleep(0.05)
|
|
1516
|
+
if not stopped and _pid_alive(metadata.pid):
|
|
1517
|
+
current = _read_metadata(root)
|
|
1518
|
+
if current == metadata:
|
|
1519
|
+
force_signal = (
|
|
1520
|
+
signal.SIGKILL
|
|
1521
|
+
if os.name != "nt" and hasattr(signal, "SIGKILL")
|
|
1522
|
+
else signal.SIGTERM
|
|
1523
|
+
)
|
|
1524
|
+
with contextlib.suppress(OSError):
|
|
1525
|
+
os.kill(metadata.pid, force_signal)
|
|
1526
|
+
force_deadline = time.monotonic() + _STOP_FORCE_TIMEOUT_SECONDS
|
|
1527
|
+
while time.monotonic() < force_deadline:
|
|
1528
|
+
if _reap_child_if_exited(metadata.pid) or not _pid_alive(
|
|
1529
|
+
metadata.pid
|
|
1530
|
+
):
|
|
1531
|
+
stopped = True
|
|
1532
|
+
break
|
|
1533
|
+
time.sleep(0.05)
|
|
1479
1534
|
if not stopped and _pid_alive(metadata.pid):
|
|
1480
1535
|
raise CacheClientError("stop_failed", "Cache daemon did not stop.")
|
|
1481
1536
|
_remove_metadata_if_owned(metadata)
|
delphi_lsp/agent_cli.py
CHANGED
|
@@ -27,6 +27,7 @@ from .agent_cache import (
|
|
|
27
27
|
from .agent_context import AgentContext
|
|
28
28
|
from .agent_layers import (
|
|
29
29
|
build_codebase_index,
|
|
30
|
+
build_metrics_payload,
|
|
30
31
|
build_references_payload,
|
|
31
32
|
layer_payload,
|
|
32
33
|
render_layer,
|
|
@@ -45,6 +46,12 @@ from .agent_templates import install_opencode_support, install_skill
|
|
|
45
46
|
from .agent_wiki import WikiExportError, WikiProgressEvent, export_okf_wiki
|
|
46
47
|
from .parallel_outline import ParallelOutlineError, parse_worker_setting
|
|
47
48
|
from .project_config import ProjectConfigError
|
|
49
|
+
from .response_cache import (
|
|
50
|
+
ResponseCacheKey,
|
|
51
|
+
ResponseCacheStore,
|
|
52
|
+
WorkspaceContentSnapshot,
|
|
53
|
+
workspace_content_snapshot,
|
|
54
|
+
)
|
|
48
55
|
|
|
49
56
|
_MAX_WORKER_RECORD_BYTES = 1024 * 1024
|
|
50
57
|
_INVALID_JSON_MESSAGE = "Invalid JSON request."
|
|
@@ -323,6 +330,17 @@ def _discard_broken_stdout() -> None:
|
|
|
323
330
|
def _view(args: argparse.Namespace) -> None:
|
|
324
331
|
args.root = _workspace_root(args.root)
|
|
325
332
|
args.project_file = _project_file(args.root, args.project_file)
|
|
333
|
+
store, cache_key, snapshot = _batch_response_cache(
|
|
334
|
+
args,
|
|
335
|
+
command="view",
|
|
336
|
+
layer=args.layer,
|
|
337
|
+
query=args.query,
|
|
338
|
+
output_format=args.format,
|
|
339
|
+
)
|
|
340
|
+
cached = store.load(cache_key)
|
|
341
|
+
if cached is not None:
|
|
342
|
+
sys.stdout.write(cached.decode("utf-8"))
|
|
343
|
+
return
|
|
326
344
|
if args.layer == "references":
|
|
327
345
|
payload = build_references_payload(
|
|
328
346
|
args.root,
|
|
@@ -330,40 +348,112 @@ def _view(args: argparse.Namespace) -> None:
|
|
|
330
348
|
project_file=args.project_file,
|
|
331
349
|
workers=args.workers,
|
|
332
350
|
)
|
|
333
|
-
|
|
334
|
-
|
|
351
|
+
rendered = render_payload(payload, output_format=args.format)
|
|
352
|
+
elif args.layer == "metrics":
|
|
353
|
+
payload = build_metrics_payload(
|
|
354
|
+
args.root,
|
|
355
|
+
query=args.query,
|
|
356
|
+
project_file=args.project_file,
|
|
357
|
+
workers=args.workers,
|
|
358
|
+
discovery=snapshot.discovery,
|
|
335
359
|
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
360
|
+
if args.format == "json":
|
|
361
|
+
rendered = _json_text(payload)
|
|
362
|
+
else:
|
|
363
|
+
rendered = render_payload(payload, output_format=args.format)
|
|
364
|
+
else:
|
|
365
|
+
index = build_codebase_index(
|
|
366
|
+
args.root,
|
|
367
|
+
project_file=args.project_file,
|
|
368
|
+
index_projects=args.deep_projects,
|
|
369
|
+
workers=args.workers,
|
|
370
|
+
use_artifact_cache=False,
|
|
371
|
+
discovery=snapshot.discovery,
|
|
372
|
+
)
|
|
373
|
+
rendered = render_layer(
|
|
374
|
+
index,
|
|
375
|
+
args.layer,
|
|
376
|
+
query=args.query,
|
|
377
|
+
output_format=args.format,
|
|
378
|
+
)
|
|
379
|
+
encoded = rendered.encode("utf-8")
|
|
380
|
+
_store_batch_response(store, cache_key, encoded)
|
|
381
|
+
sys.stdout.write(rendered)
|
|
344
382
|
|
|
345
383
|
|
|
346
384
|
def _index(args: argparse.Namespace) -> None:
|
|
347
385
|
args.root = _workspace_root(args.root)
|
|
348
386
|
args.project_file = _project_file(args.root, args.project_file)
|
|
349
387
|
output = _resolve_output_path(args.root, args.out)
|
|
350
|
-
|
|
351
|
-
args
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
388
|
+
store, cache_key, snapshot = _batch_response_cache(
|
|
389
|
+
args,
|
|
390
|
+
command="index",
|
|
391
|
+
layer="",
|
|
392
|
+
query="",
|
|
393
|
+
output_format="json",
|
|
355
394
|
)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
395
|
+
encoded = store.load(cache_key)
|
|
396
|
+
if encoded is None:
|
|
397
|
+
index = build_codebase_index(
|
|
398
|
+
args.root,
|
|
399
|
+
project_file=args.project_file,
|
|
400
|
+
index_projects=args.deep_projects,
|
|
401
|
+
workers=args.workers,
|
|
402
|
+
use_artifact_cache=False,
|
|
403
|
+
discovery=snapshot.discovery,
|
|
404
|
+
)
|
|
405
|
+
payload = {
|
|
406
|
+
"deep_projects": bool(args.deep_projects),
|
|
407
|
+
"overview": layer_payload(index, "overview"),
|
|
408
|
+
"projects": layer_payload(index, "projects"),
|
|
409
|
+
"problems": layer_payload(index, "problems"),
|
|
410
|
+
}
|
|
411
|
+
encoded = (json.dumps(payload, indent=2, sort_keys=True) + "\n").encode(
|
|
412
|
+
"utf-8"
|
|
413
|
+
)
|
|
414
|
+
_store_batch_response(store, cache_key, encoded)
|
|
362
415
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
363
|
-
output.
|
|
416
|
+
output.write_bytes(encoded)
|
|
364
417
|
print(output)
|
|
365
418
|
|
|
366
419
|
|
|
420
|
+
def _batch_response_cache(
|
|
421
|
+
args: argparse.Namespace,
|
|
422
|
+
*,
|
|
423
|
+
command: str,
|
|
424
|
+
layer: str,
|
|
425
|
+
query: str,
|
|
426
|
+
output_format: str,
|
|
427
|
+
) -> tuple[ResponseCacheStore, ResponseCacheKey, WorkspaceContentSnapshot]:
|
|
428
|
+
snapshot = workspace_content_snapshot(
|
|
429
|
+
args.root,
|
|
430
|
+
project_file=args.project_file,
|
|
431
|
+
)
|
|
432
|
+
return (
|
|
433
|
+
ResponseCacheStore(args.root / ".delphi-lsp" / "response-cache"),
|
|
434
|
+
ResponseCacheKey(
|
|
435
|
+
workspace_revision=snapshot.revision,
|
|
436
|
+
command=command,
|
|
437
|
+
layer=layer,
|
|
438
|
+
query=query,
|
|
439
|
+
output_format=output_format,
|
|
440
|
+
deep_projects=bool(args.deep_projects),
|
|
441
|
+
),
|
|
442
|
+
snapshot,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _store_batch_response(
|
|
447
|
+
store: ResponseCacheStore,
|
|
448
|
+
key: ResponseCacheKey,
|
|
449
|
+
payload: bytes,
|
|
450
|
+
) -> None:
|
|
451
|
+
try:
|
|
452
|
+
store.store(key, payload)
|
|
453
|
+
except (OSError, ValueError):
|
|
454
|
+
pass
|
|
455
|
+
|
|
456
|
+
|
|
367
457
|
def _wiki_export(args: argparse.Namespace) -> None:
|
|
368
458
|
args.root = _workspace_root(args.root)
|
|
369
459
|
args.project_file = _project_file(args.root, args.project_file)
|
|
@@ -518,7 +608,11 @@ def _open_worker_context(
|
|
|
518
608
|
|
|
519
609
|
|
|
520
610
|
def _write_json(payload: object) -> None:
|
|
521
|
-
sys.stdout.write(
|
|
611
|
+
sys.stdout.write(_json_text(payload))
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def _json_text(payload: object) -> str:
|
|
615
|
+
return json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n"
|
|
522
616
|
|
|
523
617
|
|
|
524
618
|
def _write_warning(warning: str) -> None:
|