Class: Rigor::LanguageServer::ProjectContext
- Inherits:
-
Object
- Object
- Rigor::LanguageServer::ProjectContext
- Defined in:
- lib/rigor/language_server/project_context.rb
Overview
Per-session cache of the project-wide analyzer state the LSP reads on every request — chiefly the ‘Environment` (with its ~100-300ms RBS env build), a read-only `Cache::Store` that lets the runner hit the on-disk RBS cache without writing back, and (since the pre-pass cache slice) a frozen Analysis::ProjectScan snapshot covering the plugin registry, dependency-source index, and pre-pass scanner outputs.
The pre-pass scan lets ‘DiagnosticPublisher#run_analysis` build a `Runner` with `prebuilt:` so per-buffer publishes skip plugin `#prepare`, the synthetic-method scanner, the project-patched scanner, and the dependency-source walker. For projects with substrate plugins / opt-in dependency source / sizeable `pre_eval:` configuration this cuts publish wall time substantially — for the trivial case the savings are small (the per-publish path is already ≈2ms once Environment is warm).
Invalidation:
-
‘#invalidate!` drops the cached environment AND project scan + bumps the generation counter; the next reader rebuilds. Watched-file changes (`workspace/didChangeWatchedFiles`) and configuration refreshes (`workspace/didChangeConfiguration`) both trigger this — the next publish observes the new project state.
-
The cache store is NOT invalidated on file change — it’s content-addressed (digests over file contents), so stale entries naturally lose their key match. We DO keep a single Store instance across the session so the in-process memo serves repeat reads cheaply.
Editor-mode trade-off: the cached ‘project_scan` was built without any `buffer:` binding so scanners observed on-disk bytes for every project file (including the file the user is editing right now). Edits to a file that itself declares `Plugin::Macro::HeredocTemplate` consumers or `pre_eval:`-listed methods are not visible until a watched-file change triggers `invalidate!`. The common editor flow (save → file watch fires → publish) refreshes automatically; the rare in-flight edit to a substrate-DSL file is the documented edge case.
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
Returns the value of attribute configuration.
-
#generation ⇒ Object
readonly
Returns the value of attribute generation.
Instance Method Summary collapse
-
#cache_store ⇒ Object
Returns the per-session read-only ‘Cache::Store`.
-
#environment ⇒ Object
Returns the cached ‘Rigor::Environment` for this session, building it on first access.
-
#initialize(configuration:) ⇒ ProjectContext
constructor
A new instance of ProjectContext.
-
#invalidate! ⇒ Object
Drops every cached collaborator and bumps the generation.
-
#project_scan ⇒ Object
Returns the cached Analysis::ProjectScan for this session, building it lazily by spinning up a project-only ‘Runner` (no buffer binding, no `paths` override) and calling `#prepare_project_scan`.
Constructor Details
#initialize(configuration:) ⇒ ProjectContext
Returns a new instance of ProjectContext.
55 56 57 58 59 60 61 |
# File 'lib/rigor/language_server/project_context.rb', line 55 def initialize(configuration:) @configuration = configuration @generation = 0 @environment = nil @cache_store = nil @project_scan = nil end |
Instance Attribute Details
#configuration ⇒ Object (readonly)
Returns the value of attribute configuration.
53 54 55 |
# File 'lib/rigor/language_server/project_context.rb', line 53 def configuration @configuration end |
#generation ⇒ Object (readonly)
Returns the value of attribute generation.
53 54 55 |
# File 'lib/rigor/language_server/project_context.rb', line 53 def generation @generation end |
Instance Method Details
#cache_store ⇒ Object
Returns the per-session read-only ‘Cache::Store`. Read-only so multiple LSP sessions against the same project don’t race on cache writes — same contract editor mode v1 already uses for the CLI ‘–tmp-file` path.
105 106 107 |
# File 'lib/rigor/language_server/project_context.rb', line 105 def cache_store @cache_store ||= Cache::Store.new(root: @configuration.cache_path, read_only: true) end |
#environment ⇒ Object
Returns the cached ‘Rigor::Environment` for this session, building it on first access. The build includes the project’s full scan state (plugin registry, dependency-source index, synthetic-method / project-patched indexes — drawn from #project_scan) AND every Bundler / RBS-collection axis the runner consults at build time, so the resulting env is bit-for-bit equivalent to what ‘Runner.run` would have built on its own.
‘DiagnosticPublisher` passes this env through `Runner.new(environment: …)` so per-buffer publishes share one instance instead of repeating the `Environment.for_project` build per call (bundler discovery, RbsLoader construction, signature_paths composition). Subsequent calls return the same instance until `#invalidate!` drops the cache.
The runner attaches its own per-call reporter pair onto the shared env’s ‘Reporters` slot at the start of each `#analyze_files` — so diagnostic events stay scoped to a single publish and do NOT accumulate across publishes.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/rigor/language_server/project_context.rb', line 84 def environment @environment ||= Environment.for_project( libraries: @configuration.libraries, signature_paths: @configuration.signature_paths, cache_store: cache_store, plugin_registry: project_scan.plugin_registry, dependency_source_index: project_scan.dependency_source_index, synthetic_method_index: project_scan.synthetic_method_index, project_patched_methods: project_scan.project_patched_methods, bundler_bundle_path: @configuration.bundler_bundle_path, bundler_auto_detect: @configuration.bundler_auto_detect, bundler_lockfile: @configuration.bundler_lockfile, rbs_collection_lockfile: @configuration.rbs_collection_lockfile, rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect ) end |
#invalidate! ⇒ Object
Drops every cached collaborator and bumps the generation. The next reader rebuilds from scratch. Triggered by ‘workspace/didChangeWatchedFiles` for project source files and by `workspace/didChangeConfiguration`.
123 124 125 126 127 128 129 130 131 |
# File 'lib/rigor/language_server/project_context.rb', line 123 def invalidate! @generation += 1 @environment = nil @project_scan = nil # Cache store stays — it's content-addressed; a stale env # build won't be served because the file digest mixed into # the cache key has changed. nil end |
#project_scan ⇒ Object
Returns the cached Analysis::ProjectScan for this session, building it lazily by spinning up a project-only ‘Runner` (no buffer binding, no `paths` override) and calling `#prepare_project_scan`. The cold build pays the full pre-pass cost once per generation; every subsequent `Runner.new(prebuilt: project_scan)` skips it.
115 116 117 |
# File 'lib/rigor/language_server/project_context.rb', line 115 def project_scan @project_scan ||= build_project_scan end |