ruby-lsp 0.18.3 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp-check +1 -1
  4. data/lib/core_ext/uri.rb +9 -4
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +63 -32
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +8 -5
  9. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +52 -8
  10. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +324 -0
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
  13. data/lib/ruby_indexer/test/constant_test.rb +8 -0
  14. data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
  15. data/lib/ruby_indexer/test/index_test.rb +3 -0
  16. data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
  17. data/lib/ruby_indexer/test/method_test.rb +10 -0
  18. data/lib/ruby_indexer/test/rbs_indexer_test.rb +22 -0
  19. data/lib/ruby_indexer/test/reference_finder_test.rb +242 -0
  20. data/lib/ruby_lsp/addon.rb +79 -17
  21. data/lib/ruby_lsp/base_server.rb +6 -0
  22. data/lib/ruby_lsp/erb_document.rb +9 -3
  23. data/lib/ruby_lsp/global_state.rb +8 -0
  24. data/lib/ruby_lsp/internal.rb +5 -1
  25. data/lib/ruby_lsp/listeners/completion.rb +1 -1
  26. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  27. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +24 -21
  28. data/lib/ruby_lsp/requests/code_action_resolve.rb +9 -3
  29. data/lib/ruby_lsp/requests/completion.rb +1 -0
  30. data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
  31. data/lib/ruby_lsp/requests/definition.rb +1 -0
  32. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  33. data/lib/ruby_lsp/requests/hover.rb +1 -0
  34. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  35. data/lib/ruby_lsp/requests/range_formatting.rb +55 -0
  36. data/lib/ruby_lsp/requests/references.rb +146 -0
  37. data/lib/ruby_lsp/requests/rename.rb +196 -0
  38. data/lib/ruby_lsp/requests/signature_help.rb +6 -1
  39. data/lib/ruby_lsp/requests/support/common.rb +2 -2
  40. data/lib/ruby_lsp/requests/support/formatter.rb +3 -0
  41. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +6 -0
  42. data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
  43. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +8 -0
  44. data/lib/ruby_lsp/ruby_document.rb +23 -8
  45. data/lib/ruby_lsp/scope.rb +47 -0
  46. data/lib/ruby_lsp/server.rb +127 -34
  47. data/lib/ruby_lsp/static_docs.rb +15 -0
  48. data/lib/ruby_lsp/store.rb +12 -0
  49. data/lib/ruby_lsp/test_helper.rb +1 -1
  50. data/lib/ruby_lsp/type_inferrer.rb +6 -1
  51. data/lib/ruby_lsp/utils.rb +3 -6
  52. data/static_docs/yield.md +81 -0
  53. metadata +21 -8
  54. data/lib/ruby_lsp/parameter_scope.rb +0 -33
@@ -13,6 +13,9 @@ module RubyLsp
13
13
  sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
14
14
  def run_formatting(uri, document); end
15
15
 
