Class: Ibex::LSP::DocumentStore

Inherits:
Object
  • Object
show all
Includes:
DocumentStoreDiagnostics, DocumentStoreValidation
Defined in:
lib/ibex/lsp/document_store.rb,
sig/ibex/lsp/document_store.rbs

Overview

Owns open buffers, parsed snapshots, include closures, and reverse dependencies.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DocumentStoreDiagnostics

#aggregated_diagnostics, #empty_diagnostics, #replace_root_diagnostics

Methods included from DocumentStoreValidation

#disk_path_for_close, #parse_candidate, #valid_replacements?, #validate_source, #validate_version

Constructor Details

#initialize(workspace, loader) ⇒ DocumentStore

Returns a new instance of DocumentStore.

RBS:

  • (Workspace workspace, Frontend::SourceLoader loader) -> void

Parameters:



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/ibex/lsp/document_store.rb', line 29

def initialize(workspace, loader)
  @workspace = workspace
  @loader = loader
  @analyzer = WorkspaceAnalyzer.new(workspace, loader)
  @snapshots = {} #: Hash[String, snapshot]
  @roots = {} #: Hash[String, bool]
  @closures = {} #: Hash[String, Array[String]]
  @reverse_dependencies = {} #: Hash[String, Array[String]]
  @resolutions = {} #: Hash[String, Frontend::Resolution]
  @document_diagnostics = {} #: Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]
  @root_diagnostics = {} #: Hash[String, Hash[String, Array[WorkspaceAnalyzer::lsp_diagnostic]]]
end

Instance Attribute Details

#loaderFrontend::SourceLoader (readonly)

RBS:

  • type snapshot = {
      uri: String,
      path: String,
      version: Integer?,
      source: String,
      open: bool,
      document: Frontend::SourceDocument?
    }
    type publication = {
      uri: String,
      ?version: Integer,
      diagnostics: Array[WorkspaceAnalyzer::lsp_diagnostic]
    }

Returns:



25
26
27
# File 'lib/ibex/lsp/document_store.rb', line 25

def loader
  @loader
end

#workspaceWorkspace (readonly)

Signature:

  • Workspace

Returns:



26
27
28
# File 'lib/ibex/lsp/document_store.rb', line 26

def workspace
  @workspace
end

Instance Method Details

#affected_roots(path) ⇒ Array[String]

RBS:

  • (String path) -> Array[String]

Parameters:

  • path (String)

Returns:

  • (Array[String])


186
187
188
189
190
# File 'lib/ibex/lsp/document_store.rb', line 186

def affected_roots(path)
  roots = @reverse_dependencies.fetch(path, empty_paths).dup
  roots << path if @roots[path]
  roots.uniq
end

#change(uri, version, source) ⇒ Array[publication]

RBS:

  • (String uri, Integer version, String source) -> Array[publication]

Parameters:

  • uri (String)
  • version (Integer)
  • source (String)

Returns:

  • (Array[publication])


55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/ibex/lsp/document_store.rb', line 55

def change(uri, version, source)
  path = workspace.path(uri)
  current = open_snapshot(path, uri)
  validate_version(version)
  unless current.fetch(:version) && version > current.fetch(:version)
    raise ProtocolError.new("document version must increase monotonically", code: -32_602)
  end

  validate_source(source)
  loader.set_overlay(path, source)
  @snapshots[path] = snapshot(uri, path, version, source, true, nil)
  refresh(path)
end

#close(uri) ⇒ Array[publication]

Clear the open version first, then replace it with a disk snapshot if one exists.

RBS:

  • (String uri) -> Array[publication]

Parameters:

  • uri (String)

Returns:

  • (Array[publication])


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ibex/lsp/document_store.rb', line 84

def close(uri)
  path = workspace.path(uri)
  open_snapshot(path, uri)
  clear = { uri: uri, diagnostics: [] } #: publication
  old_files = files_for(path)
  was_root = @roots[path]
  disk_path = disk_path_for_close(path, uri)
  disk_source = loader.disk_source(disk_path) if disk_path && loader.disk_file?(disk_path)
  loader.delete_overlay(path)
  if disk_source
    @snapshots[path] = snapshot(uri, path, nil, disk_source, false, nil)
    if was_root
      analyzed = @analyzer.analyze(path)
      update_snapshot(path, analyzed)
      remove_root_state(path)
      [clear] + publish_paths((old_files + [path]).uniq)
    else
      [clear] + refresh(path)
    end
  else
    remove_path(path)
    [clear] + refresh_dependents(path)
  end
end

#empty_pathsArray[String]

RBS:

  • () -> Array[String]

Returns:

  • (Array[String])


243
244
245
# File 'lib/ibex/lsp/document_store.rb', line 243

def empty_paths
  [] #: Array[String]
end

