ruby-lsp 0.13.0 → 0.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6bace664e1540c1333df9dabfb2e7bdd8bda0f910033b47b188e4a82ddacb15
4
- data.tar.gz: be9e5da0492b26b5cb6e07074d01d7874104b70286c1578342b9639158fed147
3
+ metadata.gz: 78df9c8d35fc18cea6fc06a0f679b48b69e3daaaf504d762d2f5ae4df9d99ad0
4
+ data.tar.gz: 3c251eed7939e81c5aa115d303b2876d3769c509ef0487b0faa26d1a2de79c50
5
5
  SHA512:
6
- metadata.gz: 7d8e61433686271a68dafe8db79765ed509a115ab6f06060d7669d279f48218fe2eada2c12e3b45f155d27a41774771681cdab92700d27f426e75ed89287ca03
7
- data.tar.gz: 611c050b751050934d0fd93e9e52089fe63fc4d947be75a440dfa0465933814d2682268a10135d036c2cee83fa739156d2b11e42e679ecbed7de62acfee9d621
6
+ metadata.gz: af33b81309429318e7fe61dc4a2bd989328b8e9127097ab621eab162e61e1b2d93f1be1b192307d3b48f47659489343e093d92125e9c68895c40517803dba5ed
7
+ data.tar.gz: 6373eb77209eb865e29026fba603f4db8d3249949bcff7c0aee3f550569a7225c84b76d5872fd61a8798c0ef04da2d538b50b4d3045697ef40cc85336a2c715b
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0
1
+ 0.13.1
@@ -106,6 +106,21 @@ module RubyIndexer
106
106
  class OptionalKeywordParameter < Parameter
107
107
  end
108
108
 
109
+ # A rest method parameter, e.g. `def foo(*a)`
110
+ class RestParameter < Parameter
111
+ DEFAULT_NAME = T.let(:"<anonymous splat>", Symbol)
112
+ end
113
+
114
+ # A keyword rest method parameter, e.g. `def foo(**a)`
115
+ class KeywordRestParameter < Parameter
116
+ DEFAULT_NAME = T.let(:"<anonymous keyword splat>", Symbol)
117
+ end
118
+
119
+ # A block method parameter, e.g. `def foo(&block)`
120
+ class BlockParameter < Parameter
121
+ DEFAULT_NAME = T.let(:"<anonymous block>", Symbol)
122
+ end
123
+
109
124
  class Member < Entry
110
125
  extend T::Sig
111
126
  extend T::Helpers
@@ -203,17 +218,50 @@ module RubyIndexer
203
218
  end
204
219
  end
205
220
 
221
+ rest = parameters_node.rest
222
+
223
+ if rest
224
+ rest_name = rest.name || RestParameter::DEFAULT_NAME
225
+ parameters << RestParameter.new(name: rest_name)
226
+ end
227
+
228
+ keyword_rest = parameters_node.keyword_rest
229
+
230
+ if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
231
+ keyword_rest_name = parameter_name(keyword_rest) || KeywordRestParameter::DEFAULT_NAME
232
+ parameters << KeywordRestParameter.new(name: keyword_rest_name)
233
+ end
234
+
235
+ parameters_node.posts.each do |post|
236
+ name = parameter_name(post)
237
+ next unless name
238
+
239
+ parameters << RequiredParameter.new(name: name)
240
+ end
241
+
242
+ block = parameters_node.block
243
+ parameters << BlockParameter.new(name: block.name || BlockParameter::DEFAULT_NAME) if block
244
+
206
245
  parameters
207
246
  end
208
247
 
209
- sig { params(node: Prism::Node).returns(T.nilable(Symbol)) }
248
+ sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(Symbol)) }
210
249
  def parameter_name(node)
211
250
  case node
212
251
  when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
213
- Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode
252
+ Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
253
+ Prism::RestParameterNode, Prism::KeywordRestParameterNode
214
254
  node.name
215
255
  when Prism::MultiTargetNode
216
- names = [*node.lefts, *node.rest, *node.rights].map { |parameter_node| parameter_name(parameter_node) }
256
+ names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }
257
+
258
+ rest = node.rest
259
+ if rest.is_a?(Prism::SplatNode)
260
+ name = rest.expression&.slice
261
+ names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
262
+ end
263
+
264
+ names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })
217
265
 
218
266
  names_with_commas = names.join(", ")
219
267
  :"(#{names_with_commas})"
