ruby-lsp 0.17.7 → 0.17.9

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.
@@ -20,10 +20,10 @@ module RubyLsp
20
20
  uri: URI::Generic,
21
21
  node_context: NodeContext,
22
22
  dispatcher: Prism::Dispatcher,
23
- typechecker_enabled: T::Boolean,
23
+ sorbet_level: Document::SorbetLevel,
24
24
  ).void
25
25
  end
26
- def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
26
+ def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
27
27
  @response_builder = response_builder
28
28
  @global_state = global_state
29
29
  @index = T.let(global_state.index, RubyIndexer::Index)
@@ -31,7 +31,7 @@ module RubyLsp
31
31
  @language_id = language_id
32
32
  @uri = uri
33
33
  @node_context = node_context
34
- @typechecker_enabled = typechecker_enabled
34
+ @sorbet_level = sorbet_level
35
35
 
36
36
  dispatcher.register(
37
37
  self,
@@ -53,6 +53,11 @@ module RubyLsp
53
53
 
54
54
  sig { params(node: Prism::CallNode).void }
55
55
  def on_call_node_enter(node)
56
+ # Sorbet can handle go to definition for methods invoked on self on typed true or higher
57
+ if (@sorbet_level == Document::SorbetLevel::True || @sorbet_level == Document::SorbetLevel::Strict) &&
58
+ self_receiver?(node)
59
+ end
60
+
56
61
  message = node.message
57
62
  return unless message
58
63
 
@@ -60,7 +65,7 @@ module RubyLsp
60
65
 
61
66
  # Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
62
67
  # treating method calls' type as `nil` will allow users to get some completion support first
63
- if @language_id == Document::LanguageId::ERB && inferrer_receiver_type == "Object"
68
+ if @language_id == Document::LanguageId::ERB && inferrer_receiver_type&.name == "Object"
64
69
  inferrer_receiver_type = nil
65
70
  end
66
71
 
@@ -149,6 +154,9 @@ module RubyLsp
149
154
 
150
155
  sig { void }
151
156
  def handle_super_node_definition
157
+ # Sorbet can handle super hover on typed true or higher
158
+ return if sorbet_level_true_or_higher?(@sorbet_level)
159
+
152
160
  surrounding_method = @node_context.surrounding_method
153
161
  return unless surrounding_method
154
162
 
@@ -161,10 +169,14 @@ module RubyLsp
161
169
 
162
170
  sig { params(name: String).void }
163
171
  def handle_instance_variable_definition(name)
172
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
173
+ # to provide all features for them
174
+ return if @sorbet_level == Document::SorbetLevel::Strict
175
+
164
176
  type = @type_inferrer.infer_receiver_type(@node_context)
165
177
  return unless type
166
178
 
167
- entries = @index.resolve_instance_variable(name, type)
179
+ entries = @index.resolve_instance_variable(name, type.name)
168
180
  return unless entries
169
181
 
170
182
  entries.each do |entry|
@@ -182,10 +194,10 @@ module RubyLsp
182
194
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
183
195
  end
184
196
 
185
- sig { params(message: String, receiver_type: T.nilable(String), inherited_only: T::Boolean).void }
197
+ sig { params(message: String, receiver_type: T.nilable(TypeInferrer::Type), inherited_only: T::Boolean).void }
186
198
  def handle_method_definition(message, receiver_type, inherited_only: false)
187
199
  methods = if receiver_type
188
- @index.resolve_method(message, receiver_type, inherited_only: inherited_only)
200
+ @index.resolve_method(message, receiver_type.name, inherited_only: inherited_only)
189
201
  else
190
202
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
191
203
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -196,7 +208,7 @@ module RubyLsp
196
208
 
197
209
  methods.each do |target_method|
198
210
  file_path = target_method.file_path
199
- next if @typechecker_enabled && not_in_dependencies?(file_path)
211
+ next if sorbet_level_true_or_higher?(@sorbet_level) && not_in_dependencies?(file_path)
200
212
 
201
213
  @response_builder << Interface::LocationLink.new(
202
214
  target_uri: URI::Generic.from_path(path: file_path).to_s,
@@ -253,10 +265,10 @@ module RubyLsp
253
265
 
254
266
  entries.each do |entry|
255
267
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
256
- # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
257
- # in the project, even if the files are typed false
268
+ # additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
269
+ # ignore
258
270
  file_path = entry.file_path
259
- next if @typechecker_enabled && not_in_dependencies?(file_path)
271
+ next if @sorbet_level != Document::SorbetLevel::Ignore && not_in_dependencies?(file_path)
260
272
 
261
273
  @response_builder << Interface::LocationLink.new(
262
274
  target_uri: URI::Generic.from_path(path: file_path).to_s,
@@ -42,17 +42,17 @@ module RubyLsp
42
42
  uri: URI::Generic,
43
43
  node_context: NodeContext,
44
44
  dispatcher: Prism::Dispatcher,
45
- typechecker_enabled: T::Boolean,
45
+ sorbet_level: Document::SorbetLevel,
46
46
  ).void
47
47
  end
48
- def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
48
+ def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
49
49
  @response_builder = response_builder
50
50
  @global_state = global_state
51
51
  @index = T.let(global_state.index, RubyIndexer::Index)
52
52
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
53
53
  @path = T.let(uri.to_standardized_path, T.nilable(String))
54
54
  @node_context = node_context
55
- @typechecker_enabled = typechecker_enabled
55
+ @sorbet_level = sorbet_level
56
56
 
57
57
  dispatcher.register(
58
58
  self,
@@ -73,7 +73,7 @@ module RubyLsp
73
73
 
74
74
  sig { params(node: Prism::ConstantReadNode).void }
75
75
  def on_constant_read_node_enter(node)
76
- return if @typechecker_enabled
76
+ return if @sorbet_level != Document::SorbetLevel::Ignore
77
77
 
78
78
  name = constant_name(node)
79
79
  return if name.nil?
@@ -83,14 +83,14 @@ module RubyLsp
83
83
 
84
84
  sig { params(node: Prism::ConstantWriteNode).void }
85
85
  def on_constant_write_node_enter(node)
86
- return if @global_state.has_type_checker
86
+ return if @sorbet_level != Document::SorbetLevel::Ignore
87
87
 
88
88
  generate_hover(node.name.to_s, node.name_loc)
89
89
  end
90
90
 
91
91
  sig { params(node: Prism::ConstantPathNode).void }
92
92
  def on_constant_path_node_enter(node)
93
- return if @global_state.has_type_checker
93
+ return if @sorbet_level != Document::SorbetLevel::Ignore
94
94
 
95
95
  name = constant_name(node)
96
96
  return if name.nil?
@@ -105,7 +105,7 @@ module RubyLsp
105
105
  return
106
106
  end
107
107
 
108
- return if @typechecker_enabled
108
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
109
109
 
110
110
  message = node.message
111
111
  return unless message
@@ -157,6 +157,9 @@ module RubyLsp
157
157
 
158
158
  sig { void }
159
159
  def handle_super_node_hover
160
+ # Sorbet can handle super hover on typed true or higher
161
+ return if sorbet_level_true_or_higher?(@sorbet_level)
162
+
160
163
  surrounding_method = @node_context.surrounding_method
161
164
  return unless surrounding_method
162
165
 
@@ -168,11 +171,16 @@ module RubyLsp
168
171
  type = @type_inferrer.infer_receiver_type(@node_context)
169
172
  return unless type
170
173
 
171
- methods = @index.resolve_method(message, type, inherited_only: inherited_only)
174
+ methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
172
175
  return unless methods
173
176
 
174
177
  title = "#{message}#{T.must(methods.first).decorated_parameters}"
175
178
 
179
+ if type.is_a?(TypeInferrer::GuessedType)
180
+ title << "\n\nGuessed receiver: #{type.name}"
181
+ @response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
182
+ end
183
+
176
184
  categorized_markdown_from_index_entries(title, methods).each do |category, content|
177
185
  @response_builder.push(content, category: category)
178
186
  end
@@ -180,10 +188,14 @@ module RubyLsp
180
188
 
181
189
  sig { params(name: String).void }
182
190
  def handle_instance_variable_hover(name)
191
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
192
+ # to provide all features for them
193
+ return if @sorbet_level == Document::SorbetLevel::Strict
194
+
183
195
  type = @type_inferrer.infer_receiver_type(@node_context)
184
196
  return unless type
185
197
 
186
- entries = @index.resolve_instance_variable(name, type)
198
+ entries = @index.resolve_instance_variable(name, type.name)
187
199
  return unless entries
188
200
 
189
201
  categorized_markdown_from_index_entries(name, entries).each do |category, content|
@@ -13,11 +13,11 @@ module RubyLsp
13
13
  global_state: GlobalState,
14
14
  node_context: NodeContext,
15
15
  dispatcher: Prism::Dispatcher,
16
- typechecker_enabled: T::Boolean,
16
+ sorbet_level: Document::SorbetLevel,
17
17
  ).void
18
18
  end
19
- def initialize(response_builder, global_state, node_context, dispatcher, typechecker_enabled)
20
- @typechecker_enabled = typechecker_enabled
19
+ def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
20
+ @sorbet_level = sorbet_level
21
21
  @response_builder = response_builder
22
22
  @global_state = global_state
23
23
  @index = T.let(global_state.index, RubyIndexer::Index)
@@ -28,7 +28,7 @@ module RubyLsp
28
28
 
29
29
  sig { params(node: Prism::CallNode).void }
30
30
  def on_call_node_enter(node)
31
- return if @typechecker_enabled
31
+ return if sorbet_level_true_or_higher?(@sorbet_level)
32
32
 
33
33
  message = node.message
34
34
  return unless message
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  type = @type_inferrer.infer_receiver_type(@node_context)
37
37
  return unless type
38
38
 
39
- methods = @index.resolve_method(message, type)
39
+ methods = @index.resolve_method(message, type.name)
40
40
  return unless methods
41
41
 
42
42
  target_method = methods.first
@@ -61,6 +61,13 @@ module RubyLsp
61
61
  active_parameter += 1
62
62
  end
63
63
 
64
+ title = +""
65
+
66
+ extra_links = if type.is_a?(TypeInferrer::GuessedType)
67
+ title << "\n\nGuessed receiver: #{type.name}"
68
+ "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
69
+ end
70
+
64
71
  signature_help = Interface::SignatureHelp.new(
65
72
  signatures: [
66
73
  Interface::SignatureInformation.new(
@@ -68,7 +75,7 @@ module RubyLsp
68
75
  parameters: parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
69
76
  documentation: Interface::MarkupContent.new(
70
77
  kind: "markdown",
71
- value: markdown_from_index_entries("", methods),
78
+ value: markdown_from_index_entries(title, methods, extra_links: extra_links),
72
79
  ),
73
80
  ),
74
81
  ],
@@ -49,11 +49,11 @@ module RubyLsp
49
49
  document: Document,
50
50
  global_state: GlobalState,
51
51
  params: T::Hash[Symbol, T.untyped],
52
- typechecker_enabled: T::Boolean,
52
+ sorbet_level: Document::SorbetLevel,
53
53
  dispatcher: Prism::Dispatcher,
54
54
  ).void
55
55
  end
56
- def initialize(document, global_state, params, typechecker_enabled, dispatcher)
56
+ def initialize(document, global_state, params, sorbet_level, dispatcher)
57
57
  super()
58
58
  @target = T.let(nil, T.nilable(Prism::Node))
59
59
  @dispatcher = dispatcher
@@ -84,7 +84,7 @@ module RubyLsp
84
84
  @response_builder,
85
85
  global_state,
86
86
  node_context,
87
- typechecker_enabled,
87
+ sorbet_level,
88
88
  dispatcher,
89
89
  document.uri,
90
90
  params.dig(:context, :triggerCharacter),
@@ -47,7 +47,7 @@ module RubyLsp
47
47
  #
48
48
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
49
49
  # `label` for the text edit instead
50
- label = @item[:label]
50
+ label = @item[:label].dup
51
51
  entries = @index[label] || []
52
52
 
53
53
  owner_name = @item.dig(:data, :owner_name)
@@ -62,12 +62,19 @@ module RubyLsp
62
62
  first_entry = T.must(entries.first)
63
63
 
64
64
  if first_entry.is_a?(RubyIndexer::Entry::Member)
65
- label = "#{label}#{first_entry.decorated_parameters}"
65
+ label = +"#{label}#{first_entry.decorated_parameters}"
66
+ end
67
+
68
+ guessed_type = @item.dig(:data, :guessed_type)
69
+
70
+ extra_links = if guessed_type
71
+ label << "\n\nGuessed receiver: #{guessed_type}"
72
+ "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
66
73
  end
67
74
 
68
75
  @item[:documentation] = Interface::MarkupContent.new(
69
76
  kind: "markdown",
70
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
77
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
71
78
  )
72
79
 
73
80
  @item
@@ -36,10 +36,10 @@ module RubyLsp
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
39
- typechecker_enabled: T::Boolean,
39
+ sorbet_level: Document::SorbetLevel,
40
40
  ).void
41
41
  end
42
- def initialize(document, global_state, position, dispatcher, typechecker_enabled)
42
+ def initialize(document, global_state, position, dispatcher, sorbet_level)
43
43
  super()
44
44
  @response_builder = T.let(
45
45
  ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)].new,
