ruby-lsp 0.20.1 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +19 -4
  5. data/exe/ruby-lsp-launcher +124 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +233 -59
  8. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +34 -16
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +15 -15
  11. data/lib/ruby_indexer/test/classes_and_modules_test.rb +4 -4
  12. data/lib/ruby_indexer/test/configuration_test.rb +10 -0
  13. data/lib/ruby_indexer/test/constant_test.rb +8 -8
  14. data/lib/ruby_indexer/test/enhancements_test.rb +169 -41
  15. data/lib/ruby_indexer/test/index_test.rb +41 -2
  16. data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
  17. data/lib/ruby_indexer/test/method_test.rb +139 -0
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  19. data/lib/ruby_lsp/addon.rb +9 -2
  20. data/lib/ruby_lsp/base_server.rb +14 -5
  21. data/lib/ruby_lsp/client_capabilities.rb +67 -0
  22. data/lib/ruby_lsp/document.rb +1 -1
  23. data/lib/ruby_lsp/global_state.rb +33 -20
  24. data/lib/ruby_lsp/internal.rb +3 -0
  25. data/lib/ruby_lsp/listeners/completion.rb +62 -0
  26. data/lib/ruby_lsp/listeners/definition.rb +48 -13
  27. data/lib/ruby_lsp/listeners/document_highlight.rb +91 -4
  28. data/lib/ruby_lsp/listeners/document_symbol.rb +37 -4
  29. data/lib/ruby_lsp/listeners/hover.rb +52 -0
  30. data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
  31. data/lib/ruby_lsp/requests/completion.rb +7 -1
  32. data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
  33. data/lib/ruby_lsp/requests/definition.rb +28 -11
  34. data/lib/ruby_lsp/requests/document_highlight.rb +7 -1
  35. data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
  36. data/lib/ruby_lsp/requests/hover.rb +26 -6
  37. data/lib/ruby_lsp/requests/rename.rb +1 -1
  38. data/lib/ruby_lsp/requests/request.rb +1 -1
  39. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +12 -1
  40. data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
  41. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
  42. data/lib/ruby_lsp/server.rb +85 -55
  43. data/lib/ruby_lsp/setup_bundler.rb +154 -47
  44. data/lib/ruby_lsp/store.rb +0 -4
  45. data/lib/ruby_lsp/utils.rb +63 -0
  46. metadata +8 -3
@@ -41,6 +41,9 @@ module RubyLsp
41
41
  :on_module_node_enter,
42
42
  :on_module_node_leave,
43
43
  :on_instance_variable_write_node_enter,
44
+ :on_instance_variable_operator_write_node_enter,
45
+ :on_instance_variable_or_write_node_enter,
46
+ :on_instance_variable_and_write_node_enter,
44
47
  :on_class_variable_write_node_enter,
45
48
  :on_singleton_class_node_enter,
46
49
  :on_singleton_class_node_leave,
@@ -249,21 +252,51 @@ module RubyLsp
249
252
  @response_builder.pop
250
253
  end
251
254
 
255
+ sig { params(node: Prism::ClassVariableWriteNode).void }
256
+ def on_class_variable_write_node_enter(node)
257
+ create_document_symbol(
258
+ name: node.name.to_s,
259
+ kind: Constant::SymbolKind::VARIABLE,
260
+ range_location: node.name_loc,
261
+ selection_range_location: node.name_loc,
262
+ )
263
+ end
264
+
252
265
  sig { params(node: Prism::InstanceVariableWriteNode).void }
253
266
  def on_instance_variable_write_node_enter(node)
254
267
  create_document_symbol(
255
268
  name: node.name.to_s,
256
- kind: Constant::SymbolKind::VARIABLE,
269
+ kind: Constant::SymbolKind::FIELD,
257
270
  range_location: node.name_loc,
258
271
  selection_range_location: node.name_loc,
259
272
  )
260
273
  end
261
274
 
