Class: Rigor::LanguageServer::DiagnosticPublisher
- Inherits:
-
Object
- Object
- Rigor::LanguageServer::DiagnosticPublisher
- Defined in:
- lib/rigor/language_server/diagnostic_publisher.rb
Overview
Converts buffer state into textDocument/publishDiagnostics notifications. Owns the Rigor
Analysis::Runner orchestration for the per-buffer single-file scope path that editor mode v1 already
supports — every publish_for(uri) call materialises a BufferBinding from the BufferTable entry, runs
the Runner, and pushes the resulting LSP Diagnostic[] through the writer.
Debouncing is wired via an optional Debouncer injected at construction (delay defaults to 200ms
quiet-time); without a debouncer each call blocks synchronously (primarily for specs). When several
buffers' debounce timers elapse around the same moment, the PublishBatcher coalesces them into one
#publish_many round dispatched across the fork-based worker pool (issue #142) instead of N
independent, GVL-serialized Runner calls.
Constant Summary collapse
- SEVERITY_MAP =
Maps Rigor severity symbols to LSP DiagnosticSeverity integers per spec § "Diagnostic":
1 = Error, 2 = Warning, 3 = Information, 4 = Hint. { error: 1, warning: 2, info: 3, hint: 4 }.freeze
- PROJECT_ROUND_KEY =
The single Debouncer key every save round shares (#246). Per-URI keys would let two saves start two concurrent rounds; one key means a burst collapses into the last one.
:__rigor_project_round__
Instance Method Summary collapse
-
#cancel_pending ⇒ Object
Cancels every in-flight debounced task.
-
#initialize(writer:, buffer_table:, project_context:, debouncer: nil, debounce_seconds: 0.2) ⇒ DiagnosticPublisher
constructor
A new instance of DiagnosticPublisher.
-
#publish_empty(uri) ⇒ Object
Publishes an EMPTY diagnostic array for
uri. -
#publish_for(uri) ⇒ Object
Run analysis for the buffer at
uri(looked up in the BufferTable) and push atextDocument/publishDiagnosticsnotification. -
#publish_many(uris) ⇒ Object
Issue #142 — publishes N dirty buffers' diagnostics through ONE dispatch across the fork-based worker pool (
Analysis::Runner::BufferPoolDispatcher) instead of N independent, GVL-serializedRunnercalls. -
#publish_project(saved_uri) ⇒ Object
Runs one whole-project save round and publishes to the publish set (#246).
Constructor Details
#initialize(writer:, buffer_table:, project_context:, debouncer: nil, debounce_seconds: 0.2) ⇒ DiagnosticPublisher
Returns a new instance of DiagnosticPublisher.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/rigor/language_server/diagnostic_publisher.rb', line 47 def initialize(writer:, buffer_table:, project_context:, debouncer: nil, debounce_seconds: 0.2) @writer = writer @buffer_table = buffer_table @project_context = project_context @debouncer = debouncer @debounce_seconds = debounce_seconds @round_lock = Mutex.new @round_running = false @round_pending = false # Issue #142 — coalesces buffers whose OWN debounce timers elapse close together into one # `#publish_many` round instead of N independent GVL-serialized `Runner` calls. Separate from # `@round_lock` above, which single-flights the whole-project SAVE round; this single-flights the # per-buffer DIDCHANGE batch instead. See `PublishBatcher` for the coalescing mechanics. @batcher = PublishBatcher.new( on_batch: ->(uris) { publish_many(uris) }, on_error: ->(e) { warn "DiagnosticPublisher batch round: #{e.class}: #{e.}" } ) end |
Instance Method Details
#cancel_pending ⇒ Object
Cancels every in-flight debounced task. Called from Server#handle_shutdown so pending publishes
don't fire against a closed STDOUT.
128 129 130 |
# File 'lib/rigor/language_server/diagnostic_publisher.rb', line 128 def cancel_pending @debouncer&.cancel_all end |
#publish_empty(uri) ⇒ Object
Publishes an EMPTY diagnostic array for uri. The LSP-spec idiom for "clear inline markers" — called
from didClose so clients drop stale highlights when the user closes a buffer.
122 123 124 |
# File 'lib/rigor/language_server/diagnostic_publisher.rb', line 122 def publish_empty(uri) notify(uri, []) end |
#publish_for(uri) ⇒ Object
Run analysis for the buffer at uri (looked up in the BufferTable) and push a
textDocument/publishDiagnostics notification. No-op when the URI isn't a file:// form or the
buffer isn't currently open. When a Debouncer is wired, the analysis is scheduled async per the
configured debounce_seconds and joins the batch coalescing layer (PublishBatcher) once its own
quiet-time elapses; otherwise it runs inline (primarily for specs).
72 73 74 75 76 77 78 79 80 81 |
# File 'lib/rigor/language_server/diagnostic_publisher.rb', line 72 def publish_for(uri) path = Uri.to_path(uri) return if path.nil? if @debouncer @debouncer.schedule(uri, delay: @debounce_seconds) { @batcher.enqueue(uri) } else run_and_notify(uri, path) end end |
#publish_many(uris) ⇒ Object
Issue #142 — publishes N dirty buffers' diagnostics through ONE dispatch across the fork-based
worker pool (Analysis::Runner::BufferPoolDispatcher) instead of N independent, GVL-serialized
Runner calls. Each URI's OWN BufferBinding (its logical path bound to its OWN editor tempfile)
travels with it, so a worker analyses that buffer's in-flight bytes — never the file as it sits on
disk — even when several buffers are dispatched in the same round.
Degrades to #run_and_notify's existing single-buffer path when only one URI is eligible after
filtering (a buffer closed mid-debounce-window is dropped; a desynchronised one publishes empty
immediately) — a lone edit takes exactly the path it takes today. The dispatcher itself degrades to
sequential in-process execution for any other precondition (see
BufferPoolDispatcher#dispatchable?), so a pool that cannot start never fails a publish, only slows
it back down to today's wall time.
95 96 97 98 99 100 101 |
# File 'lib/rigor/language_server/diagnostic_publisher.rb', line 95 def publish_many(uris) eligible = uris.uniq.filter_map { |uri| eligible_job(uri) } return if eligible.empty? return run_and_notify(eligible.first.fetch(:uri), eligible.first.fetch(:path)) if eligible.size == 1 publish_batch(eligible) end |
#publish_project(saved_uri) ⇒ Object
Runs one whole-project save round and publishes to the publish set (#246). Called from didSave.
Analysis scope is the whole project; the PUBLISH SET is the open buffers that are not dirty, plus the
buffer that was just saved. A dirty buffer is excluded because only its own didChange analysis has
seen its bytes — publishing this round's on-disk answer for it would replace correct markers with
markers for a file the user has already changed.
Scheduled through the same Debouncer the per-buffer path uses, under one project-wide key, so the dispatcher never blocks on it.
112 113 114 115 116 117 118 |
# File 'lib/rigor/language_server/diagnostic_publisher.rb', line 112 def publish_project(saved_uri) if @debouncer @debouncer.schedule(PROJECT_ROUND_KEY, delay: 0) { run_project_round(saved_uri) } else run_project_round(saved_uri) end end |