@@ -106,6 +106,142 @@ module RubyIndexer
106
106
  assert_instance_of(Entry::OptionalKeywordParameter, b)
107
107
  end
108
108
 
109
+ def test_method_with_rest_and_keyword_rest_parameters
110
+ index(<<~RUBY)
111
+ class Foo
112
+ def bar(*a, **b)
113
+ end
114
+ end
115
+ RUBY
116
+
117
+ assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
118
+ entry = T.must(@index["bar"].first)
119
+ assert_equal(2, entry.parameters.length)
120
+ a, b = entry.parameters
121
+
122
+ assert_equal(:a, a.name)
123
+ assert_instance_of(Entry::RestParameter, a)
124
+
125
+ assert_equal(:b, b.name)
126
+ assert_instance_of(Entry::KeywordRestParameter, b)
127
+ end
128
+
129
+ def test_method_with_post_parameters
130
+ index(<<~RUBY)
131
+ class Foo
132
+ def bar(*a, b)
133
+ end
134
+
135
+ def baz(**a, b)
136
+ end
137
+
138
+ def qux(*a, (b, c))
139
+ end
140
+ RUBY
141
+
142
+ assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
143
+ entry = T.must(@index["bar"].first)
144
+ assert_equal(2, entry.parameters.length)
145
+ a, b = entry.parameters
146
+
147
+ assert_equal(:a, a.name)
148
+ assert_instance_of(Entry::RestParameter, a)
149
+
150
+ assert_equal(:b, b.name)
151
+ assert_instance_of(Entry::RequiredParameter, b)
152
+
153
+ entry = T.must(@index["baz"].first)
154
+ assert_equal(2, entry.parameters.length)
155
+ a, b = entry.parameters
156
+
157
+ assert_equal(:a, a.name)
158
+ assert_instance_of(Entry::KeywordRestParameter, a)
159
+
160
+ assert_equal(:b, b.name)
161
+ assert_instance_of(Entry::RequiredParameter, b)
162
+
163
+ entry = T.must(@index["qux"].first)
164
+ assert_equal(2, entry.parameters.length)
165
+ _a, second = entry.parameters
166
+
167
+ assert_equal(:"(b, c)", second.name)
168
+ assert_instance_of(Entry::RequiredParameter, second)
169
+ end
170
+
171
+ def test_method_with_destructured_rest_parameters
172
+ index(<<~RUBY)
173
+ class Foo
174
+ def bar((a, *b))
175
+ end
176
+ end
177
+ RUBY
178
+
179
+ assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
180
+ entry = T.must(@index["bar"].first)
181
+ assert_equal(1, entry.parameters.length)
182
+ param = entry.parameters.first
183
+
184
+ assert_equal(:"(a, *b)", param.name)
185
+ assert_instance_of(Entry::RequiredParameter, param)
186
+ end
187
+
188
+ def test_method_with_block_parameters
189
+ index(<<~RUBY)
190
+ class Foo
191
+ def bar(&block)
192
+ end
193
+
194
+ def baz(&)
195
+ end
196
+ end
197
+ RUBY
198
+
199
+ entry = T.must(@index["bar"].first)
200
+ param = entry.parameters.first
201
+ assert_equal(:block, param.name)
202
+ assert_instance_of(Entry::BlockParameter, param)
203
+
204
+ entry = T.must(@index["baz"].first)
205
+ assert_equal(1, entry.parameters.length)
206
+
207
+ param = entry.parameters.first
208
+ assert_equal(Entry::BlockParameter::DEFAULT_NAME, param.name)
209
+ assert_instance_of(Entry::BlockParameter, param)
210
+ end
211
+
212
+ def test_method_with_anonymous_rest_parameters
213
+ index(<<~RUBY)
214
+ class Foo
215
+ def bar(*, **)
216
+ end
217
+ end
218
+ RUBY
219
+
220
+ assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
221
+ entry = T.must(@index["bar"].first)
222
+ assert_equal(2, entry.parameters.length)
223
+ first, second = entry.parameters
224
+
225
+ assert_equal(Entry::RestParameter::DEFAULT_NAME, first.name)
226
+ assert_instance_of(Entry::RestParameter, first)
227
+
228
+ assert_equal(Entry::KeywordRestParameter::DEFAULT_NAME, second.name)
229
+ assert_instance_of(Entry::KeywordRestParameter, second)
230
+ end
231
+
232
+ def test_method_with_forbidden_keyword_splat_parameter
233
+ index(<<~RUBY)
234
+ class Foo
235
+ def bar(**nil)
236
+ end
237
+ end
238
+ RUBY
239
+
240
+ assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
241
+ entry = T.must(@index["bar"].first)
242
+ assert_empty(entry.parameters)
243
+ end
244
+
109
245
  def test_keeps_track_of_method_owner