@@ -96,7 +96,7 @@ module RubyLsp
96
96
  document.uri,
97
97
  node_context,
98
98
  dispatcher,
99
- typechecker_enabled,
99
+ sorbet_level,
100
100
  )
101
101
 
102
102
  Addon.addons.each do |addon|
@@ -36,10 +36,10 @@ module RubyLsp
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
39
- typechecker_enabled: T::Boolean,
39
+ sorbet_level: Document::SorbetLevel,
40
40
  ).void
41
41
  end
42
- def initialize(document, global_state, position, dispatcher, typechecker_enabled)
42
+ def initialize(document, global_state, position, dispatcher, sorbet_level)
43
43
  super()
44
44
  node_context = document.locate_node(position, node_types: Listeners::Hover::ALLOWED_TARGETS)
45
45
  target = node_context.node
@@ -65,7 +65,7 @@ module RubyLsp
65
65
  @target = T.let(target, T.nilable(Prism::Node))
66
66
  uri = document.uri
67
67
  @response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
68
- Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled)
68
+ Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, sorbet_level)
69
69
  Addon.addons.each do |addon|
70
70
  addon.create_hover_listener(@response_builder, node_context, dispatcher)
71
71
  end
@@ -46,10 +46,10 @@ module RubyLsp
46
46
  position: T::Hash[Symbol, T.untyped],
