python-delphi-lsp 3.2.2__py3-none-any.whl → 3.3.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 +157 -30
- delphi_lsp/agent_cli.py +18 -1
- delphi_lsp/agent_context.py +106 -24
- delphi_lsp/agent_layers.py +266 -10
- delphi_lsp/agent_relations.py +268 -12
- delphi_lsp/agent_wiki.py +149 -6
- delphi_lsp/lsp_server.py +161 -28
- delphi_lsp/semantic.py +27 -20
- delphi_lsp/semantic_builder.py +240 -93
- {python_delphi_lsp-3.2.2.dist-info → python_delphi_lsp-3.3.0.dist-info}/METADATA +2 -2
- {python_delphi_lsp-3.2.2.dist-info → python_delphi_lsp-3.3.0.dist-info}/RECORD +16 -16
- {python_delphi_lsp-3.2.2.dist-info → python_delphi_lsp-3.3.0.dist-info}/WHEEL +0 -0
- {python_delphi_lsp-3.2.2.dist-info → python_delphi_lsp-3.3.0.dist-info}/entry_points.txt +0 -0
- {python_delphi_lsp-3.2.2.dist-info → python_delphi_lsp-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {python_delphi_lsp-3.2.2.dist-info → python_delphi_lsp-3.3.0.dist-info}/top_level.txt +0 -0
delphi_lsp/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "3.
|
|
1
|
+
__version__ = "3.3.0"
|
delphi_lsp/agent_cache.py
CHANGED
|
@@ -8,6 +8,8 @@ import math
|
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
import secrets
|
|
11
|
+
import select
|
|
12
|
+
import signal
|
|
11
13
|
import socket
|
|
12
14
|
import stat
|
|
13
15
|
import subprocess
|
|
@@ -20,7 +22,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
20
22
|
from dataclasses import dataclass, fields, is_dataclass, replace
|
|
21
23
|
from functools import lru_cache
|
|
22
24
|
from pathlib import Path
|
|
23
|
-
from types import ModuleType
|
|
25
|
+
from types import FrameType, ModuleType
|
|
24
26
|
|
|
25
27
|
from watchfiles import watch
|
|
26
28
|
|
|
@@ -50,6 +52,8 @@ _START_LOCK_INCOMPLETE_GRACE_SECONDS = 1.0
|
|
|
50
52
|
_CACHE_REVISION_CHECK_INTERVAL_SECONDS = 3600.0
|
|
51
53
|
_WATCHER_READY_TIMEOUT_SECONDS = 1.0
|
|
52
54
|
_WATCHER_READY_POLL_MILLISECONDS = 50
|
|
55
|
+
_STOP_TIMEOUT_SECONDS = 15.0
|
|
56
|
+
_STOP_SIGNAL_DELAY_SECONDS = 3.0
|
|
53
57
|
_WATCHED_SUFFIXES = frozenset(
|
|
54
58
|
{".pas", ".pp", ".inc", ".dpr", ".dpk", ".dproj", ".cfg"}
|
|
55
59
|
)
|
|
@@ -356,6 +360,51 @@ class CacheClientError(RuntimeError):
|
|
|
356
360
|
super().__init__(message)
|
|
357
361
|
|
|
358
362
|
|
|
363
|
+
class _RequestGuard:
|
|
364
|
+
def __init__(
|
|
365
|
+
self,
|
|
366
|
+
service: _CacheService,
|
|
367
|
+
connection: socket.socket,
|
|
368
|
+
) -> None:
|
|
369
|
+
self._service = service
|
|
370
|
+
self._connection = connection
|
|
371
|
+
|
|
372
|
+
def check(self) -> None:
|
|
373
|
+
if self._service.shutdown.is_set():
|
|
374
|
+
raise CacheClientError(
|
|
375
|
+
"request_cancelled",
|
|
376
|
+
"Cache request was cancelled.",
|
|
377
|
+
)
|
|
378
|
+
try:
|
|
379
|
+
readable, _, _ = select.select(
|
|
380
|
+
(self._connection,),
|
|
381
|
+
(),
|
|
382
|
+
(),
|
|
383
|
+
0,
|
|
384
|
+
)
|
|
385
|
+
if readable and not self._connection.recv(1, socket.MSG_PEEK):
|
|
386
|
+
raise CacheClientError(
|
|
387
|
+
"client_disconnected",
|
|
388
|
+
"Cache client disconnected.",
|
|
389
|
+
)
|
|
390
|
+
except CacheClientError:
|
|
391
|
+
raise
|
|
392
|
+
except (OSError, ValueError) as error:
|
|
393
|
+
raise CacheClientError(
|
|
394
|
+
"client_disconnected",
|
|
395
|
+
"Cache client disconnected.",
|
|
396
|
+
) from error
|
|
397
|
+
rss_bytes = current_process_rss_bytes()
|
|
398
|
+
if (
|
|
399
|
+
rss_bytes > 0
|
|
400
|
+
and rss_bytes > self._service.metadata.max_memory_bytes
|
|
401
|
+
):
|
|
402
|
+
raise CacheClientError(
|
|
403
|
+
"memory_budget_exceeded",
|
|
404
|
+
"Cache request exceeded the configured RSS memory budget.",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
359
408
|
def cache_metadata_path(root: str | Path) -> Path:
|
|
360
409
|
return Path(root).resolve() / ".delphi-lsp" / "agent-cache" / "daemon.json"
|
|
361
410
|
|
|
@@ -752,6 +801,8 @@ class _CacheService:
|
|
|
752
801
|
self.budget = CacheBudget(metadata.max_memory_bytes)
|
|
753
802
|
self.stats = CacheStats()
|
|
754
803
|
self.lock = threading.Lock()
|
|
804
|
+
self._request_state_lock = threading.Lock()
|
|
805
|
+
self._active_requests = 0
|
|
755
806
|
self.started = time.monotonic()
|
|
756
807
|
self.last_activity = self.started
|
|
757
808
|
self.cache_state = "warming"
|
|
@@ -836,7 +887,12 @@ class _CacheService:
|
|
|
836
887
|
)
|
|
837
888
|
self.prewarm_thread.start()
|
|
838
889
|
|
|
839
|
-
def request(
|
|
890
|
+
def request(
|
|
891
|
+
self,
|
|
892
|
+
request: dict[str, object],
|
|
893
|
+
*,
|
|
894
|
+
cancel_check: Callable[[], None] | None = None,
|
|
895
|
+
) -> CacheClientResponse:
|
|
840
896
|
action = request.get("action")
|
|
841
897
|
if action == "status":
|
|
842
898
|
warning = "" if request.get("_startup_probe") is True else self._consume_warning()
|
|
@@ -863,32 +919,62 @@ class _CacheService:
|
|
|
863
919
|
self.watcher_ready.wait(timeout=_WATCHER_READY_TIMEOUT_SECONDS)
|
|
864
920
|
if self.watcher_failed.is_set():
|
|
865
921
|
self.context.invalidate_revision_cache()
|
|
866
|
-
with self.
|
|
867
|
-
self.
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
922
|
+
with self._request_state_lock:
|
|
923
|
+
self._active_requests += 1
|
|
924
|
+
try:
|
|
925
|
+
with self.lock:
|
|
926
|
+
self.last_activity = time.monotonic()
|
|
927
|
+
before = self.last_revision
|
|
928
|
+
was_warm = self.context.navigation_cache_is_warm
|
|
871
929
|
try:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
930
|
+
try:
|
|
931
|
+
handled = (
|
|
932
|
+
self.context.handle(request)
|
|
933
|
+
if cancel_check is None
|
|
934
|
+
else self.context.handle(
|
|
935
|
+
request,
|
|
936
|
+
cancel_check=cancel_check,
|
|
937
|
+
)
|
|
938
|
+
)
|
|
939
|
+
response = handled.to_mapping()
|
|
940
|
+
except CacheClientError:
|
|
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:
|
|
953
|
+
raise CacheClientError(
|
|
954
|
+
"internal_error",
|
|
955
|
+
"Internal cache error.",
|
|
956
|
+
) from None
|
|
957
|
+
finally:
|
|
958
|
+
self._enforce_budget()
|
|
959
|
+
after = str(response["workspace_revision"])
|
|
960
|
+
self.last_revision = after
|
|
961
|
+
self.stats.requests += 1
|
|
962
|
+
if was_warm:
|
|
963
|
+
self.stats.warm_hits += 1
|
|
964
|
+
else:
|
|
965
|
+
self.stats.rebuilds += 1
|
|
966
|
+
self.stats.parallel_fallbacks += (
|
|
967
|
+
self.context.parallel_stats.fallbacks
|
|
968
|
+
)
|
|
969
|
+
if before != after:
|
|
970
|
+
self.stats.invalidations += 1
|
|
971
|
+
return CacheClientResponse(
|
|
972
|
+
response,
|
|
973
|
+
self._consume_warning(),
|
|
974
|
+
)
|
|
975
|
+
finally:
|
|
976
|
+
with self._request_state_lock:
|
|
977
|
+
self._active_requests -= 1
|
|
892
978
|
|
|
893
979
|
def _enforce_budget(self) -> None:
|
|
894
980
|
self.last_budget = self.budget.enforce(
|
|
@@ -932,6 +1018,8 @@ class _CacheService:
|
|
|
932
1018
|
if context is not None
|
|
933
1019
|
else None
|
|
934
1020
|
)
|
|
1021
|
+
with self._request_state_lock:
|
|
1022
|
+
busy = self._active_requests > 0
|
|
935
1023
|
return {
|
|
936
1024
|
"pid": self.metadata.pid, "root": self.metadata.root, "project_file": self.metadata.project_file,
|
|
937
1025
|
"version": self.metadata.version, "uptime": now - self.started, "idle_seconds": idle,
|
|
@@ -958,6 +1046,7 @@ class _CacheService:
|
|
|
958
1046
|
"workspace_revision": self.last_revision,
|
|
959
1047
|
"startup_error": self.startup_error,
|
|
960
1048
|
"watcher_active": not self.watcher_failed.is_set(),
|
|
1049
|
+
"busy": busy,
|
|
961
1050
|
}
|
|
962
1051
|
|
|
963
1052
|
|
|
@@ -1053,7 +1142,15 @@ def _serve_connection(connection: socket.socket, service: _CacheService) -> None
|
|
|
1053
1142
|
response: dict[str, object] = {"error": {"code": "authentication_failed", "message": "authentication failed"}}
|
|
1054
1143
|
else:
|
|
1055
1144
|
try:
|
|
1056
|
-
|
|
1145
|
+
guard = (
|
|
1146
|
+
_RequestGuard(service, connection)
|
|
1147
|
+
if request.get("action") in {"trace", "cpg"}
|
|
1148
|
+
else None
|
|
1149
|
+
)
|
|
1150
|
+
result = service.request(
|
|
1151
|
+
request,
|
|
1152
|
+
cancel_check=guard.check if guard is not None else None,
|
|
1153
|
+
)
|
|
1057
1154
|
response = {"payload": result.payload, "warning": result.warning}
|
|
1058
1155
|
except CacheClientError as error:
|
|
1059
1156
|
response = {"error": {"code": error.code, "message": error.message}}
|
|
@@ -1105,6 +1202,11 @@ def run_cache_daemon(
|
|
|
1105
1202
|
max_disk_cache_bytes,
|
|
1106
1203
|
)
|
|
1107
1204
|
watcher: threading.Thread | None = None
|
|
1205
|
+
previous_sigterm: (
|
|
1206
|
+
Callable[[int, FrameType | None], object]
|
|
1207
|
+
| int
|
|
1208
|
+
| None
|
|
1209
|
+
) = None
|
|
1108
1210
|
connection_slots = threading.BoundedSemaphore(_CONNECTION_WORKERS)
|
|
1109
1211
|
connection_pool = ThreadPoolExecutor(
|
|
1110
1212
|
max_workers=_CONNECTION_WORKERS,
|
|
@@ -1112,6 +1214,16 @@ def run_cache_daemon(
|
|
|
1112
1214
|
)
|
|
1113
1215
|
try:
|
|
1114
1216
|
service = _CacheService(metadata, defer_context=True)
|
|
1217
|
+
if threading.current_thread() is threading.main_thread():
|
|
1218
|
+
previous_sigterm = signal.getsignal(signal.SIGTERM)
|
|
1219
|
+
|
|
1220
|
+
def request_shutdown(
|
|
1221
|
+
_signum: int,
|
|
1222
|
+
_frame: FrameType | None,
|
|
1223
|
+
) -> None:
|
|
1224
|
+
service.shutdown.set()
|
|
1225
|
+
|
|
1226
|
+
signal.signal(signal.SIGTERM, request_shutdown)
|
|
1115
1227
|
_write_metadata(metadata)
|
|
1116
1228
|
watcher = threading.Thread(
|
|
1117
1229
|
target=_watch_workspace,
|
|
@@ -1150,6 +1262,8 @@ def run_cache_daemon(
|
|
|
1150
1262
|
_remove_metadata_if_owned(metadata)
|
|
1151
1263
|
if watcher is not None:
|
|
1152
1264
|
watcher.join(timeout=2.0)
|
|
1265
|
+
if previous_sigterm is not None:
|
|
1266
|
+
signal.signal(signal.SIGTERM, previous_sigterm)
|
|
1153
1267
|
|
|
1154
1268
|
|
|
1155
1269
|
def _start_cache_unlocked(
|
|
@@ -1339,15 +1453,28 @@ def stop_cache(root: str | Path) -> None:
|
|
|
1339
1453
|
_client_exchange(metadata, {"action": "stop"})
|
|
1340
1454
|
except CacheClientError:
|
|
1341
1455
|
pass
|
|
1342
|
-
|
|
1456
|
+
started = time.monotonic()
|
|
1457
|
+
deadline = started + _STOP_TIMEOUT_SECONDS
|
|
1458
|
+
signal_sent = False
|
|
1343
1459
|
stopped = False
|
|
1344
|
-
while
|
|
1460
|
+
while True:
|
|
1461
|
+
now = time.monotonic()
|
|
1462
|
+
if now >= deadline:
|
|
1463
|
+
break
|
|
1345
1464
|
if _reap_child_if_exited(metadata.pid):
|
|
1346
1465
|
stopped = True
|
|
1347
1466
|
break
|
|
1348
1467
|
if not _pid_alive(metadata.pid):
|
|
1349
1468
|
stopped = True
|
|
1350
1469
|
break
|
|
1470
|
+
if (
|
|
1471
|
+
not signal_sent
|
|
1472
|
+
and os.name != "nt"
|
|
1473
|
+
and now - started >= _STOP_SIGNAL_DELAY_SECONDS
|
|
1474
|
+
):
|
|
1475
|
+
with contextlib.suppress(OSError):
|
|
1476
|
+
os.kill(metadata.pid, signal.SIGTERM)
|
|
1477
|
+
signal_sent = True
|
|
1351
1478
|
time.sleep(0.05)
|
|
1352
1479
|
if not stopped and _pid_alive(metadata.pid):
|
|
1353
1480
|
raise CacheClientError("stop_failed", "Cache daemon did not stop.")
|
delphi_lsp/agent_cli.py
CHANGED
|
@@ -25,7 +25,13 @@ from .agent_cache import (
|
|
|
25
25
|
watch_workspace_changes,
|
|
26
26
|
)
|
|
27
27
|
from .agent_context import AgentContext
|
|
28
|
-
from .agent_layers import
|
|
28
|
+
from .agent_layers import (
|
|
29
|
+
build_codebase_index,
|
|
30
|
+
build_references_payload,
|
|
31
|
+
layer_payload,
|
|
32
|
+
render_layer,
|
|
33
|
+
render_payload,
|
|
34
|
+
)
|
|
29
35
|
from .agent_protocol import (
|
|
30
36
|
SCHEMA_VERSION,
|
|
31
37
|
SUPPORTED_ACTIONS,
|
|
@@ -317,6 +323,17 @@ def _discard_broken_stdout() -> None:
|
|
|
317
323
|
def _view(args: argparse.Namespace) -> None:
|
|
318
324
|
args.root = _workspace_root(args.root)
|
|
319
325
|
args.project_file = _project_file(args.root, args.project_file)
|
|
326
|
+
if args.layer == "references":
|
|
327
|
+
payload = build_references_payload(
|
|
328
|
+
args.root,
|
|
329
|
+
query=args.query,
|
|
330
|
+
project_file=args.project_file,
|
|
331
|
+
workers=args.workers,
|
|
332
|
+
)
|
|
333
|
+
sys.stdout.write(
|
|
334
|
+
render_payload(payload, output_format=args.format)
|
|
335
|
+
)
|
|
336
|
+
return
|
|
320
337
|
index = build_codebase_index(
|
|
321
338
|
args.root,
|
|
322
339
|
project_file=args.project_file,
|
delphi_lsp/agent_context.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import gc
|
|
3
4
|
import hashlib
|
|
4
5
|
import json
|
|
5
6
|
import sys
|
|
@@ -7,7 +8,7 @@ import time
|
|
|
7
8
|
import unicodedata
|
|
8
9
|
from bisect import bisect_left, bisect_right
|
|
9
10
|
from collections import OrderedDict
|
|
10
|
-
from collections.abc import Mapping, Sequence
|
|
11
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
11
12
|
from dataclasses import dataclass, replace
|
|
12
13
|
from heapq import heappop, heappush
|
|
13
14
|
from pathlib import Path, PureWindowsPath
|
|
@@ -704,6 +705,26 @@ def _cpg_target(entry: _SymbolEntry) -> CpgTarget:
|
|
|
704
705
|
)
|
|
705
706
|
|
|
706
707
|
|
|
708
|
+
def _relation_target(entry: _SymbolEntry) -> RelationTarget:
|
|
709
|
+
return RelationTarget(
|
|
710
|
+
target_id=entry.target_id,
|
|
711
|
+
source_path=str(entry.source_path),
|
|
712
|
+
path=entry.path,
|
|
713
|
+
unit_id=entry.unit_id,
|
|
714
|
+
unit_name=entry.unit_name,
|
|
715
|
+
name=entry.name,
|
|
716
|
+
qualified_name=entry.qualified_name,
|
|
717
|
+
kind=entry.kind.value,
|
|
718
|
+
signature=entry.signature,
|
|
719
|
+
line=entry.line,
|
|
720
|
+
column=entry.column,
|
|
721
|
+
card={},
|
|
722
|
+
visibility=entry.visibility.value,
|
|
723
|
+
type_name=entry.type_name,
|
|
724
|
+
owner=entry.owner,
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
|
|
707
728
|
class AgentContext:
|
|
708
729
|
def __init__(
|
|
709
730
|
self,
|
|
@@ -875,8 +896,15 @@ class AgentContext:
|
|
|
875
896
|
self._revision_epoch += 1
|
|
876
897
|
self._last_revision_check_at = float("-inf")
|
|
877
898
|
|
|
878
|
-
def handle(
|
|
899
|
+
def handle(
|
|
900
|
+
self,
|
|
901
|
+
request: AgentRequest | Mapping[str, object],
|
|
902
|
+
*,
|
|
903
|
+
cancel_check: Callable[[], None] | None = None,
|
|
904
|
+
) -> AgentResponse:
|
|
879
905
|
parsed = _validated_request(request)
|
|
906
|
+
if cancel_check is not None:
|
|
907
|
+
cancel_check()
|
|
880
908
|
revision = self._refresh_workspace(parsed.project_id)
|
|
881
909
|
|
|
882
910
|
if parsed.action == "cpg":
|
|
@@ -890,13 +918,57 @@ class AgentContext:
|
|
|
890
918
|
target_id=entry.target_id,
|
|
891
919
|
)
|
|
892
920
|
if parsed.action == "trace":
|
|
921
|
+
if cancel_check is not None:
|
|
922
|
+
cancel_check()
|
|
893
923
|
if parsed.relation is None:
|
|
894
924
|
raise AgentProtocolError("relation_required", "Trace requires a relation.")
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
925
|
+
targeted_references = (
|
|
926
|
+
parsed.relation == "references"
|
|
927
|
+
and len(self._workspace.units) >= 256
|
|
928
|
+
)
|
|
929
|
+
relation_index = (
|
|
930
|
+
self._relation_index
|
|
931
|
+
if (
|
|
932
|
+
targeted_references
|
|
933
|
+
and self._relation_index is not None
|
|
934
|
+
and self._relation_index.project_id
|
|
935
|
+
== self._workspace.active_project_id
|
|
936
|
+
and self._relation_index.revision == revision
|
|
937
|
+
and self._relation_index.has_target(parsed.target_id)
|
|
938
|
+
)
|
|
939
|
+
else None
|
|
940
|
+
)
|
|
941
|
+
resolved_target_id = parsed.target_id
|
|
942
|
+
if relation_index is None:
|
|
943
|
+
registry = self._require_registry(revision)
|
|
944
|
+
entry = self._resolve_target(registry, parsed.target_id)
|
|
945
|
+
resolved_target_id = entry.target_id
|
|
946
|
+
if targeted_references:
|
|
947
|
+
relation_index = self._require_reference_relation_index(
|
|
948
|
+
registry,
|
|
949
|
+
entry,
|
|
950
|
+
)
|
|
951
|
+
registry.sources.clear_loaded()
|
|
952
|
+
self._registry = None
|
|
953
|
+
del registry
|
|
954
|
+
gc.collect()
|
|
955
|
+
else:
|
|
956
|
+
relation_index = self._require_relation_index(
|
|
957
|
+
registry,
|
|
958
|
+
parsed.relation,
|
|
959
|
+
)
|
|
960
|
+
items = relation_index.trace(
|
|
961
|
+
resolved_target_id,
|
|
962
|
+
parsed.relation,
|
|
963
|
+
cancel_check=cancel_check,
|
|
964
|
+
max_relations=parsed.max_items,
|
|
965
|
+
)
|
|
966
|
+
return self._response(
|
|
967
|
+
parsed,
|
|
968
|
+
revision,
|
|
969
|
+
items,
|
|
970
|
+
target_id=resolved_target_id,
|
|
971
|
+
)
|
|
900
972
|
if parsed.action == "open":
|
|
901
973
|
items = self._open_items()
|
|
902
974
|
return self._response(parsed, revision, items)
|
|
@@ -1265,23 +1337,7 @@ class AgentContext:
|
|
|
1265
1337
|
):
|
|
1266
1338
|
return self._relation_index
|
|
1267
1339
|
targets = tuple(
|
|
1268
|
-
|
|
1269
|
-
target_id=entry.target_id,
|
|
1270
|
-
source_path=str(entry.source_path),
|
|
1271
|
-
path=entry.path,
|
|
1272
|
-
unit_id=entry.unit_id,
|
|
1273
|
-
unit_name=entry.unit_name,
|
|
1274
|
-
name=entry.name,
|
|
1275
|
-
qualified_name=entry.qualified_name,
|
|
1276
|
-
kind=entry.kind.value,
|
|
1277
|
-
signature=entry.signature,
|
|
1278
|
-
line=entry.line,
|
|
1279
|
-
column=entry.column,
|
|
1280
|
-
card={},
|
|
1281
|
-
visibility=entry.visibility.value,
|
|
1282
|
-
type_name=entry.type_name,
|
|
1283
|
-
owner=entry.owner,
|
|
1284
|
-
)
|
|
1340
|
+
_relation_target(entry)
|
|
1285
1341
|
for entry in registry.entries
|
|
1286
1342
|
if requires_complete_targets or entry.kind == SymbolKind.UNIT
|
|
1287
1343
|
)
|
|
@@ -1294,6 +1350,32 @@ class AgentContext:
|
|
|
1294
1350
|
)
|
|
1295
1351
|
return self._relation_index
|
|
1296
1352
|
|
|
1353
|
+
def _require_reference_relation_index(
|
|
1354
|
+
self,
|
|
1355
|
+
registry: _Registry,
|
|
1356
|
+
entry: _SymbolEntry,
|
|
1357
|
+
) -> ProjectRelationIndex:
|
|
1358
|
+
if (
|
|
1359
|
+
self._relation_index is not None
|
|
1360
|
+
and self._relation_index.project_id == registry.project_id
|
|
1361
|
+
and self._relation_index.revision == registry.revision
|
|
1362
|
+
and self._relation_index.has_target(entry.target_id)
|
|
1363
|
+
):
|
|
1364
|
+
return self._relation_index
|
|
1365
|
+
targets = tuple(
|
|
1366
|
+
_relation_target(candidate)
|
|
1367
|
+
for candidate in registry.entries
|
|
1368
|
+
if candidate.source_path == entry.source_path
|
|
1369
|
+
)
|
|
1370
|
+
self._relation_index = ProjectRelationIndex(
|
|
1371
|
+
self._workspace,
|
|
1372
|
+
registry.project_id,
|
|
1373
|
+
registry.revision,
|
|
1374
|
+
targets,
|
|
1375
|
+
targets_complete=False,
|
|
1376
|
+
)
|
|
1377
|
+
return self._relation_index
|
|
1378
|
+
|
|
1297
1379
|
def _require_selected_project(self) -> str:
|
|
1298
1380
|
project_id = self._workspace.active_project_id
|
|
1299
1381
|
if not project_id:
|