ruby-lsp 0.13.0 → 0.13.1

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