ruby-lsp 0.23.11 → 0.26.1

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +10 -4
  5. data/exe/ruby-lsp-check +0 -4
  6. data/exe/ruby-lsp-launcher +45 -22
  7. data/exe/ruby-lsp-test-exec +6 -0
  8. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
  9. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
  10. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +82 -116
  11. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +140 -183
  12. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +107 -236
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +166 -281
  15. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  16. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
  17. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +25 -57
  18. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
  19. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
  20. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
  21. data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
  22. data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
  23. data/lib/ruby_indexer/test/configuration_test.rb +49 -9
  24. data/lib/ruby_indexer/test/constant_test.rb +34 -34
  25. data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
  26. data/lib/ruby_indexer/test/index_test.rb +185 -135
  27. data/lib/ruby_indexer/test/instance_variables_test.rb +61 -37
  28. data/lib/ruby_indexer/test/method_test.rb +166 -123
  29. data/lib/ruby_indexer/test/prefix_tree_test.rb +21 -21
  30. data/lib/ruby_indexer/test/rbs_indexer_test.rb +70 -75
  31. data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
  32. data/lib/ruby_indexer/test/test_case.rb +9 -3
  33. data/lib/ruby_indexer/test/uri_test.rb +15 -2
  34. data/lib/ruby_lsp/addon.rb +88 -86
  35. data/lib/ruby_lsp/base_server.rb +59 -54
  36. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  37. data/lib/ruby_lsp/document.rb +205 -104
  38. data/lib/ruby_lsp/erb_document.rb +45 -47
  39. data/lib/ruby_lsp/global_state.rb +73 -57
  40. data/lib/ruby_lsp/internal.rb +8 -3
  41. data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
  42. data/lib/ruby_lsp/listeners/completion.rb +81 -76
  43. data/lib/ruby_lsp/listeners/definition.rb +44 -58
  44. data/lib/ruby_lsp/listeners/document_highlight.rb +123 -150
  45. data/lib/ruby_lsp/listeners/document_link.rb +50 -70
  46. data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
  47. data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
  48. data/lib/ruby_lsp/listeners/hover.rb +107 -115
  49. data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
  50. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
  51. data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
  52. data/lib/ruby_lsp/listeners/spec_style.rb +214 -0
  53. data/lib/ruby_lsp/listeners/test_discovery.rb +92 -0
  54. data/lib/ruby_lsp/listeners/test_style.rb +205 -95
  55. data/lib/ruby_lsp/node_context.rb +12 -39
  56. data/lib/ruby_lsp/rbs_document.rb +10 -11
  57. data/lib/ruby_lsp/requests/code_action_resolve.rb +65 -61
  58. data/lib/ruby_lsp/requests/code_actions.rb +14 -26
  59. data/lib/ruby_lsp/requests/code_lens.rb +31 -21
  60. data/lib/ruby_lsp/requests/completion.rb +8 -21
  61. data/lib/ruby_lsp/requests/completion_resolve.rb +6 -6
  62. data/lib/ruby_lsp/requests/definition.rb +8 -20
  63. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  64. data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
  65. data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
  66. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  67. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  68. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  69. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  70. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +85 -0
  71. data/lib/ruby_lsp/requests/hover.rb +12 -25
  72. data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
  73. data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
  74. data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
  75. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
  76. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  77. data/lib/ruby_lsp/requests/references.rb +17 -57
  78. data/lib/ruby_lsp/requests/rename.rb +27 -51
  79. data/lib/ruby_lsp/requests/request.rb +13 -25
  80. data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
  81. data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
  82. data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
  83. data/lib/ruby_lsp/requests/signature_help.rb +9 -27
  84. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  85. data/lib/ruby_lsp/requests/support/common.rb +16 -58
  86. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  87. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  88. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
  89. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
  90. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  91. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  92. data/lib/ruby_lsp/requests/support/source_uri.rb +20 -32
  93. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  94. data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
  95. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  96. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  97. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
  98. data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
  99. data/lib/ruby_lsp/response_builders/hover.rb +12 -18
  100. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  101. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
  102. data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
  103. data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
  104. data/lib/ruby_lsp/ruby_document.rb +32 -98
  105. data/lib/ruby_lsp/scope.rb +7 -11
  106. data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
  107. data/lib/ruby_lsp/server.rb +303 -196
  108. data/lib/ruby_lsp/setup_bundler.rb +121 -82
  109. data/lib/ruby_lsp/static_docs.rb +12 -7
  110. data/lib/ruby_lsp/store.rb +21 -49
  111. data/lib/ruby_lsp/test_helper.rb +3 -16
  112. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +233 -0
  113. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
  114. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
  115. data/lib/ruby_lsp/type_inferrer.rb +13 -14
  116. data/lib/ruby_lsp/utils.rb +138 -93
  117. data/static_docs/break.md +103 -0
  118. metadata +14 -20
  119. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -12,12 +12,12 @@ module RubyIndexer