262
- sig { params(node: Prism::ClassVariableWriteNode).void }
263
- def on_class_variable_write_node_enter(node)
275
+ sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
276
+ def on_instance_variable_operator_write_node_enter(node)
264
277
  create_document_symbol(
265
278
  name: node.name.to_s,
266
- kind: Constant::SymbolKind::VARIABLE,
279
+ kind: Constant::SymbolKind::FIELD,
280
+ range_location: node.name_loc,
281
+ selection_range_location: node.name_loc,
282
+ )
283
+ end
284
+
285
+ sig { params(node: Prism::InstanceVariableOrWriteNode).void }
286
+ def on_instance_variable_or_write_node_enter(node)
287
+ create_document_symbol(
288
+ name: node.name.to_s,
289
+ kind: Constant::SymbolKind::FIELD,
290
+ range_location: node.name_loc,
291
+ selection_range_location: node.name_loc,
292
+ )
293
+ end
294
+
295
+ sig { params(node: Prism::InstanceVariableAndWriteNode).void }
296
+ def on_instance_variable_and_write_node_enter(node)
297
+ create_document_symbol(
298
+ name: node.name.to_s,
299
+ kind: Constant::SymbolKind::FIELD,
267
300
  range_location: node.name_loc,
268
301
  selection_range_location: node.name_loc,
269
302
  )
@@ -13,6 +13,12 @@ module RubyLsp
13
13
  Prism::ConstantReadNode,
14
14
  Prism::ConstantWriteNode,
15
15
  Prism::ConstantPathNode,
16
+ Prism::GlobalVariableAndWriteNode,
17
+ Prism::GlobalVariableOperatorWriteNode,
18
+ Prism::GlobalVariableOrWriteNode,
19
+ Prism::GlobalVariableReadNode,
20
+ Prism::GlobalVariableTargetNode,
21
+ Prism::GlobalVariableWriteNode,
16
22
  Prism::InstanceVariableReadNode,
17
23
  Prism::InstanceVariableAndWriteNode,
18
24
  Prism::InstanceVariableOperatorWriteNode,
@@ -62,6 +68,12 @@ module RubyLsp
62
68
  :on_constant_write_node_enter,
63
69
  :on_constant_path_node_enter,
64
70
  :on_call_node_enter,
71
+ :on_global_variable_and_write_node_enter,
72
+ :on_global_variable_operator_write_node_enter,
73
+ :on_global_variable_or_write_node_enter,
74
+ :on_global_variable_read_node_enter,
75
+ :on_global_variable_target_node_enter,
76
+ :on_global_variable_write_node_enter,
65
77
  :on_instance_variable_read_node_enter,
66
78
  :on_instance_variable_write_node_enter,
67
79
  :on_instance_variable_and_write_node_enter,
@@ -128,6 +140,36 @@ module RubyLsp
128
140
  handle_method_hover(message)
129
141
  end
130
142
 
143
+ sig { params(node: Prism::GlobalVariableAndWriteNode).void }
144
+ def on_global_variable_and_write_node_enter(node)
145
+ handle_global_variable_hover(node.name.to_s)
146
+ end
147
+
148
+ sig { params(node: Prism::GlobalVariableOperatorWriteNode).void }
149
+ def on_global_variable_operator_write_node_enter(node)
150
+ handle_global_variable_hover(node.name.to_s)
151
+ end
152
+
153
+ sig { params(node: Prism::GlobalVariableOrWriteNode).void }
154
+ def on_global_variable_or_write_node_enter(node)
155
+ handle_global_variable_hover(node.name.to_s)
156
+ end
157
+
158
+ sig { params(node: Prism::GlobalVariableReadNode).void }
159
+ def on_global_variable_read_node_enter(node)
160
+ handle_global_variable_hover(node.name.to_s)
161
+ end
162
+
163
+ sig { params(node: Prism::GlobalVariableTargetNode).void }
164
+ def on_global_variable_target_node_enter(node)
165
+ handle_global_variable_hover(node.name.to_s)
166
+ end
167
+
168
+ sig { params(node: Prism::GlobalVariableWriteNode).void }
169
+ def on_global_variable_write_node_enter(node)
170
+ handle_global_variable_hover(node.name.to_s)
171
+ end
172
+
131
173
  sig { params(node: Prism::InstanceVariableReadNode).void }
132
174
  def on_instance_variable_read_node_enter(node)
133
175
  handle_instance_variable_hover(node.name.to_s)
@@ -265,6 +307,16 @@ module RubyLsp
265
307
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
266
308
  end
267
309
 
