ruby-lsp 0.17.12 → 0.17.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34cae8b53cfb0811cb26d3b3dd4198a72585d1f15a98a7d8f656fd1676886630
4
- data.tar.gz: f4868222c46c4cbad9a099fce69562838b5dda567f1666a82752f45fe3f501c5
3
+ metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
4
+ data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
5
5
  SHA512:
6
- metadata.gz: db8e991497aa6e9388b2778fb9a417adbef07c6bb29165ff9a1718ab7cf1db6d2a6586453320d72adf0cffe78b846b553c3a0ba24b63807c52170f02f1b7d2e3
7
- data.tar.gz: 80aac045ca6aaf7dbb9344f947a9b1419eaac4711448d997c902f0fb9da68e7c2338e0a5b1d81a30c0a2609defe1b656457d1a4a23b0ba5288162d142cf7c9e9
6
+ metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
7
+ data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.12
1
+ 0.17.13
data/exe/ruby-lsp CHANGED
@@ -112,20 +112,20 @@ if options[:time_index]
112
112
  end
113
113
 
114
114
  if options[:doctor]
115
+ index = RubyIndexer::Index.new
116
+
115
117
  if File.exist?(".index.yml")
116
118
  begin
117
119
  config = YAML.parse_file(".index.yml").to_ruby
118
120
  rescue => e
119
121
  abort("Error parsing config: #{e.message}")
120
122
  end
121
- RubyIndexer.configuration.apply_config(config)
123
+ index.configuration.apply_config(config)
122
124
  end
123
125
 
124
- index = RubyIndexer::Index.new
125
-
126
126
  puts "Globbing for indexable files"
127
127
 
128
- RubyIndexer.configuration.indexables.each do |indexable|
128
+ index.configuration.indexables.each do |indexable|
129
129
  puts "indexing: #{indexable.full_path}"
130
130
  index.index_single(indexable)
131
131
  end
data/exe/ruby-lsp-check CHANGED
@@ -44,7 +44,7 @@ puts "\n"
44
44
  puts "Verifying that indexing executes successfully. This may take a while..."
45
45
 
46
46
  index = RubyIndexer::Index.new
47
- indexables = RubyIndexer.configuration.indexables
47
+ indexables = index.configuration.indexables
48
48
 
49
49
  indexables.each_with_index do |indexable, i|
50
50
  index.index_single(indexable)
@@ -552,7 +552,7 @@ module RubyIndexer
552
552
  comment_content = comment.location.slice.chomp
553
553
 
554
554
  # invalid encodings would raise an "invalid byte sequence" exception
555
- if !comment_content.valid_encoding? || comment_content.match?(RubyIndexer.configuration.magic_comment_regex)
555
+ if !comment_content.valid_encoding? || comment_content.match?(@index.configuration.magic_comment_regex)
556
556
  next
557
557
  end
558
558
 
@@ -11,6 +11,9 @@ module RubyIndexer
11
11
  # The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
12
12
  ENTRY_SIMILARITY_THRESHOLD = 0.7
13
13
 
14
+ sig { returns(Configuration) }
15
+ attr_reader :configuration
16
+
14
17
  sig { void }
15
18
  def initialize
16
19
  # Holds all entries in the index using the following format:
@@ -44,6 +47,8 @@ module RubyIndexer
44
47
  {},
45
48
  T::Hash[String, T::Array[T.proc.params(index: Index, base: Entry::Namespace).void]],
46
49
  )
50
+
51
+ @configuration = T.let(RubyIndexer::Configuration.new, Configuration)
47
52
  end
48
53
 
49
54
  # Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
@@ -296,7 +301,7 @@ module RubyIndexer
296
301
  block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
297
302
  ).void
298
303
  end
299
- def index_all(indexable_paths: RubyIndexer.configuration.indexables, &block)
304
+ def index_all(indexable_paths: @configuration.indexables, &block)
300
305
  RBSIndexer.new(self).index_ruby_core