16
+ sig { abstract.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
17
+ def run_range_formatting(uri, source, base_indentation); end
18
+
16
19
  sig do
17
20
  abstract.params(
18
21
  uri: URI::Generic,
@@ -28,6 +28,12 @@ module RubyLsp
28
28
  @format_runner.formatted_source
29
29
  end
30
30
 
31
+ # RuboCop does not support range formatting
32
+ sig { override.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
33
+ def run_range_formatting(uri, source, base_indentation)
34
+ nil
35
+ end
36
+
31
37
  sig do
32
38
  override.params(
33
39
  uri: URI::Generic,
@@ -19,6 +19,13 @@ module URI
19
19
  T::Array[Symbol],
20
20
  )
21
21
 
22
+ # `uri` for Ruby 3.4 switched the default parser from RFC2396 to RFC3986. The new parser emits a deprecation
23
+ # warning on a few methods and delegates them to RFC2396, namely `extract`/`make_regexp`/`escape`/`unescape`.
24
+ # On earlier versions of the uri gem, the RFC2396_PARSER constant doesn't exist, so it needs some special
25
+ # handling to select a parser that doesn't emit deprecations. While it was backported to Ruby 3.1, users may
26
+ # have the uri gem in their own bundle and thus not use a compatible version.
27
+ PARSER = T.let(const_defined?(:RFC2396_PARSER) ? RFC2396_PARSER : DEFAULT_PARSER, RFC2396_Parser)
28
+
22
29
  T.unsafe(self).alias_method(:gem_name, :host)
23
30
  T.unsafe(self).alias_method(:line_number, :fragment)
24
31
 
@@ -41,7 +48,7 @@ module URI
41
48
  {
42
49
  scheme: "source",
43
50
  host: gem_name,
44
- path: DEFAULT_PARSER.escape("/#{gem_version}/#{path}"),
51
+ path: PARSER.escape("/#{gem_version}/#{path}"),
45
52
  fragment: line_number,
46
53
  }
47
54
  )
@@ -37,6 +37,14 @@ module RubyLsp
37
37
  SyntaxTree.format(document.source, @options.print_width, options: @options.formatter_options)
38
38
  end
39
39
 
40
+ sig { override.params(uri: URI::Generic, source: String, base_indentation: Integer).returns(T.nilable(String)) }
41
+ def run_range_formatting(uri, source, base_indentation)
42
+ path = uri.to_standardized_path
43
+ return if path && @options.ignore_files.any? { |pattern| File.fnmatch?("*/#{pattern}", path) }
44
+
45
+ SyntaxTree.format(source, @options.print_width, base_indentation, options: @options.formatter_options)
46
+ end
47
+
40
48
  sig do
41
49
  override.params(
42
50
  uri: URI::Generic,
@@ -26,9 +26,10 @@ module RubyLsp
26
26
  node: Prism::Node,
27
27
  char_position: Integer,
28
28
  node_types: T::Array[T.class_of(Prism::Node)],
29
+ encoding: Encoding,
29
30
  ).returns(NodeContext)
30
31
  end
31
- def locate(node, char_position, node_types: [])
32
+ def locate(node, char_position, node_types: [], encoding: Encoding::UTF_8)
32
33
  queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
33
34
  closest = node
34
35
  parent = T.let(nil, T.nilable(Prism::Node))
@@ -61,16 +62,21 @@ module RubyLsp
61
62
 
62
63
  # Skip if the current node doesn't cover the desired position
63
64
  loc = candidate.location
64
- next unless (loc.start_offset...loc.end_offset).cover?(char_position)
65
+ loc_start_offset = loc.start_code_units_offset(encoding)
66
+ loc_end_offset = loc.end_code_units_offset(encoding)
67
+ next unless (loc_start_offset...loc_end_offset).cover?(char_position)
65
68
 
66
69
  # If the node's start character is already past the position, then we should've found the closest node
67
70
  # already
68
- break if char_position < loc.start_offset
71
+ break if char_position < loc_start_offset
69
72
 
70
73
  # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level
71
74
  # and need to pop the stack
72
75
  previous_level = nesting_nodes.last
73
- nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
76
+ if previous_level &&
77
+ (loc_start_offset > previous_level.location.end_code_units_offset(encoding))
78
+ nesting_nodes.pop
79
+ end
74
80
 
75
81
  # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of
76
82
  # the target when it is a constant
@@ -83,8 +89,10 @@ module RubyLsp
83
89
  if candidate.is_a?(Prism::CallNode)
84
90
  arg_loc = candidate.arguments&.location
85
91
  blk_loc = candidate.block&.location
86
- if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
87
- (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
92
+ if (arg_loc && (arg_loc.start_code_units_offset(encoding)...
93
+ arg_loc.end_code_units_offset(encoding)).cover?(char_position)) ||
94
+ (blk_loc && (blk_loc.start_code_units_offset(encoding)...
95
+ blk_loc.end_code_units_offset(encoding)).cover?(char_position))
88
96
  call_node = candidate
89
97
  end
90
98
  end
@@ -94,7 +102,9 @@ module RubyLsp
94
102
 
95
103
  # If the current node is narrower than or equal to the previous closest node, then it is more precise
96
104
  closest_loc = closest.location
97
- if loc.end_offset - loc.start_offset <= closest_loc.end_offset - closest_loc.start_offset
105
+ closest_node_start_offset = closest_loc.start_code_units_offset(encoding)
106
+ closest_node_end_offset = closest_loc.end_code_units_offset(encoding)
107
+ if loc_end_offset - loc_start_offset <= closest_node_end_offset - closest_node_start_offset
98
108
  parent = closest
99
109
  closest = candidate
100
110
  end
@@ -201,7 +211,12 @@ module RubyLsp
201
211
  ).returns(NodeContext)
202
212
  end
203
213
  def locate_node(position, node_types: [])
204
- RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
214
+ RubyDocument.locate(
215
+ @parse_result.value,
216
+ create_scanner.find_char_position(position),
217
+ node_types: node_types,
218
+ encoding: @encoding,
219
+ )
205
220
  end
206
221
  end
207
222
  end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class Scope
6
+ extend T::Sig
7
+
8
+ sig { returns(T.nilable(Scope)) }
9
+ attr_reader :parent
10
+
11
+ sig { params(parent: T.nilable(Scope)).void }
12
+ def initialize(parent = nil)
13
+ @parent = parent
14
+
15
+ # A hash of name => type
16
+ @locals = T.let({}, T::Hash[Symbol, Local])
17
+ end
18
+
19
+ # Add a new local to this scope. The types should only be `:parameter` or `:variable`
20
+ sig { params(name: T.any(String, Symbol), type: Symbol).void }
21
+ def add(name, type)
22
+ @locals[name.to_sym] = Local.new(type)
23
+ end
24
+
25
+ sig { params(name: T.any(String, Symbol)).returns(T.nilable(Local)) }
26
+ def lookup(name)
27
+ sym = name.to_sym
28
+ entry = @locals[sym]
29
+ return entry if entry
30
+ return unless @parent
31
+
32
+ @parent.lookup(sym)
33
+ end
34
+
35
+ class Local
36
+ extend T::Sig
37
+
38
+ sig { returns(Symbol) }
39
+ attr_reader :type
40
+
41
+ sig { params(type: Symbol).void }
42
+ def initialize(type)
43
+ @type = type
44
+ end
45
+ end
46
+ end
47
+ end
@@ -43,6 +43,8 @@ module RubyLsp
43
43
  text_document_semantic_tokens_range(message)
44
44
  when "textDocument/formatting"
45
45
  text_document_formatting(message)
46
+ when "textDocument/rangeFormatting"
47
+ text_document_range_formatting(message)
46
48
  when "textDocument/documentHighlight"
47
49
  text_document_document_highlight(message)
48
50
  when "textDocument/onTypeFormatting"
@@ -67,6 +69,10 @@ module RubyLsp
67
69
  text_document_definition(message)
68
70
  when "textDocument/prepareTypeHierarchy"
69
71
  text_document_prepare_type_hierarchy(message)
72
+ when "textDocument/rename"
73
+ text_document_rename(message)
74
+ when "textDocument/references"
75
+ text_document_references(message)
70
76
  when "typeHierarchy/supertypes"
71
77
  type_hierarchy_supertypes(message)
72
78
  when "typeHierarchy/subtypes"
@@ -85,7 +91,16 @@ module RubyLsp
85
91
  id: message[:id],
86
92
  response:
87
93
  Addon.addons.map do |addon|
88
- { name: addon.name, errored: addon.error? }
94
+ version_method = addon.method(:version)
95
+
96
+ # If the add-on doesn't define a `version` method, we'd be calling the abstract method defined by
97
+ # Sorbet, which would raise an error.
98
+ # Therefore, we only call the method if it's defined by the add-on itself
99
+ if version_method.owner != Addon
100
+ version = addon.version
101
+ end
102
+
103
+ { name: addon.name, version: version, errored: addon.error? }
89
104
  end,
90
105
  ),
91
106
  )