310
+ sig { params(name: String).void }
311
+ def handle_global_variable_hover(name)
312
+ entries = @index[name]
313
+ return unless entries
314
+
315
+ categorized_markdown_from_index_entries(name, entries).each do |category, content|
316
+ @response_builder.push(content, category: category)
317
+ end
318
+ end
319
+
268
320
  sig { params(name: String, location: Prism::Location).void }
269
321
  def generate_hover(name, location)
270
322
  entries = @index.resolve(name, @node_context.nesting)
@@ -121,7 +121,7 @@ module RubyLsp
121
121
  return Error::InvalidTargetRange if closest_node.is_a?(Prism::MissingNode)
122
122
 
123
123
  closest_node_loc = closest_node.location
124
- # If the parent expression is a single line block, then we have to extract it inside of the oneline block
124
+ # If the parent expression is a single line block, then we have to extract it inside of the one-line block
125
125
  if parent_statements.is_a?(Prism::BlockNode) &&
126
126
  parent_statements.location.start_line == parent_statements.location.end_line
127
127
 
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  def provider
18
18
  Interface::CompletionOptions.new(
19
19
  resolve_provider: true,
20
- trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<"],
20
+ trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<", "$"],
21
21
  completion_item: {
22
22
  labelDetailsSupport: true,
23
23
  },
@@ -50,6 +50,12 @@ module RubyLsp
50
50
  Prism::CallNode,
51
51
  Prism::ConstantReadNode,
52
52
  Prism::ConstantPathNode,
53
+ Prism::GlobalVariableAndWriteNode,
54
+ Prism::GlobalVariableOperatorWriteNode,
55
+ Prism::GlobalVariableOrWriteNode,
56
+ Prism::GlobalVariableReadNode,
57
+ Prism::GlobalVariableTargetNode,
58
+ Prism::GlobalVariableWriteNode,
53
59
  Prism::InstanceVariableReadNode,
54
60
  Prism::InstanceVariableAndWriteNode,
55
61
  Prism::InstanceVariableOperatorWriteNode,
@@ -34,7 +34,7 @@ module RubyLsp
34
34
 
35
35
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
36
36
  # a completion resolve request must always return the original completion item without modifying ANY fields
37
- # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
37
+ # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behavior might
38
38
  # be broken.
39
39
  #
40
40
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
@@ -12,12 +12,6 @@ module RubyLsp
12
12
  extend T::Sig
13
13
  extend T::Generic
14
14
 
15
- SPECIAL_METHOD_CALLS = [
16
- :require,
17
- :require_relative,
18
- :autoload,
19
- ].freeze
20
-
21
15
  sig do
22
16
  params(
23
17
  document: T.any(RubyDocument, ERBDocument),
@@ -46,7 +40,12 @@ module RubyLsp
46
40
  Prism::ConstantReadNode,
47
41
  Prism::ConstantPathNode,
48
42
  Prism::BlockArgumentNode,
43
+ Prism::GlobalVariableAndWriteNode,
44
+ Prism::GlobalVariableOperatorWriteNode,
45
+ Prism::GlobalVariableOrWriteNode,
49
46
  Prism::GlobalVariableReadNode,
47
+ Prism::GlobalVariableTargetNode,
48
+ Prism::GlobalVariableWriteNode,
50
49
  Prism::InstanceVariableReadNode,
51
50
  Prism::InstanceVariableAndWriteNode,
52
51
  Prism::InstanceVariableOperatorWriteNode,
@@ -72,11 +71,7 @@ module RubyLsp
72
71
  parent,
73
72
  position,
74
73
  )
75
- elsif target.is_a?(Prism::CallNode) && !SPECIAL_METHOD_CALLS.include?(target.message) && !covers_position?(
76
- target.message_loc, position
77
- )
78
- # If the target is a method call, we need to ensure that the requested position is exactly on top of the
79
- # method identifier. Otherwise, we risk showing definitions for unrelated things
74
+ elsif position_outside_target?(position, target)
80
75
  target = nil
81
76
  # For methods with block arguments using symbol-to-proc
82
77
  elsif target.is_a?(Prism::SymbolNode) && parent.is_a?(Prism::BlockArgumentNode)
@@ -107,6 +102,28 @@ module RubyLsp
107
102
  @dispatcher.dispatch_once(@target) if @target
108
103
  @response_builder.response
109
104
  end
105
+
106
+ private
107
+
108
+ sig { params(position: T::Hash[Symbol, T.untyped], target: T.nilable(Prism::Node)).returns(T::Boolean) }
109
+ def position_outside_target?(position, target)
110
+ case target
111
+ when Prism::GlobalVariableAndWriteNode,
112
+ Prism::GlobalVariableOperatorWriteNode,
113
+ Prism::GlobalVariableOrWriteNode,
114
+ Prism::GlobalVariableWriteNode,
115
+ Prism::InstanceVariableAndWriteNode,
116
+ Prism::InstanceVariableOperatorWriteNode,
117
+ Prism::InstanceVariableOrWriteNode,
118
+ Prism::InstanceVariableWriteNode
119
+
120
+ !covers_position?(target.name_loc, position)
121
+ when Prism::CallNode
122
+ !covers_position?(target.message_loc, position)
123
+ else
124
+ false
125
+ end
126
+ end
110
127
  end
111
128
  end
112
129
  end
@@ -38,7 +38,13 @@ module RubyLsp
38
38
  ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight].new,