47
47
  context: T.nilable(T::Hash[Symbol, T.untyped]),
48
48
  dispatcher: Prism::Dispatcher,
49
- typechecker_enabled: T::Boolean,
49
+ sorbet_level: Document::SorbetLevel,
50
50
  ).void
51
51
  end
52
- def initialize(document, global_state, position, context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
52
+ def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
53
53
  super()
54
54
  node_context = document.locate_node(
55
55
  { line: position[:line], character: position[:character] },
@@ -61,7 +61,7 @@ module RubyLsp
61
61
  @target = T.let(target, T.nilable(Prism::Node))
62
62
  @dispatcher = dispatcher
63
63
  @response_builder = T.let(ResponseBuilders::SignatureHelp.new, ResponseBuilders::SignatureHelp)
64
- Listeners::SignatureHelp.new(@response_builder, global_state, node_context, dispatcher, typechecker_enabled)
64
+ Listeners::SignatureHelp.new(@response_builder, global_state, node_context, dispatcher, sorbet_level)
65
65
  end
66
66
 
67
67
  sig { override.returns(T.nilable(Interface::SignatureHelp)) }
@@ -130,13 +130,17 @@ module RubyLsp
130
130
  title: String,
131
131
  entries: T.any(T::Array[RubyIndexer::Entry], RubyIndexer::Entry),
132
132
  max_entries: T.nilable(Integer),
133
+ extra_links: T.nilable(String),
133
134
  ).returns(String)
134
135
  end
135
- def markdown_from_index_entries(title, entries, max_entries = nil)
136
+ def markdown_from_index_entries(title, entries, max_entries = nil, extra_links: nil)
136
137
  categorized_markdown = categorized_markdown_from_index_entries(title, entries, max_entries)
137
138
 
139
+ markdown = +(categorized_markdown[:title] || "")
140
+ markdown << "\n\n#{extra_links}" if extra_links
141
+
138
142
  <<~MARKDOWN.chomp
139
- #{categorized_markdown[:title]}
143
+ #{markdown}
140
144
 
141
145
  #{categorized_markdown[:links]}
142
146
 
@@ -204,6 +208,11 @@ module RubyLsp
204
208
  Constant::SymbolKind::FIELD
205
209
  end
206
210
  end
211
+
212
+ sig { params(sorbet_level: Document::SorbetLevel).returns(T::Boolean) }
213
+ def sorbet_level_true_or_higher?(sorbet_level)
214
+ sorbet_level == Document::SorbetLevel::True || sorbet_level == Document::SorbetLevel::Strict
215
+ end
207
216
  end
208
217
  end
209
218
  end
@@ -98,16 +98,27 @@ module RubyLsp
98
98
  rescue StandardError, LoadError => e
99
99
  # If an error occurred in a request, we have to return an error response or else the editor will hang
100
100
  if message[:id]
101
- send_message(Error.new(
102
- id: message[:id],
103
- code: Constant::ErrorCodes::INTERNAL_ERROR,
104
- message: e.full_message,
105
- data: {
106
- errorClass: e.class.name,
107
- errorMessage: e.message,
108
- backtrace: e.backtrace&.join("\n"),
109
- },
110
- ))
101
+ # If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
102
+ # from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
103
+ # reporting these to our telemetry
104
+ if e.is_a?(Store::NonExistingDocumentError)
105
+ send_message(Error.new(
106
+ id: message[:id],
107
+ code: Constant::ErrorCodes::INVALID_PARAMS,
108
+ message: e.full_message,
109
+ ))
110
+ else
111
+ send_message(Error.new(
112
+ id: message[:id],
113
+ code: Constant::ErrorCodes::INTERNAL_ERROR,
114
+ message: e.full_message,
115
+ data: {
116
+ errorClass: e.class.name,
117
+ errorMessage: e.message,
118
+ backtrace: e.backtrace&.join("\n"),
119
+ },
120
+ ))
121
+ end
111
122
  end
112
123
 
113
124
  $stderr.puts("Error processing #{message[:method]}: #{e.full_message}")
@@ -243,6 +254,8 @@ module RubyLsp
243
254
  )
