mt-lang 0.3.8 → 0.3.10

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 (200) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -2
  3. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +0 -2
  4. data/lib/milk_tea/bindings/bindgen/declaration.rb +0 -2
  5. data/lib/milk_tea/bindings/bindgen/emitter.rb +0 -2
  6. data/lib/milk_tea/bindings/bindgen/overrides.rb +0 -2
  7. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +51 -53
  8. data/lib/milk_tea/bindings/imported_bindings/generator.rb +7 -9
  9. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +0 -2
  10. data/lib/milk_tea/bindings/imported_bindings/naming.rb +6 -8
  11. data/lib/milk_tea/bindings/imported_bindings.rb +0 -2
  12. data/lib/milk_tea/bindings/opengl_registry.rb +3 -7
  13. data/lib/milk_tea/bindings/steamworks.rb +12 -16
  14. data/lib/milk_tea/bindings/upstream_sources.rb +10 -14
  15. data/lib/milk_tea/bindings/vendored_box2d.rb +10 -12
  16. data/lib/milk_tea/bindings/vendored_cjson.rb +6 -8
  17. data/lib/milk_tea/bindings/vendored_flecs.rb +10 -12
  18. data/lib/milk_tea/bindings/vendored_glfw.rb +11 -13
  19. data/lib/milk_tea/bindings/vendored_libuv.rb +11 -13
  20. data/lib/milk_tea/bindings/vendored_pcre2.rb +11 -13
  21. data/lib/milk_tea/bindings/vendored_raylib.rb +6 -10
  22. data/lib/milk_tea/bindings/vendored_sdl3.rb +11 -13
  23. data/lib/milk_tea/bindings/vendored_steamworks.rb +1 -5
  24. data/lib/milk_tea/core/ast.rb +103 -45
  25. data/lib/milk_tea/core/async_runtime_installer.rb +0 -2
  26. data/lib/milk_tea/core/bindings/attribute_binding.rb +1 -3
  27. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +147 -84
  28. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +394 -396
  29. data/lib/milk_tea/core/c_backend/expressions.rb +489 -476
  30. data/lib/milk_tea/core/c_backend/feature_detection.rb +446 -437
  31. data/lib/milk_tea/core/c_backend/format_helpers.rb +0 -2
  32. data/lib/milk_tea/core/c_backend/reachability.rb +339 -341
  33. data/lib/milk_tea/core/c_backend/reinterpret.rb +231 -217
  34. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +531 -526
  35. data/lib/milk_tea/core/c_backend/statements.rb +528 -530
  36. data/lib/milk_tea/core/c_backend/type_collectors.rb +1344 -1346
  37. data/lib/milk_tea/core/c_backend/type_declaration.rb +215 -213
  38. data/lib/milk_tea/core/c_backend/type_system.rb +347 -349
  39. data/lib/milk_tea/core/c_backend.rb +17 -9
  40. data/lib/milk_tea/core/compatibility_helpers.rb +0 -2
  41. data/lib/milk_tea/core/compile_time/const_eval.rb +7 -12
  42. data/lib/milk_tea/core/compile_time.rb +32 -38
  43. data/lib/milk_tea/core/control_flow/builder.rb +3 -5
  44. data/lib/milk_tea/core/control_flow/constant_propagation.rb +14 -17
  45. data/lib/milk_tea/core/control_flow/definite_assignment.rb +0 -1
  46. data/lib/milk_tea/core/ir.rb +10 -10
  47. data/lib/milk_tea/core/lexer/character_classes.rb +5 -7
  48. data/lib/milk_tea/core/lexer/format_strings.rb +7 -9
  49. data/lib/milk_tea/core/lexer/heredocs.rb +22 -22
  50. data/lib/milk_tea/core/lexer/indentation.rb +0 -2
  51. data/lib/milk_tea/core/lexer/numbers.rb +0 -2
  52. data/lib/milk_tea/core/lexer/recovery.rb +0 -2
  53. data/lib/milk_tea/core/lexer/strings.rb +0 -2
  54. data/lib/milk_tea/core/lexer/symbols.rb +0 -2
  55. data/lib/milk_tea/core/lexer/trivia.rb +0 -2
  56. data/lib/milk_tea/core/lexer.rb +0 -2
  57. data/lib/milk_tea/core/lowering/async/analysis.rb +0 -2
  58. data/lib/milk_tea/core/lowering/async/lowering.rb +1264 -1265
  59. data/lib/milk_tea/core/lowering/async/normalization.rb +0 -2
  60. data/lib/milk_tea/core/lowering/async.rb +627 -630
  61. data/lib/milk_tea/core/lowering/block.rb +413 -416
  62. data/lib/milk_tea/core/lowering/calls.rb +1381 -1384
  63. data/lib/milk_tea/core/lowering/declarations.rb +180 -183
  64. data/lib/milk_tea/core/lowering/dyn.rb +48 -50
  65. data/lib/milk_tea/core/lowering/events.rb +983 -985
  66. data/lib/milk_tea/core/lowering/expressions.rb +1513 -1518
  67. data/lib/milk_tea/core/lowering/foreign_cstr.rb +181 -184
  68. data/lib/milk_tea/core/lowering/functions.rb +185 -188
  69. data/lib/milk_tea/core/lowering/loops.rb +1028 -1031
  70. data/lib/milk_tea/core/lowering/proc.rb +363 -366
  71. data/lib/milk_tea/core/lowering/resolve.rb +2133 -2135
  72. data/lib/milk_tea/core/lowering/scans.rb +179 -181
  73. data/lib/milk_tea/core/lowering/str_buffer.rb +102 -105
  74. data/lib/milk_tea/core/lowering/utils.rb +1040 -1044
  75. data/lib/milk_tea/core/lowering.rb +5 -7
  76. data/lib/milk_tea/core/module_binder.rb +0 -2
  77. data/lib/milk_tea/core/module_loader/errors.rb +4 -2
  78. data/lib/milk_tea/core/module_loader.rb +113 -20
  79. data/lib/milk_tea/core/module_path_resolver.rb +0 -2
  80. data/lib/milk_tea/core/module_roots.rb +6 -8
  81. data/lib/milk_tea/core/parser/attributes.rb +0 -2
  82. data/lib/milk_tea/core/parser/blocks.rb +0 -2
  83. data/lib/milk_tea/core/parser/declarations.rb +0 -2
  84. data/lib/milk_tea/core/parser/expressions.rb +31 -14
  85. data/lib/milk_tea/core/parser/recovery.rb +0 -2
  86. data/lib/milk_tea/core/parser/statements.rb +0 -2
  87. data/lib/milk_tea/core/parser/types.rb +8 -10
  88. data/lib/milk_tea/core/parser.rb +1 -3
  89. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +31 -33
  90. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +0 -2
  91. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +0 -2
  92. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +0 -2
  93. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +14 -16
  94. data/lib/milk_tea/core/semantic_analyzer/calls.rb +0 -2
  95. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1 -14
  96. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +18 -21
  97. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +0 -3
  98. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +12 -15
  99. data/lib/milk_tea/core/semantic_analyzer/generics.rb +57 -59
  100. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +0 -2
  101. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +5 -9
  102. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +0 -3
  103. data/lib/milk_tea/core/semantic_analyzer/statements.rb +35 -40
  104. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
  105. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +0 -6
  106. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +102 -105
  107. data/lib/milk_tea/core/semantic_analyzer.rb +17 -8
  108. data/lib/milk_tea/core/types/layout.rb +21 -22
  109. data/lib/milk_tea/core/types/predicates.rb +0 -2
  110. data/lib/milk_tea/core/types/registry.rb +16 -18
  111. data/lib/milk_tea/core/types/visitor.rb +0 -4
  112. data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -2
  113. data/lib/milk_tea/dap/protocol.rb +1 -1
  114. data/lib/milk_tea/dap/server/breakpoints.rb +0 -2
  115. data/lib/milk_tea/dap/server/debug_map.rb +0 -2
  116. data/lib/milk_tea/dap/server/handlers.rb +0 -2
  117. data/lib/milk_tea/dap/server/launch.rb +0 -2
  118. data/lib/milk_tea/dap/server/lldb_backend.rb +8 -10
  119. data/lib/milk_tea/dap/server/pause_diagnostics.rb +14 -16
  120. data/lib/milk_tea/dap/server/utilities.rb +0 -2
  121. data/lib/milk_tea/dap/server/wire.rb +0 -2
  122. data/lib/milk_tea/dap/server.rb +0 -2
  123. data/lib/milk_tea/lsp/diagnostics.rb +16 -16
  124. data/lib/milk_tea/lsp/server/call_hierarchy.rb +11 -12
  125. data/lib/milk_tea/lsp/server/code_actions.rb +355 -357
  126. data/lib/milk_tea/lsp/server/code_lens.rb +23 -25
  127. data/lib/milk_tea/lsp/server/completion.rb +873 -875
  128. data/lib/milk_tea/lsp/server/configuration.rb +129 -131
  129. data/lib/milk_tea/lsp/server/debug_info.rb +0 -2
  130. data/lib/milk_tea/lsp/server/definition.rb +564 -565
  131. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +191 -193
  132. data/lib/milk_tea/lsp/server/execute_command.rb +0 -2
  133. data/lib/milk_tea/lsp/server/folding_range.rb +0 -4
  134. data/lib/milk_tea/lsp/server/formatting.rb +478 -480
  135. data/lib/milk_tea/lsp/server/hover.rb +1282 -1284
  136. data/lib/milk_tea/lsp/server/inlay_hints.rb +179 -181
  137. data/lib/milk_tea/lsp/server/lifecycle.rb +192 -194
  138. data/lib/milk_tea/lsp/server/linked_editing_range.rb +0 -2
  139. data/lib/milk_tea/lsp/server/on_type_formatting.rb +0 -2
  140. data/lib/milk_tea/lsp/server/progress.rb +0 -2
  141. data/lib/milk_tea/lsp/server/references.rb +316 -318
  142. data/lib/milk_tea/lsp/server/rename.rb +445 -447
  143. data/lib/milk_tea/lsp/server/selection_range.rb +0 -4
  144. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1318 -1320
  145. data/lib/milk_tea/lsp/server/signature_help.rb +146 -148
  146. data/lib/milk_tea/lsp/server/text_documents.rb +99 -101
  147. data/lib/milk_tea/lsp/server/type_hierarchy.rb +2 -4
  148. data/lib/milk_tea/lsp/server/utilities.rb +397 -399
  149. data/lib/milk_tea/lsp/server.rb +0 -3
  150. data/lib/milk_tea/lsp/workspace/analysis.rb +40 -42
  151. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +4 -10
  152. data/lib/milk_tea/lsp/workspace/utilities.rb +19 -19
  153. data/lib/milk_tea/packages/atomic_write.rb +4 -5
  154. data/lib/milk_tea/packages/dependency_solver.rb +15 -15
  155. data/lib/milk_tea/packages/lock.rb +4 -4
  156. data/lib/milk_tea/packages/manager_cli.rb +1 -1
  157. data/lib/milk_tea/packages/manifest.rb +3 -3
  158. data/lib/milk_tea/packages/registry_store.rb +4 -6
  159. data/lib/milk_tea/packages/services.rb +16 -16
  160. data/lib/milk_tea/packages/source_cache.rb +4 -6
  161. data/lib/milk_tea/packages/source_resolver.rb +44 -44
  162. data/lib/milk_tea/packages/version.rb +8 -8
  163. data/lib/milk_tea/tooling/build.rb +3 -7
  164. data/lib/milk_tea/tooling/build_cache.rb +0 -2
  165. data/lib/milk_tea/tooling/debug_info_formatter.rb +31 -33
  166. data/lib/milk_tea/tooling/debug_map.rb +8 -10
  167. data/lib/milk_tea/tooling/docs_app.rb +0 -2
  168. data/lib/milk_tea/tooling/linter/doc_tags.rb +25 -27
  169. data/lib/milk_tea/tooling/linter/fix_engine.rb +87 -24
  170. data/lib/milk_tea/tooling/linter/flow_rules.rb +44 -46
  171. data/lib/milk_tea/tooling/linter/imports_platform.rb +14 -16
  172. data/lib/milk_tea/tooling/linter/release_rules.rb +3 -5
  173. data/lib/milk_tea/tooling/linter/reserved_names.rb +23 -25
  174. data/lib/milk_tea/tooling/linter/rules.rb +87 -89
  175. data/lib/milk_tea/tooling/linter/source_helpers.rb +40 -42
  176. data/lib/milk_tea/tooling/linter/trailing_comma.rb +17 -19
  177. data/lib/milk_tea/tooling/linter/visitors.rb +162 -50
  178. data/lib/milk_tea/tooling/linter.rb +0 -2
  179. data/lib/milk_tea/tooling/project_scaffold.rb +0 -2
  180. data/lib/milk_tea/tooling/run.rb +0 -2
  181. data/lib/milk_tea/tooling/toolchain_cli.rb +0 -2
  182. data/std/cli.mt +13 -32
  183. data/std/color.mt +28 -28
  184. data/std/encoding.mt +4 -4
  185. data/std/graph.mt +1 -2
  186. data/std/hash.mt +14 -14
  187. data/std/http/server.mt +3 -3
  188. data/std/input.mt +1 -1
  189. data/std/json.mt +1 -1
  190. data/std/linear_algebra.mt +6 -6
  191. data/std/net/mux.mt +1 -1
  192. data/std/noise.mt +23 -23
  193. data/std/parse.mt +2 -2
  194. data/std/process.mt +3 -4
  195. data/std/sparse_set.mt +1 -1
  196. data/std/steering.mt +5 -5
  197. data/std/str.mt +1 -1
  198. data/std/tween.mt +4 -4
  199. data/std/utility.mt +3 -3
  200. metadata +2 -2
