mt-lang 0.2.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 (258) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/AUTHORS +3 -0
  4. data/Gemfile +19 -0
  5. data/Gemfile.lock +82 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1208 -0
  8. data/Rakefile +332 -0
  9. data/bin/mtc +8 -0
  10. data/bin/profile-mtc-checks +133 -0
  11. data/bin/tracy-profiler +0 -0
  12. data/docs/build-guide.md +536 -0
  13. data/docs/index.html +2778 -0
  14. data/docs/language-design.md +1519 -0
  15. data/docs/language-manual.md +1534 -0
  16. data/lib/milk_tea/base.rb +49 -0
  17. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
  18. data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
  19. data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
  20. data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
  21. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
  22. data/lib/milk_tea/bindings/bindgen.rb +207 -0
  23. data/lib/milk_tea/bindings/cli.rb +121 -0
  24. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
  25. data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
  26. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
  27. data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
  28. data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
  29. data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
  30. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
  31. data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
  32. data/lib/milk_tea/bindings/steamworks.rb +629 -0
  33. data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
  34. data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
  35. data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
  36. data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
  37. data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
  38. data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
  39. data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
  40. data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
  41. data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
  42. data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
  43. data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
  44. data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
  45. data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
  46. data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
  47. data/lib/milk_tea/bindings.rb +23 -0
  48. data/lib/milk_tea/core/ast.rb +311 -0
  49. data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
  50. data/lib/milk_tea/core/binding_types.rb +18 -0
  51. data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
  52. data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
  53. data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
  54. data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
  55. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
  56. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
  57. data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
  58. data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
  59. data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
  60. data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
  61. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
  62. data/lib/milk_tea/core/c_backend/statements.rb +563 -0
  63. data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
  64. data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
  65. data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
  66. data/lib/milk_tea/core/c_backend.rb +287 -0
  67. data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
  68. data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
  69. data/lib/milk_tea/core/compile_time.rb +329 -0
  70. data/lib/milk_tea/core/control_flow/builder.rb +582 -0
  71. data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
  72. data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
  73. data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
  74. data/lib/milk_tea/core/control_flow/graph.rb +90 -0
  75. data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
  76. data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
  77. data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
  78. data/lib/milk_tea/core/control_flow/termination.rb +31 -0
  79. data/lib/milk_tea/core/control_flow.rb +34 -0
  80. data/lib/milk_tea/core/cst.rb +48 -0
  81. data/lib/milk_tea/core/cst_builder.rb +19 -0
  82. data/lib/milk_tea/core/ir.rb +85 -0
  83. data/lib/milk_tea/core/keywords.rb +97 -0
  84. data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
  85. data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
  86. data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
  87. data/lib/milk_tea/core/lexer/indentation.rb +62 -0
  88. data/lib/milk_tea/core/lexer/numbers.rb +96 -0
  89. data/lib/milk_tea/core/lexer/recovery.rb +32 -0
  90. data/lib/milk_tea/core/lexer/strings.rb +167 -0
  91. data/lib/milk_tea/core/lexer/symbols.rb +71 -0
  92. data/lib/milk_tea/core/lexer/trivia.rb +53 -0
  93. data/lib/milk_tea/core/lexer.rb +430 -0
  94. data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
  95. data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
  97. data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
  98. data/lib/milk_tea/core/lowering/async.rb +714 -0
  99. data/lib/milk_tea/core/lowering/block.rb +1052 -0
  100. data/lib/milk_tea/core/lowering/calls.rb +1565 -0
  101. data/lib/milk_tea/core/lowering/declarations.rb +214 -0
  102. data/lib/milk_tea/core/lowering/dyn.rb +206 -0
  103. data/lib/milk_tea/core/lowering/events.rb +1054 -0
  104. data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
  105. data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
  106. data/lib/milk_tea/core/lowering/functions.rb +242 -0
  107. data/lib/milk_tea/core/lowering/loops.rb +1087 -0
  108. data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
  109. data/lib/milk_tea/core/lowering/proc.rb +419 -0
  110. data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
  111. data/lib/milk_tea/core/lowering/scans.rb +220 -0
  112. data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
  113. data/lib/milk_tea/core/lowering/utils.rb +1453 -0
  114. data/lib/milk_tea/core/lowering.rb +378 -0
  115. data/lib/milk_tea/core/module_binder.rb +181 -0
  116. data/lib/milk_tea/core/module_loader/errors.rb +21 -0
  117. data/lib/milk_tea/core/module_loader.rb +479 -0
  118. data/lib/milk_tea/core/module_path_resolver.rb +153 -0
  119. data/lib/milk_tea/core/module_roots.rb +88 -0
  120. data/lib/milk_tea/core/parser/attributes.rb +71 -0
  121. data/lib/milk_tea/core/parser/blocks.rb +119 -0
  122. data/lib/milk_tea/core/parser/declarations.rb +749 -0
  123. data/lib/milk_tea/core/parser/expressions.rb +624 -0
  124. data/lib/milk_tea/core/parser/recovery.rb +131 -0
  125. data/lib/milk_tea/core/parser/statements.rb +756 -0
  126. data/lib/milk_tea/core/parser/types.rb +271 -0
  127. data/lib/milk_tea/core/parser.rb +400 -0
  128. data/lib/milk_tea/core/prelude_installer.rb +29 -0
  129. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
  130. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
  131. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
  132. data/lib/milk_tea/core/pretty_printer.rb +17 -0
  133. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
  134. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
  135. data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
  136. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
  137. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
  138. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
  139. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
  140. data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
  141. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
  142. data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
  143. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
  144. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
  145. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
  146. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
  147. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
  148. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
  149. data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
  150. data/lib/milk_tea/core/token.rb +26 -0
  151. data/lib/milk_tea/core/token_stream.rb +30 -0
  152. data/lib/milk_tea/core/types/layout.rb +243 -0
  153. data/lib/milk_tea/core/types/predicates.rb +609 -0
  154. data/lib/milk_tea/core/types/registry.rb +83 -0
  155. data/lib/milk_tea/core/types/types.rb +1696 -0
  156. data/lib/milk_tea/core/types/visitor.rb +442 -0
  157. data/lib/milk_tea/core.rb +25 -0
  158. data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
  159. data/lib/milk_tea/dap/protocol.rb +58 -0
  160. data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
  161. data/lib/milk_tea/dap/server/debug_map.rb +291 -0
  162. data/lib/milk_tea/dap/server/handlers.rb +374 -0
  163. data/lib/milk_tea/dap/server/launch.rb +261 -0
  164. data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
  165. data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
  166. data/lib/milk_tea/dap/server/utilities.rb +160 -0
  167. data/lib/milk_tea/dap/server/wire.rb +55 -0
  168. data/lib/milk_tea/dap/server.rb +131 -0
  169. data/lib/milk_tea/dap/session.rb +152 -0
  170. data/lib/milk_tea/dap.rb +6 -0
  171. data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
  172. data/lib/milk_tea/lsp/diagnostics.rb +611 -0
  173. data/lib/milk_tea/lsp/protocol.rb +104 -0
  174. data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
  175. data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
  176. data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
  177. data/lib/milk_tea/lsp/server/completion.rb +1099 -0
  178. data/lib/milk_tea/lsp/server/configuration.rb +167 -0
  179. data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
  180. data/lib/milk_tea/lsp/server/definition.rb +779 -0
  181. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
  182. data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
  183. data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
  184. data/lib/milk_tea/lsp/server/formatting.rb +575 -0
  185. data/lib/milk_tea/lsp/server/hover.rb +1465 -0
  186. data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
  187. data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
  188. data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
  189. data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
  190. data/lib/milk_tea/lsp/server/progress.rb +47 -0
  191. data/lib/milk_tea/lsp/server/references.rb +433 -0
  192. data/lib/milk_tea/lsp/server/rename.rb +598 -0
  193. data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
  194. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
  195. data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
  196. data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
  197. data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
  198. data/lib/milk_tea/lsp/server/utilities.rb +520 -0
  199. data/lib/milk_tea/lsp/server.rb +415 -0
  200. data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
  201. data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
  202. data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
  203. data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
  204. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
  205. data/lib/milk_tea/lsp/workspace/store.rb +154 -0
  206. data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
  207. data/lib/milk_tea/lsp/workspace.rb +127 -0
  208. data/lib/milk_tea/lsp.rb +13 -0
  209. data/lib/milk_tea/packages/atomic_write.rb +63 -0
  210. data/lib/milk_tea/packages/dependency_solver.rb +194 -0
  211. data/lib/milk_tea/packages/graph.rb +139 -0
  212. data/lib/milk_tea/packages/lock.rb +421 -0
  213. data/lib/milk_tea/packages/manager_cli.rb +485 -0
  214. data/lib/milk_tea/packages/manifest.rb +356 -0
  215. data/lib/milk_tea/packages/manifest_editor.rb +134 -0
  216. data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
  217. data/lib/milk_tea/packages/registry_store.rb +420 -0
  218. data/lib/milk_tea/packages/services.rb +41 -0
  219. data/lib/milk_tea/packages/source_cache.rb +71 -0
  220. data/lib/milk_tea/packages/source_fetcher.rb +124 -0
  221. data/lib/milk_tea/packages/source_resolver.rb +389 -0
  222. data/lib/milk_tea/packages/version.rb +164 -0
  223. data/lib/milk_tea/packages.rb +16 -0
  224. data/lib/milk_tea/tooling/asset_pack.rb +141 -0
  225. data/lib/milk_tea/tooling/build.rb +1124 -0
  226. data/lib/milk_tea/tooling/build_cache.rb +240 -0
  227. data/lib/milk_tea/tooling/cli.rb +2688 -0
  228. data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
  229. data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
  230. data/lib/milk_tea/tooling/debug_map.rb +248 -0
  231. data/lib/milk_tea/tooling/docs_app.rb +369 -0
  232. data/lib/milk_tea/tooling/error_formatter.rb +86 -0
  233. data/lib/milk_tea/tooling/formatter.rb +787 -0
  234. data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
  235. data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
  236. data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
  237. data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
  238. data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
  239. data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
  240. data/lib/milk_tea/tooling/linter/rules.rb +593 -0
  241. data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
  242. data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
  243. data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
  244. data/lib/milk_tea/tooling/linter.rb +798 -0
  245. data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
  246. data/lib/milk_tea/tooling/public/css/docs.css +166 -0
  247. data/lib/milk_tea/tooling/public/js/docs.js +94 -0
  248. data/lib/milk_tea/tooling/run.rb +408 -0
  249. data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
  250. data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
  251. data/lib/milk_tea/tooling/views/404.erb +7 -0
  252. data/lib/milk_tea/tooling/views/index.erb +87 -0
  253. data/lib/milk_tea/tooling/views/layout.erb +69 -0
  254. data/lib/milk_tea/tooling/views/module.erb +97 -0
  255. data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
  256. data/lib/milk_tea/tooling.rb +20 -0
  257. data/lib/milk_tea.rb +8 -0
  258. metadata +426 -0