12
12
 
13
13
  def test_from_path_on_windows
14
14
  uri = URI::Generic.from_path(path: "C:/some/windows/path/to/file.rb")
15
- assert_equal("/C:/some/windows/path/to/file.rb", uri.path)
15
+ assert_equal("/C%3A/some/windows/path/to/file.rb", uri.path)
16
16
  end
17
17
 
18
18
  def test_from_path_on_windows_with_lowercase_drive
19
19
  uri = URI::Generic.from_path(path: "c:/some/windows/path/to/file.rb")
20
- assert_equal("/c:/some/windows/path/to/file.rb", uri.path)
20
+ assert_equal("/c%3A/some/windows/path/to/file.rb", uri.path)
21
21
  end
22
22
 
23
23
  def test_to_standardized_path_on_unix
@@ -68,5 +68,18 @@ module RubyIndexer
68
68
  uri.add_require_path_from_load_entry("/some/unix/path")
69
69
  assert_equal("to/file", uri.require_path)
70
70
  end
71
+
72
+ def test_from_path_escapes_colon_characters
73
+ uri = URI::Generic.from_path(path: "c:/some/windows/path with/spaces/file.rb")
74
+ assert_equal("c:/some/windows/path with/spaces/file.rb", uri.to_standardized_path)
75
+ assert_equal("file:///c%3A/some/windows/path%20with/spaces/file.rb", uri.to_s)
76
+ end
77
+
78
+ def test_from_path_with_unicode_characters
79
+ path = "/path/with/unicode/文件.rb"
80
+ uri = URI::Generic.from_path(path: path)
81
+ assert_equal(path, uri.to_standardized_path)
82
+ assert_equal("file:///path/with/unicode/%E6%96%87%E4%BB%B6.rb", uri.to_s)
83
+ end
71
84
  end
72
85
  end
@@ -19,48 +19,36 @@ module RubyLsp
19
19
  # end
20
20
  # end
21
21
  # ```
22
+ # @abstract
22
23
  class Addon
23
- extend T::Sig
24
- extend T::Helpers
25
-
26
- abstract!
27
-
28
- @addons = T.let([], T::Array[Addon])
29
- @addon_classes = T.let([], T::Array[T.class_of(Addon)])
24
+ @addons = [] #: Array[Addon]
25
+ @addon_classes = [] #: Array[singleton(Addon)]
30
26
  # Add-on instances that have declared a handler to accept file watcher events
31
- @file_watcher_addons = T.let([], T::Array[Addon])
27
+ @file_watcher_addons = [] #: Array[Addon]
32
28
 
33
29
  AddonNotFoundError = Class.new(StandardError)
34
30
 
35
31
  class IncompatibleApiError < StandardError; end
36
32
 
37
33
  class << self
38
- extend T::Sig
39
-
40
- sig { returns(T::Array[Addon]) }
34
+ #: Array[Addon]
41
35
  attr_accessor :addons
42
36
 
43
- sig { returns(T::Array[Addon]) }
37
+ #: Array[Addon]
44
38
  attr_accessor :file_watcher_addons
45
39
 
46
- sig { returns(T::Array[T.class_of(Addon)]) }
40
+ #: Array[singleton(Addon)]
47
41
  attr_reader :addon_classes
48
42
 
49
43
  # Automatically track and instantiate add-on classes
50
- sig { params(child_class: T.class_of(Addon)).void }
44
+ #: (singleton(Addon) child_class) -> void
51
45
  def inherited(child_class)
