python-delphi-lsp 2.0.3__tar.gz → 2.0.4__tar.gz
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.
- {python_delphi_lsp-2.0.3/python_delphi_lsp.egg-info → python_delphi_lsp-2.0.4}/PKG-INFO +15 -2
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/README.md +14 -1
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/__init__.py +3 -0
- python_delphi_lsp-2.0.4/delphi_lsp/_version.py +1 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_context.py +8 -1
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_layers.py +115 -6
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/lsp_server.py +195 -33
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/preprocessor.py +32 -10
- python_delphi_lsp-2.0.4/delphi_lsp/progress.py +26 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/project_discovery.py +125 -16
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/project_indexer.py +43 -1
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/pyproject.toml +1 -1
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4/python_delphi_lsp.egg-info}/PKG-INFO +15 -2
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/python_delphi_lsp.egg-info/SOURCES.txt +2 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_codebase.py +79 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_lsp_support.py +243 -18
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_package_metadata.py +2 -2
- python_delphi_lsp-2.0.4/tests/test_progress.py +58 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_project_discovery.py +77 -5
- python_delphi_lsp-2.0.3/delphi_lsp/_version.py +0 -1
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/.github/workflows/ci.yml +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/LICENSE +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/MANIFEST.in +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_cli.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_metrics.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_protocol.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_relations.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_templates.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/agent_workspace.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/binary.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/comment_builder.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/consts.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/grammar.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/lark_builder.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/lark_tokens.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/metrics.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/nodes.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/parser.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/semantic.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/semantic_builder.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/source_reader.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/workspace.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/delphi_lsp/writer.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/opencode.json +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/python_delphi_lsp.egg-info/dependency_links.txt +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/python_delphi_lsp.egg-info/entry_points.txt +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/python_delphi_lsp.egg-info/requires.txt +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/python_delphi_lsp.egg-info/top_level.txt +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/audit_delphi_language_features.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/benchmark_github_corpus.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/bootstrap_vllm_codebase_skill_test.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/bootstrap_vllm_opencode_test.ps1 +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/bootstrap_vllm_opencode_test.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/bootstrap_vllm_opencode_test.sh +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/build_github_performance_corpus.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/check_ornith_cache.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/generate_progress_pdf.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/generate_release_evidence.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/ollama/ornith-lspctx.Modelfile +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/prepare_ornith_cache.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/run_opencode_lsp_probe.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/run_openrouter_github_e2e.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/start_ornith_vllm.sh +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/scripts/summarize_opencode_request_payloads.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/setup.cfg +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/corpora.performance.lock.json +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/DeprecatedOnConst.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/VariantRecordFieldAttributes.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/alignedrecords.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/constset.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/deprecatedtype.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/dottedtypes.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/endtoken.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/experimentals.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/externalfunction.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/finalizationinitializationexports.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/forwardoverloaded.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/forwardwithoutsemicolon.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/genericconstraints.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/genericinterfacemethoddelegation.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/implementsgenerictype.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/include file2.inc +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/includefile.inc +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/includefile.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/managedrecords.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/messagemethod.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/multiline.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/nonalignedrecords.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/numbers.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/pointerchars.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/properties.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/strictvisibility.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/tryexcept.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/umlauts.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/legacy_snippets/whitespacearoundifdefcondition.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/library_demo.dpr +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/package_demo.dpk +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/program_demo.dpr +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_advanced.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_attributes.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_basic.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_consumer.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_generics.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_inheritance.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_math.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_properties.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_sections.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_statements.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_types.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_unresolved.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/fixtures/unit_with.pas +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_context.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_metrics.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_protocol.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_relations.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_worker.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_agent_workspace.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_bootstrap_vllm_codebase_skill.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_bootstrap_vllm_opencode.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_diagnostics.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_github_performance_corpus.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_language_feature_matrix.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_legacy_snippets.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_lsp_features.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_metrics.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_opencode_config.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_opencode_probe.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_opencode_request_payloads.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_openrouter_github_e2e.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_ornith_cache_prepare.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_parser.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_preprocessor.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_project_indexer.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_release_evidence.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_semantic.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_vllm_start_script.py +0 -0
- {python_delphi_lsp-2.0.3 → python_delphi_lsp-2.0.4}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-delphi-lsp
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
4
4
|
Summary: Python Delphi/Object Pascal parser, semantic indexer, and language server.
|
|
5
5
|
Author: Dark Light
|
|
6
6
|
License-Expression: MPL-2.0
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
`python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
|
|
43
43
|
indexes, serves LSP, and provides bounded codebase navigation for agents.
|
|
44
|
-
Version 2.0.
|
|
44
|
+
Version 2.0.4 is authored by Dark Light and supports Windows, macOS, and Linux.
|
|
45
45
|
|
|
46
46
|
## Install and quick start
|
|
47
47
|
|
|
@@ -82,6 +82,19 @@ project = ProjectIndexer(
|
|
|
82
82
|
print(project.parsed_units)
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
Long-running discovery and indexing accept a keyword-only `on_progress`
|
|
86
|
+
callback. It receives an immutable `ProgressEvent` with package-controlled
|
|
87
|
+
phase, path, and monotonic counters; callback exceptions are not suppressed.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from delphi_lsp import ProjectIndexer, ProgressEvent
|
|
91
|
+
|
|
92
|
+
def report(event: ProgressEvent) -> None:
|
|
93
|
+
print(event.phase, event.files_completed, event.path)
|
|
94
|
+
|
|
95
|
+
ProjectIndexer(on_progress=report).index("Main.dpr")
|
|
96
|
+
```
|
|
97
|
+
|
|
85
98
|
## Architecture metrics
|
|
86
99
|
|
|
87
100
|
The public metrics API analyzes a single unit or aggregates a complete project:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`python-delphi-lsp` parses Delphi/Object Pascal, builds semantic and project
|
|
4
4
|
indexes, serves LSP, and provides bounded codebase navigation for agents.
|
|
5
|
-
Version 2.0.
|
|
5
|
+
Version 2.0.4 is authored by Dark Light and supports Windows, macOS, and Linux.
|
|
6
6
|
|
|
7
7
|
## Install and quick start
|
|
8
8
|
|
|
@@ -43,6 +43,19 @@ project = ProjectIndexer(
|
|
|
43
43
|
print(project.parsed_units)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
Long-running discovery and indexing accept a keyword-only `on_progress`
|
|
47
|
+
callback. It receives an immutable `ProgressEvent` with package-controlled
|
|
48
|
+
phase, path, and monotonic counters; callback exceptions are not suppressed.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from delphi_lsp import ProjectIndexer, ProgressEvent
|
|
52
|
+
|
|
53
|
+
def report(event: ProgressEvent) -> None:
|
|
54
|
+
print(event.phase, event.files_completed, event.path)
|
|
55
|
+
|
|
56
|
+
ProjectIndexer(on_progress=report).index("Main.dpr")
|
|
57
|
+
```
|
|
58
|
+
|
|
46
59
|
## Architecture metrics
|
|
47
60
|
|
|
48
61
|
The public metrics API analyzes a single unit or aggregates a complete project:
|
|
@@ -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',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.4"
|
|
@@ -695,7 +695,14 @@ def _build_registry(workspace: AgentWorkspace, project_id: str, revision: str) -
|
|
|
695
695
|
include_paths=workspace.include_paths,
|
|
696
696
|
)
|
|
697
697
|
sources[source_path] = document
|
|
698
|
-
|
|
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))
|
|
699
706
|
unit_symbols = _collect_raw_symbols(model.unit_scope, unit, source_path, document)
|
|
700
707
|
raw_symbols.extend(_exclude_routine_locals(unit_symbols, document))
|
|
701
708
|
|
|
@@ -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(
|
|
@@ -7,6 +7,7 @@ from urllib.parse import unquote, urlparse
|
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
|
+
from .preprocessor import Preprocessor
|
|
10
11
|
from .semantic import (
|
|
11
12
|
ReferenceKind,
|
|
12
13
|
Scope,
|
|
@@ -249,7 +250,11 @@ class LspWorkspaceState:
|
|
|
249
250
|
text = read_source_text(path)
|
|
250
251
|
except (OSError, UnicodeError):
|
|
251
252
|
return None
|
|
252
|
-
return build_outline_semantic_model(
|
|
253
|
+
return build_outline_semantic_model(
|
|
254
|
+
text,
|
|
255
|
+
file_name,
|
|
256
|
+
defines=self.config.defines,
|
|
257
|
+
)
|
|
253
258
|
|
|
254
259
|
def full_semantic_for_uri(self, uri: str) -> Optional[SemanticModel]:
|
|
255
260
|
file_name = uri_to_path(uri)
|
|
@@ -267,7 +272,11 @@ class LspWorkspaceState:
|
|
|
267
272
|
text = self.text_for_uri(uri)
|
|
268
273
|
if text is None:
|
|
269
274
|
return None
|
|
270
|
-
return build_outline_semantic_model(
|
|
275
|
+
return build_outline_semantic_model(
|
|
276
|
+
text,
|
|
277
|
+
uri_to_path(uri),
|
|
278
|
+
defines=self.config.defines,
|
|
279
|
+
)
|
|
271
280
|
|
|
272
281
|
def text_for_uri(self, uri: str) -> Optional[str]:
|
|
273
282
|
doc = self.documents.get(uri)
|
|
@@ -293,7 +302,14 @@ class LspWorkspaceState:
|
|
|
293
302
|
) -> Optional[WorkspaceSemanticResult]:
|
|
294
303
|
if not sources:
|
|
295
304
|
return None
|
|
296
|
-
models = {
|
|
305
|
+
models = {
|
|
306
|
+
file_name: build_outline_semantic_model(
|
|
307
|
+
text,
|
|
308
|
+
file_name,
|
|
309
|
+
defines=self.config.defines,
|
|
310
|
+
)
|
|
311
|
+
for file_name, text in sources.items()
|
|
312
|
+
}
|
|
297
313
|
index = SymbolIndex()
|
|
298
314
|
for model in models.values():
|
|
299
315
|
index.register_unit(model.unit_scope.name, model.unit_scope)
|
|
@@ -343,8 +359,51 @@ def uri_to_path(uri: str) -> str:
|
|
|
343
359
|
return path or uri
|
|
344
360
|
|
|
345
361
|
|
|
346
|
-
def outline_source(text: str) -> str:
|
|
347
|
-
|
|
362
|
+
def outline_source(text: str, *, defines: Iterable[str] | None = None) -> str:
|
|
363
|
+
scan_text = (
|
|
364
|
+
text
|
|
365
|
+
if defines is None
|
|
366
|
+
else _conditional_outline_scan_text(text, defines=defines)
|
|
367
|
+
)
|
|
368
|
+
return _blank_compound_statement_bodies(
|
|
369
|
+
text,
|
|
370
|
+
scan_text=scan_text,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _conditional_outline_scan_text(
|
|
375
|
+
text: str,
|
|
376
|
+
*,
|
|
377
|
+
defines: Iterable[str],
|
|
378
|
+
) -> str:
|
|
379
|
+
if '{$' not in text and '(*$' not in text:
|
|
380
|
+
return text
|
|
381
|
+
selected = Preprocessor(
|
|
382
|
+
defines=defines,
|
|
383
|
+
include_loader=lambda _name, _current: None,
|
|
384
|
+
preserve_active_directives=True,
|
|
385
|
+
).process(text, '<outline>').text
|
|
386
|
+
original_lines = text.splitlines(keepends=True)
|
|
387
|
+
selected_lines = selected.splitlines(keepends=True)
|
|
388
|
+
if len(original_lines) != len(selected_lines):
|
|
389
|
+
return text
|
|
390
|
+
restored: list[str] = []
|
|
391
|
+
for original_line, selected_line in zip(original_lines, selected_lines, strict=True):
|
|
392
|
+
original_body, original_ending = _split_line_ending(original_line)
|
|
393
|
+
selected_body, _selected_ending = _split_line_ending(selected_line)
|
|
394
|
+
if len(original_body) != len(selected_body):
|
|
395
|
+
return text
|
|
396
|
+
restored.append(selected_body + original_ending)
|
|
397
|
+
result = ''.join(restored)
|
|
398
|
+
return result if len(result) == len(text) else text
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _split_line_ending(line: str) -> tuple[str, str]:
|
|
402
|
+
if line.endswith('\r\n'):
|
|
403
|
+
return line[:-2], '\r\n'
|
|
404
|
+
if line.endswith(('\r', '\n')):
|
|
405
|
+
return line[:-1], line[-1]
|
|
406
|
+
return line, ''
|
|
348
407
|
|
|
349
408
|
|
|
350
409
|
def multiline_string_block_end(text: str, start: int) -> int | None:
|
|
@@ -406,11 +465,29 @@ _CLASS_MEMBER_BOUNDARIES = frozenset(
|
|
|
406
465
|
_GENERIC_ROUTINE_HEADINGS = frozenset(
|
|
407
466
|
{'constructor', 'destructor', 'function', 'operator', 'procedure'}
|
|
408
467
|
)
|
|
468
|
+
_CONDITIONAL_DIRECTIVE_OPENERS = frozenset({'if', 'ifdef', 'ifndef', 'ifopt'})
|
|
469
|
+
_CONDITIONAL_DIRECTIVE_ALTERNATIVES = frozenset({'else', 'elseif'})
|
|
470
|
+
_CONDITIONAL_DIRECTIVE_CLOSERS = frozenset({'endif', 'ifend'})
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@dataclass
|
|
474
|
+
class _ConditionalOutlineFrame:
|
|
475
|
+
base_stack: tuple[str, ...]
|
|
476
|
+
branch_stacks: list[tuple[str, ...]] = field(default_factory=list)
|
|
477
|
+
has_alternative: bool = False
|
|
409
478
|
|
|
410
479
|
|
|
411
|
-
def _blank_compound_statement_bodies(
|
|
412
|
-
|
|
480
|
+
def _blank_compound_statement_bodies(
|
|
481
|
+
text: str,
|
|
482
|
+
*,
|
|
483
|
+
scan_text: str | None = None,
|
|
484
|
+
) -> str:
|
|
485
|
+
scanned = scan_text if scan_text is not None else text
|
|
486
|
+
if len(scanned) != len(text):
|
|
487
|
+
return text
|
|
488
|
+
chars = list(scanned)
|
|
413
489
|
end_stack: list[str] = []
|
|
490
|
+
conditional_frames: list[_ConditionalOutlineFrame] = []
|
|
414
491
|
body_start: int | None = None
|
|
415
492
|
previous_token: str | None = None
|
|
416
493
|
previous_token_is_identifier = False
|
|
@@ -421,26 +498,48 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
421
498
|
routine_heading_active = False
|
|
422
499
|
type_constructors: dict[tuple[int, int], str] = {}
|
|
423
500
|
i = 0
|
|
424
|
-
n = len(
|
|
501
|
+
n = len(scanned)
|
|
425
502
|
while i < n:
|
|
426
|
-
ch =
|
|
427
|
-
nxt =
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
503
|
+
ch = scanned[i]
|
|
504
|
+
nxt = scanned[i + 1] if i + 1 < n else ''
|
|
505
|
+
|
|
506
|
+
directive = _compiler_directive_at(scanned, i)
|
|
507
|
+
if directive is not None and (end_stack or conditional_frames):
|
|
508
|
+
directive_name, directive_end = directive
|
|
509
|
+
if directive_name in _CONDITIONAL_DIRECTIVE_OPENERS:
|
|
510
|
+
conditional_frames.append(
|
|
511
|
+
_ConditionalOutlineFrame(base_stack=tuple(end_stack))
|
|
512
|
+
)
|
|
513
|
+
elif directive_name in _CONDITIONAL_DIRECTIVE_ALTERNATIVES:
|
|
514
|
+
if not conditional_frames:
|
|
515
|
+
return scanned
|
|
516
|
+
frame = conditional_frames[-1]
|
|
517
|
+
frame.branch_stacks.append(tuple(end_stack))
|
|
518
|
+
frame.has_alternative = True
|
|
519
|
+
end_stack[:] = frame.base_stack
|
|
520
|
+
elif directive_name in _CONDITIONAL_DIRECTIVE_CLOSERS:
|
|
521
|
+
if not conditional_frames:
|
|
522
|
+
return scanned
|
|
523
|
+
frame = conditional_frames.pop()
|
|
524
|
+
frame.branch_stacks.append(tuple(end_stack))
|
|
525
|
+
if not frame.has_alternative:
|
|
526
|
+
frame.branch_stacks.append(frame.base_stack)
|
|
527
|
+
merged_stack = _merge_conditional_outline_stacks(frame)
|
|
528
|
+
if merged_stack is None:
|
|
529
|
+
merged_stack = _common_conditional_outline_stack(frame)
|
|
530
|
+
body_start = None
|
|
531
|
+
end_stack[:] = merged_stack
|
|
532
|
+
i = directive_end
|
|
533
|
+
continue
|
|
435
534
|
|
|
436
535
|
if ch.isspace():
|
|
437
536
|
i += 1
|
|
438
|
-
while i < n and
|
|
537
|
+
while i < n and scanned[i].isspace():
|
|
439
538
|
i += 1
|
|
440
539
|
continue
|
|
441
540
|
|
|
442
541
|
if ch == "'":
|
|
443
|
-
block_end = multiline_string_block_end(
|
|
542
|
+
block_end = multiline_string_block_end(scanned, i)
|
|
444
543
|
if block_end is not None:
|
|
445
544
|
i = block_end
|
|
446
545
|
previous_token = 'literal'
|
|
@@ -449,8 +548,8 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
449
548
|
continue
|
|
450
549
|
i += 1
|
|
451
550
|
while i < n:
|
|
452
|
-
if
|
|
453
|
-
if i + 1 < n and
|
|
551
|
+
if scanned[i] == "'":
|
|
552
|
+
if i + 1 < n and scanned[i + 1] == "'":
|
|
454
553
|
i += 2
|
|
455
554
|
continue
|
|
456
555
|
i += 1
|
|
@@ -463,13 +562,13 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
463
562
|
|
|
464
563
|
if ch == '/' and nxt == '/':
|
|
465
564
|
i += 2
|
|
466
|
-
while i < n and
|
|
565
|
+
while i < n and scanned[i] not in {'\n', '\r'}:
|
|
467
566
|
i += 1
|
|
468
567
|
continue
|
|
469
568
|
|
|
470
569
|
if ch == '{':
|
|
471
570
|
i += 1
|
|
472
|
-
while i < n and
|
|
571
|
+
while i < n and scanned[i] != '}':
|
|
473
572
|
i += 1
|
|
474
573
|
if i < n:
|
|
475
574
|
i += 1
|
|
@@ -477,7 +576,7 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
477
576
|
|
|
478
577
|
if ch == '(' and nxt == '*':
|
|
479
578
|
i += 2
|
|
480
|
-
while i + 1 < n and not (
|
|
579
|
+
while i + 1 < n and not (scanned[i] == '*' and scanned[i + 1] == ')'):
|
|
481
580
|
i += 1
|
|
482
581
|
if i + 1 < n:
|
|
483
582
|
i += 2
|
|
@@ -486,9 +585,9 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
486
585
|
if ch == '&' and (nxt.isalpha() or nxt == '_'):
|
|
487
586
|
start = i
|
|
488
587
|
i += 2
|
|
489
|
-
while i < n and (
|
|
588
|
+
while i < n and (scanned[i].isalnum() or scanned[i] == '_'):
|
|
490
589
|
i += 1
|
|
491
|
-
previous_token =
|
|
590
|
+
previous_token = scanned[start:i].casefold()
|
|
492
591
|
previous_token_is_identifier = True
|
|
493
592
|
generic_owner_ready = False
|
|
494
593
|
continue
|
|
@@ -496,9 +595,9 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
496
595
|
if ch.isalpha() or ch == '_':
|
|
497
596
|
start = i
|
|
498
597
|
i += 1
|
|
499
|
-
while i < n and (
|
|
598
|
+
while i < n and (scanned[i].isalnum() or scanned[i] == '_'):
|
|
500
599
|
i += 1
|
|
501
|
-
word =
|
|
600
|
+
word = scanned[start:i].casefold()
|
|
502
601
|
generic_owner_ready = False
|
|
503
602
|
opens_construct: bool | None = False
|
|
504
603
|
if not end_stack:
|
|
@@ -517,14 +616,14 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
517
616
|
else:
|
|
518
617
|
opens_construct = _opens_end_terminated_construct(
|
|
519
618
|
word,
|
|
520
|
-
text=
|
|
619
|
+
text=scanned,
|
|
521
620
|
word_end=i,
|
|
522
621
|
previous_token=previous_token,
|
|
523
622
|
end_stack=end_stack,
|
|
524
623
|
type_constructor=type_constructors.get((paren_depth, bracket_depth)),
|
|
525
624
|
)
|
|
526
625
|
if opens_construct is None:
|
|
527
|
-
return
|
|
626
|
+
return scanned
|
|
528
627
|
if opens_construct:
|
|
529
628
|
end_stack.append(word)
|
|
530
629
|
if word == 'class' and opens_construct:
|
|
@@ -546,7 +645,7 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
546
645
|
if ch == '<' and nxt not in {'=', '>'}:
|
|
547
646
|
if angle_depth > 0:
|
|
548
647
|
if not previous_token_is_identifier:
|
|
549
|
-
return
|
|
648
|
+
return scanned
|
|
550
649
|
angle_depth += 1
|
|
551
650
|
elif generic_owner_ready or (
|
|
552
651
|
routine_heading_active
|
|
@@ -582,7 +681,64 @@ def _blank_compound_statement_bodies(text: str) -> str:
|
|
|
582
681
|
previous_token = ch
|
|
583
682
|
previous_token_is_identifier = False
|
|
584
683
|
i += 1
|
|
585
|
-
return
|
|
684
|
+
return scanned if angle_depth > 0 or conditional_frames else ''.join(chars)
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _compiler_directive_at(text: str, start: int) -> tuple[str, int] | None:
|
|
688
|
+
if text.startswith('{$', start):
|
|
689
|
+
content_start = start + 2
|
|
690
|
+
close = text.find('}', content_start)
|
|
691
|
+
if close < 0:
|
|
692
|
+
return None
|
|
693
|
+
end = close + 1
|
|
694
|
+
elif text.startswith('(*$', start):
|
|
695
|
+
content_start = start + 3
|
|
696
|
+
close = text.find('*)', content_start)
|
|
697
|
+
if close < 0:
|
|
698
|
+
return None
|
|
699
|
+
end = close + 2
|
|
700
|
+
else:
|
|
701
|
+
return None
|
|
702
|
+
match = re.match(r'\s*([A-Za-z]+)', text[content_start:close])
|
|
703
|
+
return ((match.group(1).casefold() if match else ''), end)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _merge_conditional_outline_stacks(
|
|
707
|
+
frame: _ConditionalOutlineFrame,
|
|
708
|
+
) -> list[str] | None:
|
|
709
|
+
base = frame.base_stack
|
|
710
|
+
branches = frame.branch_stacks
|
|
711
|
+
if not branches or any(
|
|
712
|
+
len(branch) < len(base) or branch[:len(base)] != base
|
|
713
|
+
for branch in branches
|
|
714
|
+
):
|
|
715
|
+
return None
|
|
716
|
+
tails = [branch[len(base):] for branch in branches]
|
|
717
|
+
shapes = [tuple(_outline_block_shape(item) for item in tail) for tail in tails]
|
|
718
|
+
if any(shape != shapes[0] for shape in shapes[1:]):
|
|
719
|
+
return None
|
|
720
|
+
merged = list(base)
|
|
721
|
+
for values in zip(*tails, strict=True):
|
|
722
|
+
merged.append(values[0] if all(value == values[0] for value in values) else 'begin')
|
|
723
|
+
return merged
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def _outline_block_shape(value: str) -> str:
|
|
727
|
+
return 'statement' if value in _END_TERMINATED_STATEMENTS else value
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _common_conditional_outline_stack(
|
|
731
|
+
frame: _ConditionalOutlineFrame,
|
|
732
|
+
) -> list[str]:
|
|
733
|
+
common: list[str] = []
|
|
734
|
+
for values in zip(*frame.branch_stacks):
|
|
735
|
+
shapes = {_outline_block_shape(value) for value in values}
|
|
736
|
+
if len(shapes) != 1:
|
|
737
|
+
break
|
|
738
|
+
common.append(
|
|
739
|
+
values[0] if all(value == values[0] for value in values) else 'begin'
|
|
740
|
+
)
|
|
741
|
+
return common
|
|
586
742
|
|
|
587
743
|
|
|
588
744
|
def _opens_end_terminated_construct(
|
|
@@ -684,7 +840,13 @@ _TYPE_REF_STOP_RE = re.compile(
|
|
|
684
840
|
)
|
|
685
841
|
|
|
686
842
|
|
|
687
|
-
def build_outline_semantic_model(
|
|
843
|
+
def build_outline_semantic_model(
|
|
844
|
+
text: str,
|
|
845
|
+
file_name: str,
|
|
846
|
+
*,
|
|
847
|
+
defines: Iterable[str] = (),
|
|
848
|
+
) -> SemanticModel:
|
|
849
|
+
text = outline_source(text, defines=defines)
|
|
688
850
|
unit_name = _outline_unit_name(text, file_name)
|
|
689
851
|
unit_scope = Scope(kind=ScopeKind.UNIT, name=unit_name)
|
|
690
852
|
unit_range = SourceRange(file_name, 1, 1, 1, max(2, len(unit_name) + 1))
|