301
306
  # Calculate how many paths are worth 1% of progress
302
307
  progress_step = (indexable_paths.length / 100.0).ceil
@@ -15,12 +15,4 @@ require "ruby_indexer/lib/ruby_indexer/location"
15
15
  require "ruby_indexer/lib/ruby_indexer/rbs_indexer"
16
16
 
17
17
  module RubyIndexer
18
- @configuration = T.let(Configuration.new, Configuration)
19
-
20
- class << self
21
- extend T::Sig
22
-
23
- sig { returns(Configuration) }
24
- attr_reader :configuration
25
- end
26
18
  end
@@ -108,7 +108,7 @@ module RubyIndexer
108
108
  end
109
109
 
110
110
  def test_magic_comments_regex
111
- regex = RubyIndexer.configuration.magic_comment_regex
111
+ regex = @config.magic_comment_regex
112
112
 
113
113
  [
114
114
  "# frozen_string_literal:",
@@ -49,15 +49,18 @@ module RubyLsp
49
49
  super
50
50
  end
51
51
 
52
- # Discovers and loads all addons. Returns the list of activated addons
53
- sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[Addon]) }
52
+ # Discovers and loads all addons. Returns a list of errors when trying to require addons
53
+ sig do
54
+ params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[StandardError])
55
+ end
54
56
  def load_addons(global_state, outgoing_queue)
55
57
  # Require all addons entry points, which should be placed under
56
58
  # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
57
- Gem.find_files("ruby_lsp/**/addon.rb").each do |addon|
59
+ errors = Gem.find_files("ruby_lsp/**/addon.rb").filter_map do |addon|
58
60
  require File.expand_path(addon)
61
+ nil
59
62
  rescue => e
60
- $stderr.puts(e.full_message)
63
+ e
61
64
  end
62
65
 
63
66
  # Instantiate all discovered addon classes
@@ -71,6 +74,8 @@ module RubyLsp
71
74
  rescue => e
72
75
  addon.add_error(e)
73
76
  end
77
+
78
+ errors
74
79
  end
75
80
 
76
81
  # Intended for use by tests for addons
@@ -65,7 +65,7 @@ module RubyLsp
65
65
  when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
66
66
  process_message(message)
67
67
  when "shutdown"
68
- $stderr.puts("Shutting down Ruby LSP...")
68
+ send_log_message("Shutting down Ruby LSP...")
69
69
 
70
70
  shutdown
71
71
 
@@ -76,7 +76,7 @@ module RubyLsp
76
76
  when "exit"
77
77
  @mutex.synchronize do
78
78
  status = @incoming_queue.closed? ? 0 : 1
79
- $stderr.puts("Shutdown complete with status #{status}")
79
+ send_log_message("Shutdown complete with status #{status}")
80
80
  exit(status)
81
81
  end
82
82
  else
@@ -145,5 +145,10 @@ module RubyLsp
145
145
  def send_empty_response(id)
146
146
  send_message(Result.new(id: id, response: nil))
147
147
  end
148
+
149
+ sig { params(message: String, type: Integer).void }
150
+ def send_log_message(message, type: Constant::MessageType::LOG)
151
+ send_message(Notification.window_log_message(message, type: Constant::MessageType::LOG))
152
+ end
148
153
  end
149
154
  end
@@ -10,16 +10,6 @@ module RubyLsp
10
10
  end
11
11
  end
12
12
 
13
- class SorbetLevel < T::Enum
14
- enums do
15
- None = new("none")
16
- Ignore = new("ignore")
17
- False = new("false")
18
- True = new("true")
19
- Strict = new("strict")
20
- end
21
- end
22
-
23
13
  extend T::Sig
24
14
  extend T::Helpers
25
15
 
@@ -223,60 +213,6 @@ module RubyLsp
223
213
  NodeContext.new(closest, parent, nesting_nodes, call_node)
