theme-check 1.7.0 → 1.9.0

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +49 -0
  4. data/README.md +10 -0
  5. data/RELEASING.md +13 -0
  6. data/config/default.yml +5 -0
  7. data/data/shopify_liquid/deprecated_filters.yml +4 -0
  8. data/data/shopify_liquid/filters.yml +3 -1
  9. data/data/shopify_liquid/tags.yml +9 -9
  10. data/docs/checks/TEMPLATE.md.erb +24 -19
  11. data/docs/checks/schema_json_format.md +76 -0
  12. data/docs/language_server/code-action-command-palette.png +0 -0
  13. data/docs/language_server/code-action-flow.png +0 -0
  14. data/docs/language_server/code-action-keyboard.png +0 -0
  15. data/docs/language_server/code-action-light-bulb.png +0 -0
  16. data/docs/language_server/code-action-problem.png +0 -0
  17. data/docs/language_server/code-action-quickfix.png +0 -0
  18. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  19. data/exe/theme-check-language-server +0 -4
  20. data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
  21. data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
  22. data/lib/theme_check/checks/asset_url_filters.rb +2 -0
  23. data/lib/theme_check/checks/default_locale.rb +1 -1
  24. data/lib/theme_check/checks/deprecated_filter.rb +79 -4
  25. data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
  26. data/lib/theme_check/checks/matching_schema_translations.rb +14 -9
  27. data/lib/theme_check/checks/matching_translations.rb +1 -0
  28. data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
  29. data/lib/theme_check/checks/missing_template.rb +1 -1
  30. data/lib/theme_check/checks/pagination_size.rb +2 -3
  31. data/lib/theme_check/checks/remote_asset.rb +5 -0
  32. data/lib/theme_check/checks/required_directories.rb +1 -1
  33. data/lib/theme_check/checks/required_layout_theme_object.rb +9 -4
  34. data/lib/theme_check/checks/schema_json_format.rb +29 -0
  35. data/lib/theme_check/checks/space_inside_braces.rb +132 -87
  36. data/lib/theme_check/checks/translation_key_exists.rb +33 -25
  37. data/lib/theme_check/checks/unused_assign.rb +3 -2
  38. data/lib/theme_check/checks/unused_snippet.rb +1 -1
  39. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  40. data/lib/theme_check/checks/valid_schema.rb +2 -2
  41. data/lib/theme_check/corrector.rb +34 -23
  42. data/lib/theme_check/exceptions.rb +1 -0
  43. data/lib/theme_check/file_system_storage.rb +8 -3
  44. data/lib/theme_check/html_node.rb +99 -6
  45. data/lib/theme_check/html_visitor.rb +1 -32
  46. data/lib/theme_check/in_memory_storage.rb +9 -0
  47. data/lib/theme_check/json_helpers.rb +14 -0
  48. data/lib/theme_check/language_server/bridge.rb +142 -0
  49. data/lib/theme_check/language_server/channel.rb +69 -0
  50. data/lib/theme_check/language_server/client_capabilities.rb +27 -0
  51. data/lib/theme_check/language_server/code_action_engine.rb +32 -0
  52. data/lib/theme_check/language_server/code_action_provider.rb +42 -0
  53. data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
  54. data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
  55. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +3 -1
  56. data/lib/theme_check/language_server/configuration.rb +69 -0
  57. data/lib/theme_check/language_server/diagnostic.rb +124 -0
  58. data/lib/theme_check/language_server/diagnostics_engine.rb +80 -0
  59. data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
  60. data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
  61. data/lib/theme_check/language_server/document_link_provider.rb +6 -6
  62. data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
  63. data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
  64. data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  65. data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
  66. data/lib/theme_check/language_server/handler.rb +92 -217
  67. data/lib/theme_check/language_server/io_messenger.rb +112 -0
  68. data/lib/theme_check/language_server/messenger.rb +12 -42
  69. data/lib/theme_check/language_server/protocol.rb +4 -0
  70. data/lib/theme_check/language_server/server.rb +54 -110
  71. data/lib/theme_check/language_server/uri_helper.rb +1 -0
  72. data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
  73. data/lib/theme_check/language_server.rb +28 -6
  74. data/lib/theme_check/liquid_node.rb +255 -12
  75. data/lib/theme_check/locale_diff.rb +48 -10
  76. data/lib/theme_check/node.rb +16 -0
  77. data/lib/theme_check/offense.rb +27 -23
  78. data/lib/theme_check/position.rb +4 -4
  79. data/lib/theme_check/regex_helpers.rb +1 -1
  80. data/lib/theme_check/schema_helper.rb +70 -0
  81. data/lib/theme_check/shopify_liquid/system_translations.rb +35 -0
  82. data/lib/theme_check/shopify_liquid/tag.rb +19 -1
  83. data/lib/theme_check/shopify_liquid.rb +1 -0
  84. data/lib/theme_check/storage.rb +4 -0
  85. data/lib/theme_check/tags.rb +0 -1
  86. data/lib/theme_check/theme.rb +1 -1
  87. data/lib/theme_check/theme_file.rb +8 -1
  88. data/lib/theme_check/theme_file_rewriter.rb +28 -6
  89. data/lib/theme_check/version.rb +1 -1
  90. data/lib/theme_check.rb +11 -2
  91. metadata +31 -3
  92. data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