244
255
  end
245
256
 
257
+ process_indexing_configuration(options.dig(:initializationOptions, :indexing))
258
+
246
259
  begin_progress("indexing-progress", "Ruby LSP: indexing files")
247
260
  end
248
261
 
@@ -251,28 +264,6 @@ module RubyLsp
251
264
  load_addons
252
265
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
253
266
 
254
- indexing_config = {}
255
-
256
- # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
257
- index_path = File.join(@global_state.workspace_path, ".index.yml")
258
-
259
- if File.exist?(index_path)
260
- begin
261
- indexing_config = YAML.parse_file(index_path).to_ruby
262
- rescue Psych::SyntaxError => e
263
- message = "Syntax error while loading configuration: #{e.message}"
264
- send_message(
265
- Notification.new(
266
- method: "window/showMessage",
267
- params: Interface::ShowMessageParams.new(
268
- type: Constant::MessageType::WARNING,
269
- message: message,
270
- ),
271
- ),
272
- )
273
- end
274
- end
275
-
276
267
  if defined?(Requests::Support::RuboCopFormatter)
277
268
  @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
278
269
  end
@@ -280,7 +271,7 @@ module RubyLsp
280
271
  @global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
281
272
  end
282
273
 
283
- perform_initial_indexing(indexing_config)
274
+ perform_initial_indexing
284
275
  check_formatter_is_available