52
46
  addon_classes << child_class
53
47
  super
54
48
  end
55
49
 
56
50
  # Discovers and loads all add-ons. Returns a list of errors when trying to require add-ons
57
- sig do
58
- params(
59
- global_state: GlobalState,
60
- outgoing_queue: Thread::Queue,
61
- include_project_addons: T::Boolean,
62
- ).returns(T::Array[StandardError])
63
- end
51
+ #: (GlobalState global_state, Thread::Queue outgoing_queue, ?include_project_addons: bool) -> Array[StandardError]
64
52
  def load_addons(global_state, outgoing_queue, include_project_addons: true)
65
53
  # Require all add-ons entry points, which should be placed under
66
54
  # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb` or in the workspace under
@@ -68,7 +56,28 @@ module RubyLsp
68
56
  addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
69
57
 
70
58
  if include_project_addons
71
- addon_files.concat(Dir.glob(File.join(global_state.workspace_path, "**", "ruby_lsp/**/addon.rb")))
59
+ project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
60
+ bundle_path = Bundler.bundle_path.to_s
61
+ gems_dir = Bundler.bundle_path.join("gems")
62
+
63
+ # Create an array of rejection glob patterns to ignore add-ons already discovered through Gem.find_files if
64
+ # they are also copied inside the workspace for whatever reason. We received reports of projects having gems
65
+ # installed in vendor/bundle despite BUNDLE_PATH pointing elsewhere. Without this mechanism, we will
66
+ # double-require the same add-on, potentially for different versions of the same gem, which leads to incorrect
67
+ # behavior
68
+ reject_glob_patterns = addon_files.map do |path|
69
+ relative_gem_path = Pathname.new(path).relative_path_from(gems_dir)
70
+ first_part, *parts = relative_gem_path.to_s.split(File::SEPARATOR)
71
+ first_part&.gsub!(/-([0-9.]+)$/, "*")
72
+ "**/#{first_part}/#{parts.join("/")}"
73
+ end
74
+
75
+ project_addons.reject! do |path|
76
+ path.start_with?(bundle_path) ||
77
+ reject_glob_patterns.any? { |pattern| File.fnmatch?(pattern, path, File::Constants::FNM_PATHNAME) }
78
+ end
79
+
80
+ addon_files.concat(project_addons)
72
81
  end
73
82
 
74
83
  errors = addon_files.filter_map do |addon_path|
@@ -98,7 +107,7 @@ module RubyLsp
98
107
  end
99
108
 
100
109
  # Unloads all add-ons. Only intended to be invoked once when shutting down the Ruby LSP server
101
- sig { void }
110
+ #: -> void
102
111
  def unload_addons
103
112
  @addons.each(&:deactivate)
104
113
  @addons.clear
@@ -112,7 +121,7 @@ module RubyLsp
112
121
  # Important: if the add-on is not found, AddonNotFoundError will be raised. If the add-on is found, but its
113
122
  # current version does not satisfy the given version constraint, then IncompatibleApiError will be raised. It is
114
123
  # the responsibility of the add-ons using this API to handle these errors appropriately.
115
- sig { params(addon_name: String, version_constraints: String).returns(Addon) }
124
+ #: (String addon_name, *String version_constraints) -> Addon
116
125
  def get(addon_name, *version_constraints)
117
126
  if version_constraints.empty?
118
127
  raise IncompatibleApiError, "Must specify version constraints when accessing other add-ons"
@@ -144,7 +153,7 @@ module RubyLsp
144
153
  # end
145
154
  # end
146
155
  # ```
147
- sig { params(version_constraints: String).void }
156
+ #: (*String version_constraints) -> void
148
157
  def depend_on_ruby_lsp!(*version_constraints)
149
158
  version_object = Gem::Version.new(RubyLsp::VERSION)
150
159
 
@@ -155,23 +164,23 @@ module RubyLsp
155
164
  end
156
165
  end
157
166
 
158
- sig { void }
167
+ #: -> void
159
168
  def initialize
160
- @errors = T.let([], T::Array[StandardError])
169
+ @errors = [] #: Array[StandardError]
161
170
  end
162
171
 
163
- sig { params(error: StandardError).returns(T.self_type) }
172
+ #: (StandardError error) -> self
164
173
  def add_error(error)
