ruby-lsp 0.3.5 → 0.3.7

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