python-delphi-lsp 2.0.2__py3-none-any.whl → 2.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.
- delphi_lsp/__init__.py +3 -0
- delphi_lsp/_version.py +1 -1
- delphi_lsp/agent_context.py +171 -63
- delphi_lsp/agent_layers.py +115 -6
- delphi_lsp/agent_templates.py +18 -15
- delphi_lsp/agent_workspace.py +7 -2
- delphi_lsp/lsp_server.py +195 -33
- delphi_lsp/preprocessor.py +32 -10
- delphi_lsp/progress.py +26 -0
- delphi_lsp/project_discovery.py +125 -16
- delphi_lsp/project_indexer.py +43 -1
- {python_delphi_lsp-2.0.2.dist-info → python_delphi_lsp-2.0.4.dist-info}/METADATA +33 -4
- {python_delphi_lsp-2.0.2.dist-info → python_delphi_lsp-2.0.4.dist-info}/RECORD +17 -16
- {python_delphi_lsp-2.0.2.dist-info → python_delphi_lsp-2.0.4.dist-info}/WHEEL +0 -0
- {python_delphi_lsp-2.0.2.dist-info → python_delphi_lsp-2.0.4.dist-info}/entry_points.txt +0 -0
- {python_delphi_lsp-2.0.2.dist-info → python_delphi_lsp-2.0.4.dist-info}/licenses/LICENSE +0 -0
- {python_delphi_lsp-2.0.2.dist-info → python_delphi_lsp-2.0.4.dist-info}/top_level.txt +0 -0
delphi_lsp/__init__.py
CHANGED
|
@@ -48,6 +48,7 @@ from .semantic_builder import SemanticBuilder, SemanticModel, SemanticProblem
|
|
|
48
48
|
from .workspace import WorkspaceSemanticResult, build_workspace_semantics
|
|
49
49
|
from .lsp_server import LspWorkspaceState, create_server
|
|
50
50
|
from .project_discovery import DelphiProjectDiscovery, DiscoveryProblem, discover_delphi_project
|
|
51
|
+
from .progress import ProgressCallback, ProgressEvent
|
|
51
52
|
from .project_indexer import (
|
|
52
53
|
GetUnitSyntaxHook,
|
|
53
54
|
IncludeFileInfo,
|
|
@@ -136,6 +137,8 @@ __all__ = [
|
|
|
136
137
|
'DelphiProjectDiscovery',
|
|
137
138
|
'DiscoveryProblem',
|
|
138
139
|
'discover_delphi_project',
|
|
140
|
+
'ProgressCallback',
|
|
141
|
+
'ProgressEvent',
|
|
139
142
|
'ProjectIndexer',
|
|
140
143
|
'ProjectIndexResult',
|
|
141
144
|
'ProjectProblemType',
|
delphi_lsp/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.4"
|
delphi_lsp/agent_context.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from bisect import bisect_left, bisect_right
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from heapq import heappop, heappush
|
|
6
7
|
import hashlib
|
|
7
8
|
import json
|
|
8
9
|
from pathlib import Path, PureWindowsPath
|
|
@@ -14,13 +15,12 @@ from .agent_protocol import (
|
|
|
14
15
|
AgentResponse,
|
|
15
16
|
ContextBudget,
|
|
16
17
|
Focus,
|
|
17
|
-
Page,
|
|
18
18
|
make_target_id,
|
|
19
19
|
paginate_items,
|
|
20
20
|
)
|
|
21
21
|
from .agent_metrics import build_workspace_metrics, project_metric_item, unit_metric_item
|
|
22
22
|
from .agent_relations import ProjectRelationIndex, RelationTarget
|
|
23
|
-
from .agent_workspace import AgentWorkspace, unit_display_path, unit_source_path, unit_target_id
|
|
23
|
+
from .agent_workspace import AgentUnit, AgentWorkspace, unit_display_path, unit_source_path, unit_target_id
|
|
24
24
|
from .consts import AttributeName, SyntaxNodeType
|
|
25
25
|
from .lsp_server import build_outline_semantic_model, multiline_string_block_end
|
|
26
26
|
from .nodes import CompoundSyntaxNode, SyntaxNode
|
|
@@ -121,6 +121,10 @@ class _SourceDocument:
|
|
|
121
121
|
self.tokens = tuple(_lex_delphi(text))
|
|
122
122
|
self.token_starts = tuple(token.start for token in self.tokens)
|
|
123
123
|
self.directive_starts = tuple(token.start for token in self.tokens if token.directive)
|
|
124
|
+
self.declaration_section_indexes = [0]
|
|
125
|
+
self.declaration_section_checkpoints: dict[int, tuple[str, int, int, int]] = {
|
|
126
|
+
0: ("", 0, 0, 0)
|
|
127
|
+
}
|
|
124
128
|
words = [token for token in self.tokens if token.word and not token.escaped]
|
|
125
129
|
self.unit_kind = next(
|
|
126
130
|
(token.value for token in words if token.value in {"unit", "program", "library", "package"}),
|
|
@@ -129,6 +133,7 @@ class _SourceDocument:
|
|
|
129
133
|
implementation = next((token for token in words if token.value == "implementation"), None)
|
|
130
134
|
self.implementation_line = self.line_col(implementation.start)[0] if implementation else 0
|
|
131
135
|
self.routine_spans: dict[int, tuple[int, int] | None] = {}
|
|
136
|
+
self.routine_token_spans: dict[int, tuple[int, int, int] | None] = {}
|
|
132
137
|
self.parser_spans: dict[str, tuple[int, int] | None] = {}
|
|
133
138
|
self._full_parse_attempted = False
|
|
134
139
|
self._full_parse_result: object | None = None
|
|
@@ -230,6 +235,7 @@ class _Registry:
|
|
|
230
235
|
entries: tuple[_SymbolEntry, ...]
|
|
231
236
|
by_target: dict[str, _SymbolEntry]
|
|
232
237
|
sources: dict[Path, _SourceDocument]
|
|
238
|
+
ranked_queries: dict[str, tuple[_SymbolEntry, ...]]
|
|
233
239
|
|
|
234
240
|
|
|
235
241
|
class AgentContext:
|
|
@@ -280,7 +286,12 @@ class AgentContext:
|
|
|
280
286
|
return self._handle_focus(parsed, revision)
|
|
281
287
|
if parsed.action == "find":
|
|
282
288
|
registry = self._require_registry(revision)
|
|
283
|
-
|
|
289
|
+
ranked = registry.ranked_queries.get(parsed.query)
|
|
290
|
+
if ranked is None:
|
|
291
|
+
ranked = tuple(_ranked_entries(registry.entries, parsed.query))
|
|
292
|
+
registry.ranked_queries.clear()
|
|
293
|
+
registry.ranked_queries[parsed.query] = ranked
|
|
294
|
+
items = [entry.card() for entry in ranked]
|
|
284
295
|
return self._response(parsed, revision, items)
|
|
285
296
|
if parsed.action == "inspect":
|
|
286
297
|
registry = self._require_registry(revision)
|
|
@@ -293,9 +304,10 @@ class AgentContext:
|
|
|
293
304
|
previous_project_id = self._workspace.active_project_id
|
|
294
305
|
selected_project_id = requested_project_id or previous_project_id
|
|
295
306
|
if selected_project_id:
|
|
296
|
-
self._workspace.
|
|
307
|
+
revision = self._workspace._select_project_with_revision(selected_project_id)
|
|
308
|
+
else:
|
|
309
|
+
revision = self._workspace.workspace_revision
|
|
297
310
|
current_project_id = self._workspace.active_project_id
|
|
298
|
-
revision = self._workspace.workspace_revision
|
|
299
311
|
|
|
300
312
|
if current_project_id != previous_project_id:
|
|
301
313
|
self._registry = None
|
|
@@ -683,7 +695,14 @@ def _build_registry(workspace: AgentWorkspace, project_id: str, revision: str) -
|
|
|
683
695
|
include_paths=workspace.include_paths,
|
|
684
696
|
)
|
|
685
697
|
sources[source_path] = document
|
|
686
|
-
|
|
698
|
+
if workspace.defines:
|
|
699
|
+
model = build_outline_semantic_model(
|
|
700
|
+
text,
|
|
701
|
+
str(source_path),
|
|
702
|
+
defines=workspace.defines,
|
|
703
|
+
)
|
|
704
|
+
else:
|
|
705
|
+
model = build_outline_semantic_model(text, str(source_path))
|
|
687
706
|
unit_symbols = _collect_raw_symbols(model.unit_scope, unit, source_path, document)
|
|
688
707
|
raw_symbols.extend(_exclude_routine_locals(unit_symbols, document))
|
|
689
708
|
|
|
@@ -782,6 +801,7 @@ def _build_registry(workspace: AgentWorkspace, project_id: str, revision: str) -
|
|
|
782
801
|
entries=entries_tuple,
|
|
783
802
|
by_target={entry.target_id: entry for entry in entries_tuple},
|
|
784
803
|
sources=sources,
|
|
804
|
+
ranked_queries={},
|
|
785
805
|
)
|
|
786
806
|
|
|
787
807
|
|
|
@@ -925,32 +945,47 @@ def _correct_outline_symbol_kind(document: _SourceDocument, symbol: Symbol) -> N
|
|
|
925
945
|
symbol.kind = SymbolKind.TYPE
|
|
926
946
|
|
|
927
947
|
|
|
948
|
+
def _advance_declaration_section(
|
|
949
|
+
state: tuple[str, int, int, int],
|
|
950
|
+
token: _Token,
|
|
951
|
+
) -> tuple[str, int, int, int]:
|
|
952
|
+
section, parentheses, brackets, angles = state
|
|
953
|
+
if token.directive:
|
|
954
|
+
return state
|
|
955
|
+
if token.value == "(":
|
|
956
|
+
parentheses += 1
|
|
957
|
+
elif token.value == ")":
|
|
958
|
+
parentheses = max(0, parentheses - 1)
|
|
959
|
+
elif token.value == "[":
|
|
960
|
+
brackets += 1
|
|
961
|
+
elif token.value == "]":
|
|
962
|
+
brackets = max(0, brackets - 1)
|
|
963
|
+
elif token.value == "<":
|
|
964
|
+
angles += 1
|
|
965
|
+
elif token.value == ">":
|
|
966
|
+
angles = max(0, angles - 1)
|
|
967
|
+
elif not parentheses and not brackets and not angles and token.word:
|
|
968
|
+
if token.value in {"const", "resourcestring", "threadvar", "type", "var"}:
|
|
969
|
+
section = token.value
|
|
970
|
+
elif token.value in {"implementation", "initialization", "finalization"}:
|
|
971
|
+
section = ""
|
|
972
|
+
return section, parentheses, brackets, angles
|
|
973
|
+
|
|
974
|
+
|
|
928
975
|
def _declaration_section(document: _SourceDocument, offset: int) -> str:
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
elif token.value == "]":
|
|
943
|
-
brackets = max(0, brackets - 1)
|
|
944
|
-
elif token.value == "<":
|
|
945
|
-
angles += 1
|
|
946
|
-
elif token.value == ">":
|
|
947
|
-
angles = max(0, angles - 1)
|
|
948
|
-
elif not any((parentheses, brackets, angles)) and token.word:
|
|
949
|
-
if token.value in {"const", "resourcestring", "threadvar", "type", "var"}:
|
|
950
|
-
section = token.value
|
|
951
|
-
elif token.value in {"implementation", "initialization", "finalization"}:
|
|
952
|
-
section = ""
|
|
953
|
-
return section
|
|
976
|
+
target_index = document.first_token_index(offset)
|
|
977
|
+
cached = document.declaration_section_checkpoints.get(target_index)
|
|
978
|
+
if cached is not None:
|
|
979
|
+
return cached[0]
|
|
980
|
+
checkpoint_position = bisect_right(document.declaration_section_indexes, target_index) - 1
|
|
981
|
+
checkpoint_index = document.declaration_section_indexes[checkpoint_position]
|
|
982
|
+
state = document.declaration_section_checkpoints[checkpoint_index]
|
|
983
|
+
for token in document.tokens[checkpoint_index:target_index]:
|
|
984
|
+
state = _advance_declaration_section(state, token)
|
|
985
|
+
insert_at = bisect_left(document.declaration_section_indexes, target_index)
|
|
986
|
+
document.declaration_section_indexes.insert(insert_at, target_index)
|
|
987
|
+
document.declaration_section_checkpoints[target_index] = state
|
|
988
|
+
return state[0]
|
|
954
989
|
|
|
955
990
|
|
|
956
991
|
def _declared_symbol_name(document: _SourceDocument, symbol: Symbol) -> str:
|
|
@@ -1208,20 +1243,47 @@ def _exclude_routine_locals(
|
|
|
1208
1243
|
span = _raw_routine_span(raw, document)
|
|
1209
1244
|
if span is not None:
|
|
1210
1245
|
containers.append((span[0], span[1], raw))
|
|
1246
|
+
if not containers:
|
|
1247
|
+
return symbols
|
|
1211
1248
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1249
|
+
containers.sort(key=lambda item: (item[0], item[1]))
|
|
1250
|
+
positioned = sorted(
|
|
1251
|
+
(
|
|
1252
|
+
document.offset(
|
|
1253
|
+
raw.symbol.decl_range.start_line,
|
|
1254
|
+
raw.symbol.decl_range.start_col,
|
|
1255
|
+
),
|
|
1256
|
+
order,
|
|
1257
|
+
raw,
|
|
1217
1258
|
)
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1259
|
+
for order, raw in enumerate(symbols)
|
|
1260
|
+
)
|
|
1261
|
+
active_ends: list[tuple[int, int, int]] = []
|
|
1262
|
+
active_ids: set[int] = set()
|
|
1263
|
+
excluded_orders: set[int] = set()
|
|
1264
|
+
container_index = 0
|
|
1265
|
+
for offset, order, raw in positioned:
|
|
1266
|
+
while (
|
|
1267
|
+
container_index < len(containers)
|
|
1268
|
+
and containers[container_index][0] < offset
|
|
1221
1269
|
):
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1270
|
+
_, end, container = containers[container_index]
|
|
1271
|
+
container_id = id(container)
|
|
1272
|
+
heappush(active_ends, (end, container_index, container_id))
|
|
1273
|
+
active_ids.add(container_id)
|
|
1274
|
+
container_index += 1
|
|
1275
|
+
while active_ends and active_ends[0][0] <= offset:
|
|
1276
|
+
_, _, container_id = heappop(active_ends)
|
|
1277
|
+
active_ids.discard(container_id)
|
|
1278
|
+
raw_id = id(raw)
|
|
1279
|
+
if active_ids and (raw_id not in active_ids or len(active_ids) > 1):
|
|
1280
|
+
excluded_orders.add(order)
|
|
1281
|
+
|
|
1282
|
+
return [
|
|
1283
|
+
raw
|
|
1284
|
+
for order, raw in enumerate(symbols)
|
|
1285
|
+
if order not in excluded_orders
|
|
1286
|
+
]
|
|
1225
1287
|
|
|
1226
1288
|
|
|
1227
1289
|
def _raw_routine_span(
|
|
@@ -1523,6 +1585,7 @@ def _routine_span(
|
|
|
1523
1585
|
document.tokens,
|
|
1524
1586
|
document.token_starts,
|
|
1525
1587
|
token_index,
|
|
1588
|
+
cache=document.routine_token_spans,
|
|
1526
1589
|
)
|
|
1527
1590
|
span = (start, found[1]) if found is not None else None
|
|
1528
1591
|
document.routine_spans[start] = span
|
|
@@ -1534,6 +1597,7 @@ def _find_routine_token_span(
|
|
|
1534
1597
|
token_starts: tuple[int, ...],
|
|
1535
1598
|
start_index: int,
|
|
1536
1599
|
*,
|
|
1600
|
+
cache: dict[int, tuple[int, int, int] | None] | None = None,
|
|
1537
1601
|
depth: int = 0,
|
|
1538
1602
|
) -> tuple[int, int, int] | None:
|
|
1539
1603
|
if depth > 64:
|
|
@@ -1541,56 +1605,100 @@ def _find_routine_token_span(
|
|
|
1541
1605
|
routine_index = _routine_keyword_index(tokens, start_index)
|
|
1542
1606
|
if routine_index is None:
|
|
1543
1607
|
return None
|
|
1608
|
+
spans = cache if cache is not None else {}
|
|
1609
|
+
missing = object()
|
|
1610
|
+
cached = spans.get(routine_index, missing)
|
|
1611
|
+
if cached is not missing:
|
|
1612
|
+
if cached is None:
|
|
1613
|
+
return None
|
|
1614
|
+
return tokens[start_index].start, cached[1], cached[2]
|
|
1615
|
+
|
|
1544
1616
|
heading_end = _heading_semicolon_index(tokens, routine_index)
|
|
1545
1617
|
if heading_end is None:
|
|
1618
|
+
spans[routine_index] = None
|
|
1546
1619
|
return None
|
|
1547
1620
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1621
|
+
frames: list[list[int]] = [[routine_index, heading_end + 1]]
|
|
1622
|
+
|
|
1623
|
+
def reject_active_frames() -> None:
|
|
1624
|
+
for active_routine_index, _ in frames:
|
|
1625
|
+
spans[active_routine_index] = None
|
|
1626
|
+
frames.clear()
|
|
1627
|
+
|
|
1628
|
+
while frames:
|
|
1629
|
+
frame = frames[-1]
|
|
1630
|
+
frame_routine_index, index = frame
|
|
1631
|
+
if index >= len(tokens):
|
|
1632
|
+
reject_active_frames()
|
|
1633
|
+
continue
|
|
1634
|
+
|
|
1550
1635
|
token = tokens[index]
|
|
1551
1636
|
if token.directive:
|
|
1552
|
-
|
|
1637
|
+
reject_active_frames()
|
|
1638
|
+
continue
|
|
1553
1639
|
if token.word and not token.escaped:
|
|
1554
1640
|
if token.value in _NO_BODY_DIRECTIVES:
|
|
1555
|
-
|
|
1641
|
+
spans[frame_routine_index] = None
|
|
1642
|
+
frames.pop()
|
|
1643
|
+
continue
|
|
1556
1644
|
if token.value in {"implementation", "initialization", "finalization"}:
|
|
1557
|
-
|
|
1645
|
+
reject_active_frames()
|
|
1646
|
+
continue
|
|
1558
1647
|
if (
|
|
1559
1648
|
token.value in _STRUCTURED_TYPE_WORDS
|
|
1560
1649
|
and _is_structured_type_opener(tokens, index)
|
|
1561
1650
|
):
|
|
1562
1651
|
if index + 1 < len(tokens) and tokens[index + 1].value == ";":
|
|
1563
|
-
index
|
|
1652
|
+
frame[1] = index + 2
|
|
1564
1653
|
continue
|
|
1565
1654
|
structured_end = _match_end_terminated_block(tokens, index)
|
|
1566
1655
|
if structured_end is None:
|
|
1567
|
-
|
|
1568
|
-
|
|
1656
|
+
reject_active_frames()
|
|
1657
|
+
continue
|
|
1658
|
+
frame[1] = bisect_left(token_starts, structured_end)
|
|
1569
1659
|
continue
|
|
1570
1660
|
if token.value == "end":
|
|
1571
|
-
|
|
1661
|
+
reject_active_frames()
|
|
1662
|
+
continue
|
|
1572
1663
|
if token.value in {"begin", "asm"}:
|
|
1573
1664
|
end = _match_end_terminated_block(tokens, index)
|
|
1574
1665
|
if end is None:
|
|
1575
|
-
|
|
1666
|
+
reject_active_frames()
|
|
1667
|
+
continue
|
|
1576
1668
|
end_index = bisect_left(token_starts, end)
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
token_starts,
|
|
1582
|
-
index,
|
|
1583
|
-
depth=depth + 1,
|
|
1669
|
+
spans[frame_routine_index] = (
|
|
1670
|
+
tokens[frame_routine_index].start,
|
|
1671
|
+
end,
|
|
1672
|
+
end_index,
|
|
1584
1673
|
)
|
|
1674
|
+
frames.pop()
|
|
1675
|
+
continue
|
|
1676
|
+
if token.value in _ROUTINE_WORDS and _is_nested_routine_declaration(tokens, index):
|
|
1677
|
+
nested_routine_index = _routine_keyword_index(tokens, index)
|
|
1678
|
+
if nested_routine_index is None:
|
|
1679
|
+
frame[1] = index + 1
|
|
1680
|
+
continue
|
|
1681
|
+
nested = spans.get(nested_routine_index, missing)
|
|
1682
|
+
if nested is missing:
|
|
1683
|
+
nested_heading_end = _heading_semicolon_index(tokens, nested_routine_index)
|
|
1684
|
+
if nested_heading_end is None:
|
|
1685
|
+
spans[nested_routine_index] = None
|
|
1686
|
+
continue
|
|
1687
|
+
frames.append([nested_routine_index, nested_heading_end + 1])
|
|
1688
|
+
continue
|
|
1585
1689
|
if nested is not None:
|
|
1586
|
-
|
|
1690
|
+
frame[1] = max(index + 1, nested[2])
|
|
1587
1691
|
continue
|
|
1588
1692
|
skipped = _routine_declaration_end_index(tokens, index)
|
|
1589
1693
|
if skipped is not None:
|
|
1590
|
-
|
|
1694
|
+
frame[1] = skipped + 1
|
|
1591
1695
|
continue
|
|
1592
|
-
index
|
|
1593
|
-
|
|
1696
|
+
frame[1] = index + 1
|
|
1697
|
+
|
|
1698
|
+
result = spans.get(routine_index)
|
|
1699
|
+
if result is None:
|
|
1700
|
+
return None
|
|
1701
|
+
return tokens[start_index].start, result[1], result[2]
|
|
1594
1702
|
|
|
1595
1703
|
|
|
1596
1704
|
def _routine_keyword_index(
|
delphi_lsp/agent_layers.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass
|
|
3
|
+
from dataclasses import dataclass, replace
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any, Iterable
|
|
6
6
|
import json
|
|
7
7
|
|
|
8
|
-
from .lsp_server import build_outline_semantic_model
|
|
8
|
+
from .lsp_server import build_outline_semantic_model
|
|
9
9
|
from .metrics import analyze_project
|
|
10
10
|
from .project_discovery import DelphiProjectDiscovery, discover_delphi_project
|
|
11
11
|
from .project_indexer import ProjectIndexResult, ProjectIndexer
|
|
12
|
+
from .progress import ProgressCallback, ProgressEvent
|
|
12
13
|
from .semantic import Scope, SourceRange, Symbol, SymbolIndex, SymbolKind
|
|
13
14
|
from .semantic_builder import SemanticModel
|
|
14
15
|
from .source_reader import read_source_text
|
|
@@ -28,10 +29,14 @@ def build_codebase_index(
|
|
|
28
29
|
*,
|
|
29
30
|
project_file: str | Path | None = None,
|
|
30
31
|
index_projects: bool = False,
|
|
32
|
+
on_progress: ProgressCallback | None = None,
|
|
31
33
|
) -> CodebaseIndex:
|
|
32
|
-
|
|
34
|
+
progress = _MonotonicProgress(on_progress)
|
|
35
|
+
discovery = discover_delphi_project(root, project_file=project_file, on_progress=progress)
|
|
33
36
|
models: dict[str, SemanticModel] = {}
|
|
34
|
-
|
|
37
|
+
lines_processed = 0
|
|
38
|
+
symbols_discovered = 0
|
|
39
|
+
for completed, source in enumerate(discovery.source_files, start=1):
|
|
35
40
|
path = Path(source)
|
|
36
41
|
if path.suffix.casefold() not in {".pas", ".dpr", ".dpk", ".inc"}:
|
|
37
42
|
continue
|
|
@@ -39,7 +44,25 @@ def build_codebase_index(
|
|
|
39
44
|
text = read_source_text(path)
|
|
40
45
|
except (OSError, UnicodeError):
|
|
41
46
|
continue
|
|
42
|
-
|
|
47
|
+
model = build_outline_semantic_model(
|
|
48
|
+
text,
|
|
49
|
+
source,
|
|
50
|
+
defines=discovery.defines,
|
|
51
|
+
)
|
|
52
|
+
models[source] = model
|
|
53
|
+
lines_processed += text.count("\n") + (0 if not text or text.endswith("\n") else 1)
|
|
54
|
+
symbols_discovered += sum(len(items) for items in model.index.name_index.values())
|
|
55
|
+
_emit_progress(
|
|
56
|
+
progress,
|
|
57
|
+
"outline",
|
|
58
|
+
source,
|
|
59
|
+
len(discovery.source_files),
|
|
60
|
+
completed,
|
|
61
|
+
len(discovery.source_files),
|
|
62
|
+
"source outlined",
|
|
63
|
+
lines_processed=lines_processed,
|
|
64
|
+
symbols_discovered=symbols_discovered,
|
|
65
|
+
)
|
|
43
66
|
|
|
44
67
|
symbol_index = SymbolIndex()
|
|
45
68
|
for model in models.values():
|
|
@@ -47,6 +70,18 @@ def build_codebase_index(
|
|
|
47
70
|
for model in models.values():
|
|
48
71
|
model.index = symbol_index
|
|
49
72
|
|
|
73
|
+
_emit_progress(
|
|
74
|
+
progress,
|
|
75
|
+
"relations",
|
|
76
|
+
str(Path(root).expanduser().resolve()),
|
|
77
|
+
len(discovery.source_files),
|
|
78
|
+
len(discovery.source_files),
|
|
79
|
+
len(discovery.source_files),
|
|
80
|
+
"semantic relations indexed",
|
|
81
|
+
lines_processed=lines_processed,
|
|
82
|
+
symbols_discovered=sum(len(items) for items in symbol_index.name_index.values()),
|
|
83
|
+
)
|
|
84
|
+
|
|
50
85
|
project_results: dict[str, ProjectIndexResult] = {}
|
|
51
86
|
if index_projects:
|
|
52
87
|
for project in discovery.project_files:
|
|
@@ -54,16 +89,90 @@ def build_codebase_index(
|
|
|
54
89
|
search_paths=discovery.search_paths,
|
|
55
90
|
include_paths=discovery.include_paths,
|
|
56
91
|
defines=discovery.defines,
|
|
92
|
+
on_progress=progress,
|
|
57
93
|
)
|
|
58
94
|
project_results[project] = indexer.index(project)
|
|
59
95
|
|
|
60
|
-
|
|
96
|
+
index = CodebaseIndex(
|
|
61
97
|
root=str(Path(root).expanduser().resolve()),
|
|
62
98
|
discovery=discovery,
|
|
63
99
|
models=models,
|
|
64
100
|
symbol_index=symbol_index,
|
|
65
101
|
project_results=project_results,
|
|
66
102
|
)
|
|
103
|
+
_emit_progress(
|
|
104
|
+
progress,
|
|
105
|
+
"complete",
|
|
106
|
+
str(Path(root).expanduser().resolve()),
|
|
107
|
+
len(discovery.source_files),
|
|
108
|
+
len(discovery.source_files),
|
|
109
|
+
len(discovery.source_files),
|
|
110
|
+
"codebase index complete",
|
|
111
|
+
lines_processed=lines_processed,
|
|
112
|
+
symbols_discovered=sum(len(items) for items in symbol_index.name_index.values()),
|
|
113
|
+
)
|
|
114
|
+
return index
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _emit_progress(
|
|
118
|
+
callback: ProgressCallback | None,
|
|
119
|
+
phase: str,
|
|
120
|
+
path: str,
|
|
121
|
+
files_discovered: int,
|
|
122
|
+
files_completed: int,
|
|
123
|
+
files_total: int | None,
|
|
124
|
+
detail: str,
|
|
125
|
+
*,
|
|
126
|
+
lines_processed: int = 0,
|
|
127
|
+
symbols_discovered: int = 0,
|
|
128
|
+
) -> None:
|
|
129
|
+
if callback is not None:
|
|
130
|
+
callback(
|
|
131
|
+
ProgressEvent(
|
|
132
|
+
phase,
|
|
133
|
+
"delphi",
|
|
134
|
+
path,
|
|
135
|
+
files_discovered,
|
|
136
|
+
files_completed,
|
|
137
|
+
files_total,
|
|
138
|
+
lines_processed,
|
|
139
|
+
symbols_discovered,
|
|
140
|
+
0,
|
|
141
|
+
detail,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class _MonotonicProgress:
|
|
147
|
+
def __init__(self, callback: ProgressCallback | None) -> None:
|
|
148
|
+
self._callback = callback
|
|
149
|
+
self._files_discovered = 0
|
|
150
|
+
self._files_completed = 0
|
|
151
|
+
self._files_total: int | None = None
|
|
152
|
+
self._lines_processed = 0
|
|
153
|
+
self._symbols_discovered = 0
|
|
154
|
+
self._cached_files = 0
|
|
155
|
+
|
|
156
|
+
def __call__(self, event: ProgressEvent) -> None:
|
|
157
|
+
self._files_discovered = max(self._files_discovered, event.files_discovered)
|
|
158
|
+
self._files_completed = max(self._files_completed, event.files_completed)
|
|
159
|
+
if event.files_total is not None:
|
|
160
|
+
self._files_total = max(self._files_total or 0, event.files_total)
|
|
161
|
+
self._lines_processed = max(self._lines_processed, event.lines_processed)
|
|
162
|
+
self._symbols_discovered = max(self._symbols_discovered, event.symbols_discovered)
|
|
163
|
+
self._cached_files = max(self._cached_files, event.cached_files)
|
|
164
|
+
if self._callback is not None:
|
|
165
|
+
self._callback(
|
|
166
|
+
replace(
|
|
167
|
+
event,
|
|
168
|
+
files_discovered=self._files_discovered,
|
|
169
|
+
files_completed=self._files_completed,
|
|
170
|
+
files_total=self._files_total,
|
|
171
|
+
lines_processed=self._lines_processed,
|
|
172
|
+
symbols_discovered=self._symbols_discovered,
|
|
173
|
+
cached_files=self._cached_files,
|
|
174
|
+
)
|
|
175
|
+
)
|
|
67
176
|
|
|
68
177
|
|
|
69
178
|
def render_layer(
|
delphi_lsp/agent_templates.py
CHANGED
|
@@ -239,27 +239,30 @@ Prefer `summary` and `declaration`, narrow `max_items` and `max_chars`, and requ
|
|
|
239
239
|
def _agent_markdown() -> str:
|
|
240
240
|
return """---
|
|
241
241
|
description: Inspect Delphi and Object Pascal codebases through python-delphi-lsp.
|
|
242
|
-
mode:
|
|
242
|
+
mode: all
|
|
243
243
|
temperature: 0
|
|
244
|
-
tools:
|
|
245
|
-
delphi_codebase: true
|
|
246
|
-
skill: true
|
|
247
|
-
lsp: false
|
|
248
|
-
bash: false
|
|
249
|
-
read: false
|
|
250
|
-
glob: false
|
|
251
|
-
grep: false
|
|
252
|
-
edit: false
|
|
253
|
-
write: false
|
|
254
|
-
task: false
|
|
255
|
-
webfetch: false
|
|
256
|
-
todowrite: false
|
|
257
244
|
permission:
|
|
258
|
-
"*": deny
|
|
259
245
|
delphi_codebase: allow
|
|
260
246
|
skill:
|
|
261
247
|
"*": deny
|
|
262
248
|
python-delphi-lsp: allow
|
|
249
|
+
lsp: deny
|
|
250
|
+
bash: deny
|
|
251
|
+
read: deny
|
|
252
|
+
glob: deny
|
|
253
|
+
grep: deny
|
|
254
|
+
list: deny
|
|
255
|
+
edit: deny
|
|
256
|
+
write: deny
|
|
257
|
+
patch: deny
|
|
258
|
+
task: deny
|
|
259
|
+
webfetch: deny
|
|
260
|
+
websearch: deny
|
|
261
|
+
question: deny
|
|
262
|
+
todowrite: deny
|
|
263
|
+
todoread: deny
|
|
264
|
+
codebase_map: deny
|
|
265
|
+
code_guidelines: deny
|
|
263
266
|
---
|
|
264
267
|
|
|
265
268
|
Load `python-delphi-lsp` first, then use only `delphi_codebase` for Delphi and
|
delphi_lsp/agent_workspace.py
CHANGED
|
@@ -264,6 +264,9 @@ class AgentWorkspace:
|
|
|
264
264
|
return f"workspace_v2_{fingerprint}"
|
|
265
265
|
|
|
266
266
|
def select_project(self, project_id: str) -> None:
|
|
267
|
+
self._select_project_with_revision(project_id)
|
|
268
|
+
|
|
269
|
+
def _select_project_with_revision(self, project_id: str) -> str:
|
|
267
270
|
if project_id not in self._project_paths:
|
|
268
271
|
raise AgentProtocolError("project_not_found", f"Project not found: {project_id}.")
|
|
269
272
|
project_path = self._project_paths[project_id]
|
|
@@ -280,7 +283,7 @@ class AgentWorkspace:
|
|
|
280
283
|
fingerprint = _selection_fingerprint(discovery, cached.result, root=self._root)
|
|
281
284
|
if fingerprint == cached.fingerprint:
|
|
282
285
|
self._activate_project(project_id, discovery, cached.result)
|
|
283
|
-
return
|
|
286
|
+
return f"workspace_v2_{fingerprint}"
|
|
284
287
|
|
|
285
288
|
if project_path is None:
|
|
286
289
|
result = _catalog_workspace_sources(discovery)
|
|
@@ -292,11 +295,13 @@ class AgentWorkspace:
|
|
|
292
295
|
source_transform=_outline_agent_source,
|
|
293
296
|
)
|
|
294
297
|
result = indexer.index(str(project_path))
|
|
298
|
+
fingerprint = _selection_fingerprint(discovery, result, root=self._root)
|
|
295
299
|
self._project_cache[project_id] = _ProjectCache(
|
|
296
300
|
result=result,
|
|
297
|
-
fingerprint=
|
|
301
|
+
fingerprint=fingerprint,
|
|
298
302
|
)
|
|
299
303
|
self._activate_project(project_id, discovery, result)
|
|
304
|
+
return f"workspace_v2_{fingerprint}"
|
|
300
305
|
|
|
301
306
|
def _activate_project(
|
|
302
307
|
self,
|