165
174
  @errors << error
166
175
  self
167
176
  end
168
177
 
169
- sig { returns(T::Boolean) }
178
+ #: -> bool
170
179
  def error?
171
180
  @errors.any?
172
181
  end
173
182
 
174
- sig { returns(String) }
183
+ #: -> String
175
184
  def formatted_errors
176
185
  <<~ERRORS
177
186
  #{name}:
@@ -179,97 +188,90 @@ module RubyLsp
179
188
  ERRORS
180
189
  end
181
190
 
182
- sig { returns(String) }
191
+ #: -> String
183
192
  def errors_details
184
193
  @errors.map(&:full_message).join("\n\n")
185
194
  end
186
195
 
187
196
  # Each add-on should implement `MyAddon#activate` and use to perform any sort of initialization, such as
188
197
  # reading information into memory or even spawning a separate process
189
- sig { abstract.params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
190
- def activate(global_state, outgoing_queue); end
198
+ # @abstract
199
+ #: (GlobalState, Thread::Queue) -> void
200
+ def activate(global_state, outgoing_queue)
201
+ raise AbstractMethodInvokedError
202
+ end
191
203
 
192
- # Each add-on should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
204
+ # Each add-on must implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
193
205
  # child process
194
- sig { abstract.void }
195
- def deactivate; end
206
+ # @abstract
207
+ #: -> void
208
+ def deactivate
209
+ raise AbstractMethodInvokedError
210
+ end
196
211
 
197
212
  # Add-ons should override the `name` method to return the add-on name
198
- sig { abstract.returns(String) }
199
- def name; end
213
+ # @abstract
214
+ #: -> String
215
+ def name
216
+ raise AbstractMethodInvokedError
217
+ end
200
218
 
201
219
  # Add-ons should override the `version` method to return a semantic version string representing the add-on's
202
220
  # version. This is used for compatibility checks
203
- sig { abstract.returns(String) }
204
- def version; end
221
+ # @abstract
222
+ #: -> String
223
+ def version
224
+ raise AbstractMethodInvokedError
225
+ end
205
226
 
206
227
  # Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
207
228
  # original request so that the response is delegated to the correct add-on and must override this method to handle
208
229
  # the response
209
230
  # https://microsoft.github.io/language-server-protocol/specification#window_showMessageRequest
210
- sig { overridable.params(title: String).void }
231
+ # @overridable
232
+ #: (String title) -> void
211
233
  def handle_window_show_message_response(title); end
212
234
 
213
235
  # Creates a new CodeLens listener. This method is invoked on every CodeLens request
214
- sig do
215
- overridable.params(
216
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
217
- uri: URI::Generic,
218
- dispatcher: Prism::Dispatcher,
219
- ).void
220
- end
236
+ # @overridable
237
+ #: (ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens] response_builder, URI::Generic uri, Prism::Dispatcher dispatcher) -> void
221
238
  def create_code_lens_listener(response_builder, uri, dispatcher); end
222
239
 
223
240
  # Creates a new Hover listener. This method is invoked on every Hover request
224
- sig do
225
- overridable.params(
226
- response_builder: ResponseBuilders::Hover,
227
- node_context: NodeContext,
228
- dispatcher: Prism::Dispatcher,
229
- ).void
230
- end
241
+ # @overridable
242
+ #: (ResponseBuilders::Hover response_builder, NodeContext node_context, Prism::Dispatcher dispatcher) -> void
231
243
  def create_hover_listener(response_builder, node_context, dispatcher); end
232
244
 
233
245
  # Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
234
- sig do
235
- overridable.params(
236
- response_builder: ResponseBuilders::DocumentSymbol,
237
- dispatcher: Prism::Dispatcher,
238
- ).void
239
- end
246
+ # @overridable
247
+ #: (ResponseBuilders::DocumentSymbol response_builder, Prism::Dispatcher dispatcher) -> void
240
248
  def create_document_symbol_listener(response_builder, dispatcher); end
241
249
 
242
- sig do
243
- overridable.params(
244
- response_builder: ResponseBuilders::SemanticHighlighting,
245
- dispatcher: Prism::Dispatcher,
246
- ).void
247
- end
250
+ # @overridable
251
+ #: (ResponseBuilders::SemanticHighlighting response_builder, Prism::Dispatcher dispatcher) -> void
248
252
  def create_semantic_highlighting_listener(response_builder, dispatcher); end
249
253
 
250
254
  # Creates a new Definition listener. This method is invoked on every Definition request
251
- sig do
252
- overridable.params(
253
- response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
254
- Interface::Location,
255
- Interface::LocationLink,
256
- )],
257
- uri: URI::Generic,
258
- node_context: NodeContext,
259
- dispatcher: Prism::Dispatcher,
260
- ).void
261
- end
255
+ # @overridable
256
+ #: (ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)] response_builder, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher) -> void
262
257
  def create_definition_listener(response_builder, uri, node_context, dispatcher); end