#files_for(path) ⇒ Array[String]

RBS:

  • (String path) -> Array[String]

Parameters:

  • path (String)

Returns:

  • (Array[String])


124
125
126
127
128
# File 'lib/ibex/lsp/document_store.rb', line 124

def files_for(path)
  roots = affected_roots(path)
  files = roots.flat_map { |root| @closures.fetch(root, [root]) }
  (files + [path]).uniq
end

#open(uri, version, source) ⇒ Array[publication]

RBS:

  • (String uri, Integer version, String source) -> Array[publication]

Parameters:

  • uri (String)
  • version (Integer)
  • source (String)

Returns:

  • (Array[publication])


43
44
45
46
47
48
49
50
51
52
# File 'lib/ibex/lsp/document_store.rb', line 43

def open(uri, version, source)
  path = workspace.path(uri)
  raise ProtocolError.new("document is already open: #{uri}", code: -32_602) if @snapshots.dig(path, :open)

  validate_version(version)
  validate_source(source)
  loader.set_overlay(path, source)
  @snapshots[path] = snapshot(uri, path, version, source, true, nil)
  refresh(path)
end

#open_snapshot(path, uri) ⇒ snapshot

RBS:

  • (String path, String uri) -> snapshot

Parameters:

  • path (String)
  • uri (String)

Returns:



235
236
237
238
239
240
# File 'lib/ibex/lsp/document_store.rb', line 235

def open_snapshot(path, uri)
  entry = @snapshots[path]
  return entry if entry&.fetch(:open) && entry.fetch(:uri) == uri

  raise ProtocolError.new("document is not open: #{uri}", code: -32_602)
end

#publish_paths(paths) ⇒ Array[publication]

RBS:

  • (Array[String] paths) -> Array[publication]

Parameters:

  • paths (Array[String])

Returns:

  • (Array[publication])


214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/ibex/lsp/document_store.rb', line 214

def publish_paths(paths)
  paths.filter_map do |path|
    entry = @snapshots[path]
    next unless entry

    publication = {
      uri: entry.fetch(:uri),
      diagnostics: aggregated_diagnostics(path)
    } #: publication
    publication[:version] = entry.fetch(:version) if entry.fetch(:open) && entry.fetch(:version)
    publication
  end
end

#rebuild_reverse_dependenciesvoid

This method returns an undefined value.

RBS:

  • () -> void



179
180
181
182
183
# File 'lib/ibex/lsp/document_store.rb', line 179

def rebuild_reverse_dependencies
  reverse = Hash.new { |hash, key| hash[key] = [] } #: Hash[String, Array[String]]
  @closures.each { |root, files| files.each { |file| reverse[file] << root } }
  @reverse_dependencies = reverse
end

#refresh(path) ⇒ Array[publication]

RBS:

  • (String path) -> Array[publication]

Parameters:

  • path (String)

Returns:

  • (Array[publication])


143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ibex/lsp/document_store.rb', line 143

def refresh(path)
  old_files = files_for(path)
  previous_roots = affected_roots(path)
  was_root = @roots[path]
  analyzed = @analyzer.analyze(path)
  update_snapshot(path, analyzed)
  update_root_membership(path, analyzed.fetch(:document))
  remove_root_state(path) if was_root && !@roots[path]
  roots = (previous_roots + affected_roots(path)).uniq
  roots.delete(path) unless @roots[path]
  roots.each { |root| refresh_root(root) }
  publish_paths((old_files + files_for(path) + [path]).uniq)
end

#refresh_dependents(path) ⇒ Array[publication]

RBS:

  • (String path) -> Array[publication]

Parameters:

  • path (String)

Returns:

  • (Array[publication])


158
159
160
161
162
# File 'lib/ibex/lsp/document_store.rb', line 158

def refresh_dependents(path)
  roots = affected_roots(path)
  roots.each { |root| refresh_root(root) }
  publish_paths(roots.flat_map { |root| @closures.fetch(root, [root]) }.uniq)
end

#refresh_root(root) ⇒ void

This method returns an undefined value.

RBS:

  • (String root) -> void

Parameters:

  • root (String)


165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ibex/lsp/document_store.rb', line 165

def refresh_root(root)
  files, analyzed = @analyzer.closure(root)
  @closures[root] = files
  rebuild_reverse_dependencies
  analyzed.each { |path, result| update_snapshot(path, result, own_diagnostics: false) }
  parse_failed = analyzed.any? do |path, result|
    !result.fetch(:diagnostics).empty? && loader.file?(path)
  end
  resolution, errors = parse_failed ? [nil, []] : @analyzer.resolve(root)
  resolution ? @resolutions[root] = resolution : @resolutions.delete(root)
  replace_root_diagnostics(root, files, analyzed, errors)
end

#remove_path(path) ⇒ void