224
214
  end
225
215
 
226
- sig do
227
- params(
228
- range: T::Hash[Symbol, T.untyped],
229
- node_types: T::Array[T.class_of(Prism::Node)],
230
- ).returns(T.nilable(Prism::Node))
231
- end
232
- def locate_first_within_range(range, node_types: [])
233
- scanner = create_scanner
234
- start_position = scanner.find_char_position(range[:start])
235
- end_position = scanner.find_char_position(range[:end])
236
- desired_range = (start_position...end_position)
237
- queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
238
-
239
- until queue.empty?
240
- candidate = queue.shift
241
-
242
- # Skip nil child nodes
243
- next if candidate.nil?
244
-
245
- # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
246
- # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
247
- # sibling
248
- T.unsafe(queue).unshift(*candidate.child_nodes)
249
-
250
- # Skip if the current node doesn't cover the desired position
251
- loc = candidate.location
252
-
253
- if desired_range.cover?(loc.start_offset...loc.end_offset) &&
254
- (node_types.empty? || node_types.any? { |type| candidate.class == type })
255
- return candidate
256
- end
257
- end
258
- end
259
-
260
- sig { returns(SorbetLevel) }
261
- def sorbet_level
262
- sigil = parse_result.magic_comments.find do |comment|
263
- comment.key == "typed"
264
- end&.value
265
-
266
- case sigil
267
- when "ignore"
268
- SorbetLevel::Ignore
269
- when "false"
270
- SorbetLevel::False
271
- when "true"
272
- SorbetLevel::True
273
- when "strict", "strong"
274
- SorbetLevel::Strict
275
- else
276
- SorbetLevel::None
277
- end
278
- end
279
-
280
216
  class Scanner
281
217
  extend T::Sig
282
218
 
@@ -57,21 +57,48 @@ module RubyLsp
57
57
  @linters.filter_map { |name| @supported_formatters[name] }
58
58
  end
59
59
 
60
- sig { params(options: T::Hash[Symbol, T.untyped]).void }
60
+ # Applies the options provided by the editor and returns an array of notifications to send back to the client
61
+ sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Array[Notification]) }
61
62
  def apply_options(options)
63
+ notifications = []
62
64
  direct_dependencies = gather_direct_dependencies
63
65
  all_dependencies = gather_direct_and_indirect_dependencies
64
66
  workspace_uri = options.dig(:workspaceFolders, 0, :uri)
65
67
  @workspace_uri = URI(workspace_uri) if workspace_uri
66
68
 
67
69
  specified_formatter = options.dig(:initializationOptions, :formatter)
68
- @formatter = specified_formatter if specified_formatter
69
- @formatter = detect_formatter(direct_dependencies, all_dependencies) if @formatter == "auto"
70
+
71
+ if specified_formatter
72
+ @formatter = specified_formatter
73
+
74
+ if specified_formatter != "auto"
75
+ notifications << Notification.window_log_message("Using formatter specified by user: #{@formatter}")
76
+ end
77
+ end
78
+
79
+ if @formatter == "auto"
80
+ @formatter = detect_formatter(direct_dependencies, all_dependencies)
81
+ notifications << Notification.window_log_message("Auto detected formatter: #{@formatter}")
82
+ end
70
83
 
71
84
  specified_linters = options.dig(:initializationOptions, :linters)
72
85
  @linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
86
+
87
+ notifications << if specified_linters
88
+ Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
89
+ else
90
+ Notification.window_log_message("Auto detected linters: #{@linters.join(", ")}")
91
+ end
92
+
73
93
  @test_library = detect_test_library(direct_dependencies)
94
+ notifications << Notification.window_log_message("Detected test library: #{@test_library}")
95
+
74
96
  @has_type_checker = detect_typechecker(direct_dependencies)