263
258
 
264
259
  # Creates a new Completion listener. This method is invoked on every Completion request
265
- sig do
266
- overridable.params(
267
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
268
- node_context: NodeContext,
269
- dispatcher: Prism::Dispatcher,
270
- uri: URI::Generic,
271
- ).void
272
- end
260
+ # @overridable
261
+ #: (ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem] response_builder, NodeContext node_context, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
273
262
  def create_completion_listener(response_builder, node_context, dispatcher, uri); end
263
+
264
+ # Creates a new Discover Tests listener. This method is invoked on every DiscoverTests request
265
+ # @overridable
266
+ #: (ResponseBuilders::TestCollection response_builder, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
267
+ def create_discover_tests_listener(response_builder, dispatcher, uri); end
268
+
269
+ # Resolves the minimal set of commands required to execute the requested tests. Add-ons are responsible for only
270
+ # handling items related to the framework they add support for and have discovered themselves
271
+ # @overridable
272
+ #: (Array[Hash[Symbol, untyped]]) -> Array[String]
273
+ def resolve_test_commands(items)
274
+ []
275
+ end
274
276
  end
275
277
  end
@@ -2,36 +2,29 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ # @abstract
5
6
  class BaseServer
6
- extend T::Sig
7
- extend T::Helpers
8
-
9
- abstract!
10
-
11
- sig { params(options: T.untyped).void }
7
+ #: (**untyped options) -> void
12
8
  def initialize(**options)
13
- @test_mode = T.let(options[:test_mode], T.nilable(T::Boolean))
14
- @setup_error = T.let(options[:setup_error], T.nilable(StandardError))
15
- @install_error = T.let(options[:install_error], T.nilable(StandardError))
16
- @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
17
- @reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
18
- @incoming_queue = T.let(Thread::Queue.new, Thread::Queue)
19
- @outgoing_queue = T.let(Thread::Queue.new, Thread::Queue)
20
- @cancelled_requests = T.let([], T::Array[Integer])
21
- @worker = T.let(new_worker, Thread)
22
- @current_request_id = T.let(1, Integer)
23
- @global_state = T.let(GlobalState.new, GlobalState)
24
- @store = T.let(Store.new(@global_state), Store)
25
- @outgoing_dispatcher = T.let(
26
- Thread.new do
27
- unless @test_mode
28
- while (message = @outgoing_queue.pop)
29
- @global_state.synchronize { @writer.write(message.to_hash) }
30
- end
9
+ @reader = MessageReader.new(options[:reader] || $stdin) #: MessageReader
10
+ @writer = MessageWriter.new(options[:writer] || $stdout) #: MessageWriter
11
+ @test_mode = options[:test_mode] #: bool?
12
+ @setup_error = options[:setup_error] #: StandardError?
13
+ @install_error = options[:install_error] #: StandardError?
14
+ @incoming_queue = Thread::Queue.new #: Thread::Queue
15
+ @outgoing_queue = Thread::Queue.new #: Thread::Queue
16
+ @cancelled_requests = [] #: Array[Integer]
17
+ @worker = new_worker #: Thread
18
+ @current_request_id = 1 #: Integer
19
+ @global_state = GlobalState.new #: GlobalState
20
+ @store = Store.new(@global_state) #: Store
21
+ @outgoing_dispatcher = Thread.new do
22
+ unless @test_mode
23
+ while (message = @outgoing_queue.pop)
24
+ @global_state.synchronize { @writer.write(message.to_hash) }
31
25
  end
32
- end,
33
- Thread,
34
- )
26
+ end
27
+ end #: Thread
35
28
 