@@ -4,516 +4,514 @@ module MilkTea
4
4
  module LSP
5
5
  class Server
6
6
  module ServerUtilities
7
- private
7
+ def perf_logging?
8
+ @perf_logging ||= !ENV.fetch('MILK_TEA_LSP_PERF', nil).to_s.empty?
9
+ end
8
10
 
9
- def perf_logging?
10
- @perf_logging ||= !ENV.fetch('MILK_TEA_LSP_PERF', nil).to_s.empty?
11
- end
11
+ def perf_verbose?
12
+ @perf_verbose ||= ENV.fetch('MILK_TEA_LSP_PERF', nil).to_s == 'verbose'
13
+ end
12
14
 
13
- def perf_verbose?
14
- @perf_verbose ||= ENV.fetch('MILK_TEA_LSP_PERF', nil).to_s == 'verbose'
15
- end
15
+ def perf_breakdown_logging?(elapsed_ms)
16
+ perf_logging? && (perf_verbose? || elapsed_ms > Workspace::PERF_LOG_THRESHOLD_MS)
17
+ end
16
18
 
17
- def perf_breakdown_logging?(elapsed_ms)
18
- perf_logging? && (perf_verbose? || elapsed_ms > Workspace::PERF_LOG_THRESHOLD_MS)
19
- end
19
+ def monotonic_time
20
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
21
+ end
20
22
 