@@ -123,9 +138,9 @@ module RubyLsp
123
138
  send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
124
139
  end
125
140
 
126
- sig { void }
127
- def load_addons
128
- errors = Addon.load_addons(@global_state, @outgoing_queue)
141
+ sig { params(include_project_addons: T::Boolean).void }
142
+ def load_addons(include_project_addons: true)
143
+ errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
129
144
 
130
145
  if errors.any?
131
146
  send_log_message(
@@ -142,7 +157,7 @@ module RubyLsp
142
157
  method: "window/showMessage",
143
158
  params: Interface::ShowMessageParams.new(
144
159
  type: Constant::MessageType::WARNING,
145
- message: "Error loading addons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
160
+ message: "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
146
161
  ),
147
162
  ),
148
163
  )
@@ -227,6 +242,9 @@ module RubyLsp
227
242
  workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.has_type_checker,
228
243
  signature_help_provider: signature_help_provider,
229
244
  type_hierarchy_provider: type_hierarchy_provider,
245
+ rename_provider: !@global_state.has_type_checker,
246
+ references_provider: !@global_state.has_type_checker,
247
+ document_range_formatting_provider: true,
230
248
  experimental: {
231
249
  addon_detection: true,
232
250
  },
@@ -285,8 +303,9 @@ module RubyLsp
285
303
  rescue RuboCop::Error => e