36
29
  Thread.main.priority = 1
37
30
 
@@ -41,9 +34,9 @@ module RubyLsp
41
34
  process_message(initialize_request) if initialize_request
42
35
  end
43
36
 
44
- sig { void }
37
+ #: -> void
45
38
  def start
46
- @reader.read do |message|
39
+ @reader.each_message do |message|
47
40
  method = message[:method]
48
41
 
49
42
  # We must parse the document under a mutex lock or else we might switch threads and accept text edits in the
@@ -90,8 +83,7 @@ module RubyLsp
90
83
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
91
84
  # else is pushed into the incoming queue
92
85
  case method
93
- when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange",
94
- "rubyLsp/diagnoseState"
86
+ when "initialize", "initialized", "rubyLsp/diagnoseState"
95
87
  process_message(message)
96
88
  when "shutdown"
97
89
  @global_state.synchronize do
@@ -101,14 +93,37 @@ module RubyLsp
101
93
  @writer.write(Result.new(id: message[:id], response: nil).to_hash)
102
94
  end
103
95
  when "exit"
104
- @global_state.synchronize { exit(@incoming_queue.closed? ? 0 : 1) }
96
+ exit(@incoming_queue.closed? ? 0 : 1)
105
97
  else
106
98
  @incoming_queue << message
107
99
  end
108
100
  end
109
101
  end
110
102
 
111
- sig { void }
103
+ # This method is only intended to be used in tests! Pops the latest response that would be sent to the client
104
+ #: -> untyped
105
+ def pop_response
106
+ @outgoing_queue.pop
107
+ end
108
+
109
+ # This method is only intended to be used in tests! Pushes a message to the incoming queue directly
110
+ #: (Hash[Symbol, untyped] message) -> void
111
+ def push_message(message)
112
+ @incoming_queue << message
113
+ end
114
+
115
+ # @abstract
116
+ #: (Hash[Symbol, untyped] message) -> void
117
+ def process_message(message)
118
+ raise AbstractMethodInvokedError
119
+ end
120
+
121
+ #: -> bool?
122
+ def test_mode?
123
+ @test_mode
124
+ end
125
+
126
+ #: -> void
112
127
  def run_shutdown
113
128
  @incoming_queue.clear
114
129
  @outgoing_queue.clear
@@ -121,34 +136,24 @@ module RubyLsp
121
136
  @store.clear
122
137
  end
123
138
 
124
- # This method is only intended to be used in tests! Pops the latest response that would be sent to the client
125
- sig { returns(T.untyped) }
126
- def pop_response
127
- @outgoing_queue.pop
128
- end
139
+ private
129
140
 
130
- # This method is only intended to be used in tests! Pushes a message to the incoming queue directly
131
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
132
- def push_message(message)
133
- @incoming_queue << message
141
+ # @abstract
142
+ #: -> void
143
+ def shutdown
144
+ raise AbstractMethodInvokedError
134
145
  end
135
146
 
136
- sig { abstract.params(message: T::Hash[Symbol, T.untyped]).void }
137
- def process_message(message); end
138
-
139
- sig { abstract.void }
140
- def shutdown; end
141
-
142
- sig { params(id: Integer, message: String, type: Integer).void }
147
+ #: (Integer id, String message, ?type: Integer) -> void
143
148
  def fail_request_and_notify(id, message, type: Constant::MessageType::INFO)
144
149
  send_message(Error.new(id: id, code: Constant::ErrorCodes::REQUEST_FAILED, message: message))
145
150
  send_message(Notification.window_show_message(message, type: type))
146
151
  end
147
152
 
148
- sig { returns(Thread) }
153
+ #: -> Thread
149
154
  def new_worker
150
155
  Thread.new do
151
- while (message = T.let(@incoming_queue.pop, T.nilable(T::Hash[Symbol, T.untyped])))
156
+ while (message = @incoming_queue.pop)
152
157
  id = message[:id]
153
158
 
154
159
  # Check if the request was cancelled before trying to process it
@@ -165,7 +170,7 @@ module RubyLsp
165
170
  end
166
171
  end
167
172
 