110
246
  index(<<~RUBY)
111
247
  class Foo
@@ -24,6 +24,9 @@ module RubyLsp
24
24
  sig { returns(URI::Generic) }
25
25
  attr_reader :uri
26
26
 
27
+ sig { returns(String) }
28
+ attr_reader :encoding
29
+
27
30
  sig { params(source: String, version: Integer, uri: URI::Generic, encoding: String).void }
28
31
  def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
29
32
  @cache = T.let({}, T::Hash[String, T.untyped])
@@ -98,7 +98,8 @@ module RubyLsp
98
98
  folding_range = Requests::FoldingRanges.new(document.parse_result.comments, dispatcher)
99
99
  document_symbol = Requests::DocumentSymbol.new(dispatcher)
100
100
  document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
101
- code_lens = Requests::CodeLens.new(uri, dispatcher)
101
+ lenses_configuration = T.must(@store.features_configuration.dig(:codeLens))
102
+ code_lens = Requests::CodeLens.new(uri, lenses_configuration, dispatcher)
102
103
 
103
104
  semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
104
105
  dispatcher.dispatch(document.tree)
@@ -392,7 +393,8 @@ module RubyLsp
392
393
  end_line = range.dig(:end, :line)
393
394
 
394
395
  dispatcher = Prism::Dispatcher.new
395
- listener = Requests::InlayHints.new(start_line..end_line, dispatcher)
396
+ hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
397
+ listener = Requests::InlayHints.new(start_line..end_line, hints_configurations, dispatcher)
396
398
  dispatcher.visit(document.tree)
397
399
  listener.response
398
400
  end
@@ -602,6 +604,11 @@ module RubyLsp
602
604
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
603
605
  @store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
604
606
 
607
+ configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
608
+ configured_lenses = options.dig(:initializationOptions, :featuresConfiguration, :codeLens)
609
+ T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
610
+ T.must(@store.features_configuration.dig(:codeLens)).configuration.merge!(configured_lenses) if configured_lenses
611
+
605
612
  enabled_features = case configured_features
606
613
  when Array
607
614
  # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
@@ -663,7 +670,7 @@ module RubyLsp
663
670
  on_type_formatting_provider = if enabled_features["onTypeFormatting"]
664
671
  Interface::DocumentOnTypeFormattingOptions.new(
665
672
  first_trigger_character: "{",
666
- more_trigger_character: ["\n", "|"],
673
+ more_trigger_character: ["\n", "|", "d"],
667
674
  )
668
675
  end
669
676
 
@@ -38,14 +38,8 @@ module RubyLsp
38
38
  def run
39
39
  diagnostics = @context[:diagnostics]
40
40
 
41
- code_actions = diagnostics.filter_map do |diagnostic|
42
- code_action = diagnostic.dig(:data, :code_action)
43
- next if code_action.nil?
44
-
45
- # We want to return only code actions that are within range or that do not have any edits, such as refactor
46
- # code actions
47
- range = code_action.dig(:edit, :documentChanges, 0, :edits, 0, :range)
48
- code_action if diagnostic.dig(:data, :correctable) && cover?(range)
41
+ code_actions = diagnostics.flat_map do |diagnostic|
42
+ diagnostic.dig(:data, :code_actions) || []
49
43
  end
50
44
 
51
45
  # Only add refactor actions if there's a non empty selection in the editor
@@ -55,14 +49,6 @@ module RubyLsp
55
49
 
56
50
  private
57
51
 
58
- sig { params(range: T.nilable(Document::RangeShape)).returns(T::Boolean) }
59
- def cover?(range)
60
- range.nil? ||
61
- ((@range.dig(:start, :line))..(@range.dig(:end, :line))).cover?(
62
- (range.dig(:start, :line))..(range.dig(:end, :line)),
63
- )
64
- end
65
-
66
52
  sig { params(range: Document::RangeShape, uri: URI::Generic).returns(Interface::CodeAction) }
67
53
  def refactor_code_action(range, uri)