39
39
  ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight],
40
40
  )
41
- Listeners::DocumentHighlight.new(@response_builder, node_context.node, node_context.parent, dispatcher)
41
+ Listeners::DocumentHighlight.new(
42
+ @response_builder,
43
+ node_context.node,
44
+ node_context.parent,
45
+ dispatcher,
46
+ position,
47
+ )
42
48
  end
43
49
 
44
50
  sig { override.returns(T::Array[Interface::DocumentHighlight]) }
@@ -10,7 +10,8 @@ module RubyLsp
10
10
  # informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
11
11
  # this information, the editor can populate breadcrumbs, file outline and allow for fuzzy symbol searches.
12
12
  #
13
- # In VS Code, fuzzy symbol search can be accessed by opening the command palette and inserting an `@` symbol.
13
+ # In VS Code, symbol search known as 'Go To Symbol in Editor' and can be accessed with Ctrl/Cmd-Shift-O,
14
+ # or by opening the command palette and inserting an `@` symbol.
14
15
  class DocumentSymbol < Request
15
16
  extend T::Sig
16
17
 
@@ -46,17 +46,13 @@ module RubyLsp
46
46
  target = node_context.node
47
47
  parent = node_context.parent
48
48
 
49
- if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
50
- !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
51
- (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
49
+ if should_refine_target?(parent, target)
52
50
  target = determine_target(
53
51
  T.must(target),
54
52
  T.must(parent),
55
53
  position,
56
54
  )
57
- elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
58
- !covers_position?(target.message_loc, position)
59
-
55
+ elsif position_outside_target?(position, target)
60
56
  target = nil
61
57
  end
62
58
 
@@ -89,6 +85,30 @@ module RubyLsp
89
85
  ),
90
86
  )
91
87
  end