21
- def monotonic_time
22
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
- end
23
+ def elapsed_ms(start_time)
24
+ ((monotonic_time - start_time) * 1000).round(1)
25
+ end
24
26
 
25
- def elapsed_ms(start_time)
26
- ((monotonic_time - start_time) * 1000).round(1)
27
- end
27
+ def log_perf_breakdown(method_name, elapsed_ms_value, detail)
28
+ return unless perf_breakdown_logging?(elapsed_ms_value)
28
29
 
29
- def log_perf_breakdown(method_name, elapsed_ms_value, detail)
30
- return unless perf_breakdown_logging?(elapsed_ms_value)
30
+ id_detail = @current_request_id ? " id=#{@current_request_id}" : ''
31
+ warn "[LSP perf] breakdown #{method_name} #{elapsed_ms_value}ms#{id_detail} #{detail}"
32
+ end
31
33
 
32
- id_detail = @current_request_id ? " id=#{@current_request_id}" : ''
33
- warn "[LSP perf] breakdown #{method_name} #{elapsed_ms_value}ms#{id_detail} #{detail}"
34
- end
34
+ def new_perf_stages
35
+ perf_logging? ? [] : nil
36
+ end
35
37
 
36
- def new_perf_stages
37
- perf_logging? ? [] : nil
38
- end
38
+ def measure_perf_stage(stages, name)
39
+ return yield unless stages
39
40
 
40
- def measure_perf_stage(stages, name)
41
- return yield unless stages
41
+ start_time = monotonic_time
42
+ result = yield
43
+ stages << [name, elapsed_ms(start_time)]
44
+ result
45
+ end
42
46
 
43
- start_time = monotonic_time
44
- result = yield
45
- stages << [name, elapsed_ms(start_time)]
46
- result
47
- end
47
+ def log_request_stage_breakdown(method_name, total_start, uri: nil, stages: nil, summary: nil)
48
+ return unless total_start
48
49
 
49
- def log_request_stage_breakdown(method_name, total_start, uri: nil, stages: nil, summary: nil)
50
- return unless total_start
50
+ detail = []
51
+ detail << "uri=#{shorten_uri(uri) || uri}" if uri
52
+ detail << summary if summary && !summary.empty?
53
+ unless stages.nil? || stages.empty?
54
+ detail << "stages_ms=#{stages.map { |name, ms| "#{name}:#{ms}" }.join(',')}"
55
+ end
51
56
 
52
- detail = []
53
- detail << "uri=#{shorten_uri(uri) || uri}" if uri
54
- detail << summary if summary && !summary.empty?
55
- unless stages.nil? || stages.empty?
56
- detail << "stages_ms=#{stages.map { |name, ms| "#{name}:#{ms}" }.join(',')}"
57
+ log_perf_breakdown(method_name, elapsed_ms(total_start), detail.join(' '))
57
58
  end
58
59
 
59
- log_perf_breakdown(method_name, elapsed_ms(total_start), detail.join(' '))
60
- end
60
+ def perf_log_context(method_name, params, verbose: false)
61
+ return "" unless params.is_a?(Hash)
61
62
 
62
- def perf_log_context(method_name, params, verbose: false)
63
- return "" unless params.is_a?(Hash)
63
+ summary = summarize_lsp_params(method_name, params)
64
+ return summary.empty? ? "" : " #{summary}" if verbose
64
65
 