68
54
  Interface::CodeAction.new(
@@ -11,6 +11,10 @@ module RubyLsp
11
11
  # [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
12
12
  # request informs the editor of runnable commands such as tests
13
13
  #
14
+ # # Configuration
15
+ #
16
+ # To disable gem code lenses, set `rubyLsp.featuresConfiguration.codeLens.gemfileLinks` to `false`.
17
+ #
14
18
  # # Example
15
19
  #
16
20
  # ```ruby
@@ -47,8 +51,14 @@ module RubyLsp
47
51
  sig { override.returns(ResponseType) }
48
52
  attr_reader :_response
49
53
 
50
- sig { params(uri: URI::Generic, dispatcher: Prism::Dispatcher).void }
51
- def initialize(uri, dispatcher)
54
+ sig do
55
+ params(
56
+ uri: URI::Generic,
57
+ lenses_configuration: RequestConfig,
58
+ dispatcher: Prism::Dispatcher,
59
+ ).void
60
+ end
61
+ def initialize(uri, lenses_configuration, dispatcher)
52
62
  @uri = T.let(uri, URI::Generic)
53
63
  @_response = T.let([], ResponseType)
54
64
  @path = T.let(uri.to_standardized_path, T.nilable(String))
@@ -57,6 +67,7 @@ module RubyLsp
57
67
  @class_stack = T.let([], T::Array[String])
58
68
  @group_id = T.let(1, Integer)
59
69
  @group_id_stack = T.let([], T::Array[Integer])
70
+ @lenses_configuration = lenses_configuration
60
71
 
61
72
  super(dispatcher)
62
73
 
@@ -134,6 +145,8 @@ module RubyLsp
134
145
  end
135
146
 
136
147
  if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
148
+ return unless @lenses_configuration.enabled?(:gemfileLinks)
149
+
137
150
  first_argument = arguments.arguments.first
138
151
  return unless first_argument.is_a?(Prism::StringNode)
139
152
 
@@ -9,6 +9,14 @@ module RubyLsp
9
9
  # are labels added directly in the code that explicitly show the user something that might
10
10
  # otherwise just be implied.
11
11
  #
12
+ # # Configuration
13
+ #
14
+ # To enable rescue hints, set `rubyLsp.featuresConfiguration.inlayHint.implicitRescue` to `true`.
15
+ #
16
+ # To enable hash value hints, set `rubyLsp.featuresConfiguration.inlayHint.implicitHashValue` to `true`.
17
+ #
18
+ # To enable all hints, set `rubyLsp.featuresConfiguration.inlayHint.enableAll` to `true`.
19
+ #
12
20
  # # Example
13
21
  #
14
22
  # ```ruby
@@ -39,18 +47,26 @@ module RubyLsp
39
47
  sig { override.returns(ResponseType) }
40
48
  attr_reader :_response
41
49
 
42
- sig { params(range: T::Range[Integer], dispatcher: Prism::Dispatcher).void }
43
- def initialize(range, dispatcher)
50
+ sig do
51
+ params(
52
+ range: T::Range[Integer],
53
+ hints_configuration: RequestConfig,
54
+ dispatcher: Prism::Dispatcher,
55
+ ).void
56
+ end
57
+ def initialize(range, hints_configuration, dispatcher)
44
58
  super(dispatcher)
45
59
 
46
60
  @_response = T.let([], ResponseType)
47
61
  @range = range
62
+ @hints_configuration = hints_configuration
48
63
 
49
64
  dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
50
65
  end
51
66
 
52
67
  sig { params(node: Prism::RescueNode).void }
53
68
  def on_rescue_node_enter(node)
69
+ return unless @hints_configuration.enabled?(:implicitRescue)
54
70
  return unless node.exceptions.empty?
55
71
 
56
72
  loc = node.location
@@ -66,6 +82,7 @@ module RubyLsp
66
82
 
67
83
  sig { params(node: Prism::ImplicitNode).void }
68
84
  def on_implicit_node_enter(node)
85
+ return unless @hints_configuration.enabled?(:implicitHashValue)
69
86
  return unless visible?(node, @range)
70
87
 
71
88
  node_value = node.value
@@ -60,6 +60,8 @@ module RubyLsp
60
60
  handle_statement_end
61
61
  end
62
62
  end
63
+ when "d"
64
+ auto_indent_after_end_keyword
63
65
  end
64
66
 
65
67
  @edits
@@ -118,7 +120,7 @@ module RubyLsp
118
120
  current_line = @lines[@position[:line]]
119
121
  next_line = @lines[@position[:line] + 1]
120
122
 
121
- if current_line.nil? || current_line.strip.empty?
123
+ if current_line.nil? || current_line.strip.empty? || current_line.include?(")") || current_line.include?("]")
122
124
  add_edit_with_text("\n")
123
125
  add_edit_with_text("#{indents}end")
124
126
  move_cursor_to(@position[:line], @indentation + 2)
@@ -185,6 +187,32 @@ module RubyLsp
185
187
 
186
188
  count
187
189
  end
190
+
191
+ sig { void }
192
+ def auto_indent_after_end_keyword
193
+ current_line = @lines[@position[:line]]
194
+ return unless current_line&.strip == "end"
195
+
196
+ target, _parent, _nesting = @document.locate_node({
197
+ line: @position[:line],
198
+ character: @position[:character] - 1,
199
+ })
200
+
201
+ statements = case target
202
+ when Prism::IfNode, Prism::UnlessNode, Prism::ForNode, Prism::WhileNode, Prism::UntilNode
203
+ target.statements
204
+ end
205
+ return unless statements
206
+
207
+ statements.body.each do |node|
208
+ loc = node.location
209
+ next unless loc.start_column == @indentation
210
+
211
+ add_edit_with_text(" ", { line: loc.start_line - 1, character: 0 })
212
+ end
213
+
214
+ move_cursor_to(@position[:line], @position[:character])
215
+ end
188
216
  end
189
217
  end
190
218
  end
@@ -19,30 +19,23 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
- sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
23
- def initialize(offense, uri)
22
+ # TODO: avoid passing document once we have alternative ways to get at
23
+ # encoding and file source
24
+ sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
25
+ def initialize(document, offense, uri)
26
+ @document = document
24
27
  @offense = offense
25
28
  @uri = uri
26
29
  end
27
30
 
28
- sig { returns(Interface::CodeAction) }
29
- def to_lsp_code_action
30
- Interface::CodeAction.new(
31
- title: "Autocorrect #{@offense.cop_name}",
32
- kind: Constant::CodeActionKind::QUICK_FIX,
33
- edit: Interface::WorkspaceEdit.new(
34
- document_changes: [
35
- Interface::TextDocumentEdit.new(
36
- text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
37
- uri: @uri.to_s,
38
- version: nil,
39
- ),
40
- edits: @offense.correctable? ? offense_replacements : [],
41
- ),
42
- ],
43
- ),
44
- is_preferred: true,
45
- )
31
+ sig { returns(T::Array[Interface::CodeAction]) }
32
+ def to_lsp_code_actions
33
+ code_actions = []
34
+
35
+ code_actions << autocorrect_action if @offense.correctable?
36
+ code_actions << disable_line_action
37
+
38
+ code_actions
46
39
  end
