konpeito 0.1.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 (180) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +75 -0
  4. data/CONTRIBUTING.md +123 -0
  5. data/LICENSE +21 -0
  6. data/README.md +257 -0
  7. data/Rakefile +11 -0
  8. data/bin/konpeito +6 -0
  9. data/konpeito.gemspec +43 -0
  10. data/lib/konpeito/ast/typed_ast.rb +620 -0
  11. data/lib/konpeito/ast/visitor.rb +78 -0
  12. data/lib/konpeito/cache/cache_manager.rb +230 -0
  13. data/lib/konpeito/cache/dependency_graph.rb +192 -0
  14. data/lib/konpeito/cache.rb +8 -0
  15. data/lib/konpeito/cli/base_command.rb +187 -0
  16. data/lib/konpeito/cli/build_command.rb +220 -0
  17. data/lib/konpeito/cli/check_command.rb +104 -0
  18. data/lib/konpeito/cli/config.rb +231 -0
  19. data/lib/konpeito/cli/deps_command.rb +128 -0
  20. data/lib/konpeito/cli/doctor_command.rb +340 -0
  21. data/lib/konpeito/cli/fmt_command.rb +199 -0
  22. data/lib/konpeito/cli/init_command.rb +312 -0
  23. data/lib/konpeito/cli/lsp_command.rb +40 -0
  24. data/lib/konpeito/cli/run_command.rb +150 -0
  25. data/lib/konpeito/cli/test_command.rb +248 -0
  26. data/lib/konpeito/cli/watch_command.rb +212 -0
  27. data/lib/konpeito/cli.rb +301 -0
  28. data/lib/konpeito/codegen/builtin_methods.rb +229 -0
  29. data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
  30. data/lib/konpeito/codegen/debug_info.rb +352 -0
  31. data/lib/konpeito/codegen/inliner.rb +486 -0
  32. data/lib/konpeito/codegen/jvm_backend.rb +197 -0
  33. data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
  34. data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
  35. data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
  36. data/lib/konpeito/codegen/monomorphizer.rb +359 -0
  37. data/lib/konpeito/codegen/profile_runtime.c +341 -0
  38. data/lib/konpeito/codegen/profiler.rb +99 -0
  39. data/lib/konpeito/compiler.rb +592 -0
  40. data/lib/konpeito/dependency_resolver.rb +296 -0
  41. data/lib/konpeito/diagnostics/collector.rb +127 -0
  42. data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
  43. data/lib/konpeito/diagnostics/renderer.rb +144 -0
  44. data/lib/konpeito/formatter/formatter.rb +1214 -0
  45. data/lib/konpeito/hir/builder.rb +7167 -0
  46. data/lib/konpeito/hir/nodes.rb +2465 -0
  47. data/lib/konpeito/lsp/document_manager.rb +820 -0
  48. data/lib/konpeito/lsp/server.rb +183 -0
  49. data/lib/konpeito/lsp/transport.rb +38 -0
  50. data/lib/konpeito/parser/prism_adapter.rb +65 -0
  51. data/lib/konpeito/platform.rb +103 -0
  52. data/lib/konpeito/profile/report.rb +136 -0
  53. data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
  54. data/lib/konpeito/stdlib/compression/compression.rb +72 -0
  55. data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
  56. data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
  57. data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
  58. data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
  59. data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
  60. data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
  61. data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
  62. data/lib/konpeito/stdlib/http/extconf.rb +19 -0
  63. data/lib/konpeito/stdlib/http/http.rb +125 -0
  64. data/lib/konpeito/stdlib/http/http.rbs +57 -0
  65. data/lib/konpeito/stdlib/http/http_native.c +440 -0
  66. data/lib/konpeito/stdlib/json/extconf.rb +17 -0
  67. data/lib/konpeito/stdlib/json/json.rb +44 -0
  68. data/lib/konpeito/stdlib/json/json.rbs +33 -0
  69. data/lib/konpeito/stdlib/json/json_native.c +286 -0
  70. data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
  71. data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
  72. data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
  73. data/lib/konpeito/stdlib/ui/ui.rb +318 -0
  74. data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
  75. data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
  76. data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
  77. data/lib/konpeito/type_checker/inferrer.rb +565 -0
  78. data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
  79. data/lib/konpeito/type_checker/type_resolver.rb +276 -0
  80. data/lib/konpeito/type_checker/types.rb +1434 -0
  81. data/lib/konpeito/type_checker/unification.rb +323 -0
  82. data/lib/konpeito/ui/animation/animated_state.rb +80 -0
  83. data/lib/konpeito/ui/animation/easing.rb +59 -0
  84. data/lib/konpeito/ui/animation/value_tween.rb +66 -0
  85. data/lib/konpeito/ui/app.rb +379 -0
  86. data/lib/konpeito/ui/box.rb +38 -0
  87. data/lib/konpeito/ui/castella.rb +70 -0
  88. data/lib/konpeito/ui/castella_native.rb +76 -0
  89. data/lib/konpeito/ui/chart/area_chart.rb +305 -0
  90. data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
  91. data/lib/konpeito/ui/chart/base_chart.rb +210 -0
  92. data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
  93. data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
  94. data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
  95. data/lib/konpeito/ui/chart/line_chart.rb +289 -0
  96. data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
  97. data/lib/konpeito/ui/chart/scales.rb +77 -0
  98. data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
  99. data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
  100. data/lib/konpeito/ui/column.rb +271 -0
  101. data/lib/konpeito/ui/core.rb +2199 -0
  102. data/lib/konpeito/ui/dsl.rb +443 -0
  103. data/lib/konpeito/ui/frame.rb +171 -0
  104. data/lib/konpeito/ui/frame_native.rb +494 -0
  105. data/lib/konpeito/ui/markdown/ast.rb +124 -0
  106. data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
  107. data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
  108. data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
  109. data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
  110. data/lib/konpeito/ui/markdown/parser.rb +805 -0
  111. data/lib/konpeito/ui/markdown/renderer.rb +639 -0
  112. data/lib/konpeito/ui/markdown/theme.rb +165 -0
  113. data/lib/konpeito/ui/render_node.rb +260 -0
  114. data/lib/konpeito/ui/row.rb +207 -0
  115. data/lib/konpeito/ui/spacer.rb +18 -0
  116. data/lib/konpeito/ui/style.rb +799 -0
  117. data/lib/konpeito/ui/theme.rb +563 -0
  118. data/lib/konpeito/ui/themes/material.rb +35 -0
  119. data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
  120. data/lib/konpeito/ui/widgets/button.rb +103 -0
  121. data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
  122. data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
  123. data/lib/konpeito/ui/widgets/container.rb +91 -0
  124. data/lib/konpeito/ui/widgets/data_table.rb +667 -0
  125. data/lib/konpeito/ui/widgets/divider.rb +29 -0
  126. data/lib/konpeito/ui/widgets/image.rb +105 -0
  127. data/lib/konpeito/ui/widgets/input.rb +485 -0
  128. data/lib/konpeito/ui/widgets/markdown.rb +57 -0
  129. data/lib/konpeito/ui/widgets/modal.rb +163 -0
  130. data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
  131. data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
  132. data/lib/konpeito/ui/widgets/net_image.rb +100 -0
  133. data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
  134. data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
  135. data/lib/konpeito/ui/widgets/slider.rb +133 -0
  136. data/lib/konpeito/ui/widgets/switch.rb +84 -0
  137. data/lib/konpeito/ui/widgets/tabs.rb +157 -0
  138. data/lib/konpeito/ui/widgets/text.rb +110 -0
  139. data/lib/konpeito/ui/widgets/tree.rb +426 -0
  140. data/lib/konpeito/version.rb +5 -0
  141. data/lib/konpeito.rb +109 -0
  142. data/test_native_array.rb +172 -0
  143. data/test_native_array_class.rb +197 -0
  144. data/test_native_class.rb +151 -0
  145. data/tools/konpeito-asm/build.sh +65 -0
  146. data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
  147. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
  148. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
  149. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
  150. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
  151. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
  152. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
  153. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
  154. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
  155. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
  156. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
  157. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
  158. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
  159. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
  160. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
  161. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
  162. data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
  163. data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
  164. data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
  165. data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
  166. data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
  167. data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
  168. data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
  169. data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
  170. data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
  171. data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
  172. data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
  173. data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
  174. data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
  175. data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
  176. data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
  177. data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
  178. data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
  179. data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
  180. metadata +267 -0