168
- sig { params(message: T.any(Result, Error, Notification, Request)).void }
173
+ #: ((Result | Error | Notification | Request) message) -> void
169
174
  def send_message(message)
170
175
  # When we're shutting down the server, there's a small race condition between closing the thread queues and
171
176
  # finishing remaining requests. We may close the queue in the middle of processing a request, which will then fail
@@ -176,12 +181,12 @@ module RubyLsp
176
181
  @current_request_id += 1 if message.is_a?(Request)
177
182
  end
178
183
 
179
- sig { params(id: Integer).void }
184
+ #: (Integer id) -> void
180
185
  def send_empty_response(id)
181
186
  send_message(Result.new(id: id, response: nil))
182
187
  end
183
188
 
184
- sig { params(message: String, type: Integer).void }
189
+ #: (String message, ?type: Integer) -> void
185
190
  def send_log_message(message, type: Constant::MessageType::LOG)
186
191
  send_message(Notification.window_log_message(message, type: Constant::MessageType::LOG))
187
192
  end
@@ -5,40 +5,42 @@ module RubyLsp
5
5
  # This class stores all client capabilities that the Ruby LSP and its add-ons depend on to ensure that we're
6
6
  # not enabling functionality unsupported by the editor connecting to the server
7
7
  class ClientCapabilities
8
- extend T::Sig
9
-
10
- sig { returns(T::Boolean) }
8
+ #: bool
11
9
  attr_reader :supports_watching_files,
12
10
  :supports_request_delegation,
13
11
  :window_show_message_supports_extra_properties,
14
12
  :supports_progress,
15
- :supports_diagnostic_refresh
13
+ :supports_diagnostic_refresh,
14
+ :supports_code_lens_refresh
16
15
 
17
- sig { void }
16
+ #: -> void
18
17
  def initialize
19
18
  # The editor supports watching files. This requires two capabilities: dynamic registration and relative pattern
20
19
  # support
21
- @supports_watching_files = T.let(false, T::Boolean)
20
+ @supports_watching_files = false #: bool
22
21
 
23
22
  # The editor supports request delegation. This is an experimental capability since request delegation has not been
24
23
  # standardized into the LSP spec yet
25
- @supports_request_delegation = T.let(false, T::Boolean)
24
+ @supports_request_delegation = false #: bool
26
25
 
27
26
  # The editor supports extra arbitrary properties for `window/showMessageRequest`. Necessary for add-ons to show
28
27
  # dialogs with user interactions
29
- @window_show_message_supports_extra_properties = T.let(false, T::Boolean)
28
+ @window_show_message_supports_extra_properties = false #: bool
30
29
 
31
30
  # Which resource operations the editor supports, like renaming files
32
- @supported_resource_operations = T.let([], T::Array[String])
31
+ @supported_resource_operations = [] #: Array[String]
33
32
 
34
33
  # The editor supports displaying progress requests
35
- @supports_progress = T.let(false, T::Boolean)
34
+ @supports_progress = false #: bool
36
35
 
37
36
  # The editor supports server initiated refresh for diagnostics
38
- @supports_diagnostic_refresh = T.let(false, T::Boolean)
37
+ @supports_diagnostic_refresh = false #: bool
38
+
39
+ # The editor supports server initiated refresh for code lenses
40
+ @supports_code_lens_refresh = false #: bool
39
41
  end
40
42
 
41
- sig { params(capabilities: T::Hash[Symbol, T.untyped]).void }
43
+ #: (Hash[Symbol, untyped] capabilities) -> void
42
44
  def apply_client_capabilities(capabilities)
43
45
  workspace_capabilities = capabilities[:workspace] || {}
44
46
 
@@ -63,9 +65,10 @@ module RubyLsp
63
65
  @supports_progress = progress if progress
64
66
 
65
67
  @supports_diagnostic_refresh = workspace_capabilities.dig(:diagnostics, :refreshSupport) || false
68
+ @supports_code_lens_refresh = workspace_capabilities.dig(:codeLens, :refreshSupport) || false
66
69
  end
67
70
 
68
- sig { returns(T::Boolean) }
71
+ #: -> bool
69
72
  def supports_rename?
70
73
  @supported_resource_operations.include?("rename")
71
74
  end