88
+
89
+ private
90
+
91
+ sig { params(parent: T.nilable(Prism::Node), target: T.nilable(Prism::Node)).returns(T::Boolean) }
92
+ def should_refine_target?(parent, target)
93
+ (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
94
+ !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
95
+ (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
96
+ end
97
+
98
+ sig { params(position: T::Hash[Symbol, T.untyped], target: T.nilable(Prism::Node)).returns(T::Boolean) }
99
+ def position_outside_target?(position, target)
100
+ case target
101
+ when Prism::GlobalVariableAndWriteNode,
102
+ Prism::GlobalVariableOperatorWriteNode,
103
+ Prism::GlobalVariableOrWriteNode,
104
+ Prism::GlobalVariableWriteNode
105
+ !covers_position?(target.name_loc, position)
106
+ when Prism::CallNode
107
+ !covers_position?(target.message_loc, position)
108
+ else
109
+ false
110
+ end
111
+ end
92
112
  end
93
113
  end
94
114
  end
@@ -72,7 +72,7 @@ module RubyLsp
72
72
 
73
73
  # If the client doesn't support resource operations, such as renaming files, then we can only return the basic
74
74
  # text changes
75
- unless @global_state.supported_resource_operations.include?("rename")
75
+ unless @global_state.client_capabilities.supports_rename?
76
76
  return Interface::WorkspaceEdit.new(changes: changes)
77
77
  end
78
78
 
@@ -26,7 +26,7 @@ module RubyLsp
26
26
  ).void
27
27
  end
28
28
  def delegate_request_if_needed!(global_state, document, char_position)
29
- if global_state.supports_request_delegation &&
29
+ if global_state.client_capabilities.supports_request_delegation &&
30
30
  document.is_a?(ERBDocument) &&
31
31
  document.inside_host_language?(char_position)
32
32
  raise DelegateRequestError
@@ -1,16 +1,27 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ # If there's no top level Gemfile, don't load RuboCop from a global installation
5
+ begin
6
+ Bundler.with_original_env { Bundler.default_gemfile }
7
+ rescue Bundler::GemfileNotFound
8
+ return
9
+ end
10
+
11
+ # Ensure that RuboCop is available
4
12
  begin
5
13
  require "rubocop"
6
14
  rescue LoadError
7
15
  return
8
16
  end
9
17
 
18
+ # Remember to update the version in the documentation (usage/dependency-compatibility section) if you change this
19
+ # Ensure that RuboCop is at least version 1.4.0
10
20
  begin
11
21
  gem("rubocop", ">= 1.4.0")
12
22
  rescue LoadError
13
- raise StandardError, "Incompatible RuboCop version. Ruby LSP requires >= 1.4.0"
23
+ $stderr.puts "Incompatible RuboCop version. Ruby LSP requires >= 1.4.0"
24
+ return
14
25
  end
15
26
 
16
27
  if RuboCop.const_defined?(:LSP) # This condition will be removed when requiring RuboCop >= 1.61.
@@ -0,0 +1,20 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ def compose(raw_initialize)
5
+ require_relative "../setup_bundler"
6
+ require "json"
7
+ require "uri"
8
+ require_relative "../../core_ext/uri"
9
+
10
+ initialize_request = JSON.parse(raw_initialize, symbolize_names: true)
11
+ workspace_uri = initialize_request.dig(:params, :workspaceFolders, 0, :uri)
12
+ workspace_path = workspace_uri && URI(workspace_uri).to_standardized_path
13
+ workspace_path ||= Dir.pwd
14
+
15
+ env = RubyLsp::SetupBundler.new(workspace_path, launcher: true).setup!
16
+ File.write(
17
+ File.join(".ruby-lsp", "bundle_env"),
18
+ env.map { |k, v| "#{k}=#{v}" }.join("\n"),
19
+ )
20
+ end
@@ -0,0 +1,8 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "compose_bundle"
5
+
6
+ # When this is invoked on Windows, we pass the raw initialize as an argument to this script. On other platforms, we
7
+ # invoke the compose method from inside a forked process
8
+ compose(ARGV.first)
@@ -106,6 +106,8 @@ module RubyLsp
106
106
  )
107
107
  when "$/cancelRequest"
108
108
  @mutex.synchronize { @cancelled_requests << message[:params][:id] }
109
+ when nil
110
+ process_response(message) if message[:result]
109
111
  end
110
112
  rescue DelegateRequestError
111
113
  send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
@@ -138,8 +140,22 @@ module RubyLsp
138
140
  send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
139
141
  end
140
142
 
143
+ # Process responses to requests that were sent to the client
144
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
145
+ def process_response(message)
146
+ case message.dig(:result, :method)
147
+ when "window/showMessageRequest"
148
+ window_show_message_request(message)
149
+ end
150
+ end
151
+
141
152
  sig { params(include_project_addons: T::Boolean).void }
142
153
  def load_addons(include_project_addons: true)
154
+ # If invoking Bundler.setup failed, then the load path will not be configured properly and trying to load add-ons
155
+ # with Gem.find_files will find every single version installed of an add-on, leading to requiring several
156
+ # different versions of the same files. We cannot load add-ons if Bundler.setup failed
157
+ return if @setup_error
158
+
143
159
  errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
144
160
 
145
161
  if errors.any?
@@ -181,8 +197,6 @@ module RubyLsp
181
197
  client_name = options.dig(:clientInfo, :name)
182
198
  @store.client_name = client_name if client_name
183
199
 
184
- progress = options.dig(:capabilities, :window, :workDoneProgress)
185
- @store.supports_progress = progress.nil? ? true : progress
186
200
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
187
201
 
188
202
  configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
@@ -202,6 +216,13 @@ module RubyLsp
202
216
  Hash.new(true)
203
217
  end
204
218
 