65
- summary = summarize_lsp_params(method_name, params)
66
- return summary.empty? ? "" : " #{summary}" if verbose
66
+ text_document = hget(params, 'textDocument')
67
+ uri = text_document.is_a?(Hash) ? hget(text_document, 'uri') : nil
68
+ bits = []
69
+ bits << "uri=#{shorten_uri(uri) || uri}" if uri
67
70
 
68
- text_document = hget(params, 'textDocument')
69
- uri = text_document.is_a?(Hash) ? hget(text_document, 'uri') : nil
70
- bits = []
71
- bits << "uri=#{shorten_uri(uri) || uri}" if uri
71
+ if method_name == 'textDocument/didChange'
72
+ changes = hget(params, 'contentChanges')
73
+ bits << "changes=#{changes.length}" if changes.respond_to?(:length)
74
+ end
72
75
 
73
- if method_name == 'textDocument/didChange'
74
- changes = hget(params, 'contentChanges')
75
- bits << "changes=#{changes.length}" if changes.respond_to?(:length)
76
+ bits.empty? ? "" : " #{bits.join(' ')}"
77
+ rescue StandardError
78
+ ""
76
79
  end
77
80
 
78
- bits.empty? ? "" : " #{bits.join(' ')}"
79
- rescue StandardError
80
- ""
81
- end
82
-
83
- def summarize_lsp_params(method_name, params)
84
- return "" unless params.is_a?(Hash)
85
-
86
- text_document = hget(params, 'textDocument')
87
- uri = text_document.is_a?(Hash) ? hget(text_document, 'uri') : nil
88
- file_path = uri_to_path(uri)
89
- short_uri = shorten_uri(uri)
90
- position = hget(params, 'position')
91
- line = position.is_a?(Hash) ? hget(position, 'line') : nil
92
- char = position.is_a?(Hash) ? hget(position, 'character') : nil
93
- pos = "#{line}:#{char}" if line && char
94
- query = hget(params, 'query')
81
+ def summarize_lsp_params(method_name, params)
82
+ return "" unless params.is_a?(Hash)
83
+
84
+ text_document = hget(params, 'textDocument')
85
+ uri = text_document.is_a?(Hash) ? hget(text_document, 'uri') : nil
86
+ file_path = uri_to_path(uri)
87
+ short_uri = shorten_uri(uri)
88
+ position = hget(params, 'position')
89
+ line = position.is_a?(Hash) ? hget(position, 'line') : nil
90
+ char = position.is_a?(Hash) ? hget(position, 'character') : nil
91
+ pos = "#{line}:#{char}" if line && char
92
+ query = hget(params, 'query')
93
+
94
+ bits = []
95
+ bits << "uri=#{short_uri || uri}" if uri
96
+ bits << "pos=#{pos}" if pos
97
+ if file_path && line.is_a?(Integer) && char.is_a?(Integer)
98
+ bits << "loc=#{file_path}:#{line + 1}:#{char + 1}"
99
+ end
100
+ bits << "query=#{query.inspect}" if query
101
+ bits << "keys=#{params.keys.map(&:to_s).sort.join(',')}" unless params.empty?
95
102
 
96
- bits = []
97
- bits << "uri=#{short_uri || uri}" if uri
98
- bits << "pos=#{pos}" if pos
99
- if file_path && line.is_a?(Integer) && char.is_a?(Integer)
100
- bits << "loc=#{file_path}:#{line + 1}:#{char + 1}"
101
- end
102
- bits << "query=#{query.inspect}" if query
103
- bits << "keys=#{params.keys.map(&:to_s).sort.join(',')}" unless params.empty?
103
+ if method_name == 'textDocument/didChange'
104
+ changes = hget(params, 'contentChanges')
105
+ bits << "changes=#{changes.length}" if changes.respond_to?(:length)
106
+ end
104
107
 
105
- if method_name == 'textDocument/didChange'
106
- changes = hget(params, 'contentChanges')
107
- bits << "changes=#{changes.length}" if changes.respond_to?(:length)
108
+ bits.join(' ')
109
+ rescue StandardError
110
+ ""
108
111
  end
109
112
 
110
- bits.join(' ')
111
- rescue StandardError
112
- ""
113
- end
113
+ def hget(hash, key)
114
+ return nil unless hash.is_a?(Hash)
114
115
 
115
- def hget(hash, key)
116
- return nil unless hash.is_a?(Hash)
116
+ hash[key] || hash[key.to_sym]
117
+ end
117
118
 
118
- hash[key] || hash[key.to_sym]
119
- end
119
+ def shorten_uri(uri)
120
+ return nil unless uri
121
+ return uri unless uri.is_a?(String) && uri.start_with?('file://')
120
122
 
121
- def shorten_uri(uri)
122
- return nil unless uri
123
- return uri unless uri.is_a?(String) && uri.start_with?('file://')
123
+ file_path = uri_to_path(uri)
124
+ return uri unless file_path
124
125
 
125
- file_path = uri_to_path(uri)
126
- return uri unless file_path
126
+ root_path = uri_to_path(@root_uri)
127
+ return uri unless root_path
127
128
 
128
- root_path = uri_to_path(@root_uri)
129
- return uri unless root_path
129
+ begin
130
+ relative = Pathname.new(file_path).relative_path_from(Pathname.new(root_path)).to_s
131
+ return relative unless relative.start_with?('..')
132
+ rescue StandardError
133
+ # Keep the original URI if path normalization fails.
134
+ end
130
135
 
131
- begin
132
- relative = Pathname.new(file_path).relative_path_from(Pathname.new(root_path)).to_s
133
- return relative unless relative.start_with?('..')
134
- rescue StandardError
135
- # Keep the original URI if path normalization fails.
136
+ uri
136
137
  end
137
138
 
138
- uri
139
- end
139
+ def uri_to_path(uri)
140
+ parsed = URI.parse(uri)
141
+ return nil unless parsed.scheme == 'file'
140
142
 
141
- def uri_to_path(uri)
142
- parsed = URI.parse(uri)
143
- return nil unless parsed.scheme == 'file'
143
+ CGI.unescape(parsed.path)
144
+ rescue URI::InvalidURIError
145
+ nil
146
+ end
144
147
 