286
304
  # The user may have provided unknown config switches in .rubocop or
287
305
  # is trying to load a non-existant config file.
288
- send_message(Notification.window_show_error(
306
+ send_message(Notification.window_show_message(
289
307
  "RuboCop configuration error: #{e.message}. Formatting will not be available.",
308
+ type: Constant::MessageType::ERROR,
290
309
  ))
291
310
  end
292
311
  end
@@ -319,14 +338,18 @@ module RubyLsp
319
338
  language_id: language_id,
320
339
  )
321
340
 
322
- if document.past_expensive_limit?
341
+ if document.past_expensive_limit? && text_document[:uri].scheme == "file"
342
+ log_message = <<~MESSAGE
343
+ The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
344
+ diagnostics will be disabled.
345
+ MESSAGE
346
+
323
347
  send_message(
324
348
  Notification.new(
325
- method: "window/showMessage",
326
- params: Interface::ShowMessageParams.new(
349
+ method: "window/logMessage",
350
+ params: Interface::LogMessageParams.new(
327
351
  type: Constant::MessageType::WARNING,
328
- message: "This file is too long. For performance reasons, semantic highlighting and " \
329
- "diagnostics will be disabled",
352
+ message: log_message,
330
353
  ),
331
354
  ),
332
355
  )
@@ -505,6 +528,34 @@ module RubyLsp
505
528
  send_message(Result.new(id: message[:id], response: request.perform))
506
529
  end
507
530
 
531
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
532
+ def text_document_range_formatting(message)
533
+ # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
534
+ if @global_state.formatter == "none"
535
+ send_empty_response(message[:id])
536
+ return
537
+ end
538
+
539
+ params = message[:params]
540
+ uri = params.dig(:textDocument, :uri)
541
+ # Do not format files outside of the workspace. For example, if someone is looking at a gem's source code, we
542
+ # don't want to format it
543
+ path = uri.to_standardized_path
544
+ unless path.nil? || path.start_with?(@global_state.workspace_path)
545
+ send_empty_response(message[:id])
546
+ return
547
+ end
548
+
549
+ document = @store.get(uri)
550
+ unless document.is_a?(RubyDocument)
551
+ send_empty_response(message[:id])
552
+ return
553
+ end
554
+
555
+ response = Requests::RangeFormatting.new(@global_state, document, params).perform
556
+ send_message(Result.new(id: message[:id], response: response))
557
+ end
558
+
508
559
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
509
560
  def text_document_formatting(message)
510
561
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
@@ -531,10 +582,16 @@ module RubyLsp
531
582
  response = Requests::Formatting.new(@global_state, document).perform
532
583
  send_message(Result.new(id: message[:id], response: response))
533
584
  rescue Requests::Request::InvalidFormatter => error
534
- send_message(Notification.window_show_error("Configuration error: #{error.message}"))
585
+ send_message(Notification.window_show_message(
586
+ "Configuration error: #{error.message}",
587
+ type: Constant::MessageType::ERROR,
588
+ ))
535
589
  send_empty_response(message[:id])
536
590
  rescue StandardError, LoadError => error
537
- send_message(Notification.window_show_error("Formatting error: #{error.message}"))
591
+ send_message(Notification.window_show_message(
592
+ "Formatting error: #{error.message}",
593
+ type: Constant::MessageType::ERROR,
594
+ ))
538
595
  send_empty_response(message[:id])
539
596
  end
540
597
 
@@ -602,6 +659,44 @@ module RubyLsp
602
659
  )
603
660
  end
604
661
 
662
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
663
+ def text_document_rename(message)
664
+ params = message[:params]
665
+ document = @store.get(params.dig(:textDocument, :uri))
666
+
667
+ unless document.is_a?(RubyDocument)
668
+ send_empty_response(message[:id])
669
+ return
670
+ end
671
+
672
+ send_message(
673
+ Result.new(
674
+ id: message[:id],
675
+ response: Requests::Rename.new(@global_state, @store, document, params).perform,
676
+ ),
677
+ )
678
+ rescue Requests::Rename::InvalidNameError => e
679
+ send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
680
+ end
681
+
682
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
683
+ def text_document_references(message)
684
+ params = message[:params]
685
+ document = @store.get(params.dig(:textDocument, :uri))
686
+
687
+ unless document.is_a?(RubyDocument)
688
+ send_empty_response(message[:id])
689
+ return
690
+ end
691
+
692
+ send_message(
693
+ Result.new(
694
+ id: message[:id],
695
+ response: Requests::References.new(@global_state, @store, document, params).perform,
696
+ ),
697
+ )
698
+ end
699
+
605
700
  sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
606
701
  def sorbet_level(document)
607
702
  return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
@@ -673,30 +768,19 @@ module RubyLsp
673
768
  document = @store.get(uri)
674
769
 
675
770
  unless document.is_a?(RubyDocument)
676
- send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
677
- raise Requests::CodeActionResolve::CodeActionError
771
+ fail_request_and_notify(message[:id], "Code actions are currently only available for Ruby documents")
772
+ return
678
773
  end
679
774
 
680
- result = Requests::CodeActionResolve.new(document, params).perform
775
+ result = Requests::CodeActionResolve.new(document, @global_state, params).perform
681
776
 
682
777
  case result
683
778
  when Requests::CodeActionResolve::Error::EmptySelection
684
- send_message(Notification.window_show_error("Invalid selection for Extract Variable refactor"))
685
- raise Requests::CodeActionResolve::CodeActionError
779
+ fail_request_and_notify(message[:id], "Invalid selection for extract variable refactor")
686
780
  when Requests::CodeActionResolve::Error::InvalidTargetRange
687
- send_message(
688
- Notification.window_show_error(
689
- "Couldn't find an appropriate location to place extracted refactor",
690
- ),
691
- )
692
- raise Requests::CodeActionResolve::CodeActionError
781
+ fail_request_and_notify(message[:id], "Couldn't find an appropriate location to place extracted refactor")
693
782
  when Requests::CodeActionResolve::Error::UnknownCodeAction
694
- send_message(
695
- Notification.window_show_error(
696
- "Unknown code action",
697
- ),
698
- )
699
- raise Requests::CodeActionResolve::CodeActionError
783
+ fail_request_and_notify(message[:id], "Unknown code action")
700
784
  else
701
785
  send_message(Result.new(id: message[:id], response: result))
702
786
  end
@@ -729,10 +813,16 @@ module RubyLsp
729
813
  ),