97
+ if @has_type_checker
98
+ notifications << Notification.window_log_message(
99
+ "Ruby LSP detected this is a Sorbet project and will defer to the Sorbet LSP for some functionality",
100
+ )
101
+ end
75
102
 
76
103
  encodings = options.dig(:capabilities, :general, :positionEncodings)
77
104
  @encoding = if !encodings || encodings.empty?
@@ -91,6 +118,8 @@ module RubyLsp
91
118
 
92
119
  @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
93
120
  @type_inferrer.experimental_features = @experimental_features
121
+
122
+ notifications
94
123
  end
95
124
 
96
125
  sig { returns(String) }
@@ -163,16 +192,7 @@ module RubyLsp
163
192
  def detect_typechecker(dependencies)
164
193
  return false if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
165
194
 
166
- # We can't read the env from within `Bundle.with_original_env` so we need to set it here.
167
- ruby_lsp_env_is_test = (ENV["RUBY_LSP_ENV"] == "test")
168
- Bundler.with_original_env do
169
- sorbet_static_detected = dependencies.any?(/^sorbet-static/)
170
- # Don't show message while running tests, since it's noisy
171
- if sorbet_static_detected && !ruby_lsp_env_is_test
172
- $stderr.puts("Ruby LSP detected this is a Sorbet project so will defer to Sorbet LSP for some functionality")
173
- end
174
- sorbet_static_detected
175
- end
195
+ dependencies.any?(/^sorbet-static/)
176
196
  rescue Bundler::GemfileNotFound
177
197
  false
178
198
  end
@@ -56,7 +56,7 @@ module RubyLsp
56
56
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
57
57
  global_state: GlobalState,
58
58
  node_context: NodeContext,
59
- sorbet_level: Document::SorbetLevel,
59
+ sorbet_level: RubyDocument::SorbetLevel,
60
60
  dispatcher: Prism::Dispatcher,
61
61
  uri: URI::Generic,
62
62
  trigger_character: T.nilable(String),
@@ -99,7 +99,7 @@ module RubyLsp
99
99
  def on_constant_read_node_enter(node)
100
100
  # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
101
101
  # no sigil, Sorbet will still provide completion for constants
102
- return if @sorbet_level != Document::SorbetLevel::Ignore
102
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
103
103
 
104
104
  name = constant_name(node)
105
105
  return if name.nil?
@@ -122,7 +122,7 @@ module RubyLsp
122
122
  def on_constant_path_node_enter(node)
123
123
  # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
124
124
  # no sigil, Sorbet will still provide completion for constants
125
- return if @sorbet_level != Document::SorbetLevel::Ignore
125
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
126
126
 
127
127
  name = constant_name(node)
128
128
  return if name.nil?
@@ -134,7 +134,7 @@ module RubyLsp
134
134
  def on_call_node_enter(node)
135
135
  # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
136
136
  # no sigil, Sorbet will still provide completion for constants
137
- if @sorbet_level == Document::SorbetLevel::Ignore
137
+ if @sorbet_level == RubyDocument::SorbetLevel::Ignore
138
138
  receiver = node.receiver
139
139
 
140
140
  # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
@@ -257,7 +257,7 @@ module RubyLsp
257
257
  def handle_instance_variable_completion(name, location)
258
258
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
259
259
  # to provide all features for them
260
- return if @sorbet_level == Document::SorbetLevel::Strict
260
+ return if @sorbet_level == RubyDocument::SorbetLevel::Strict
261
261
 
262
262
  type = @type_inferrer.infer_receiver_type(@node_context)
263
263
  return unless type
@@ -20,7 +20,7 @@ module RubyLsp
20
20
  uri: URI::Generic,
21
21
  node_context: NodeContext,
22
22
  dispatcher: Prism::Dispatcher,
23
- sorbet_level: Document::SorbetLevel,
23
+ sorbet_level: RubyDocument::SorbetLevel,
24
24
  ).void
25
25
  end