145
- CGI.unescape(parsed.path)
146
- rescue URI::InvalidURIError
147
- nil
148
- end
148
+ def library_uri?(uri)
149
+ return false unless @root_uri
149
150
 
150
- def library_uri?(uri)
151
- return false unless @root_uri
151
+ file_path = uri_to_path(uri)
152
+ root_path = uri_to_path(@root_uri)
153
+ return false unless file_path && root_path
152
154
 
153
- file_path = uri_to_path(uri)
154
- root_path = uri_to_path(@root_uri)
155
- return false unless file_path && root_path
155
+ !file_path.start_with?(root_path)
156
+ rescue StandardError
157
+ false
158
+ end
156
159
 
157
- !file_path.start_with?(root_path)
158
- rescue StandardError
159
- false
160
- end
160
+ def skip_expensive_source_fix_all?(uri, content)
161
+ !skip_expensive_work_reason(uri, content).nil?
162
+ rescue StandardError
163
+ false
164
+ end
161
165
 
162
- def skip_expensive_source_fix_all?(uri, content)
163
- !skip_expensive_work_reason(uri, content).nil?
164
- rescue StandardError
165
- false
166
- end
166
+ def skip_expensive_work_reason(uri, content)
167
+ return 'library-uri' if library_uri?(uri)
167
168
 
168
- def skip_expensive_work_reason(uri, content)
169
- return 'library-uri' if library_uri?(uri)
169
+ # Heuristic thresholds to avoid expensive full-file lint-fix runs.
170
+ return 'large-bytes' if content.bytesize > 200_000
171
+ return 'large-lines' if content.count("\n") > 1200
170
172
 
171
- # Heuristic thresholds to avoid expensive full-file lint-fix runs.
172
- return 'large-bytes' if content.bytesize > 200_000
173
- return 'large-lines' if content.count("\n") > 1200
173
+ nil
174
+ rescue StandardError
175
+ nil
176
+ end
174
177
 
175
- nil
176
- rescue StandardError
177
- nil
178
- end
178
+ def handle_did_change_watched_files(params)
179
+ changes = params['changes'] || []
180
+ affected_uris = Set.new
181
+ changes.each do |change|
182
+ uri = change['uri']
183
+ type = change['type']
184
+ next unless uri
179
185
 
180
- def handle_did_change_watched_files(params)
181
- changes = params['changes'] || []
182
- affected_uris = Set.new
183
- changes.each do |change|
184
- uri = change['uri']
185
- type = change['type']
186
- next unless uri
186
+ affected_uris.merge(@workspace.apply_watched_file_change(uri, type))
187
+ end
187
188
 
188
- affected_uris.merge(@workspace.apply_watched_file_change(uri, type))
189
+ invalidate_document_caches_for(affected_uris)
190
+ affected_uris.each do |affected_uri|
191
+ schedule_diagnostics(affected_uri, force: true, lint_tier: :full) unless @workspace.background_document?(affected_uri)
192
+ end
193
+ refresh_client_semantic_tokens if affected_uris.any?
194
+ nil
189
195
  end
190
196
 
191
- invalidate_document_caches_for(affected_uris)
192
- affected_uris.each do |affected_uri|
193
- schedule_diagnostics(affected_uri, force: true, lint_tier: :full) unless @workspace.background_document?(affected_uri)
197
+ def invalidate_document_caches(uri)
198
+ @semantic_tokens_cache.delete(uri)
199
+ @semantic_tokens_delta_cache.delete(uri)
200
+ @fixall_cache.delete(uri)
201
+ path = uri_to_path(uri)
202
+ if path
203
+ prefix = "#{path}:"
204
+ @definition_file_token_cache.delete_if { |key, _| key.start_with?(prefix) }
205
+ @definition_file_ast_cache.delete_if { |key, _| key.start_with?(prefix) }
206
+ end
194
207
  end
195
- refresh_client_semantic_tokens if affected_uris.any?
196
- nil
197
- end
198
208
 
199
- def invalidate_document_caches(uri)
200
- @semantic_tokens_cache.delete(uri)
201
- @semantic_tokens_delta_cache.delete(uri)
202
- @fixall_cache.delete(uri)
203
- path = uri_to_path(uri)
204
- if path
205
- prefix = "#{path}:"
206
- @definition_file_token_cache.delete_if { |key, _| key.start_with?(prefix) }
207
- @definition_file_ast_cache.delete_if { |key, _| key.start_with?(prefix) }
209
+ def invalidate_document_caches_for(uris)
210
+ uris.each { |uri| invalidate_document_caches(uri) }
208
211
  end
209
- end
210
-
211
- def invalidate_document_caches_for(uris)
212
- uris.each { |uri| invalidate_document_caches(uri) }
213
- end
214
212
 
215
- def refresh_open_document_dependency_state(changed_uri, previous_content: nil, current_content: nil)
216
- return [] unless dependency_refresh_required_for_edit?(changed_uri, previous_content, current_content)
213
+ def refresh_open_document_dependency_state(changed_uri, previous_content: nil, current_content: nil)
214
+ return [] unless dependency_refresh_required_for_edit?(changed_uri, previous_content, current_content)
217
215
 
218
- affected_uris = @workspace.refresh_open_document_dependency_caches(changed_uri)
219
- invalidate_document_caches_for(affected_uris)
220
- affected_uris.each do |affected_uri|
221
- schedule_diagnostics(affected_uri, force: true, lint_tier: :full) unless @workspace.background_document?(affected_uri)
216
+ affected_uris = @workspace.refresh_open_document_dependency_caches(changed_uri)
217
+ invalidate_document_caches_for(affected_uris)
218
+ affected_uris.each do |affected_uri|
219
+ schedule_diagnostics(affected_uri, force: true, lint_tier: :full) unless @workspace.background_document?(affected_uri)
220
+ end
221
+ affected_uris
222
222
  end
223
- affected_uris
224
- end
225
223
 
226
- def request_cancelled?(id)
227
- return false if id.nil?
224
+ def request_cancelled?(id)
225
+ return false if id.nil?
228
226
 