730
814
  )
731
815
  rescue Requests::Request::InvalidFormatter => error
732
- send_message(Notification.window_show_error("Configuration error: #{error.message}"))
816
+ send_message(Notification.window_show_message(
817
+ "Configuration error: #{error.message}",
818
+ type: Constant::MessageType::ERROR,
819
+ ))
733
820
  send_empty_response(message[:id])
734
821
  rescue StandardError, LoadError => error
735
- send_message(Notification.window_show_error("Error running diagnostics: #{error.message}"))
822
+ send_message(Notification.window_show_message(
823
+ "Error running diagnostics: #{error.message}",
824
+ type: Constant::MessageType::ERROR,
825
+ ))
736
826
  send_empty_response(message[:id])
737
827
  end
738
828
 
@@ -969,7 +1059,9 @@ module RubyLsp
969
1059
  false
970
1060
  end
971
1061
  rescue StandardError => error
972
- send_message(Notification.window_show_error("Error while indexing: #{error.message}"))
1062
+ message = "Error while indexing (see [troubleshooting steps]" \
1063
+ "(https://shopify.github.io/ruby-lsp/troubleshooting#indexing)): #{error.message}"
1064
+ send_message(Notification.window_show_message(message, type: Constant::MessageType::ERROR))
973
1065
  end