@@ -17,7 +17,16 @@ module ThemeCheck
17
17
  triggerCharacters: ['.', '{{ ', '{% '],
18
18
  context: true,
19
19
  },
20
+ codeActionProvider: {
21
+ codeActionKinds: CodeActionProvider.all.map(&:kind),
22
+ resolveProvider: false,
23
+ workDoneProgress: false,
24
+ },
20
25
  documentLinkProvider: true,
26
+ executeCommandProvider: {
27
+ workDoneProgress: false,
28
+ commands: ExecuteCommandProvider.all.map(&:command),
29
+ },
21
30
  textDocumentSync: {
22
31
  openClose: true,
23
32
  change: TextDocumentSyncKind::FULL,
@@ -26,68 +35,104 @@ module ThemeCheck
26
35
  },
27
36
  }
28
37
 
29
- def initialize(server)
30
- @server = server
31
- @diagnostics_tracker = DiagnosticsTracker.new
32
- @diagnostics_lock = Mutex.new
33
- @supports_progress = false
34
- end
35
-
36
- def supports_progress_notifications?
37
- @supports_progress
38
+ def initialize(bridge)
39
+ @bridge = bridge
38
40
  end
39
41
 
40
42
  def on_initialize(id, params)
41
43
  @root_path = root_path_from_params(params)
42
- @supports_progress = params.dig('capabilities', 'window', 'workDoneProgress')
43
44
 
44
45
  # Tell the client we don't support anything if there's no rootPath
45
- return send_response(id, { capabilities: {} }) if @root_path.nil?
46
+ return @bridge.send_response(id, { capabilities: {} }) if @root_path.nil?
47
+
48
+ @client_capabilities = ClientCapabilities.new(params.dig(:capabilities) || {})
49
+ @configuration = Configuration.new(@bridge, @client_capabilities)
50
+ @bridge.supports_work_done_progress = @client_capabilities.supports_work_done_progress?
46
51
  @storage = in_memory_storage(@root_path)
52
+ @diagnostics_manager = DiagnosticsManager.new
47
53
  @completion_engine = CompletionEngine.new(@storage)
48
54
  @document_link_engine = DocumentLinkEngine.new(@storage)
