ruby-lsp 0.3.5 → 0.3.7

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: 37a06a9c8be918e5c5a22986e68e378fc767f5c5becc46f79baa78d4b00c8c08
4
- data.tar.gz: 4a79a4a39aaf59c49bddd6420e526246e31ade2a944ab314328f0a30da7661c2
3
+ metadata.gz: cddece941249b188843957c1c026e6e1e10f875c26a7c2f14f556a53556cc94f
4
+ data.tar.gz: cdc7368658e3de269033b41bceb831becb68320cd1444ec9792380408ee2a4f2
5
5
  SHA512:
6
- metadata.gz: 698eaf5cf5d213f6a1ffce60e40f1a4e89d599132e1940699f3a98eb5de7b4141cb442338f97c3a35850a0ed239a52a8d27edaf8a59e7a1f169373a8c2fd9fa1
7
- data.tar.gz: d5668dfd0c88482fbb6fab215b1782a2fcfc384504a485b8047fd28a99bb3b958de9bde80ecd213b30ae369bfb50d521c60d361e631d06e398f4efb5731e3158
6
+ metadata.gz: d28c2f96939d517cacae6b86407e5990ca1587f99848bc14ff98ea24b5421e2ad38ba0ee39a7040c2a4694623e349c907c6f2a764737e0cc9316d302804687b2
7
+ data.tar.gz: a5e118206994b0e58ff9568043eb534360e591f931c0c8f078579283ab0519057a236606728c230e4be86f7405035a6e2bea1e52959af280f2067f0e52127278
data/README.md CHANGED
@@ -28,6 +28,14 @@ are expected to adhere to the
28
28
  [Contributor Covenant](https://github.com/Shopify/ruby-lsp/blob/main/CODE_OF_CONDUCT.md)
29
29
  code of conduct.
30
30
 
31
+ ### Running the test suite
32
+
33
+ Run the test suite with `bin/test`.
34
+
35
+ For more visibility into which tests are running, use the `SpecReporter`:
36
+
37
+ `SPEC_REPORTER=1 bin/test`
38
+
31
39
  ### Expectation testing
32
40
 
33
41
  To simplify the way we run tests over different pieces of Ruby code, we use a custom expectations test framework against a set of Ruby fixtures.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.5
1
+ 0.3.7
@@ -18,10 +18,11 @@ module RubyLsp
18
18
  sig { returns(T::Array[EditShape]) }
19
19
  attr_reader :syntax_error_edits
20
20
 
21
- sig { params(source: String).void }
22
- def initialize(source)
21
+ sig { params(source: String, encoding: String).void }
22
+ def initialize(source, encoding = "utf-8")
23
23
  @cache = T.let({}, T::Hash[Symbol, T.untyped])
24
24
  @syntax_error_edits = T.let([], T::Array[EditShape])
25
+ @encoding = T.let(encoding, String)
25
26
  @source = T.let(source, String)
26
27
  @parsable_source = T.let(source.dup, String)
27
28
  @unparsed_edits = T.let([], T::Array[EditShape])
@@ -83,6 +84,11 @@ module RubyLsp
83
84
  !@tree.nil?
84
85
  end
85
86
 
87
+ sig { returns(Scanner) }
88
+ def create_scanner
89
+ Scanner.new(@source, @encoding)
90
+ end
91
+
86
92
  private
87
93
 
88
94
  sig { params(edits: T::Array[EditShape]).void }
@@ -96,15 +102,16 @@ module RubyLsp
96
102
  end
97
103
 
98
104
  @tree = SyntaxTree.parse(@parsable_source)
99
- rescue SyntaxTree::Parser::ParseError
100
- # If we can't parse the source even after emptying the edits, then just fallback to the previous source
105
+ rescue StandardError
106
+ # Trying to maintain a parsable source when there are syntax errors is a best effort. If we fail to apply edits or
107
+ # parse, just ignore it
101
108
  end
102
109
 
103
110
  sig { params(source: String, range: RangeShape, text: String).void }
104
111
  def apply_edit(source, range, text)
105
- scanner = Scanner.new(source)
106
- start_position = scanner.find_position(range[:start])
107
- end_position = scanner.find_position(range[:end])
112
+ scanner = Scanner.new(source, @encoding)
113
+ start_position = scanner.find_char_position(range[:start])
114
+ end_position = scanner.find_char_position(range[:end])
108
115
 
109
116
  source[start_position...end_position] = text
110
117
  end
@@ -112,22 +119,49 @@ module RubyLsp
112
119
  class Scanner
113
120
  extend T::Sig
114
121
 
115
- sig { params(source: String).void }
116
- def initialize(source)
122
+ LINE_BREAK = T.let(0x0A, Integer)
123
+ # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
124
+ SURROGATE_PAIR_START = T.let(0xFFFF, Integer)
125
+
126
+ sig { params(source: String, encoding: String).void }
127
+ def initialize(source, encoding)
117
128
  @current_line = T.let(0, Integer)
118
129
  @pos = T.let(0, Integer)
119
- @source = source
130
+ @source = T.let(source.codepoints, T::Array[Integer])
131
+ @encoding = encoding
120
132
  end
121
133
 
134
+ # Finds the character index inside the source string for a given line and column
122
135
  sig { params(position: PositionShape).returns(Integer) }
123
- def find_position(position)
136
+ def find_char_position(position)
137
+ # Find the character index for the beginning of the requested line
124
138
  until @current_line == position[:line]
125
- @pos += 1 until /\R/.match?(@source[@pos])
139
+ @pos += 1 until LINE_BREAK == @source[@pos]
126
140
  @pos += 1
127
141
  @current_line += 1
128
142
  end
129
143
 
130
- @pos + position[:character]
144
+ # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
145
+ # need to adjust for surrogate pairs
146
+ requested_position = @pos + position[:character]
147
+ requested_position -= utf_16_character_position_correction(@pos, requested_position) if @encoding == "utf-16"
148
+ requested_position
149
+ end
150
+
151
+ # Subtract 1 for each character after 0xFFFF in the current line from the column position, so that we hit the
152
+ # right character in the UTF-8 representation
153
+ sig { params(current_position: Integer, requested_position: Integer).returns(Integer) }
154
+ def utf_16_character_position_correction(current_position, requested_position)
155
+ utf16_unicode_correction = 0
156
+
157
+ until current_position == requested_position
158
+ codepoint = @source[current_position]
159
+ utf16_unicode_correction += 1 if codepoint && codepoint > SURROGATE_PAIR_START
160
+
161
+ current_position += 1
162
+ end
163
+
164
+ utf16_unicode_correction
131
165
  end
132
166
  end
133
167
  end
@@ -79,6 +79,15 @@ module RubyLsp
79
79
  []
80
80
  end
81
81
  end
82
+
83
+ sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
84
+ def visible?(node, range)
85
+ return true if range.nil?
86
+ return false if node.nil?
87
+
88
+ loc = node.location
89
+ range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
90
+ end
82
91
  end
83
92
  end
84
93
  end
@@ -35,7 +35,7 @@ module RubyLsp
35
35
 
36
36
  sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::CodeAction], Object)) }