26
26
  def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
@@ -181,7 +181,7 @@ module RubyLsp
181
181
  def handle_instance_variable_definition(name)
182
182
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
183
183
  # to provide all features for them
184
- return if @sorbet_level == Document::SorbetLevel::Strict
184
+ return if @sorbet_level == RubyDocument::SorbetLevel::Strict
185
185
 
186
186
  type = @type_inferrer.infer_receiver_type(@node_context)
187
187
  return unless type
@@ -289,7 +289,7 @@ module RubyLsp
289
289
  # additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
290
290
  # ignore
291
291
  file_path = entry.file_path
292
- next if @sorbet_level != Document::SorbetLevel::Ignore && not_in_dependencies?(file_path)
292
+ next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(file_path)
293
293
 
294
294
  @response_builder << Interface::LocationLink.new(
295
295
  target_uri: URI::Generic.from_path(path: file_path).to_s,
@@ -42,7 +42,7 @@ module RubyLsp
42
42
  uri: URI::Generic,
43
43
  node_context: NodeContext,
44
44
  dispatcher: Prism::Dispatcher,
45
- sorbet_level: Document::SorbetLevel,
45
+ sorbet_level: RubyDocument::SorbetLevel,
46
46
  ).void
47
47
  end
48
48
  def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
@@ -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 @sorbet_level != Document::SorbetLevel::Ignore
76
+ return if @sorbet_level != RubyDocument::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 @sorbet_level != Document::SorbetLevel::Ignore
86
+ return if @sorbet_level != RubyDocument::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 @sorbet_level != Document::SorbetLevel::Ignore
93
+ return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
94
94
 
95
95
  name = constant_name(node)
96
96
  return if name.nil?
@@ -193,7 +193,7 @@ module RubyLsp
193
193
  def handle_instance_variable_hover(name)
194
194
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
195
195
  # to provide all features for them
196
- return if @sorbet_level == Document::SorbetLevel::Strict
196
+ return if @sorbet_level == RubyDocument::SorbetLevel::Strict
197
197
 
198
198
  type = @type_inferrer.infer_receiver_type(@node_context)
199
199
  return unless type
@@ -13,7 +13,7 @@ module RubyLsp
13
13
  global_state: GlobalState,
14
14
  node_context: NodeContext,
15
15
  dispatcher: Prism::Dispatcher,
16
- sorbet_level: Document::SorbetLevel,
16
+ sorbet_level: RubyDocument::SorbetLevel,
17
17
  ).void
18
18
  end
19
19
  def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
@@ -38,7 +38,7 @@ module RubyLsp
38
38
  end
39
39
  end
40
40
 
41
- sig { params(document: Document, code_action: T::Hash[Symbol, T.untyped]).void }
41
+ sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
42
42
  def initialize(document, code_action)
43
43
  super()
44
44
  @document = document
@@ -54,7 +54,7 @@ module RubyLsp
54
54
  refactor_variable
55
55
  when CodeActions::EXTRACT_TO_METHOD_TITLE
56
56
  refactor_method
57
- when CodeActions::SWITCH_BLOCK_STYLE_TITLE
57
+ when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
58
58
  switch_block_style
59
59
  else
60
60
  Error::UnknownCodeAction
@@ -81,7 +81,7 @@ module RubyLsp
81
81
  indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
82
82
 