49
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
50
- send_response(id, {
55
+ @diagnostics_engine = DiagnosticsEngine.new(@storage, @bridge, @diagnostics_manager)
56
+ @execute_command_engine = ExecuteCommandEngine.new
57
+ @execute_command_engine << CorrectionExecuteCommandProvider.new(@storage, @bridge, @diagnostics_manager)
58
+ @execute_command_engine << RunChecksExecuteCommandProvider.new(@diagnostics_engine, @root_path, config_for_path(@root_path))
59
+ @code_action_engine = CodeActionEngine.new(@storage, @diagnostics_manager)
60
+ @bridge.send_response(id, {
51
61
  capabilities: CAPABILITIES,
52
62
  serverInfo: SERVER_INFO,
53
63
  })
54
64
  end
55
65
 
66
+ def on_initialized(_id, _params)
67
+ @configuration.fetch
68
+ @configuration.register_did_change_capability
69
+ end
70
+
71
+ def on_shutdown(id, _params)
72
+ @bridge.send_response(id, nil)
73
+ end
74
+
56
75
  def on_exit(_id, _params)
57
76
  close!
58
77
  end
59
- alias_method :on_shutdown, :on_exit
60
78
 
61
- def on_text_document_did_change(_id, params)
79
+ def on_text_document_did_open(_id, params)
62
80
  relative_path = relative_path_from_text_document_uri(params)
63
- @storage.write(relative_path, content_changes_text(params))
81
+ @storage.write(relative_path, text_document_text(params), text_document_version(params))
82
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_open?
64
83
  end
65
84
 
66
- def on_text_document_did_close(_id, params)
85
+ def on_text_document_did_change(_id, params)
67
86
  relative_path = relative_path_from_text_document_uri(params)
68
- @storage.write(relative_path, "")
87
+ @storage.write(relative_path, content_changes_text(params), text_document_version(params))
88
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_change?
69
89
  end
70
90
 
71
- def on_text_document_did_open(_id, params)
91
+ def on_text_document_did_close(_id, params)
72
92
  relative_path = relative_path_from_text_document_uri(params)
73
- @storage.write(relative_path, text_document_text(params))
74
- analyze_and_send_offenses(text_document_uri(params)) if @diagnostics_tracker.first_run?
93
+ file_system_content = Pathname.new(text_document_uri(params)).read(mode: 'rb', encoding: 'UTF-8')
94
+ # On close, the file system becomes the source of truth
95
+ @storage.write(relative_path, file_system_content, nil)
75
96
  end
76
97
 
77
98
  def on_text_document_did_save(_id, params)
78
- analyze_and_send_offenses(text_document_uri(params))
99
+ analyze_and_send_offenses(text_document_uri(params)) if @configuration.check_on_save?
79
100
  end
80
101
 
81
102
  def on_text_document_document_link(id, params)
82
103
  relative_path = relative_path_from_text_document_uri(params)
83
- send_response(id, document_links(relative_path))
104
+ @bridge.send_response(id, @document_link_engine.document_links(relative_path))
84
105
  end
85
106
 
86
107
  def on_text_document_completion(id, params)
87
108
  relative_path = relative_path_from_text_document_uri(params)
88
- line = params.dig('position', 'line')
89
- col = params.dig('position', 'character')
90
- send_response(id, completions(relative_path, line, col))
109
+ line = params.dig(:position, :line)
110
+ col = params.dig(:position, :character)
111
+ @bridge.send_response(id, @completion_engine.completions(relative_path, line, col))
112
+ end
113
+
114
+ def on_text_document_code_action(id, params)
115
+ absolute_path = text_document_uri(params)
116
+ start_position = range_element(params, :start)
117
+ end_position = range_element(params, :end)
118
+ only_code_action_kinds = params.dig(:context, :only) || []
119
+ @bridge.send_response(id, @code_action_engine.code_actions(
120
+ absolute_path,
121
+ start_position,
122
+ end_position,
123
+ only_code_action_kinds,
124
+ ))
125
+ end
126
+
127
+ def on_workspace_execute_command(id, params)
128
+ @bridge.send_response(id, @execute_command_engine.execute(
129
+ params[:command],
130
+ params[:arguments],
131
+ ))
132
+ end
133
+
134
+ def on_workspace_did_change_configuration(_id, _params)
135
+ @configuration.fetch(force: true)
91
136
  end
92
137
 
93
138
  private
@@ -101,16 +146,16 @@ module ThemeCheck
101
146
  ignored_patterns: config.ignored_patterns
102
147
  )
103
148
 
104
- # Turn that into a hash of empty buffers
149
+ # Turn that into a hash of buffers
105
150
  files = fs.files
106
- .map { |fn| [fn, ""] }
151
+ .map { |fn| [fn, fs.read(fn)] }
107
152
  .to_h
108
153
 
109
- InMemoryStorage.new(files, config.root)
154
+ VersionedInMemoryStorage.new(files, config.root)
110
155
  end
111
156
 
112
157
  def text_document_uri(params)
113
- file_path(params.dig('textDocument', 'uri'))
158
+ file_path(params.dig(:textDocument, :uri))
114
159
  end
115
160
 
116
161
  def relative_path_from_text_document_uri(params)
@@ -118,8 +163,8 @@ module ThemeCheck
118
163
  end
119
164
 
120
165
  def root_path_from_params(params)
121
- root_uri = params["rootUri"]
122
- root_path = params["rootPath"]
166
+ root_uri = params[:rootUri]
167
+ root_path = params[:rootPath]
123
168
  if root_uri
124
169
  file_path(root_uri)
125
170
  elsif root_path
@@ -128,11 +173,15 @@ module ThemeCheck
128
173
  end
129
174
 
130
175
  def text_document_text(params)
131
- params.dig('textDocument', 'text')
176
+ params.dig(:textDocument, :text)
177
+ end
178
+
179
+ def text_document_version(params)
180
+ params.dig(:textDocument, :version)
132
181
  end
133
182
 
134
183
  def content_changes_text(params)
135
- params.dig('contentChanges', 0, 'text')
184
+ params.dig(:contentChanges, 0, :text)
136
185
  end
137
186
 
138
187
  def config_for_path(path)
@@ -141,195 +190,21 @@ module ThemeCheck
141
190
  end
142
191
 
143
192
  def analyze_and_send_offenses(absolute_path)
144
- return unless @diagnostics_lock.try_lock
145
- token = send_create_work_done_progress_request
146
- config = config_for_path(absolute_path)
147
- storage = ThemeCheck::FileSystemStorage.new(
148
- config.root,
149
- ignored_patterns: config.ignored_patterns
193
+ @diagnostics_engine.analyze_and_send_offenses(
194
+ absolute_path,
195
+ config_for_path(absolute_path)
150
196
  )
151
- theme = ThemeCheck::Theme.new(storage)
152
- analyzer = ThemeCheck::Analyzer.new(theme, config.enabled_checks)
153
-
154
- if @diagnostics_tracker.first_run?
155
- send_work_done_progress_begin(token, "Full theme check")
156
- log("Checking #{config.root}")
157
- offenses = nil
158
- time = Benchmark.measure do
159
- offenses = analyzer.analyze_theme do |path, i, total|
160
- send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
161
- end
162
- end
163
- end_message = "Found #{offenses.size} offenses in #{format("%0.2f", time.real)}s"
164
- log(end_message)
165
- send_work_done_progress_end(token, end_message)
166
- send_diagnostics(offenses)
167
- else
168
- # Analyze selected files
169
- relative_path = Pathname.new(@storage.relative_path(absolute_path))
170
- file = theme[relative_path]
171
- # Skip if not a theme file
172
- if file
173
- log("Checking #{relative_path}")
174
- send_work_done_progress_begin(token, "Partial theme check")
175
- offenses = nil
176
- time = Benchmark.measure do
177
- offenses = analyzer.analyze_files([file]) do |path, i, total|
178
- send_work_done_progress_report(token, "#{i}/#{total} #{path}", (i.to_f / total * 100.0).to_i)
179
- end
180
- end
181
- end_message = "Found #{offenses.size} new offenses in #{format("%0.2f", time.real)}s"
182
- send_work_done_progress_end(token, end_message)
183
- log(end_message)
184
- send_diagnostics(offenses, [absolute_path])
185
- end
186
- end
187
- @diagnostics_lock.unlock
188
- end
189
-
190
- def completions(relative_path, line, col)
191
- @completion_engine.completions(relative_path, line, col)
192
- end
193
-
194
- def document_links(relative_path)
195
- @document_link_engine.document_links(relative_path)
196
- end
197
-
198
- def send_diagnostics(offenses, analyzed_files = nil)
199
- @diagnostics_tracker.build_diagnostics(offenses, analyzed_files: analyzed_files) do |path, diagnostic_offenses|
200
- send_diagnostic(path, diagnostic_offenses)
201
- end
202
- end
203
-
204
- def send_diagnostic(path, offenses)
205
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
206
- send_notification('textDocument/publishDiagnostics', {
207
- uri: file_uri(path),
208
- diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
209
- })
210
- end
211
-
212
- def offense_to_diagnostic(offense)
213
- diagnostic = {
214
- code: offense.code_name,
215
- message: offense.message,
216
- range: range(offense),
217
- severity: severity(offense),
218
- source: "theme-check",
219
- }
220
- diagnostic["codeDescription"] = code_description(offense) unless offense.doc.nil?
221
- diagnostic
222
- end
223
-
224
- def code_description(offense)
225
- {
226
- href: offense.doc,
227
- }
228
- end
229
-
230
- def severity(offense)
231
- case offense.severity
232
- when :error
233
- 1
234
- when :suggestion
235
- 2
236
- when :style
237
- 3
238
- else
239
- 4
240
- end
241
- end
242
-
243
- def range(offense)
244
- {
245
- start: {
246
- line: offense.start_line,
247
- character: offense.start_column,
248
- },
249
- end: {
250
- line: offense.end_line,
251
- character: offense.end_column,
252
- },
253
- }
254
- end
255
-
256
- def send_create_work_done_progress_request
257
- return unless supports_progress_notifications?
258
- token = nil
259
- @server.request do |id|
260
- token = id # we'll reuse the RQID as token
261
- send_message({
262
- id: id,
263
- method: "window/workDoneProgress/create",
264
- params: {
265
- token: id,
266
- },
267
- })
268
- end
269
- token
270
- end
271
-
272
- def send_work_done_progress_begin(token, title)
273
- return unless supports_progress_notifications?
274
- send_progress(token, {
275
- kind: 'begin',
276
- title: title,
277
- cancellable: false,
278
- percentage: 0,
279
- })
280
- end
281
-
282
- def send_work_done_progress_report(token, message, percentage)
283
- return unless supports_progress_notifications?
284
- send_progress(token, {
285
- kind: 'report',
286
- message: message,
287
- cancellable: false,
288
- percentage: percentage,
289
- })
290
- end
291
-
292
- def send_work_done_progress_end(token, message)
293
- return unless supports_progress_notifications?
294
- send_progress(token, {
295
- kind: 'end',
296
- message: message,
297
- })
298
- end
299
-
300
- def send_progress(token, value)
301
- send_notification("$/progress", token: token, value: value)
302
- end
303
-
304
- def send_message(message)
305
- message[:jsonrpc] = '2.0'
306
- @server.send_message(message)
307
- end
308
-
309
- def send_response(id, result = nil, error = nil)
310
- message = { id: id }
311
- message[:result] = result if result
312
- message[:error] = error if error
313
- send_message(message)
314
- end
315
-
316
- def send_request(method, params = nil)
317
- @server.request do |id|
318
- message = { id: id }
319
- message[:method] = method
320
- message[:params] = params if params
321
- send_message(message)
322
- end
323
197
  end
324
198
 
325
- def send_notification(method, params)
326
- message = { method: method }
327
- message[:params] = params
328
- send_message(message)
199
+ def range_element(params, start_or_end)
200
+ [
201
+ params.dig(:range, start_or_end, :line),
202
+ params.dig(:range, start_or_end, :character),
203
+ ]
329
204
  end
330
205
 
331
206
  def log(message)
332
- @server.log(message)
207
+ @bridge.log(message)
333
208
  end
334
209
 
335
210
  def close!
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThemeCheck
4
+ module LanguageServer
5
+ class IOMessenger < Messenger
6
+ def self.err_stream
7
+ if ThemeCheck.debug_log_file
8
+ File.open(ThemeCheck.debug_log_file, "w")
9
+ else
10
+ STDERR
11
+ end
12
+ end
13
+
14
+ def initialize(
15
+ in_stream: STDIN,
16
+ out_stream: STDOUT,
17
+ err_stream: IOMessenger.err_stream
18
+ )
19
+ validate!([in_stream, out_stream, err_stream])
20
+
21
+ @in = in_stream
22
+ @out = out_stream
23
+ @err = err_stream
24
+
25
+ # Because programming is fun,
26
+ #
27
+ # Ruby on Windows turns \n into \r\n. Which means that \r\n
28
+ # gets turned into \r\r\n. Which means that the protocol
29
+ # breaks on windows unless we turn STDOUT into binary mode.
30
+ #
31
+ # Hours wasted: 9.
32
+ @out.binmode
33
+
34
+ @out.sync = true # do not buffer
35
+ @err.sync = true # do not buffer
36
+
37
+ # Lock for writing, otherwise messages might be interspersed.
38
+ @writer = Mutex.new
39
+ end
40
+
41
+ def read_message
42
+ length = initial_line.match(/Content-Length: (\d+)/)[1].to_i
43
+ content = ''
44
+ length_to_read = 2 + length # 2 is the empty line length (\r\n)
45
+ while content.length < length_to_read
46
+ chunk = @in.read(length_to_read - content.length)
47
+ raise DoneStreaming if chunk.nil?
48
+ content += chunk
49
+ end
50
+ content.lstrip!
51
+ rescue IOError
52
+ raise DoneStreaming
53
+ end
54
+
55
+ def send_message(message_body)
56
+ @writer.synchronize do
57
+ @out.write("Content-Length: #{message_body.bytesize}\r\n")
58
+ @out.write("\r\n")
59
+ @out.write(message_body)
60
+ @out.flush
61
+ end
62
+ end
63
+
64
+ def log(message)
65
+ @err.puts(message)
66
+ @err.flush
67
+ end
68
+
69
+ def close_input
70
+ @in.close unless @in.closed?
71
+ end
72
+
73
+ def close_output
74
+ @err.close
75
+ @out.close
76
+ end
77
+
78
+ private
79
+
80
+ def initial_line
81
+ # Scanning for lines that fit the protocol.
82
+ while true
83
+ initial_line = @in.gets
84
+ # gets returning nil means the stream was closed.
85
+ raise DoneStreaming if initial_line.nil?
86
+
87
+ if initial_line.match(/Content-Length: (\d+)/)
88
+ break
89
+ end
90
+ end
91
+ initial_line
92
+ end
93
+
94
+ def supported_io_classes
95
+ [IO, StringIO]
96
+ end
97
+
98
+ def validate!(streams = [])
99
+ streams.each do |stream|
100
+ unless supported_io_classes.find { |klass| stream.is_a?(klass) }
101
+ raise IncompatibleStream, incompatible_stream_message
102
+ end
103
+ end
104
+ end
105
+
106
+ def incompatible_stream_message
107
+ 'if provided, in_stream, out_stream, and err_stream must be a kind of '\
108
+ "one of the following: #{supported_io_classes.join(', ')}"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -3,54 +3,24 @@
3
3
  module ThemeCheck
4
4
  module LanguageServer
5
5
  class Messenger
6
- def initialize
7
- @responses = {}
8
- @mutex = Mutex.new
9
- @id = 0
6
+ def send_message
7
+ raise NotImplementedError
10
8
  end
11
9
 
12
- # Here's how you'd use this:
13
- #
14
- # def some_method_that_communicates_both_ways
15
- #
16
- # # this will block until the JSON rpc loop has an answer
17
- # token = @server.request do |id|
18
- # send_create_work_done_progress_request(id, ...)
19
- # end
20
- #
21
- # send_create_work_done_begin_notification(token, "...")
22
- #
23
- # do_stuff do |file, i, total|
24
- # send_create_work_done_progress_notification(token, "...")
25
- # end
26
- #
27
- # send_create_work_done_end_notification(token, "...")
28
- #
29
- # end
30
- def request(&block)
31
- id = @mutex.synchronize { @id += 1 }
32
- @responses[id] = SizedQueue.new(1)
33
-
34
- # Execute the block in the parent thread with an ID
35
- # So that we're able to relinquish control in the right
36
- # place when we have a response.
37
- block.call(id)
38
-
39
- # this call is blocking until we get a response from somewhere
40
- result = @responses[id].pop
10
+ def read_message
11
+ raise NotImplementedError
12
+ end
41
13
 
42
- # cleanup when done
43
- @responses.delete(id)
14
+ def log
15
+ raise NotImplementedError
16
+ end
44
17
 
45
- # return the response
46
- result
18
+ def close_input
19
+ raise NotImplementedError
47
20
  end
48
21
 
49
- # In the JSONRPC loop, when we find the response to the
50
- # request, we unblock the thread that made the request with the
51
- # response.
52
- def respond(id, value)
53
- @responses[id] << value
22
+ def close_output
23
+ raise NotImplementedError
54
24
  end
55
25
  end
56
26
  end
@@ -37,5 +37,9 @@ module ThemeCheck
37
37
  FULL = 1
38
38
  INCREMENTAL = 2
39
39
  end
40
+
41
+ module ErrorCodes
42
+ INTERNAL_ERROR = -32603
43
+ end
40
44
  end
41
45
  end