37
37
  def run
38
- diagnostics = Diagnostics.new(@uri, @document).run
38
+ diagnostics = @document.cache_fetch(:diagnostics) { Diagnostics.new(@uri, @document).run }
39
39
  corrections = diagnostics.select do |diagnostic|
40
40
  diagnostic.correctable? && T.cast(diagnostic, Support::RuboCopDiagnostic).in_range?(@range)
41
41
  end
@@ -30,10 +30,9 @@ module RubyLsp
30
30
  super(document)
31
31
 
32
32
  @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
33
- position = Document::Scanner.new(document.source).find_position(position)
34
-
35
33
  return unless document.parsed?
36
34
 
35
+ position = document.create_scanner.find_char_position(position)
37
36
  @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
38
37
  end
39
38
 
@@ -50,9 +50,8 @@ module RubyLsp
50
50
  spec = stub.to_spec
51
51
  lookup[spec.name] = {}
52
52
  lookup[spec.name][spec.version.to_s] = {}
53
- prefix_matchers = [//]
54
- prefix_matchers.concat(spec.require_paths.map { |rp| Regexp.new("^#{rp}/") })
55
- prefix_matcher = Regexp.union(prefix_matchers)
53
+ prefix_matchers = Regexp.union(spec.require_paths.map { |rp| Regexp.new("^#{rp}/") })
54
+ prefix_matcher = Regexp.union(prefix_matchers, //)
56
55
 
57
56
  spec.files.each do |file|
58
57
  path = file.sub(prefix_matcher, "")
@@ -24,7 +24,7 @@ module RubyLsp
24
24
  def initialize(document, position)
25
25
  super(document)
26
26
 
27
- @position = T.let(Document::Scanner.new(document.source).find_position(position), Integer)
27
+ @position = T.let(document.create_scanner.find_char_position(position), Integer)
28
28
  end
29
29
 
30
30
  sig { override.returns(T.nilable(LanguageServer::Protocol::Interface::Hover)) }
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  return unless node.exception.nil?
41
41
 
42
42
  loc = node.location
43
- return unless @range.cover?(loc.start_line - 1) && @range.cover?(loc.end_line - 1)
43
+ return unless visible?(node, @range)
44
44
 
45
45
  @hints << LanguageServer::Protocol::Interface::InlayHint.new(
46
46
  position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
@@ -27,9 +27,9 @@ module RubyLsp
27
27
  def initialize(document, position, trigger_character)
28
28
  super(document)
29
29
 
30
- scanner = Document::Scanner.new(document.source)
31
- line_begin = position[:line] == 0 ? 0 : scanner.find_position({ line: position[:line] - 1, character: 0 })
32
- line_end = scanner.find_position(position)
30
+ scanner = document.create_scanner
31
+ line_begin = position[:line] == 0 ? 0 : scanner.find_char_position({ line: position[:line] - 1, character: 0 })
32
+ line_end = scanner.find_char_position(position)
33
33
  line = T.must(@document.source[line_begin..line_end])
34
34
 
35
35
  @indentation = T.let(find_indentation(line), Integer)
@@ -76,13 +76,20 @@ module RubyLsp
76
76
  const :modifier, T::Array[Integer]
77
77
  end
78
78
 
79
- sig { params(document: Document, encoder: T.nilable(Support::SemanticTokenEncoder)).void }
80
- def initialize(document, encoder: nil)
79
+ sig do
80
+ params(
81
+ document: Document,
82
+ range: T.nilable(T::Range[Integer]),
83
+ encoder: T.nilable(Support::SemanticTokenEncoder),
84
+ ).void
85
+ end
86
+ def initialize(document, range: nil, encoder: nil)
81
87
  super(document)
82
88
 
83
89
  @encoder = encoder
84
90
  @tokens = T.let([], T::Array[SemanticToken])
85
91
  @tree = T.let(T.must(document.tree), SyntaxTree::Node)
92
+ @range = range
86
93
  @special_methods = T.let(nil, T.nilable(T::Array[String]))
87
94
  end
88
95
 
@@ -105,6 +112,8 @@ module RubyLsp
105
112
 
106
113
  sig { override.params(node: SyntaxTree::Call).void }
107
114
  def visit_call(node)
115
+ return super unless visible?(node, @range)
116
+
108
117
  visit(node.receiver)
109
118
 
110
119
  message = node.message
@@ -115,12 +124,16 @@ module RubyLsp
115
124
 
116
125
  sig { override.params(node: SyntaxTree::Command).void }
117
126
  def visit_command(node)
127
+ return super unless visible?(node, @range)
128
+
118
129
  add_token(node.message.location, :method) unless special_method?(node.message.value)
119
130
  visit(node.arguments)
120
131
  end
121
132
 
122
133
  sig { override.params(node: SyntaxTree::CommandCall).void }
123
134
  def visit_command_call(node)
135
+ return super unless visible?(node, @range)
136
+
124
137
  visit(node.receiver)
125
138
  add_token(node.message.location, :method)
126
139
  visit(node.arguments)
@@ -128,11 +141,15 @@ module RubyLsp
128
141
 
129
142
  sig { override.params(node: SyntaxTree::Const).void }
130
143
  def visit_const(node)
144
+ return super unless visible?(node, @range)
145
+
131
146
  add_token(node.location, :namespace)
132
147
  end
133
148
 
134
149
  sig { override.params(node: SyntaxTree::Def).void }
135
150
  def visit_def(node)
151
+ return super unless visible?(node, @range)
152
+
136
153
  add_token(node.name.location, :method, [:declaration])
137
154
  visit(node.params)
138
155
  visit(node.bodystmt)
@@ -140,6 +157,8 @@ module RubyLsp
140
157
 
141
158
  sig { override.params(node: SyntaxTree::DefEndless).void }
142
159
  def visit_def_endless(node)
160
+ return super unless visible?(node, @range)
161
+
143
162
  add_token(node.name.location, :method, [:declaration])
144
163
  visit(node.paren)
145
164
  visit(node.operator)
@@ -148,6 +167,8 @@ module RubyLsp
148
167
 
149
168
  sig { override.params(node: SyntaxTree::Defs).void }
150
169
  def visit_defs(node)
170
+ return super unless visible?(node, @range)
171
+
151
172
  visit(node.target)
152
173
  visit(node.operator)
153
174
  add_token(node.name.location, :method, [:declaration])
@@ -157,12 +178,16 @@ module RubyLsp
157
178
 
158
179
  sig { override.params(node: SyntaxTree::FCall).void }
159
180
  def visit_fcall(node)
181
+ return super unless visible?(node, @range)
182
+
160
183
  add_token(node.value.location, :method) unless special_method?(node.value.value)
161
184
  visit(node.arguments)
162
185
  end
163
186
 
164
187
  sig { override.params(node: SyntaxTree::Kw).void }
165
188
  def visit_kw(node)
189
+ return super unless visible?(node, @range)
190
+
166
191
  case node.value
167
192
  when "self"
168
193
  add_token(node.location, :variable, [:default_library])
@@ -171,6 +196,8 @@ module RubyLsp
171
196
 
172
197
  sig { override.params(node: SyntaxTree::Params).void }
173
198
  def visit_params(node)
199
+ return super unless visible?(node, @range)
200
+
174
201
  node.keywords.each do |keyword,|
175
202
  location = keyword.location
176
203
  add_token(location_without_colon(location), :parameter)
@@ -191,6 +218,8 @@ module RubyLsp
191
218
 
192
219
  sig { override.params(node: SyntaxTree::Field).void }
193
220
  def visit_field(node)
221
+ return super unless visible?(node, @range)
222
+
194
223
  add_token(node.name.location, :method)
195
224
 
196
225
  super
@@ -198,6 +227,8 @@ module RubyLsp
198
227
 
199
228
  sig { override.params(node: SyntaxTree::VarField).void }
200
229
  def visit_var_field(node)
230
+ return super unless visible?(node, @range)
231
+
201
232
  value = node.value
202
233
 
203
234
  case value
@@ -211,6 +242,8 @@ module RubyLsp
211
242
 
212
243
  sig { override.params(node: SyntaxTree::VarRef).void }
213
244
  def visit_var_ref(node)
245
+ return super unless visible?(node, @range)
246
+
214
247
  value = node.value
215
248
 
216
249
  case value
@@ -224,11 +257,15 @@ module RubyLsp
224
257
 
225
258
  sig { override.params(node: SyntaxTree::VCall).void }
226
259
  def visit_vcall(node)
260
+ return super unless visible?(node, @range)
261
+
227
262
  add_token(node.value.location, :method) unless special_method?(node.value.value)
228
263
  end
229
264
 
230
265
  sig { override.params(node: SyntaxTree::ClassDeclaration).void }
231
266
  def visit_class(node)
267
+ return super unless visible?(node, @range)
268
+
232
269
  add_token(node.constant.location, :class, [:declaration])
233
270
  add_token(node.superclass.location, :class) if node.superclass
234
271
  visit(node.bodystmt)
@@ -236,6 +273,8 @@ module RubyLsp
236
273
 
237
274
  sig { override.params(node: SyntaxTree::ModuleDeclaration).void }
238
275
  def visit_module(node)
276
+ return super unless visible?(node, @range)
277
+
239
278
  add_token(node.constant.location, :class, [:declaration])
240
279
  visit(node.bodystmt)
241
280
  end
@@ -7,6 +7,8 @@ module RubyLsp
7
7
  Handler.start do
8
8
  on("initialize") do |request|
9
9
  store.clear
10
+ store.encoding = request.dig(:params, :capabilities, :general, :positionEncodings)
11
+
10
12
  initialization_options = request.dig(:params, :initializationOptions)
11
13
  enabled_features = initialization_options.fetch(:enabledFeatures, [])
12
14
 
@@ -38,10 +40,8 @@ module RubyLsp
38
40
  token_types: Requests::SemanticHighlighting::TOKEN_TYPES.keys,
39
41
  token_modifiers: Requests::SemanticHighlighting::TOKEN_MODIFIERS.keys,
40
42
  ),
41
- range: false,
42
- full: {
43
- delta: true,
44
- },
43
+ range: true,
44
+ full: { delta: false },
45
45
  )
46
46
  end
47
47
 
@@ -168,6 +168,19 @@ module RubyLsp
168
168
  end
169
169
  end
170
170
 
171
+ on("textDocument/semanticTokens/range", parallel: true) do |request|
172
+ document = store.get(request.dig(:params, :textDocument, :uri))
173
+ range = request.dig(:params, :range)
174
+ start_line = range.dig(:start, :line)
175
+ end_line = range.dig(:end, :line)
176
+
177
+ Requests::SemanticHighlighting.new(
178
+ document,
179
+ range: start_line..end_line,
180
+ encoder: Requests::Support::SemanticTokenEncoder.new,
181
+ ).run
182
+ end
183
+
171
184
  on("textDocument/formatting", parallel: true) do |request|
172
185
  uri = request.dig(:params, :textDocument, :uri)
173
186
 
@@ -192,13 +205,12 @@ module RubyLsp
192
205
 
193
206
  on("textDocument/codeAction", parallel: true) do |request|
194
207
  uri = request.dig(:params, :textDocument, :uri)
208
+ document = store.get(uri)
195
209
  range = request.dig(:params, :range)
196
210
  start_line = range.dig(:start, :line)
197
211
  end_line = range.dig(:end, :line)
198
212
 
199
- store.cache_fetch(uri, :code_actions) do |document|
200
- Requests::CodeActions.new(uri, document, start_line..end_line).run
201
- end
213
+ Requests::CodeActions.new(uri, document, start_line..end_line).run
202
214
  end
203
215
 
204
216
  on("textDocument/inlayHint", parallel: true) do |request|
@@ -9,9 +9,13 @@ module RubyLsp
9
9
  class Store
10
10
  extend T::Sig
11
11
 
12
+ sig { params(encoding: String).void }
13
+ attr_writer :encoding
14
+
12
15
  sig { void }
13
16
  def initialize
14
17
  @state = T.let({}, T::Hash[String, Document])
18
+ @encoding = T.let("utf-8", String)
15
19
  end
16
20
 
17
21
  sig { params(uri: String).returns(Document) }
@@ -25,7 +29,7 @@ module RubyLsp
25
29
 
26
30
  sig { params(uri: String, content: String).void }
27
31
  def set(uri, content)
28
- document = Document.new(content)
32
+ document = Document.new(content, @encoding)
29
33
  @state[uri] = document
30
34
  end
31
35
 
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.3.5
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-25 00:00:00.000000000 Z
11
+ date: 2022-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -45,6 +45,9 @@ dependencies:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: 4.0.2
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: 5.0.0
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
@@ -52,6 +55,9 @@ dependencies:
52
55
  - - ">="
53
56
  - !ruby/object:Gem::Version
54
57
  version: 4.0.2
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: 5.0.0
55
61
  description: An opinionated language server for Ruby
56
62
  email:
57
63
  - ruby@shopify.com