83
83
  Interface::CodeAction.new(
84
- title: CodeActions::SWITCH_BLOCK_STYLE_TITLE,
84
+ title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
85
85
  edit: Interface::WorkspaceEdit.new(
86
86
  document_changes: [
87
87
  Interface::TextDocumentEdit.new(
@@ -21,7 +21,7 @@ module RubyLsp
21
21
 
22
22
  EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
23
23
  EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
24
- SWITCH_BLOCK_STYLE_TITLE = "Refactor: Switch block style"
24
+ TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
25
25
 
26
26
  class << self
27
27
  extend T::Sig
@@ -71,7 +71,7 @@ module RubyLsp
71
71
  data: { range: @range, uri: @uri.to_s },
72
72
  )
73
73
  code_actions << Interface::CodeAction.new(
74
- title: SWITCH_BLOCK_STYLE_TITLE,
74
+ title: TOGGLE_BLOCK_STYLE_TITLE,
75
75
  kind: Constant::CodeActionKind::REFACTOR_REWRITE,
76
76
  data: { range: @range, uri: @uri.to_s },
77
77
  )
@@ -49,7 +49,7 @@ module RubyLsp
49
49
  document: Document,
50
50
  global_state: GlobalState,
51
51
  params: T::Hash[Symbol, T.untyped],
52
- sorbet_level: Document::SorbetLevel,
52
+ sorbet_level: RubyDocument::SorbetLevel,
53
53
  dispatcher: Prism::Dispatcher,
54
54
  ).void
55
55
  end
@@ -42,7 +42,7 @@ module RubyLsp
42
42
  global_state: GlobalState,
43
43
  position: T::Hash[Symbol, T.untyped],
44
44
  dispatcher: Prism::Dispatcher,
45
- sorbet_level: Document::SorbetLevel,
45
+ sorbet_level: RubyDocument::SorbetLevel,
46
46
  ).void
47
47
  end
48
48
  def initialize(document, global_state, position, dispatcher, sorbet_level)
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  global_state: GlobalState,
37
37
  position: T::Hash[Symbol, T.untyped],
38
38
  dispatcher: Prism::Dispatcher,
39
- sorbet_level: Document::SorbetLevel,
39
+ sorbet_level: RubyDocument::SorbetLevel,
40
40
  ).void
41
41
  end
42
42
  def initialize(document, global_state, position, dispatcher, sorbet_level)
@@ -46,7 +46,7 @@ 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
- sorbet_level: Document::SorbetLevel,
49
+ sorbet_level: RubyDocument::SorbetLevel,
50
50
  ).void
51
51
  end
52
52
  def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
@@ -209,9 +209,9 @@ module RubyLsp
209
209
  end
210
210
  end
211
211
 
212
- sig { params(sorbet_level: Document::SorbetLevel).returns(T::Boolean) }
212
+ sig { params(sorbet_level: RubyDocument::SorbetLevel).returns(T::Boolean) }
213
213
  def sorbet_level_true_or_higher?(sorbet_level)
214
- sorbet_level == Document::SorbetLevel::True || sorbet_level == Document::SorbetLevel::Strict
214
+ sorbet_level == RubyDocument::SorbetLevel::True || sorbet_level == RubyDocument::SorbetLevel::Strict
215
215
  end
216
216
  end
217
217
  end
@@ -3,6 +3,16 @@
3
3
 
4
4
  module RubyLsp
5
5
  class RubyDocument < Document
6
+ class SorbetLevel < T::Enum
7
+ enums do
8
+ None = new("none")
9
+ Ignore = new("ignore")
10
+ False = new("false")
11
+ True = new("true")
12
+ Strict = new("strict")
13
+ end
14
+ end
15
+
6
16
  sig { override.returns(Prism::ParseResult) }
7
17
  def parse
8
18
  return @parse_result unless @needs_parsing
@@ -20,5 +30,59 @@ module RubyLsp
20
30
  def language_id
21
31
  LanguageId::Ruby
22
32
  end