47
40
 
48
41
  sig { returns(Interface::Diagnostic) }
@@ -65,7 +58,7 @@ module RubyLsp
65
58
  ),
66
59
  data: {
67
60
  correctable: @offense.correctable?,
68
- code_action: to_lsp_code_action,
61
+ code_actions: to_lsp_code_actions,
69
62
  },
70
63
  )
71
64
  end
@@ -90,6 +83,26 @@ module RubyLsp
90
83
  Interface::CodeDescription.new(href: doc_url) if doc_url
91
84
  end
92
85
 
86
+ sig { returns(Interface::CodeAction) }
87
+ def autocorrect_action
88
+ Interface::CodeAction.new(
89
+ title: "Autocorrect #{@offense.cop_name}",
90
+ kind: Constant::CodeActionKind::QUICK_FIX,
91
+ edit: Interface::WorkspaceEdit.new(
92
+ document_changes: [
93
+ Interface::TextDocumentEdit.new(
94
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
95
+ uri: @uri.to_s,
96
+ version: nil,
97
+ ),
98
+ edits: @offense.correctable? ? offense_replacements : [],
99
+ ),
100
+ ],
101
+ ),
102
+ is_preferred: true,
103
+ )
104
+ end
105
+
93
106
  sig { returns(T::Array[Interface::TextEdit]) }
94
107
  def offense_replacements
95
108
  @offense.corrector.as_replacements.map do |range, replacement|
@@ -102,6 +115,64 @@ module RubyLsp
102
115
  )
103
116
  end
104
117
  end