@@ -0,0 +1,520 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module LSP
5
+ class Server
6
+ module ServerUtilities
7
+ private
8
+
9
+ def perf_logging?
10
+ @perf_logging ||= !ENV.fetch('MILK_TEA_LSP_PERF', nil).to_s.empty?
11
+ end
12
+
13
+ def perf_verbose?
14
+ @perf_verbose ||= ENV.fetch('MILK_TEA_LSP_PERF', nil).to_s == 'verbose'
15
+ end
16
+
17
+ def perf_breakdown_logging?(elapsed_ms)
18
+ perf_logging? && (perf_verbose? || elapsed_ms > Workspace::PERF_LOG_THRESHOLD_MS)
19
+ end
20
+
21
+ def monotonic_time
22
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
+ end
24
+
25
+ def elapsed_ms(start_time)
26
+ ((monotonic_time - start_time) * 1000).round(1)
27
+ end
28
+
29
+ def log_perf_breakdown(method_name, elapsed_ms_value, detail)
30
+ return unless perf_breakdown_logging?(elapsed_ms_value)
31
+
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
35
+
36
+ def new_perf_stages
37
+ perf_logging? ? [] : nil
38
+ end
39
+
40
+ def measure_perf_stage(stages, name)
41
+ return yield unless stages
42
+
43
+ start_time = monotonic_time
44
+ result = yield
45
+ stages << [name, elapsed_ms(start_time)]
46
+ result
47
+ end
48
+
49
+ def log_request_stage_breakdown(method_name, total_start, uri: nil, stages: nil, summary: nil)
50
+ return unless total_start
51
+
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
+ end
58
+
59
+ log_perf_breakdown(method_name, elapsed_ms(total_start), detail.join(' '))
60
+ end
61
+
62
+ def perf_log_context(method_name, params, verbose: false)
63
+ return "" unless params.is_a?(Hash)
64
+
65
+ summary = summarize_lsp_params(method_name, params)
66
+ return summary.empty? ? "" : " #{summary}" if verbose
67
+
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
72
+
73
+ if method_name == 'textDocument/didChange'
74
+ changes = hget(params, 'contentChanges')
75
+ bits << "changes=#{changes.length}" if changes.respond_to?(:length)
76
+ end
77
+
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')
95
+
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?
104
+
105
+ if method_name == 'textDocument/didChange'
106
+ changes = hget(params, 'contentChanges')
107
+ bits << "changes=#{changes.length}" if changes.respond_to?(:length)
108
+ end
109
+
110
+ bits.join(' ')
111
+ rescue StandardError
112
+ ""
113
+ end
114
+
115
+ def hget(hash, key)
116
+ return nil unless hash.is_a?(Hash)
117
+
118
+ hash[key] || hash[key.to_sym]
119
+ end
120
+
121
+ def shorten_uri(uri)
122
+ return nil unless uri
123
+ return uri unless uri.is_a?(String) && uri.start_with?('file://')
124
+
125
+ file_path = uri_to_path(uri)
126
+ return uri unless file_path
127
+
128
+ root_path = uri_to_path(@root_uri)
129
+ return uri unless root_path
130
+
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
+ end
137
+
138
+ uri
139
+ end
140
+
141
+ def uri_to_path(uri)
142
+ parsed = URI.parse(uri)
143
+ return nil unless parsed.scheme == 'file'
144
+
145
+ CGI.unescape(parsed.path)
146
+ rescue URI::InvalidURIError
147
+ nil
148
+ end
149
+
150
+ def library_uri?(uri)
151
+ return false unless @root_uri
152
+
153
+ file_path = uri_to_path(uri)
154
+ root_path = uri_to_path(@root_uri)
155
+ return false unless file_path && root_path
156
+
157
+ !file_path.start_with?(root_path)
158
+ rescue StandardError
159
+ false
160
+ end
161
+
162
+ def skip_expensive_source_fix_all?(uri, content)
163
+ !skip_expensive_work_reason(uri, content).nil?
164
+ rescue StandardError
165
+ false
166
+ end
167
+
168
+ def skip_expensive_work_reason(uri, content)
169
+ return 'library-uri' if library_uri?(uri)
170
+
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
174
+
175
+ nil
176
+ rescue StandardError
177
+ nil
178
+ end
179
+
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
187
+
188
+ affected_uris.merge(@workspace.apply_watched_file_change(uri, type))
189
+ end
190
+
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)
194
+ end
195
+ refresh_client_semantic_tokens if affected_uris.any?
196
+ nil
197
+ end
198
+
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) }
208
+ end
209
+ end
210
+
211
+ def invalidate_document_caches_for(uris)
212
+ uris.each { |uri| invalidate_document_caches(uri) }
213
+ end
214
+
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)
217
+
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)
222
+ end
223
+ affected_uris
224
+ end
225
+
226
+ def request_cancelled?(id)
227
+ return false if id.nil?
228
+
229
+ @cancelled_requests_mutex.synchronize do
230
+ @cancelled_request_ids.include?(id)
231
+ end
232
+ end
233
+
234
+ def clear_cancelled_request(id)
235
+ return if id.nil?
236
+
237
+ @cancelled_requests_mutex.synchronize do
238
+ @cancelled_request_ids.delete(id)
239
+ end
240
+ end
241
+
242
+ def format_symbol(sym, uri)
243
+ line = sym[:line].to_i
244
+ col = sym[:column].to_i
245
+
246
+ {
247
+ name: sym[:name],
248
+ kind: symbol_kind(sym[:kind]),
249
+ location: {
250
+ uri: uri,
251
+ range: {
252
+ start: { line: line - 1, character: col - 1 },
253
+ end: { line: line - 1, character: col - 1 + sym[:name].length }
254
+ }
255
+ }
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
276
+
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
292
+ end
293
+ end
294
+
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
307
+
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)
313
+
314
+ {
315
+ start: { line: start_line, character: start_char },
316
+ end: { line: end_line, character: end_char }
317
+ }
318
+ end
319
+
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]
326
+ end
327
+ end
328
+
329
+ def encode_char_for_client(lsp_line, internal_char)
330
+ return internal_char if @position_encoding == 'utf-16'
331
+
332
+ internal_char
333
+ end
334
+
335
+ def decode_client_char(uri, lsp_line, client_char)
336
+ return client_char if @position_encoding == 'utf-16'
337
+
338
+ content = @workspace.get_content(uri)
339
+ return client_char unless content
340
+
341
+ lines = content.split("\n", -1)
342
+ line_text = lines[lsp_line]
343
+ return client_char unless line_text
344
+
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
351
+ end
352
+ end
353
+
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
361
+ end
362
+ utf16_count
363
+ end
364
+
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
370
+ end
371
+ utf16_count
372
+ end
373
+
374
+ def diagnostics_fingerprint(content, diagnostics)
375
+ [content, diagnostics].hash.to_s(16)
376
+ end
377
+
378
+ def next_diagnostic_result_id(uri, fingerprint)
379
+ "#{uri}:#{fingerprint}"
380
+ end
381
+
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
386
+
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
407
+ end
408
+ end
409
+ j += 1
410
+ end
411
+
412
+ [starts, nil]
413
+ end
414
+
415
+ def self_describing_argument_expression?(tokens, arg_tok)
416
+ arg_index = tokens.index(arg_tok)
417
+ return false unless arg_index
418
+
419
+ simple_identifier_like_argument_expression?(tokens, arg_index)
420
+ end
421
+
422
+ def simple_identifier_like_argument_expression?(tokens, start_index)
423
+ saw_identifier = false
424
+ expect_identifier = true
425
+ i = start_index
426
+
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)
431
+
432
+ if expect_identifier
433
+ return false unless tok.type == :identifier
434
+
435
+ saw_identifier = true
436
+ expect_identifier = false
437
+ else
438
+ return false unless tok.type == :dot
439
+
440
+ expect_identifier = true
441
+ end
442
+
443
+ i += 1
444
+ end
445
+
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
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
+
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
478
+
479
+ def show_message_request(type, message, actions:, &callback)
480
+ if @protocol.respond_to?(:send_request)
481
+ @protocol.send_request('window/showMessageRequest', {
482
+ type: MESSAGE_TYPES[type] || type,
483
+ 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', {
496
+ type: MESSAGE_TYPES[type] || type,
497
+ 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)
506
+ end
507
+ end
508
+ end
509
+ end
510
+
511
+ MESSAGE_TYPES = {
512
+ error: 1,
513
+ warning: 2,
514
+ info: 3,
515
+ log: 4,
516
+ }.freeze
517
+ end
518
+ end
519
+ end
520
+ end