33
+
34
+ sig { returns(SorbetLevel) }
35
+ def sorbet_level
36
+ sigil = parse_result.magic_comments.find do |comment|
37
+ comment.key == "typed"
38
+ end&.value
39
+
40
+ case sigil
41
+ when "ignore"
42
+ SorbetLevel::Ignore
43
+ when "false"
44
+ SorbetLevel::False
45
+ when "true"
46
+ SorbetLevel::True
47
+ when "strict", "strong"
48
+ SorbetLevel::Strict
49
+ else
50
+ SorbetLevel::None
51
+ end
52
+ end
53
+
54
+ sig do
55
+ params(
56
+ range: T::Hash[Symbol, T.untyped],
57
+ node_types: T::Array[T.class_of(Prism::Node)],
58
+ ).returns(T.nilable(Prism::Node))
59
+ end
60
+ def locate_first_within_range(range, node_types: [])
61
+ scanner = create_scanner
62
+ start_position = scanner.find_char_position(range[:start])
63
+ end_position = scanner.find_char_position(range[:end])
64
+ desired_range = (start_position...end_position)
65
+ queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
66
+
67
+ until queue.empty?
68
+ candidate = queue.shift
69
+
70
+ # Skip nil child nodes
71
+ next if candidate.nil?
72
+
73
+ # Add the next child_nodes to the queue to be processed. The order here is important! We want to move in the
74
+ # same order as the visiting mechanism, which means searching the child nodes before moving on to the next
75
+ # sibling
76
+ T.unsafe(queue).unshift(*candidate.child_nodes)
77
+
78
+ # Skip if the current node doesn't cover the desired position
79
+ loc = candidate.location
80
+
81
+ if desired_range.cover?(loc.start_offset...loc.end_offset) &&
82
+ (node_types.empty? || node_types.any? { |type| candidate.class == type })
83
+ return candidate
84
+ end
85
+ end
86
+ end
23
87
  end
24
88
  end
@@ -19,10 +19,10 @@ module RubyLsp
19
19
  def process_message(message)
20
20
  case message[:method]
21
21
  when "initialize"
22
- $stderr.puts("Initializing Ruby LSP v#{VERSION}...")
22
+ send_log_message("Initializing Ruby LSP v#{VERSION}...")
23
23
  run_initialize(message)
24
24
  when "initialized"
25
- $stderr.puts("Finished initializing Ruby LSP!") unless @test_mode
25
+ send_log_message("Finished initializing Ruby LSP!") unless @test_mode
26
26
  run_initialized
27
27
  when "textDocument/didOpen"
28
28
  text_document_did_open(message)
@@ -121,12 +121,20 @@ module RubyLsp
121
121
  end
122
122
  end
123
123
 
124
- $stderr.puts("Error processing #{message[:method]}: #{e.full_message}")
124
+ send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
125
125
  end
126
126
 
127
127
  sig { void }
128
128
  def load_addons
129
- Addon.load_addons(@global_state, @outgoing_queue)
129
+ errors = Addon.load_addons(@global_state, @outgoing_queue)
130
+
131
+ if errors.any?
132
+ send_log_message(
133
+ "Error loading addons:\n\n#{errors.map(&:full_message).join("\n\n")}",
134
+ type: Constant::MessageType::WARNING,
135
+ )
136
+ end
137
+
130
138
  errored_addons = Addon.addons.select(&:error?)
131
139
 
132
140
  if errored_addons.any?
@@ -140,7 +148,12 @@ module RubyLsp
140
148
  ),
141
149
  )
142
150
 
143
- $stderr.puts(errored_addons.map(&:errors_details).join("\n\n")) unless @test_mode
151
+ unless @test_mode
152
+ send_log_message(
153
+ errored_addons.map(&:errors_details).join("\n\n"),
154
+ type: Constant::MessageType::WARNING,
155
+ )
156
+ end
144
157
  end
145
158
  end
146
159
 
@@ -149,7 +162,7 @@ module RubyLsp
149
162
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
150
163
  def run_initialize(message)
151
164
  options = message[:params]
152
- @global_state.apply_options(options)
165
+ global_state_notifications = @global_state.apply_options(options)
153
166
 
154
167
  client_name = options.dig(:clientInfo, :name)
155
168
  @store.client_name = client_name if client_name