118
+
119
+ sig { returns(Interface::CodeAction) }
120
+ def disable_line_action
121
+ Interface::CodeAction.new(
122
+ title: "Disable #{@offense.cop_name} for this line",
123
+ kind: Constant::CodeActionKind::QUICK_FIX,
124
+ edit: Interface::WorkspaceEdit.new(
125
+ document_changes: [
126
+ Interface::TextDocumentEdit.new(
127
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
128
+ uri: @uri.to_s,
129
+ version: nil,
130
+ ),
131
+ edits: line_disable_comment,
132
+ ),
133
+ ],
134
+ ),
135
+ )
136
+ end
137
+
138
+ sig { returns(T::Array[Interface::TextEdit]) }
139
+ def line_disable_comment
140
+ new_text = if @offense.source_line.include?(" # rubocop:disable ")
141
+ ",#{@offense.cop_name}"
142
+ else
143
+ " # rubocop:disable #{@offense.cop_name}"
144
+ end
145
+
146
+ eol = Interface::Position.new(
147
+ line: @offense.line - 1,
148
+ character: length_of_line(@offense.source_line),
149
+ )
150
+
151
+ # TODO: fails for multiline strings - may be preferable to use block
152
+ # comments to disable some offenses
153
+ inline_comment = Interface::TextEdit.new(
154
+ range: Interface::Range.new(start: eol, end: eol),
155
+ new_text: new_text,
156
+ )
157
+
158
+ [inline_comment]
159
+ end
160
+
161
+ sig { params(line: String).returns(Integer) }
162
+ def length_of_line(line)
163
+ if @document.encoding == Constant::PositionEncodingKind::UTF16
164
+ line_length = 0
165
+ line.codepoints.each do |codepoint|
166
+ line_length += 1
167
+ if codepoint > RubyLsp::Document::Scanner::SURROGATE_PAIR_START
168
+ line_length += 1
169
+ end
170
+ end
171
+ line_length
172
+ else
173
+ line.length
174
+ end
175
+ end
105
176
  end
106
177
  end
107
178
  end
@@ -25,7 +25,7 @@ module RubyLsp
25
25
  @runner.run(filename, document.source)
26
26
 
27
27
  @runner.offenses.map do |offense|
28
- Support::RuboCopDiagnostic.new(offense, uri)
28
+ Support::RuboCopDiagnostic.new(document, offense, uri)
29
29
  end
30
30
  end
31
31
  end
@@ -20,6 +20,9 @@ module RubyLsp
20
20
  sig { returns(URI::Generic) }
21
21
  attr_accessor :workspace_uri
22
22
 
23
+ sig { returns(T::Hash[Symbol, RequestConfig]) }
24
+ attr_accessor :features_configuration
25
+
23
26
  sig { void }
24
27
  def initialize
25
28
  @state = T.let({}, T::Hash[String, Document])
@@ -28,6 +31,20 @@ module RubyLsp
28
31
  @supports_progress = T.let(true, T::Boolean)
29
32
  @experimental_features = T.let(false, T::Boolean)
30
33
  @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
34
+ @features_configuration = T.let(
35
+ {
36
+ codeLens: RequestConfig.new({
37
+ enableAll: false,
38
+ gemfileLinks: true,
39
+ }),
40
+ inlayHint: RequestConfig.new({
41
+ enableAll: false,
42
+ implicitRescue: false,
43
+ implicitHashValue: false,
44
+ }),
45
+ },
46
+ T::Hash[Symbol, RequestConfig],
47
+ )
31
48
  end
32
49
 
33
50
  sig { params(uri: URI::Generic).returns(Document) }
@@ -74,4 +74,22 @@ module RubyLsp
74
74
  @cancelled = true
75
75
  end
76
76
  end
77
+
78
+ # A request configuration, to turn on/off features
79
+ class RequestConfig
80
+ extend T::Sig
81
+
82
+ sig { returns(T::Hash[Symbol, T::Boolean]) }
83
+ attr_accessor :configuration
84
+
85
+ sig { params(configuration: T::Hash[Symbol, T::Boolean]).void }
86
+ def initialize(configuration)
87
+ @configuration = configuration
88
+ end
89
+
90
+ sig { params(feature: Symbol).returns(T.nilable(T::Boolean)) }
91
+ def enabled?(feature)
92
+ @configuration[:enableAll] || @configuration[feature]
93
+ end
94
+ end
77
95
  end
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.13.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-30 00:00:00.000000000 Z
11
+ date: 2023-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -157,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  requirements: []
160
- rubygems_version: 3.4.21
160
+ rubygems_version: 3.4.22
161
161
  signing_key:
162
162
  specification_version: 4
163
163
  summary: An opinionated language server for Ruby