ruby-lsp 0.17.12 → 0.17.13

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.
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