229
- @cancelled_requests_mutex.synchronize do
230
- @cancelled_request_ids.include?(id)
227
+ @cancelled_requests_mutex.synchronize do
228
+ @cancelled_request_ids.include?(id)
229
+ end
231
230
  end
232
- end
233
231
 
234
- def clear_cancelled_request(id)
235
- return if id.nil?
232
+ def clear_cancelled_request(id)
233
+ return if id.nil?
236
234
 
237
- @cancelled_requests_mutex.synchronize do
238
- @cancelled_request_ids.delete(id)
235
+ @cancelled_requests_mutex.synchronize do
236
+ @cancelled_request_ids.delete(id)
237
+ end
238
+ end
239
+
240
+ def format_symbol(sym, uri)
241
+ line = sym[:line].to_i
242
+ col = sym[:column].to_i
243
+
244
+ {
245
+ name: sym[:name],
246
+ kind: symbol_kind(sym[:kind]),
247
+ location: {
248
+ uri: uri,
249
+ range: {
250
+ start: { line: line - 1, character: col - 1 },
251
+ end: { line: line - 1, character: col - 1 + sym[:name].length }
252
+ }
253
+ }
254
+ }
239
255
  end
240
- end
241
256
 
242
- def format_symbol(sym, uri)
243
- line = sym[:line].to_i
244
- col = sym[:column].to_i
257
+ def format_document_symbol(sym)
258
+ line = sym[:line].to_i
259
+ col = sym[:column].to_i
245
260
 