974
1066
 
975
1067
  # Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
@@ -1054,8 +1146,9 @@ module RubyLsp
1054
1146
  @global_state.formatter = "none"
1055
1147
 
1056
1148
  send_message(
1057
- Notification.window_show_error(
1149
+ Notification.window_show_message(
1058
1150
  "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
1151
+ type: Constant::MessageType::ERROR,
1059
1152
  ),
1060
1153
  )
1061
1154
  end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # The path to the `static_docs` directory, where we keep long-form static documentation
6
+ STATIC_DOCS_PATH = T.let(File.join(File.dirname(File.dirname(T.must(__dir__))), "static_docs"), String)
7
+
8
+ # A map of keyword => short documentation to be displayed on hover or completion
9
+ KEYWORD_DOCS = T.let(
10
+ {
11
+ "yield" => "Invokes the passed block with the given arguments",
12
+ }.freeze,
13
+ T::Hash[String, String],
14
+ )
15
+ end
@@ -99,6 +99,18 @@ module RubyLsp
99
99
  @state.delete(uri.to_s)
100
100
  end
101
101
 
102
+ sig { params(uri: URI::Generic).returns(T::Boolean) }
103
+ def key?(uri)
104
+ @state.key?(uri.to_s)
105
+ end
106
+
107
+ sig { params(block: T.proc.params(uri: String, document: Document[T.untyped]).void).void }
108
+ def each(&block)
109
+ @state.each do |uri, document|
110
+ block.call(uri, document)
111
+ end
112
+ end
113
+
102
114
  sig do
103
115
  type_parameters(:T)
104
116
  .params(
@@ -42,7 +42,7 @@ module RubyLsp
42
42
  RubyIndexer::IndexablePath.new(nil, T.must(uri.to_standardized_path)),
43
43
  source,
44
44
  )
45
- server.load_addons if load_addons
45
+ server.load_addons(include_project_addons: false) if load_addons
46
46
  block.call(server, uri)
47
47
  ensure
48
48
  if load_addons
@@ -89,7 +89,12 @@ module RubyLsp
89
89
 
90
90
  Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
91
91
  else
92
- raw_receiver = node.receiver&.slice
92
+
93
+ raw_receiver = if receiver.is_a?(Prism::CallNode)
94
+ receiver.message
95
+ else
96
+ receiver&.slice
97
+ end
93
98
 
94
99
  if raw_receiver
95
100
  guessed_name = raw_receiver
@@ -64,14 +64,11 @@ module RubyLsp
64
64
  class << self
65
65
  extend T::Sig
66
66
 
67
- sig { params(message: String).returns(Notification) }
68
- def window_show_error(message)
67
+ sig { params(message: String, type: Integer).returns(Notification) }
68
+ def window_show_message(message, type: Constant::MessageType::INFO)
69
69
  new(
70
70
  method: "window/showMessage",
71
- params: Interface::ShowMessageParams.new(
72
- type: Constant::MessageType::ERROR,
73
- message: message,
74
- ),
71
+ params: Interface::ShowMessageParams.new(type: type, message: message),
75
72
  )
76
73
  end
77
74
 
@@ -0,0 +1,81 @@
1
+ # Yield
2
+
3
+ In Ruby, every method implicitly accepts a block, even when not included in the parameters list.
4
+
5
+ ```ruby
6
+ def foo
7
+ end
8
+
9
+ foo { 123 } # works!
10
+ ```
11
+
12
+ The `yield` keyword is used to invoke the block that was passed with arguments.
13
+
14
+ ```ruby
15
+ # Consider this method call. The block being passed to the method `foo` accepts an argument called `a`.
16
+ # It then takes whatever argument was passed and multiplies it by 2
17
+ foo do |a|
18
+ a * 2
19
+ end
20
+
21
+ # In the `foo` method declaration, we can use `yield` to invoke the block that was passed and provide the block
22
+ # with the value for the `a` argument
23
+ def foo
24
+ # Invoke the block passed to `foo` with the number 10 as the argument `a`
25
+ result = yield(10)
26
+ puts result # Will print 20
27
+ end
28
+ ```
29
+
30
+ If `yield` is used to invoke the block, but no block was passed, that will result in a local jump error.
31
+
32
+ ```ruby
33
+ # If we invoke `foo` without a block, trying to `yield` will fail
34
+ foo
35
+
36
+ # `foo': no block given (yield) (LocalJumpError)
37
+ ```
38
+
39
+ We can decide to use `yield` conditionally by using Ruby's `block_given?` method, which will return `true` if a block
40
+ was passed to the method.
41
+
42
+ ```ruby
43
+ def foo
44
+ # If a block is passed when invoking `foo`, call the block with argument 10 and print the result.
45
+ # Otherwise, just print that no block was passed
46
+ if block_given?
47
+ result = yield(10)
48
+ puts result
49
+ else
50
+ puts "No block passed!"
51
+ end
52
+ end
53
+
54
+ foo do |a|
55
+ a * 2
56
+ end
57
+ # => 20
58
+
59
+ foo
60
+ # => No block passed!
61
+ ```
62
+
63
+ ## Block parameter
64
+
65
+ In addition to implicit blocks, Ruby also allows developers to use explicit block parameters as part of the method's
66
+ signature. In this scenario, we can use the reference to the block directly instead of relying on the `yield` keyword.
67
+
68
+ ```ruby
69
+ # Block parameters are prefixed with & and a name
70
+ def foo(&my_block_param)
71
+ # If a block was passed to `foo`, `my_block_param` will be a `Proc` object. Otherwise, it will be `nil`. We can use
72
+ # that to check for its presence
73
+ if my_block_param
74
+ # Explicit block parameters are invoked using the method `call`, which is present in all `Proc` objects
75
+ result = my_block_param.call(10)
76
+ puts result
77
+ else
78
+ puts "No block passed!"
79
+ end
80
+ end
81
+ ```