285
276
  end
286
277
 
@@ -486,15 +477,17 @@ module RubyLsp
486
477
  @global_state,
487
478
  params[:position],
488
479
  dispatcher,
489
- typechecker_enabled?(document),
480
+ sorbet_level(document),
490
481
  ).perform,
491
482
  ),
492
483
  )
493
484
  end
494
485
 
495
- sig { params(document: Document).returns(T::Boolean) }
496
- def typechecker_enabled?(document)
497
- @global_state.has_type_checker && document.sorbet_sigil_is_true_or_higher
486
+ sig { params(document: Document).returns(Document::SorbetLevel) }
487
+ def sorbet_level(document)
488
+ return Document::SorbetLevel::Ignore unless @global_state.has_type_checker
489
+
490
+ document.sorbet_level
498
491
  end
499
492
 
500
493
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
@@ -603,7 +596,7 @@ module RubyLsp
603
596
  document,
604
597
  @global_state,
605
598
  params,
606
- typechecker_enabled?(document),
599
+ sorbet_level(document),
607
600
  dispatcher,
608
601
  ).perform,
609
602
  ),
@@ -633,7 +626,7 @@ module RubyLsp
633
626
  params[:position],
634
627
  params[:context],
635
628
  dispatcher,
636
- typechecker_enabled?(document),
629
+ sorbet_level(document),
637
630
  ).perform,