@@ -0,0 +1,820 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require "uri"
5
+
6
+ module Konpeito
7
+ module LSP
8
+ # Manages open documents, their ASTs, and type information
9
+ class DocumentManager
10
+ def initialize(transport)
11
+ @transport = transport
12
+ @documents = {} # uri => DocumentState
13
+ end
14
+
15
+ # Open a document and analyze it
16
+ # @param uri [String] The document URI
17
+ # @param content [String] The document content
18
+ def open(uri, content)
19
+ analyze(uri, content)
20
+ publish_diagnostics(uri)
21
+ end
22
+
23
+ # Update a document and re-analyze
24
+ # @param uri [String] The document URI
25
+ # @param content [String] The new content
26
+ def change(uri, content)
27
+ analyze(uri, content)
28
+ publish_diagnostics(uri)
29
+ end
30
+
31
+ # Close a document
32
+ # @param uri [String] The document URI
33
+ def close(uri)
34
+ @documents.delete(uri)
35
+ # Clear diagnostics
36
+ @transport.notify("textDocument/publishDiagnostics", {
37
+ uri: uri,
38
+ diagnostics: []
39
+ })
40
+ end
41
+
42
+ # Get document state
43
+ # @param uri [String] The document URI
44
+ # @return [DocumentState, nil]
45
+ def get(uri)
46
+ @documents[uri]
47
+ end
48
+
49
+ # Handle hover request
50
+ # @param uri [String] The document URI
51
+ # @param position [Hash] The position { line:, character: }
52
+ # @return [Hash, nil] Hover response or nil
53
+ def hover(uri, position)
54
+ doc = @documents[uri]
55
+ return nil unless doc&.hm_inferrer
56
+
57
+ # Convert LSP position (0-indexed) to Prism position (1-indexed line)
58
+ line = position[:line] + 1
59
+ column = position[:character]
60
+
61
+ # Find the node at position
62
+ node = find_node_at_position(doc.ast, line, column)
63
+ return nil unless node
64
+
65
+ # Get type information based on node type
66
+ type_string = get_type_info(doc, node)
67
+ return nil unless type_string
68
+
69
+ {
70
+ contents: {
71
+ kind: "markdown",
72
+ value: "```ruby\n#{type_string}\n```"
73
+ },
74
+ range: node_to_lsp_range(node)
75
+ }
76
+ end
77
+
78
+ # Get type information for a node
79
+ def get_type_info(doc, node)
80
+ hm = doc.hm_inferrer
81
+
82
+ case node
83
+ when Prism::DefNode
84
+ # For method definitions, look up the function type
85
+ func_types = hm.instance_variable_get(:@function_types)
86
+ func_type = func_types[node.name]
87
+ if func_type
88
+ final_type = hm.finalize(func_type)
89
+ "def #{node.name}: #{format_type(final_type)}"
90
+ else
91
+ "def #{node.name}"
92
+ end
93
+ when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
94
+ # For local variables, look up from environment
95
+ var_name = node.name
96
+ env = hm.instance_variable_get(:@env).first
97
+ scheme = env[var_name]
98
+ if scheme
99
+ final_type = hm.finalize(scheme.type)
100
+ "#{var_name}: #{format_type(final_type)}"
101
+ else
102
+ type = hm.type_for(node)
103
+ type ? format_type(hm.finalize(type)) : nil
104
+ end
105
+ when Prism::CallNode
106
+ # For method calls, show the return type
107
+ type = hm.type_for(node)
108
+ if type
109
+ final_type = hm.finalize(type)
110
+ format_type(final_type)
111
+ end
112
+ else
113
+ # Default: try to get type from node
114
+ type = hm.type_for(node)
115
+ if type
116
+ final_type = hm.finalize(type)
117
+ format_type(final_type)
118
+ end
119
+ end
120
+ end
121
+
122
+ # Handle completion request
123
+ # @param uri [String] The document URI
124
+ # @param position [Hash] The position { line:, character: }
125
+ # @return [Hash] Completion response
126
+ def completion(uri, position)
127
+ doc = @documents[uri]
128
+ return { isIncomplete: false, items: [] } unless doc
129
+
130
+ line = position[:line] + 1
131
+ column = position[:character]
132
+
133
+ # Get the text before cursor to determine completion context
134
+ lines = doc.content.split("\n")
135
+ current_line = lines[position[:line]] || ""
136
+ text_before_cursor = current_line[0...column]
137
+
138
+ items = []
139
+
140
+ # Check if we're completing after a "."
141
+ if text_before_cursor =~ /(\w+)\.\s*$/
142
+ receiver_name = $1
143
+ items = complete_methods_for_receiver(doc, receiver_name, line, column)
144
+ else
145
+ # Complete variables and methods from scope
146
+ items = complete_from_scope(doc)
147
+ end
148
+
149
+ { isIncomplete: false, items: items }
150
+ end
151
+
152
+ # Handle definition request (go to definition)
153
+ # @param uri [String] The document URI
154
+ # @param position [Hash] The position { line:, character: }
155
+ # @return [Hash, Array, nil] Location or array of locations
156
+ def definition(uri, position)
157
+ doc = @documents[uri]
158
+ return nil unless doc&.ast
159
+
160
+ line = position[:line] + 1
161
+ column = position[:character]
162
+
163
+ # Find the node at position
164
+ node = find_node_at_position(doc.ast, line, column)
165
+ return nil unless node
166
+
167
+ # Find the definition based on node type
168
+ case node
169
+ when Prism::CallNode
170
+ # Find method definition
171
+ method_name = node.name
172
+ def_node = find_method_definition(doc.ast, method_name)
173
+ if def_node
174
+ {
175
+ uri: uri,
176
+ range: node_to_lsp_range(def_node)
177
+ }
178
+ end
179
+ when Prism::LocalVariableReadNode
180
+ # Find variable assignment
181
+ var_name = node.name
182
+ write_node = find_variable_definition(doc.ast, var_name, line)
183
+ if write_node
184
+ {
185
+ uri: uri,
186
+ range: node_to_lsp_range(write_node)
187
+ }
188
+ end
189
+ else
190
+ nil
191
+ end
192
+ end
193
+
194
+ # Handle references request (find all references)
195
+ # @param uri [String] The document URI
196
+ # @param position [Hash] The position { line:, character: }
197
+ # @param include_declaration [Boolean] Whether to include the declaration
198
+ # @return [Array<Hash>] Array of Location objects
199
+ def references(uri, position, include_declaration: true)
200
+ doc = @documents[uri]
201
+ return [] unless doc&.ast
202
+
203
+ line = position[:line] + 1
204
+ column = position[:character]
205
+
206
+ # Find the node at position
207
+ node = find_node_at_position(doc.ast, line, column)
208
+ return [] unless node
209
+
210
+ # Find all references based on node type
211
+ case node
212
+ when Prism::CallNode
213
+ # Find all calls to this method
214
+ method_name = node.name
215
+ finder = ReferenceFinder.new(method_name, :method)
216
+ finder.visit(doc.ast)
217
+ locations = finder.found_nodes.map do |ref_node|
218
+ { uri: uri, range: node_to_lsp_range(ref_node) }
219
+ end
220
+
221
+ # Include definition if requested
222
+ if include_declaration
223
+ def_node = find_method_definition(doc.ast, method_name)
224
+ if def_node
225
+ locations.unshift({ uri: uri, range: node_to_lsp_range(def_node) })
226
+ end
227
+ end
228
+
229
+ locations
230
+ when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
231
+ # Find all usages of this variable
232
+ var_name = node.name
233
+ finder = ReferenceFinder.new(var_name, :variable)
234
+ finder.visit(doc.ast)
235
+ finder.found_nodes.map do |ref_node|
236
+ { uri: uri, range: node_to_lsp_range(ref_node) }
237
+ end
238
+ when Prism::DefNode
239
+ # Find all calls to this method
240
+ method_name = node.name
241
+ finder = ReferenceFinder.new(method_name, :method)
242
+ finder.visit(doc.ast)
243
+ locations = finder.found_nodes.map do |ref_node|
244
+ { uri: uri, range: node_to_lsp_range(ref_node) }
245
+ end
246
+
247
+ # Include the definition itself if requested
248
+ if include_declaration
249
+ locations.unshift({ uri: uri, range: node_to_lsp_range(node) })
250
+ end
251
+
252
+ locations
253
+ else
254
+ []
255
+ end
256
+ end
257
+
258
+ # Handle rename request
259
+ # @param uri [String] The document URI
260
+ # @param position [Hash] The position { line:, character: }
261
+ # @param new_name [String] The new name for the symbol
262
+ # @return [Hash, nil] WorkspaceEdit or nil if rename not possible
263
+ def rename(uri, position, new_name)
264
+ doc = @documents[uri]
265
+ return nil unless doc&.ast
266
+
267
+ line = position[:line] + 1
268
+ column = position[:character]
269
+
270
+ # Find the node at position
271
+ node = find_node_at_position(doc.ast, line, column)
272
+ return nil unless node
273
+
274
+ # Get the current name and find all references
275
+ current_name, refs = case node
276
+ when Prism::CallNode
277
+ method_name = node.name
278
+ finder = ReferenceFinder.new(method_name, :method)
279
+ finder.visit(doc.ast)
280
+
281
+ # Also include the definition
282
+ def_node = find_method_definition(doc.ast, method_name)
283
+ refs = finder.found_nodes.dup
284
+ refs.unshift(def_node) if def_node
285
+
286
+ [method_name.to_s, refs]
287
+ when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
288
+ var_name = node.name
289
+ finder = ReferenceFinder.new(var_name, :variable)
290
+ finder.visit(doc.ast)
291
+ [var_name.to_s, finder.found_nodes]
292
+ when Prism::DefNode
293
+ method_name = node.name
294
+ finder = ReferenceFinder.new(method_name, :method)
295
+ finder.visit(doc.ast)
296
+
297
+ # Include the definition itself
298
+ refs = [node] + finder.found_nodes
299
+
300
+ [method_name.to_s, refs]
301
+ else
302
+ return nil
303
+ end
304
+
305
+ return nil if refs.empty?
306
+
307
+ # Build text edits - calculate the range for just the name part
308
+ text_edits = refs.map do |ref_node|
309
+ range = name_range_for_node(ref_node, current_name)
310
+ { range: range, newText: new_name }
311
+ end
312
+
313
+ # Return WorkspaceEdit
314
+ {
315
+ changes: {
316
+ uri => text_edits
317
+ }
318
+ }
319
+ end
320
+
321
+ # Prepare rename - check if rename is valid and return the current name range
322
+ # @param uri [String] The document URI
323
+ # @param position [Hash] The position { line:, character: }
324
+ # @return [Hash, nil] Range and placeholder text
325
+ def prepare_rename(uri, position)
326
+ doc = @documents[uri]
327
+ return nil unless doc&.ast
328
+
329
+ line = position[:line] + 1
330
+ column = position[:character]
331
+
332
+ node = find_node_at_position(doc.ast, line, column)
333
+ return nil unless node
334
+
335
+ case node
336
+ when Prism::CallNode
337
+ name = node.name.to_s
338
+ { range: name_range_for_node(node, name), placeholder: name }
339
+ when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
340
+ name = node.name.to_s
341
+ { range: name_range_for_node(node, name), placeholder: name }
342
+ when Prism::DefNode
343
+ name = node.name.to_s
344
+ { range: name_range_for_node(node, name), placeholder: name }
345
+ else
346
+ nil
347
+ end
348
+ end
349
+
350
+ private
351
+
352
+ # Get the LSP range for just the name part of a node
353
+ def name_range_for_node(node, name)
354
+ case node
355
+ when Prism::DefNode
356
+ # For def nodes, the name starts after "def "
357
+ loc = node.name_loc
358
+ {
359
+ start: { line: loc.start_line - 1, character: loc.start_column },
360
+ end: { line: loc.end_line - 1, character: loc.end_column }
361
+ }
362
+ when Prism::CallNode
363
+ # For call nodes, use the message_loc
364
+ loc = node.message_loc
365
+ if loc
366
+ {
367
+ start: { line: loc.start_line - 1, character: loc.start_column },
368
+ end: { line: loc.end_line - 1, character: loc.end_column }
369
+ }
370
+ else
371
+ node_to_lsp_range(node)
372
+ end
373
+ when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
374
+ # For variables, the whole node is the name
375
+ loc = node.location
376
+ {
377
+ start: { line: loc.start_line - 1, character: loc.start_column },
378
+ end: { line: loc.start_line - 1, character: loc.start_column + name.length }
379
+ }
380
+ else
381
+ node_to_lsp_range(node)
382
+ end
383
+ end
384
+
385
+ # Complete methods based on receiver type
386
+ def complete_methods_for_receiver(doc, receiver_name, line, column)
387
+ return [] unless doc.hm_inferrer
388
+
389
+ hm = doc.hm_inferrer
390
+ env = hm.instance_variable_get(:@env).first
391
+ scheme = env[receiver_name.to_sym]
392
+
393
+ return [] unless scheme
394
+
395
+ type = hm.finalize(scheme.type)
396
+ types_mod = TypeChecker::Types
397
+
398
+ items = []
399
+
400
+ case type
401
+ when types_mod::ClassInstance
402
+ # Get methods for this class from RBS or built-in
403
+ methods = get_methods_for_type(type.name)
404
+ items = methods.map do |method_name, signature|
405
+ {
406
+ label: method_name.to_s,
407
+ kind: 2, # Method
408
+ detail: signature
409
+ }
410
+ end
411
+ end
412
+
413
+ items
414
+ end
415
+
416
+ # Complete variables and methods from current scope
417
+ def complete_from_scope(doc)
418
+ items = []
419
+
420
+ if doc.hm_inferrer
421
+ hm = doc.hm_inferrer
422
+ env = hm.instance_variable_get(:@env).first
423
+ func_types = hm.instance_variable_get(:@function_types)
424
+
425
+ # Add variables
426
+ env.each do |name, scheme|
427
+ type = hm.finalize(scheme.type)
428
+ kind = type.is_a?(TypeChecker::FunctionType) ? 3 : 6 # Function or Variable
429
+ items << {
430
+ label: name.to_s,
431
+ kind: kind,
432
+ detail: format_type(type)
433
+ }
434
+ end
435
+
436
+ # Add function definitions
437
+ func_types.each do |name, func_type|
438
+ next if env.key?(name) # Already added
439
+ items << {
440
+ label: name.to_s,
441
+ kind: 3, # Function
442
+ detail: format_type(hm.finalize(func_type))
443
+ }
444
+ end
445
+ end
446
+
447
+ items
448
+ end
449
+
450
+ # Get methods for a type (from built-in knowledge)
451
+ def get_methods_for_type(type_name)
452
+ # Basic built-in methods for common types
453
+ case type_name.to_sym
454
+ when :Integer
455
+ {
456
+ to_s: "() -> String",
457
+ to_f: "() -> Float",
458
+ abs: "() -> Integer",
459
+ times: "{ (Integer) -> void } -> Integer"
460
+ }
461
+ when :Float
462
+ {
463
+ to_s: "() -> String",
464
+ to_i: "() -> Integer",
465
+ abs: "() -> Float",
466
+ round: "() -> Integer",
467
+ floor: "() -> Integer",
468
+ ceil: "() -> Integer"
469
+ }
470
+ when :String
471
+ {
472
+ to_s: "() -> String",
473
+ to_i: "() -> Integer",
474
+ length: "() -> Integer",
475
+ size: "() -> Integer",
476
+ upcase: "() -> String",
477
+ downcase: "() -> String",
478
+ strip: "() -> String"
479
+ }
480
+ when :Array
481
+ {
482
+ length: "() -> Integer",
483
+ size: "() -> Integer",
484
+ first: "() -> T",
485
+ last: "() -> T",
486
+ push: "(T) -> Array",
487
+ pop: "() -> T",
488
+ map: "{ (T) -> U } -> Array[U]",
489
+ each: "{ (T) -> void } -> Array"
490
+ }
491
+ when :Hash
492
+ {
493
+ keys: "() -> Array",
494
+ values: "() -> Array",
495
+ size: "() -> Integer",
496
+ empty?: "() -> bool"
497
+ }
498
+ else
499
+ {}
500
+ end
501
+ end
502
+
503
+ # Find method definition in AST
504
+ def find_method_definition(ast, method_name)
505
+ finder = DefinitionFinder.new(method_name, :method)
506
+ finder.visit(ast)
507
+ finder.found_node
508
+ end
509
+
510
+ # Find variable definition (first assignment)
511
+ def find_variable_definition(ast, var_name, before_line)
512
+ finder = DefinitionFinder.new(var_name, :variable, before_line)
513
+ finder.visit(ast)
514
+ finder.found_node
515
+ end
516
+
517
+ # Analyze a document and store results
518
+ def analyze(uri, content)
519
+ file_path = uri_to_path(uri)
520
+
521
+ # Parse with Prism
522
+ parse_result = Prism.parse(content)
523
+ ast = parse_result.value
524
+ parse_errors = parse_result.errors
525
+
526
+ # Initialize RBS loader
527
+ rbs_loader = TypeChecker::RBSLoader.new
528
+
529
+ # Load RBS files if they exist
530
+ rbs_path = file_path.sub(/\.rb$/, ".rbs")
531
+ if File.exist?(rbs_path)
532
+ rbs_loader.load(rbs_paths: [rbs_path])
533
+ else
534
+ rbs_loader.load(rbs_paths: [])
535
+ end
536
+
537
+ # Build typed AST with HM inference
538
+ builder = AST::TypedASTBuilder.new(
539
+ rbs_loader,
540
+ use_hm: true,
541
+ file_path: file_path,
542
+ source: content
543
+ )
544
+
545
+ typed_ast = nil
546
+ hm_inferrer = nil
547
+ diagnostics = []
548
+
549
+ begin
550
+ typed_ast = builder.build(ast)
551
+ hm_inferrer = builder.instance_variable_get(:@hm_inferrer)
552
+ diagnostics = builder.diagnostics
553
+ rescue StandardError => e
554
+ # If type checking fails, still store the AST
555
+ diagnostics << Diagnostics::Diagnostic.new(
556
+ severity: :error,
557
+ code: "E100",
558
+ message: "Type checking error: #{e.message}"
559
+ )
560
+ end
561
+
562
+ # Add parse errors to diagnostics
563
+ parse_errors.each do |error|
564
+ span = if error.location
565
+ Diagnostics::SourceSpan.new(
566
+ file_path: file_path,
567
+ start_line: error.location.start_line,
568
+ start_column: error.location.start_column,
569
+ end_line: error.location.end_line,
570
+ end_column: error.location.end_column,
571
+ source: content
572
+ )
573
+ end
574
+
575
+ diagnostics << Diagnostics::Diagnostic.new(
576
+ severity: :error,
577
+ code: "E010",
578
+ message: error.message,
579
+ span: span
580
+ )
581
+ end
582
+
583
+ @documents[uri] = DocumentState.new(
584
+ content: content,
585
+ ast: ast,
586
+ typed_ast: typed_ast,
587
+ hm_inferrer: hm_inferrer,
588
+ diagnostics: diagnostics
589
+ )
590
+ end
591
+
592
+ # Publish diagnostics to the client
593
+ def publish_diagnostics(uri)
594
+ doc = @documents[uri]
595
+ return unless doc
596
+
597
+ lsp_diagnostics = doc.diagnostics.map do |diag|
598
+ {
599
+ range: span_to_lsp_range(diag.span),
600
+ severity: severity_to_lsp(diag.severity),
601
+ code: diag.code,
602
+ source: "konpeito",
603
+ message: diag.message
604
+ }
605
+ end
606
+
607
+ @transport.notify("textDocument/publishDiagnostics", {
608
+ uri: uri,
609
+ diagnostics: lsp_diagnostics
610
+ })
611
+ end
612
+
613
+ # Convert file URI to path
614
+ def uri_to_path(uri)
615
+ if uri.start_with?("file://")
616
+ URI.decode_www_form_component(uri.sub("file://", ""))
617
+ else
618
+ uri
619
+ end
620
+ end
621
+
622
+ # Convert diagnostic severity to LSP severity
623
+ def severity_to_lsp(severity)
624
+ case severity
625
+ when :error then 1
626
+ when :warning then 2
627
+ when :note, :info then 3
628
+ when :help, :hint then 4
629
+ else 1
630
+ end
631
+ end
632
+
633
+ # Convert SourceSpan to LSP range
634
+ def span_to_lsp_range(span)
635
+ return { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } unless span
636
+
637
+ {
638
+ start: { line: span.start_line - 1, character: span.start_column },
639
+ end: { line: (span.end_line || span.start_line) - 1, character: span.end_column || span.start_column }
640
+ }
641
+ end
642
+
643
+ # Convert Prism node to LSP range
644
+ def node_to_lsp_range(node)
645
+ return nil unless node.respond_to?(:location) && node.location
646
+
647
+ loc = node.location
648
+ {
649
+ start: { line: loc.start_line - 1, character: loc.start_column },
650
+ end: { line: loc.end_line - 1, character: loc.end_column }
651
+ }
652
+ end
653
+
654
+ # Find the most specific node at a given position
655
+ def find_node_at_position(ast, line, column)
656
+ return nil unless ast
657
+
658
+ visitor = NodeFinder.new(line, column)
659
+ visitor.visit(ast)
660
+ visitor.found_node
661
+ end
662
+
663
+ # Format type for display
664
+ def format_type(type)
665
+ types_mod = TypeChecker::Types
666
+
667
+ case type
668
+ when TypeChecker::TypeVar
669
+ type.instance ? format_type(type.instance) : type.to_s
670
+ when TypeChecker::FunctionType
671
+ params = type.param_types.map { |t| format_type(t) }.join(", ")
672
+ "(#{params}) -> #{format_type(type.return_type)}"
673
+ when types_mod::ClassInstance
674
+ if type.type_args.empty?
675
+ type.name.to_s
676
+ else
677
+ args = type.type_args.map { |a| format_type(a) }.join(", ")
678
+ "#{type.name}[#{args}]"
679
+ end
680
+ when types_mod::Union
681
+ type.types.map { |t| format_type(t) }.join(" | ")
682
+ when types_mod::NativeClassType
683
+ "#{type.class_name} (native)"
684
+ else
685
+ type.to_s
686
+ end
687
+ end
688
+
689
+ # Document state container
690
+ DocumentState = Struct.new(
691
+ :content,
692
+ :ast,
693
+ :typed_ast,
694
+ :hm_inferrer,
695
+ :diagnostics,
696
+ keyword_init: true
697
+ )
698
+
699
+ # Visitor to find node at position
700
+ class NodeFinder
701
+ attr_reader :found_node
702
+
703
+ def initialize(line, column)
704
+ @target_line = line
705
+ @target_column = column
706
+ @found_node = nil
707
+ end
708
+
709
+ def visit(node)
710
+ return unless node
711
+
712
+ # Check if this node contains the target position
713
+ if node.respond_to?(:location) && node.location && position_in_range?(node.location)
714
+ @found_node = node
715
+ end
716
+
717
+ # Visit all child nodes
718
+ visit_children(node)
719
+ end
720
+
721
+ private
722
+
723
+ def visit_children(node)
724
+ node.child_nodes.each do |child|
725
+ visit(child) if child
726
+ end
727
+ end
728
+
729
+ def position_in_range?(loc)
730
+ start_ok = loc.start_line < @target_line ||
731
+ (loc.start_line == @target_line && loc.start_column <= @target_column)
732
+ end_ok = loc.end_line > @target_line ||
733
+ (loc.end_line == @target_line && loc.end_column >= @target_column)
734
+ start_ok && end_ok
735
+ end
736
+ end
737
+
738
+ # Visitor to find definition of a method or variable
739
+ class DefinitionFinder
740
+ attr_reader :found_node
741
+
742
+ def initialize(name, kind, before_line = nil)
743
+ @name = name.to_sym
744
+ @kind = kind
745
+ @before_line = before_line
746
+ @found_node = nil
747
+ end
748
+
749
+ def visit(node)
750
+ return unless node
751
+
752
+ case @kind
753
+ when :method
754
+ if node.is_a?(Prism::DefNode) && node.name == @name
755
+ @found_node = node
756
+ end
757
+ when :variable
758
+ if node.is_a?(Prism::LocalVariableWriteNode) && node.name == @name
759
+ # Only find definitions before the reference line
760
+ if @before_line.nil? || node.location.start_line < @before_line
761
+ @found_node ||= node # Keep first occurrence
762
+ end
763
+ end
764
+ end
765
+
766
+ # Visit children
767
+ visit_children(node)
768
+ end
769
+
770
+ private
771
+
772
+ def visit_children(node)
773
+ node.child_nodes.each do |child|
774
+ visit(child) if child
775
+ end
776
+ end
777
+ end
778
+
779
+ # Finds all references to a symbol (variable or method)
780
+ class ReferenceFinder
781
+ attr_reader :found_nodes
782
+
783
+ def initialize(name, kind)
784
+ @name = name.to_sym
785
+ @kind = kind
786
+ @found_nodes = []
787
+ end
788
+
789
+ def visit(node)
790
+ return unless node
791
+
792
+ case @kind
793
+ when :method
794
+ # Find method calls
795
+ if node.is_a?(Prism::CallNode) && node.name == @name
796
+ @found_nodes << node
797
+ end
798
+ when :variable
799
+ # Find variable reads and writes
800
+ if (node.is_a?(Prism::LocalVariableReadNode) || node.is_a?(Prism::LocalVariableWriteNode)) &&
801
+ node.name == @name
802
+ @found_nodes << node
803
+ end
804
+ end
805
+
806
+ # Visit children
807
+ visit_children(node)
808
+ end
809
+
810
+ private
811
+
812
+ def visit_children(node)
813
+ node.child_nodes.each do |child|
814
+ visit(child) if child
815
+ end
816
+ end
817
+ end
818
+ end
819
+ end
820
+ end