Class: Rigor::LanguageServer::BufferTable

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/language_server/buffer_table.rb

Overview

Per-session virtual file table. The LSP server maintains the canonical view of every open buffer here; analysis (slice 4+) reads from this table instead of disk so in-flight edits are reflected immediately.

Keyed by DocumentUri (LSP file://... URIs). Sync is INCREMENTAL (LSP TextDocumentSyncKind::Incremental = 2): a didChange carries range edits which #apply_changes splices into the held text through IncrementalSync. The full-text form (#change, and a contentChanges entry with no range) stays supported — it is still legal under incremental sync and is what didOpen and a client-side resync send.

Defined Under Namespace

Classes: Entry

Instance Method Summary collapse

Constructor Details

#initializeBufferTable

Returns a new instance of BufferTable.



21
22
23
24
25
# File 'lib/rigor/language_server/buffer_table.rb', line 21

def initialize
  @entries = {}
  @desynchronized = {}
  @dirty = {}
end

Instance Method Details

#[](uri) ⇒ Object



101
102
103
# File 'lib/rigor/language_server/buffer_table.rb', line 101

def [](uri)
  @entries[uri]
end

#apply_changes(uri:, changes:, version:) ⇒ Boolean

Applies a textDocument/didChange payload under INCREMENTAL sync. Each entry of changes is applied in order against the result of the previous one, per the LSP contract.

On success the entry is replaced with the spliced text at the new version. On failure — a malformed change, or a range edit for a URI with no held buffer — the held text is left EXACTLY as it was and the URI is marked desynchronised: the server's view and the editor's view have diverged, and every position computed from the stale text would be wrong. Consumers check #desynchronized? and decline to answer rather than answering wrongly; the mark clears on the next didOpen or full-text change.

Returns:

  • (Boolean)

    true when the changes were applied.



55
56
57
58
59
60
61
62
63
64
# File 'lib/rigor/language_server/buffer_table.rb', line 55

def apply_changes(uri:, changes:, version:)
  text = IncrementalSync.apply_all(@entries[uri]&.bytes, changes)
  @desynchronized.delete(uri)
  @dirty[uri] = true
  @entries[uri] = Entry.new(uri: uri, bytes: text, version: version)
  true
rescue IncrementalSync::UnappliableChange => e
  @desynchronized[uri] = e.message
  false
end

#change(uri:, bytes:, version:) ⇒ Object

Records a full-text textDocument/didChange. The full new buffer text replaces the entry. If the client sends a didChange for a URI that was never opened (spec violation), the entry is still created — defensive.



39
40
41
42
43
# File 'lib/rigor/language_server/buffer_table.rb', line 39

def change(uri:, bytes:, version:)
  @desynchronized.delete(uri)
  @dirty[uri] = true
  @entries[uri] = Entry.new(uri: uri, bytes: bytes, version: version)
end

#close(uri:) ⇒ Object

Records a textDocument/didClose event. The entry is removed. Subsequent reads via #[] return nil.



78
79
80
81
82
# File 'lib/rigor/language_server/buffer_table.rb', line 78

def close(uri:)
  @desynchronized.delete(uri)
  @dirty.delete(uri)
  @entries.delete(uri)
end

#desynchronization_reason(uri) ⇒ String?

Returns why uri is desynchronised, for the log line; nil when it is in sync.

Returns:

  • (String, nil)

    why uri is desynchronised, for the log line; nil when it is in sync.



73
74
75
# File 'lib/rigor/language_server/buffer_table.rb', line 73

def desynchronization_reason(uri)
  @desynchronized[uri]
end

#desynchronized?(uri) ⇒ Boolean

Returns true when the last didChange for uri could not be applied, so the held text no longer matches the editor's.

Returns:

  • (Boolean)

    true when the last didChange for uri could not be applied, so the held text no longer matches the editor's.



68
69
70
# File 'lib/rigor/language_server/buffer_table.rb', line 68

def desynchronized?(uri)
  @desynchronized.key?(uri)
end

#dirty?(uri) ⇒ Boolean

Returns true when uri has unsaved changes. A buffer that is dirty may only be published from an analysis that bound ITS bytes — see the publish-set invariant in docs/design/20260517-language-server.md.

Returns:

  • (Boolean)

    true when uri has unsaved changes. A buffer that is dirty may only be published from an analysis that bound ITS bytes — see the publish-set invariant in docs/design/20260517-language-server.md.



97
98
99
# File 'lib/rigor/language_server/buffer_table.rb', line 97

def dirty?(uri)
  @dirty.key?(uri)
end

#open(uri:, bytes:, version:) ⇒ Object

Records a textDocument/didOpen event. Replaces any existing entry (LSP clients may re-open a previously closed URI; the new version is authoritative) and clears any desynchronised mark — the payload carries the client's full text, so the two views agree again.



30
31
32
33
34
# File 'lib/rigor/language_server/buffer_table.rb', line 30

def open(uri:, bytes:, version:)
  @desynchronized.delete(uri)
  @dirty.delete(uri)
  @entries[uri] = Entry.new(uri: uri, bytes: bytes, version: version)
end

#open?(uri) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
# File 'lib/rigor/language_server/buffer_table.rb', line 105

def open?(uri)
  @entries.key?(uri)
end

#save(uri:) ⇒ Object

Records a textDocument/didSave. The client has written the buffer, so the held text and the file on disk agree again and the URI stops being dirty.

Dirtiness is the PROTOCOL's notion — "the client told us it changed and has not told us it saved" — not a byte comparison against disk. A comparison would look stricter and be weaker: it races with the editor's own write, and the server's truth is what the client notified.



90
91
92
# File 'lib/rigor/language_server/buffer_table.rb', line 90

def save(uri:)
  @dirty.delete(uri)
end

#sizeObject



109
110
111
# File 'lib/rigor/language_server/buffer_table.rb', line 109

def size
  @entries.size
end

#urisObject



113
114
115
# File 'lib/rigor/language_server/buffer_table.rb', line 113

def uris
  @entries.keys
end