638
631
  ),
639
632
  )
@@ -653,7 +646,7 @@ module RubyLsp
653
646
  @global_state,
654
647
  params[:position],
655
648
  dispatcher,
656
- typechecker_enabled?(document),
649
+ sorbet_level(document),
657
650
  ).perform,
658
651
  ),
659
652
  )
@@ -766,12 +759,10 @@ module RubyLsp
766
759
  Addon.addons.each(&:deactivate)
767
760
  end
768
761
 
769
- sig { params(config_hash: T::Hash[String, T.untyped]).void }
770
- def perform_initial_indexing(config_hash)
762
+ sig { void }
763
+ def perform_initial_indexing
771
764
  # The begin progress invocation happens during `initialize`, so that the notification is sent before we are
772
765
  # stuck indexing files
773
- RubyIndexer.configuration.apply_config(config_hash)
774
-
775
766
  Thread.new do
776
767
  begin
777
768
  RubyIndexer::RBSIndexer.new(@global_state.index).index_ruby_core
@@ -872,5 +863,46 @@ module RubyLsp
872
863
  )
873
864
  end
874
865
  end
866
+
867
+ sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
868
+ def process_indexing_configuration(indexing_options)
869
+ # Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
870
+ index_path = File.join(@global_state.workspace_path, ".index.yml")
871
+
872
+ if File.exist?(index_path)
873
+ begin
874
+ RubyIndexer.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
875
+ send_message(
876
+ Notification.new(
877
+ method: "window/showMessage",
878
+ params: Interface::ShowMessageParams.new(
879
+ type: Constant::MessageType::WARNING,
880
+ message: "The .index.yml configuration file is deprecated. " \
881
+ "Please use editor settings to configure the index",
882
+ ),
883
+ ),
884
+ )
885
+ rescue Psych::SyntaxError => e
886
+ message = "Syntax error while loading configuration: #{e.message}"
887
+ send_message(
888
+ Notification.new(
889
+ method: "window/showMessage",
890
+ params: Interface::ShowMessageParams.new(
891
+ type: Constant::MessageType::WARNING,
892
+ message: message,
893
+ ),
894
+ ),
895
+ )
896
+ end
897
+ return
898
+ end
899
+
900
+ return unless indexing_options
901
+
902
+ # The index expects snake case configurations, but VS Code standardizes on camel case settings
903
+ RubyIndexer.configuration.apply_config(
904
+ indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
905
+ )
906
+ end
875
907
  end
876
908
  end
@@ -5,6 +5,8 @@ module RubyLsp
5
5
  class Store
6
6
  extend T::Sig
7
7
 
8
+ class NonExistingDocumentError < StandardError; end
9
+
8
10
  sig { returns(T::Boolean) }
9
11
  attr_accessor :supports_progress
10
12
 
@@ -36,15 +38,22 @@ module RubyLsp
36
38
  document = @state[uri.to_s]
37
39
  return document unless document.nil?
38
40
 
39
- path = T.must(uri.to_standardized_path)
41
+ # For unsaved files (`untitled:Untitled-1` uris), there's no path to read from. If we don't have the untitled file
42
+ # already present in the store, then we have to raise non existing document error
43
+ path = uri.to_standardized_path
44
+ raise NonExistingDocumentError, uri.to_s unless path
45
+
40
46
  ext = File.extname(path)
41
47
  language_id = if ext == ".erb" || ext == ".rhtml"
42
48
  Document::LanguageId::ERB
43
49
  else
44
50
  Document::LanguageId::Ruby
45
51
  end
52
+
46
53
  set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
47
54
  T.must(@state[uri.to_s])
55
+ rescue Errno::ENOENT
56
+ raise NonExistingDocumentError, uri.to_s
48
57
  end
49
58
 
50
59
  sig do
@@ -21,7 +21,7 @@ module RubyLsp
21
21
  &block)
22
22
  server = RubyLsp::Server.new(test_mode: true)
23
23
  server.global_state.stubs(:has_type_checker).returns(false) if stub_no_typechecker
24
- server.global_state.apply_options({})
24
+ server.global_state.apply_options({ initializationOptions: { experimentalFeaturesEnabled: true } })
25
25
  language_id = uri.to_s.end_with?(".erb") ? "erb" : "ruby"
26
26
 
27
27
  if source