246
- {
247
- name: sym[:name],
248
- kind: symbol_kind(sym[:kind]),
249
- location: {
250
- uri: uri,
251
- range: {
261
+ {
262
+ name: sym[:name],
263
+ kind: symbol_kind(sym[:kind]),
264
+ range: {
265
+ start: { line: line - 1, character: col - 1 },
266
+ end: { line: line - 1, character: col - 1 + sym[:name].length }
267
+ },
268
+ selectionRange: {
252
269
  start: { line: line - 1, character: col - 1 },
253
270
  end: { line: line - 1, character: col - 1 + sym[:name].length }
254
271
  }
255
272
  }
256
- }
257
- end
258
-
259
- def format_document_symbol(sym)
260
- line = sym[:line].to_i
261
- col = sym[:column].to_i
262
-
263
- {
264
- name: sym[:name],
265
- kind: symbol_kind(sym[:kind]),
266
- range: {
267
- start: { line: line - 1, character: col - 1 },
268
- end: { line: line - 1, character: col - 1 + sym[:name].length }
269
- },
270
- selectionRange: {
271
- start: { line: line - 1, character: col - 1 },
272
- end: { line: line - 1, character: col - 1 + sym[:name].length }
273
- }
274
- }
275
- end
273
+ end
276
274
 
277
- def symbol_kind(kind)
278
- case kind
279
- when 'function' then 12 # Function
280
- when 'interface' then 11 # Interface
281
- when 'struct' then 23 # Struct
282
- when 'union' then 23 # Struct (union is a struct variant)
283
- when 'enum' then 10 # Enum
284
- when 'flags' then 10 # Enum (flags is an enum variant)
285
- when 'variant' then 23 # Struct (variant is a struct variant)
286
- when 'type_alias' then 5 # Class
287
- when 'constant' then 14 # Constant
288
- when 'variable' then 13 # Variable
289
- when 'event' then 24 # Event
290
- when 'type_param' then 26 # TypeParameter
291
- else 1 # File
275
+ def symbol_kind(kind)
276
+ case kind
277
+ when 'function' then 12 # Function
278
+ when 'interface' then 11 # Interface
279
+ when 'struct' then 23 # Struct
280
+ when 'union' then 23 # Struct (union is a struct variant)
281
+ when 'enum' then 10 # Enum
282
+ when 'flags' then 10 # Enum (flags is an enum variant)
283
+ when 'variant' then 23 # Struct (variant is a struct variant)
284
+ when 'type_alias' then 5 # Class
285
+ when 'constant' then 14 # Constant
286
+ when 'variable' then 13 # Variable
287
+ when 'event' then 24 # Event
288
+ when 'type_param' then 26 # TypeParameter
289
+ else 1 # File
290
+ end
292
291
  end
293
- end
294
292
 
295
- def current_word_prefix(uri, lsp_line, lsp_char)
296
- content = @workspace.get_content(uri)
297
- lines = content.split("\n", -1)
298
- line = lines[lsp_line] || ''
299
- # Walk backwards from cursor to find start of current word
300
- char_idx = [lsp_char - 1, line.length - 1].min
301
- return '' if char_idx < 0
302
-
303
- start = char_idx
304
- start -= 1 while start >= 0 && line[start] =~ /[A-Za-z0-9_]/
305
- line[(start + 1)..char_idx] || ''
306
- end
293
+ def current_word_prefix(uri, lsp_line, lsp_char)
294
+ content = @workspace.get_content(uri)
295
+ lines = content.split("\n", -1)
296
+ line = lines[lsp_line] || ''
297
+ # Walk backwards from cursor to find start of current word
298
+ char_idx = [lsp_char - 1, line.length - 1].min
299
+ return '' if char_idx < 0
300
+
301
+ start = char_idx
302
+ start -= 1 while start >= 0 && line[start] =~ /[A-Za-z0-9_]/
303
+ line[(start + 1)..char_idx] || ''
304
+ end
307
305
 
308
- def token_to_range(token)
309
- end_line, end_character = token_end_position(token)
310
- start_line = token.line - 1
311
- start_char = encode_char_for_client(start_line, token.column - 1)
312
- end_char = encode_char_for_client(end_line, end_character)
306
+ def token_to_range(token)
307
+ end_line, end_character = token_end_position(token)
308
+ start_line = token.line - 1
309
+ start_char = encode_char_for_client(start_line, token.column - 1)
310
+ end_char = encode_char_for_client(end_line, end_character)
313
311
 
314
- {
315
- start: { line: start_line, character: start_char },
316
- end: { line: end_line, character: end_char }
317
- }
318
- end
312
+ {
313
+ start: { line: start_line, character: start_char },
314
+ end: { line: end_line, character: end_char }
315
+ }
316
+ end
319
317
 
320
- def token_end_position(token)
321
- segments = token.lexeme.split("\n", -1)
322
- if segments.length == 1
323
- [token.line - 1, token.column - 1 + segments.first.length]
324
- else
325
- [token.line - 1 + segments.length - 1, segments.last.length]
318
+ def token_end_position(token)
319
+ segments = token.lexeme.split("\n", -1)
320
+ if segments.length == 1
321
+ [token.line - 1, token.column - 1 + segments.first.length]
322
+ else
323
+ [token.line - 1 + segments.length - 1, segments.last.length]
324
+ end
326
325
  end
327
- end
328
326
 
329
- def encode_char_for_client(lsp_line, internal_char)
330
- return internal_char if @position_encoding == 'utf-16'
327
+ def encode_char_for_client(lsp_line, internal_char)
328
+ return internal_char if @position_encoding == 'utf-16'
331
329
 
332
- internal_char
333
- end
330
+ internal_char
331
+ end
334
332
 
335
- def decode_client_char(uri, lsp_line, client_char)
336
- return client_char if @position_encoding == 'utf-16'
333
+ def decode_client_char(uri, lsp_line, client_char)
334
+ return client_char if @position_encoding == 'utf-16'
337
335
 
338
- content = @workspace.get_content(uri)
339
- return client_char unless content
336
+ content = @workspace.get_content(uri)
337
+ return client_char unless content
340
338
 
341
- lines = content.split("\n", -1)
342
- line_text = lines[lsp_line]
343
- return client_char unless line_text
339
+ lines = content.split("\n", -1)
340
+ line_text = lines[lsp_line]
341
+ return client_char unless line_text
344
342
 
345
- if @position_encoding == 'utf-8'
346
- utf8_to_utf16_char(line_text, client_char)
347
- elsif @position_encoding == 'utf-32'
348
- utf32_to_utf16_char(line_text, client_char)
349
- else
350
- client_char
343
+ if @position_encoding == 'utf-8'
344
+ utf8_to_utf16_char(line_text, client_char)
345
+ elsif @position_encoding == 'utf-32'
346
+ utf32_to_utf16_char(line_text, client_char)
347
+ else
348
+ client_char
349
+ end
351
350
  end
352
- end
353
351
 
354
- def utf8_to_utf16_char(line_text, utf8_offset)
355
- bytes_seen = 0
356
- utf16_count = 0
357
- line_text.each_char do |ch|
358
- break if bytes_seen >= utf8_offset
359
- bytes_seen += ch.bytesize
360
- utf16_count += ch.ord > 0xFFFF ? 2 : 1
352
+ def utf8_to_utf16_char(line_text, utf8_offset)
353
+ bytes_seen = 0
354
+ utf16_count = 0
355
+ line_text.each_char do |ch|
356
+ break if bytes_seen >= utf8_offset
357
+ bytes_seen += ch.bytesize
358
+ utf16_count += ch.ord > 0xFFFF ? 2 : 1
359
+ end
360
+ utf16_count
361
361
  end
362
- utf16_count
363
- end
364
362
 
365
- def utf32_to_utf16_char(line_text, utf32_offset)
366
- utf16_count = 0
367
- line_text.each_char.with_index do |ch, idx|
368
- break if idx >= utf32_offset
369
- utf16_count += ch.ord > 0xFFFF ? 2 : 1
363
+ def utf32_to_utf16_char(line_text, utf32_offset)
364
+ utf16_count = 0
365
+ line_text.each_char.with_index do |ch, idx|
366
+ break if idx >= utf32_offset
367
+ utf16_count += ch.ord > 0xFFFF ? 2 : 1
368
+ end
369
+ utf16_count
370
370
  end
371
- utf16_count
372
- end
373
371
 
374
- def diagnostics_fingerprint(content, diagnostics)
375
- [content, diagnostics].hash.to_s(16)
376
- end
372
+ def diagnostics_fingerprint(content, diagnostics)
373
+ [content, diagnostics].hash.to_s(16)
374
+ end
377
375
 
378
- def next_diagnostic_result_id(uri, fingerprint)
379
- "#{uri}:#{fingerprint}"
380
- end
376
+ def next_diagnostic_result_id(uri, fingerprint)
377
+ "#{uri}:#{fingerprint}"
378
+ end
381
379
 
382
- def path_to_uri(path)
383
- escaped_path = path.split('/').map { |seg| CGI.escape(seg).gsub('+', '%20') }.join('/')
384
- "file://#{escaped_path}"
385
- end
380
+ def path_to_uri(path)
381
+ escaped_path = path.split('/').map { |seg| CGI.escape(seg).gsub('+', '%20') }.join('/')
382
+ "file://#{escaped_path}"
383
+ end
386
384
 
387
- def collect_call_argument_starts(tokens, lparen_index)
388
- starts = []
389
- depth = 1
390
- j = lparen_index + 1
391
-
392
- first = next_non_trivia_token(tokens, j)
393
- starts << first if first && first.type != :rparen
394
-
395
- while j < tokens.length
396
- tok = tokens[j]
397
- case tok.type
398
- when :lparen
399
- depth += 1
400
- when :rparen
401
- depth -= 1
402
- return [starts, j] if depth.zero?
403
- when :comma
404
- if depth == 1
405
- next_tok = next_non_trivia_token(tokens, j + 1)
406
- starts << next_tok if next_tok && next_tok.type != :rparen
385
+ def collect_call_argument_starts(tokens, lparen_index)
386
+ starts = []
387
+ depth = 1
388
+ j = lparen_index + 1
389
+
390
+ first = next_non_trivia_token(tokens, j)
391
+ starts << first if first && first.type != :rparen
392
+
393
+ while j < tokens.length
394
+ tok = tokens[j]
395
+ case tok.type
396
+ when :lparen
397
+ depth += 1
398
+ when :rparen
399
+ depth -= 1
400
+ return [starts, j] if depth.zero?
401
+ when :comma
402
+ if depth == 1
403
+ next_tok = next_non_trivia_token(tokens, j + 1)
404
+ starts << next_tok if next_tok && next_tok.type != :rparen
405
+ end
407
406
  end
407
+ j += 1
408
408
  end
409
- j += 1
409
+
410
+ [starts, nil]
410
411
  end
411
412
 
412
- [starts, nil]
413
- end
413
+ def self_describing_argument_expression?(tokens, arg_tok)
414
+ arg_index = tokens.index(arg_tok)
415
+ return false unless arg_index
414
416
 
415
- def self_describing_argument_expression?(tokens, arg_tok)
416
- arg_index = tokens.index(arg_tok)
417
- return false unless arg_index
417
+ simple_identifier_like_argument_expression?(tokens, arg_index)
418
+ end
418
419
 
419
- simple_identifier_like_argument_expression?(tokens, arg_index)
420
- end
420
+ def simple_identifier_like_argument_expression?(tokens, start_index)
421
+ saw_identifier = false
422
+ expect_identifier = true
423
+ i = start_index
421
424
 
422
- def simple_identifier_like_argument_expression?(tokens, start_index)
423
- saw_identifier = false
424
- expect_identifier = true
425
- i = start_index
425
+ while i < tokens.length
426
+ tok = tokens[i]
427
+ break if [:comma, :rparen].include?(tok.type)
428
+ return false if [:newline, :indent, :dedent].include?(tok.type)
426
429
 
427
- while i < tokens.length
428
- tok = tokens[i]
429
- break if [:comma, :rparen].include?(tok.type)
430
- return false if [:newline, :indent, :dedent].include?(tok.type)
430
+ if expect_identifier
431
+ return false unless tok.type == :identifier
431
432
 
432
- if expect_identifier
433
- return false unless tok.type == :identifier
433
+ saw_identifier = true
434
+ expect_identifier = false
435
+ else
436
+ return false unless tok.type == :dot
434
437
 
435
- saw_identifier = true
436
- expect_identifier = false
437
- else
438
- return false unless tok.type == :dot
438
+ expect_identifier = true
439
+ end
439
440
 
440
- expect_identifier = true
441
+ i += 1
441
442
  end
442
443
 
443
- i += 1
444
+ saw_identifier && !expect_identifier
444
445
  end
445
446
 
446
- saw_identifier && !expect_identifier
447
- end
448
-
449
- def next_non_trivia_token(tokens, index)
450
- i = index
451
- while i < tokens.length
452
- tok = tokens[i]
453
- return tok unless [:newline, :indent, :dedent].include?(tok.type)
454
- i += 1
447
+ def next_non_trivia_token(tokens, index)
448
+ i = index
449
+ while i < tokens.length
450
+ tok = tokens[i]
451
+ return tok unless [:newline, :indent, :dedent].include?(tok.type)
452
+ i += 1
453
+ end
454
+ nil
455
455
  end
456
- nil
457
- end
458
-
459
- def position_in_range?(line, char, start_line, start_char, end_line, end_char)
460
- after_start = (line > start_line) || (line == start_line && char >= start_char)
461
- before_end = (line < end_line) || (line == end_line && char <= end_char)
462
- after_start && before_end
463
- end
464
456
 
465
- def show_message(type, message)
466
- @protocol.write_notification("window/showMessage", {
467
- type: MESSAGE_TYPES[type] || type,
468
- message: message,
469
- })
470
- end
471
-
472
- def log_message(type, message)
473
- @protocol.write_notification("window/logMessage", {
474
- type: MESSAGE_TYPES[type] || type,
475
- message: message,
476
- })
477
- end
457
+ def position_in_range?(line, char, start_line, start_char, end_line, end_char)
458
+ after_start = (line > start_line) || (line == start_line && char >= start_char)
459
+ before_end = (line < end_line) || (line == end_line && char <= end_char)
460
+ after_start && before_end
461
+ end
478
462
 
479
- def show_message_request(type, message, actions:, &callback)
480
- if @protocol.respond_to?(:send_request)
481
- @protocol.send_request('window/showMessageRequest', {
463
+ def show_message(type, message)
464
+ @protocol.write_notification("window/showMessage", {
482
465
  type: MESSAGE_TYPES[type] || type,
483
466
  message: message,
484
- actions: actions.map { |title| { title: title } }
485
- }) do |result, error|
486
- if error
487
- callback.call(nil)
488
- elsif result.is_a?(Hash) && result['title']
489
- callback.call(result['title'])
490
- else
491
- callback.call(nil)
492
- end
493
- end
494
- else
495
- Protocol.send_request('window/showMessageRequest', {
467
+ })
468
+ end
469
+
470
+ def log_message(type, message)
471
+ @protocol.write_notification("window/logMessage", {
496
472
  type: MESSAGE_TYPES[type] || type,
497
473
  message: message,
498
- actions: actions.map { |title| { title: title } }
499
- }) do |result, error|
500
- if error
501
- callback.call(nil)
502
- elsif result.is_a?(Hash) && result['title']
503
- callback.call(result['title'])
504
- else
505
- callback.call(nil)
474
+ })
475
+ end
476
+
477
+ def show_message_request(type, message, actions:, &callback)
478
+ if @protocol.respond_to?(:send_request)
479
+ @protocol.send_request('window/showMessageRequest', {
480
+ type: MESSAGE_TYPES[type] || type,
481
+ message: message,
482
+ actions: actions.map { |title| { title: title } }
483
+ }) do |result, error|
484
+ if error
485
+ callback.call(nil)
486
+ elsif result.is_a?(Hash) && result['title']
487
+ callback.call(result['title'])
488
+ else
489
+ callback.call(nil)
490
+ end
491
+ end
492
+ else
493
+ Protocol.send_request('window/showMessageRequest', {
494
+ type: MESSAGE_TYPES[type] || type,
495
+ message: message,
496
+ actions: actions.map { |title| { title: title } }
497
+ }) do |result, error|
498
+ if error
499
+ callback.call(nil)
500
+ elsif result.is_a?(Hash) && result['title']
501
+ callback.call(result['title'])
502
+ else
503
+ callback.call(nil)
504
+ end
506
505
  end
507
506
  end
508
507
  end
509
- end
510
508
 
511
- MESSAGE_TYPES = {
512
- error: 1,
513
- warning: 2,
514
- info: 3,
515
- log: 4,
516
- }.freeze
509
+ MESSAGE_TYPES = {
510
+ error: 1,
511
+ warning: 2,
512
+ info: 3,
513
+ log: 4,
514
+ }.freeze
517
515
  end
518
516
  end
519
517
  end