219
+ bundle_env_path = File.join(".ruby-lsp", "bundle_env")
220
+ bundle_env = if File.exist?(bundle_env_path)
221
+ env = File.readlines(bundle_env_path).to_h { |line| T.cast(line.chomp.split("=", 2), [String, String]) }
222
+ FileUtils.rm(bundle_env_path)
223
+ env
224
+ end
225
+
205
226
  document_symbol_provider = Requests::DocumentSymbol.provider if enabled_features["documentSymbols"]
206
227
  document_link_provider = Requests::DocumentLink.provider if enabled_features["documentLink"]
207
228
  code_lens_provider = Requests::CodeLens.provider if enabled_features["codeLens"]
@@ -254,12 +275,14 @@ module RubyLsp
254
275
  version: VERSION,
255
276
  },
256
277
  formatter: @global_state.formatter,
278
+ degraded_mode: !!(@install_error || @setup_error),
279
+ bundle_env: bundle_env,
257
280
  }
258
281
 
259
282
  send_message(Result.new(id: message[:id], response: response))
260
283
 
261
284
  # Not every client supports dynamic registration or file watching
262
- if global_state.supports_watching_files
285
+ if @global_state.client_capabilities.supports_watching_files
263
286
  send_message(
264
287
  Request.new(
265
288
  id: @current_request_id,
@@ -290,6 +313,24 @@ module RubyLsp
290
313
  begin_progress("indexing-progress", "Ruby LSP: indexing files")
291
314
 
292
315
  global_state_notifications.each { |notification| send_message(notification) }
316
+
317
+ if @setup_error
318
+ send_message(Notification.telemetry(
319
+ type: "error",
320
+ errorMessage: @setup_error.message,
321
+ errorClass: @setup_error.class,
322
+ stack: @setup_error.backtrace&.join("\n"),
323
+ ))
324
+ end
325
+
326
+ if @install_error
327
+ send_message(Notification.telemetry(
328
+ type: "error",
329
+ errorMessage: @install_error.message,
330
+ errorClass: @install_error.class,
331
+ stack: @install_error.backtrace&.join("\n"),
332
+ ))
333
+ end
293
334
  end
294
335
 
295
336
  sig { void }
@@ -297,20 +338,22 @@ module RubyLsp
297
338
  load_addons
298
339
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
299
340
 
300
- if defined?(Requests::Support::RuboCopFormatter)
301
- begin
302
- @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
303
- rescue RuboCop::Error => e
304
- # The user may have provided unknown config switches in .rubocop or
305
- # is trying to load a non-existant config file.
306
- send_message(Notification.window_show_message(
307
- "RuboCop configuration error: #{e.message}. Formatting will not be available.",
308
- type: Constant::MessageType::ERROR,
309
- ))
341
+ unless @setup_error
342
+ if defined?(Requests::Support::RuboCopFormatter)
343
+ begin
344
+ @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
345
+ rescue RuboCop::Error => e
346
+ # The user may have provided unknown config switches in .rubocop or
347
+ # is trying to load a non-existent config file.
348
+ send_message(Notification.window_show_message(
349
+ "RuboCop configuration error: #{e.message}. Formatting will not be available.",
350
+ type: Constant::MessageType::ERROR,
351
+ ))
352
+ end
353
+ end
354
+ if defined?(Requests::Support::SyntaxTreeFormatter)
355
+ @global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
310
356
  end
311
- end
312
- if defined?(Requests::Support::SyntaxTreeFormatter)
313
- @global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
314
357
  end
315
358
 
316
359
  perform_initial_indexing
@@ -569,6 +612,11 @@ module RubyLsp
569
612
  # don't want to format it
570
613
  path = uri.to_standardized_path
571
614
  unless path.nil? || path.start_with?(@global_state.workspace_path)
615
+ send_log_message(<<~MESSAGE)
616
+ Ignoring formatting request for file outside of the workspace.
617
+ Workspace path was set by editor as #{@global_state.workspace_path}.
618
+ File path requested for formatting was #{path}
619
+ MESSAGE
572
620
  send_empty_response(message[:id])
573
621
  return
574
622
  end
@@ -1017,7 +1065,7 @@ module RubyLsp
1017
1065
 
1018
1066
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
1019
1067
  def workspace_dependencies(message)
1020
- response = begin
1068
+ response = if @global_state.top_level_bundle
1021
1069
  Bundler.with_original_env do
1022
1070
  definition = Bundler.definition
1023
1071
  dep_keys = definition.locked_deps.keys.to_set
@@ -1031,7 +1079,7 @@ module RubyLsp
1031
1079
  }
1032
1080
  end
1033
1081
  end
1034
- rescue Bundler::GemfileNotFound
1082
+ else
1035
1083
  []
1036
1084
  end
1037
1085
 
@@ -1077,7 +1125,7 @@ module RubyLsp
1077
1125
 
1078
1126
  sig { params(id: String, title: String, percentage: Integer).void }
1079
1127
  def begin_progress(id, title, percentage: 0)
1080
- return unless @store.supports_progress
1128
+ return unless @global_state.client_capabilities.supports_progress
1081
1129
 
1082
1130
  send_message(Request.new(
1083
1131
  id: @current_request_id,
@@ -1085,52 +1133,21 @@ module RubyLsp
1085
1133
  params: Interface::WorkDoneProgressCreateParams.new(token: id),
1086
1134
  ))
1087
1135
 
1088
- send_message(Notification.new(
1089
- method: "$/progress",
1090
- params: Interface::ProgressParams.new(
1091
- token: id,
1092
- value: Interface::WorkDoneProgressBegin.new(
1093
- kind: "begin",
1094
- title: title,
1095
- percentage: percentage,
1096
- message: "#{percentage}% completed",
1097
- ),
1098
- ),
1099
- ))
1136
+ send_message(Notification.progress_begin(id, title, percentage: percentage, message: "#{percentage}% completed"))
1100
1137
  end
1101
1138
 
1102
1139
  sig { params(id: String, percentage: Integer).void }
1103
1140
  def progress(id, percentage)
1104
- return unless @store.supports_progress
1141
+ return unless @global_state.client_capabilities.supports_progress
1105
1142
 
1106
- send_message(
1107
- Notification.new(
1108
- method: "$/progress",
1109
- params: Interface::ProgressParams.new(
1110
- token: id,
1111
- value: Interface::WorkDoneProgressReport.new(
1112
- kind: "report",
1113
- percentage: percentage,
1114
- message: "#{percentage}% completed",
1115
- ),
1116
- ),
1117
- ),
1118
- )
1143
+ send_message(Notification.progress_report(id, percentage: percentage, message: "#{percentage}% completed"))
1119
1144
  end
1120
1145
 
1121
1146
  sig { params(id: String).void }
1122
1147
  def end_progress(id)
1123
- return unless @store.supports_progress
1148
+ return unless @global_state.client_capabilities.supports_progress
1124
1149
 
1125
- send_message(
1126
- Notification.new(
1127
- method: "$/progress",
1128
- params: Interface::ProgressParams.new(
1129
- token: id,
1130
- value: Interface::WorkDoneProgressEnd.new(kind: "end"),
1131
- ),
1132
- ),
1133
- )
1150
+ send_message(Notification.progress_end(id))
1134
1151
  rescue ClosedQueueError
1135
1152
  # If the server was killed and the message queue is already closed, there's no way to end the progress
1136
1153
  # notification
@@ -1138,6 +1155,7 @@ module RubyLsp
1138
1155
 
1139
1156
  sig { void }
1140
1157
  def check_formatter_is_available
1158
+ return if @setup_error
1141
1159
  # Warn of an unavailable `formatter` setting, e.g. `rubocop` on a project which doesn't have RuboCop.
1142
1160
  # Syntax Tree will always be available via Ruby LSP so we don't need to check for it.
1143
1161
  return unless @global_state.formatter == "rubocop"
@@ -1194,5 +1212,17 @@ module RubyLsp
1194
1212
  # The index expects snake case configurations, but VS Code standardizes on camel case settings
1195
1213
  configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
1196
1214
  end
1215
+
1216
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
1217
+ def window_show_message_request(message)
1218
+ result = message[:result]
1219
+ return unless result
1220
+
1221
+ addon_name = result[:addon_name]
1222
+ addon = Addon.addons.find { |addon| addon.name == addon_name }
1223
+ return unless addon
1224
+
1225
+ addon.handle_window_show_message_response(result[:title])
1226
+ end
1197
1227
  end
1198
1228
  end