@@ -258,6 +271,8 @@ module RubyLsp
258
271
  process_indexing_configuration(options.dig(:initializationOptions, :indexing))
259
272
 
260
273
  begin_progress("indexing-progress", "Ruby LSP: indexing files")
274
+
275
+ global_state_notifications.each { |notification| send_message(notification) }
261
276
  end
262
277
 
263
278
  sig { void }
@@ -281,7 +296,7 @@ module RubyLsp
281
296
  @mutex.synchronize do
282
297
  text_document = message.dig(:params, :textDocument)
283
298
  language_id = case text_document[:languageId]
284
- when "erb"
299
+ when "erb", "eruby"
285
300
  Document::LanguageId::ERB
286
301
  else
287
302
  Document::LanguageId::Ruby
@@ -480,9 +495,10 @@ module RubyLsp
480
495
  )
481
496
  end
482
497
 
483
- sig { params(document: Document).returns(Document::SorbetLevel) }
498
+ sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
484
499
  def sorbet_level(document)
485
- return Document::SorbetLevel::Ignore unless @global_state.has_type_checker
500
+ return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
501
+ return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
486
502
 
487
503
  document.sorbet_level
488
504
  end
@@ -520,6 +536,12 @@ module RubyLsp
520
536
  params = message[:params]
521
537
  uri = URI(params.dig(:data, :uri))
522
538
  document = @store.get(uri)
539
+
540
+ unless document.is_a?(RubyDocument)
541
+ send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
542
+ raise Requests::CodeActionResolve::CodeActionError
543
+ end
544
+
523
545
  result = Requests::CodeActionResolve.new(document, params).perform
524
546
 
525
547
  case result
@@ -862,7 +884,7 @@ module RubyLsp
862
884
 
863
885
  if File.exist?(index_path)
864
886
  begin
865
- RubyIndexer.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
887
+ @global_state.index.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
866
888
  send_message(
867
889
  Notification.new(
868
890
  method: "window/showMessage",
@@ -891,7 +913,7 @@ module RubyLsp
891
913
  return unless indexing_options
892
914
 
893
915
  # The index expects snake case configurations, but VS Code standardizes on camel case settings
894
- RubyIndexer.configuration.apply_config(
916
+ @global_state.index.configuration.apply_config(
895
917
  indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
896
918
  )
897
919
  end
@@ -53,6 +53,7 @@ module RubyLsp
53
53
  class Notification < Message
54
54
  class << self
55
55
  extend T::Sig
56
+
56
57
  sig { params(message: String).returns(Notification) }
57
58
  def window_show_error(message)
58
59
  new(
@@ -63,6 +64,14 @@ module RubyLsp
63
64
  ),
64
65
  )
65
66
  end
67
+
68
+ sig { params(message: String, type: Integer).returns(Notification) }
69
+ def window_log_message(message, type: Constant::MessageType::LOG)
70
+ new(
71
+ method: "window/logMessage",
72
+ params: Interface::LogMessageParams.new(type: type, message: message),
73
+ )
74
+ end
66
75
  end
67
76
 
68
77
  extend T::Sig
@@ -122,6 +131,9 @@ module RubyLsp
122
131
  sig { returns(T.untyped) }
123
132
  attr_reader :response
124
133
 
134
+ sig { returns(Integer) }
135
+ attr_reader :id
136
+
125
137
  sig { params(id: Integer, response: T.untyped).void }
126
138
  def initialize(id:, response:)
127
139
  @id = id
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.12
4
+ version: 0.17.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-08 00:00:00.000000000 Z
11
+ date: 2024-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -206,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
206
  - !ruby/object:Gem::Version
207
207
  version: '0'
208
208
  requirements: []
209
- rubygems_version: 3.5.16
209
+ rubygems_version: 3.5.17
210
210
  signing_key:
211
211
  specification_version: 4
212
212
  summary: An opinionated language server for Ruby