This method returns an undefined value.

RBS:

  • (String path) -> void

Parameters:

  • path (String)


248
249
250
251
252
# File 'lib/ibex/lsp/document_store.rb', line 248

def remove_path(path)
  @snapshots.delete(path)
  @document_diagnostics.delete(path)
  remove_root_state(path)
end

#remove_root_state(path) ⇒ void

This method returns an undefined value.

RBS:

  • (String path) -> void

Parameters:

  • path (String)


255
256
257
258
259
260
261
# File 'lib/ibex/lsp/document_store.rb', line 255

def remove_root_state(path)
  @roots.delete(path)
  @closures.delete(path)
  @resolutions.delete(path)
  @root_diagnostics.delete(path)
  rebuild_reverse_dependencies
end

#resolutions_for(path) ⇒ Array[Frontend::Resolution]

RBS:

  • (String path) -> Array[Frontend::Resolution]

Parameters:

  • path (String)

Returns:



131
132
133
# File 'lib/ibex/lsp/document_store.rb', line 131

def resolutions_for(path)
  affected_roots(path).filter_map { |root| @resolutions[root] }
end

#roots_for(path) ⇒ Array[String]

RBS:

  • (String path) -> Array[String]

Parameters:

  • path (String)

Returns:

  • (Array[String])


136
137
138
# File 'lib/ibex/lsp/document_store.rb', line 136

def roots_for(path)
  affected_roots(path)
end

#save(uri, source: nil) ⇒ Array[publication]

RBS:

  • (String uri, ?source: String?) -> Array[publication]

Parameters:

  • uri (String)
  • source: (String, nil) (defaults to: nil)

Returns:

  • (Array[publication])


70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ibex/lsp/document_store.rb', line 70

def save(uri, source: nil)
  path = workspace.path(uri)
  current = open_snapshot(path, uri)
  if source
    validate_source(source)
    loader.set_overlay(path, source)
    current = snapshot(uri, path, current.fetch(:version), source, true, nil)
    @snapshots[path] = current
  end
  refresh(path)
end

#snapshot(uri, path, version, source, open, document) ⇒ snapshot

RBS:

  • (String uri, String path, Integer? version, String source, bool open, Frontend::SourceDocument? document) -> snapshot

Parameters:

  • uri (String)
  • path (String)
  • version (Integer, nil)
  • source (String)
  • open (Boolean)
  • document (Frontend::SourceDocument, nil)

Returns:



230
231
232
# File 'lib/ibex/lsp/document_store.rb', line 230

def snapshot(uri, path, version, source, open, document)
  { uri: uri, path: path, version: version, source: source, open: open, document: document }
end

#snapshot_for(path) ⇒ snapshot?

RBS:

  • (String path) -> snapshot?

Parameters:

  • path (String)

Returns:



110
111
112
# File 'lib/ibex/lsp/document_store.rb', line 110

def snapshot_for(path)
  @snapshots[path]
end

#update_root_membership(path, document) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, Frontend::SourceDocument? document) -> void

Parameters:



205
206
207
208
209
210
211
# File 'lib/ibex/lsp/document_store.rb', line 205

def update_root_membership(path, document)
  if document&.ast.is_a?(Frontend::AST::Root)
    @roots[path] = true
  else
    @roots.delete(path)
  end
end

#update_snapshot(path, analyzed, own_diagnostics: true) ⇒ void

This method returns an undefined value.

RBS:

  • (String path, WorkspaceAnalyzer::analyzed_document analyzed, ?own_diagnostics: bool) -> void

Parameters:

  • path (String)
  • analyzed (WorkspaceAnalyzer::analyzed_document)
  • own_diagnostics: (Boolean) (defaults to: true)


193
194
195
196
197
198
199
200
201
202
# File 'lib/ibex/lsp/document_store.rb', line 193

def update_snapshot(path, analyzed, own_diagnostics: true)
  current = @snapshots[path]
  uri = current&.fetch(:uri) || workspace.uri(path)
  version = current&.fetch(:version)
  open = current&.fetch(:open) || false
  source = open ? current.fetch(:source) : analyzed.fetch(:source)
  document = analyzed.fetch(:document)
  @snapshots[path] = snapshot(uri, path, version, source, open, document)
  @document_diagnostics[path] = analyzed.fetch(:diagnostics) if own_diagnostics
end

#uri_for(path) ⇒ String

Preserve the URI spelling used to open a document; closed files use their canonical URI.

RBS:

  • (String path) -> String

Parameters:

  • path (String)

Returns:

  • (String)


116
117
118
119
120
121
# File 'lib/ibex/lsp/document_store.rb', line 116

def uri_for(path)
  entry = @snapshots[path]
  return entry.fetch(:uri) if entry&.fetch(:open)

  workspace.uri(path)
end