ruby-lsp 0.4.3 → 0.4.5

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: f7bf79fd6cdc704c8f874f58985f0e72ac034c76bb70a68166654185f6f00cf1
4
- data.tar.gz: '083edc726c05ffb6e5e3ae63d480b17fa0f7161ee62349665cc57e309acd5598'
3
+ metadata.gz: 437c2048862bed450fd70057489636117c99943dd7bb68df35c4e161a29f8385
4
+ data.tar.gz: 30d26e8455f9086bc934ffe23b5e37816f18f5904e3422495c316a980a860cfc
5
5
  SHA512:
6
- metadata.gz: f6192daf27dfd82dbb505f5184eebd66308690ce10ead4d4cedc4d9c6667be882a8b9de4c87e66eb88ff43e896307be24bbcc6506b0544b496e1bcb3c9804498
7
- data.tar.gz: bb68928d9a72b3b2873509bf915f753a202a520187bf5f55da1870d552e0ba0917a7d21ea70d20853ac60aa614f7d85d117d7291ec9c0778cab80e4561a7aba0
6
+ metadata.gz: 366dd4b756ded58762b598545e398059fe0eae7578b13c92cc0a90dcc9aba2d58106dbf2dc0e9867a9db7de5007e6114ebbbf7cf191bcdebb843b077de945ba9
7
+ data.tar.gz: 1f798c1462825cccafd48bff23baecb8ba8ef36f9b5733502d79c5d8e6e52550c7ab722a99678eec04ce2a8b42c4d84aab912acb2100ff7d26e422b02f2c185b
data/README.md CHANGED
@@ -44,6 +44,7 @@ See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth de
44
44
  ## Learn More
45
45
 
46
46
  * [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
47
+ * [Remote Ruby: Ruby Language Server with Vinicius Stock](https://remoteruby.com/221)
47
48
 
48
49
  ## Contributing
49
50
 
@@ -122,7 +123,7 @@ To add a new expectations test runner for a new request handler:
122
123
  3. [`vscode-ruby-lsp`] Select `Run Extension` and click the green triangle (or press F5).
123
124
  4. [`vscode-ruby-lsp`] Now VS Code will:
124
125
  - Open another workspace as the `Extension Development Host`.
125
- - Run `vscode-ruby-lsp` extension in debug mode, which will start a new `ruby-lsp` process with the `--debug` flag.
126
+ - Run `vscode-ruby-lsp` extension in debug mode, which will start a new `ruby-lsp` process with the `--debug` flag. Note that debugging is not available on Windows.
126
127
  5. Open `ruby-lsp` in VS Code.
127
128
  6. [`ruby-lsp`] Run `bin/rdbg -A` to connect to the running `ruby-lsp` process.
128
129
  7. [`ruby-lsp`] Use commands like `b <file>:<line>` or `b Class#method` to set breakpoints and type `c` to continue the process.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.4.5
data/exe/ruby-lsp CHANGED
@@ -21,6 +21,11 @@ end
21
21
  require_relative "../lib/ruby_lsp/internal"
22
22
 
23
23
  if ARGV.include?("--debug")
24
+ if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
25
+ puts "Debugging is not supported on Windows"
26
+ exit 1
27
+ end
28
+
24
29
  sockets_dir = "/tmp/ruby-lsp-debug-sockets"
25
30
  Dir.mkdir(sockets_dir) unless Dir.exist?(sockets_dir)
26
31
  # ruby-debug-ENV["USER"] is an implicit naming pattern in ruby/debug
@@ -28,7 +33,11 @@ if ARGV.include?("--debug")
28
33
  socket_identifier = "ruby-debug-#{ENV["USER"]}-#{File.basename(Dir.pwd)}.sock"
29
34
  ENV["RUBY_DEBUG_SOCK_PATH"] = "#{sockets_dir}/#{socket_identifier}"
30
35
 
31
- require "debug/open_nonstop"
36
+ begin
37
+ require "debug/open_nonstop"
38
+ rescue LoadError
39
+ warn("You need to install the debug gem to use the --debug flag")
40
+ end
32
41
  end
33
42
 
34
43
  RubyLsp::Server.new.start
@@ -22,7 +22,7 @@ module RubyLsp
22
22
  attr_reader :uri
23
23
 
24
24
  sig { params(source: String, version: Integer, uri: String, encoding: String).void }
25
- def initialize(source:, version:, uri:, encoding: "utf-8")
25
+ def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
26
26
  @cache = T.let({}, T::Hash[Symbol, T.untyped])
27
27
  @encoding = T.let(encoding, String)
28
28
  @source = T.let(source, String)
@@ -77,9 +77,9 @@ module RubyLsp
77
77
  def parse
78
78
  return if @unparsed_edits.empty?
79
79
 
80
+ @unparsed_edits.clear
80
81
  @tree = SyntaxTree.parse(@source)
81
82
  @syntax_error = false
82
- @unparsed_edits.clear
83
83
  rescue SyntaxTree::Parser::ParseError
84
84
  @syntax_error = true
85
85
  end
@@ -99,6 +99,61 @@ module RubyLsp
99
99
  Scanner.new(@source, @encoding)
100
100
  end
101
101
 
102
+ sig do
103
+ params(
104
+ position: PositionShape,
105
+ node_types: T::Array[T.class_of(SyntaxTree::Node)],
106
+ ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
107
+ end
108
+ def locate_node(position, node_types: [])
109
+ return [nil, nil] unless parsed?
110
+
111
+ locate(T.must(@tree), create_scanner.find_char_position(position))
112
+ end
113
+
114
+ sig do
115
+ params(
116
+ node: SyntaxTree::Node,
117
+ char_position: Integer,
118
+ node_types: T::Array[T.class_of(SyntaxTree::Node)],
119
+ ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
120
+ end
121
+ def locate(node, char_position, node_types: [])
122
+ queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
123
+ closest = node
124
+ parent = T.let(nil, T.nilable(SyntaxTree::Node))
125
+
126
+ until queue.empty?
127
+ candidate = queue.shift
128
+
129
+ # Skip nil child nodes
130
+ next if candidate.nil?
131
+
132
+ # Add the next child_nodes to the queue to be processed
133
+ queue.concat(candidate.child_nodes)
134
+
135
+ # Skip if the current node doesn't cover the desired position
136
+ loc = candidate.location
137
+ next unless (loc.start_char...loc.end_char).cover?(char_position)
138
+
139
+ # If the node's start character is already past the position, then we should've found the closest node
140
+ # already
141
+ break if char_position < loc.start_char
142
+
143
+ # If there are node types to filter by, and the current node is not one of those types, then skip it
144
+ next if node_types.any? && node_types.none? { |type| candidate.class == type }
145
+
146
+ # If the current node is narrower than or equal to the previous closest node, then it is more precise
147
+ closest_loc = closest.location
148
+ if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
149
+ parent = closest
150
+ closest = candidate
151
+ end
152
+ end
153
+
154
+ [closest, parent]
155
+ end
156
+
102
157
  class Scanner
103
158
  extend T::Sig
104
159
 
@@ -127,7 +182,11 @@ module RubyLsp
127
182
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
128
183
  # need to adjust for surrogate pairs
129
184
  requested_position = @pos + position[:character]
130
- requested_position -= utf_16_character_position_correction(@pos, requested_position) if @encoding == "utf-16"
185
+
186
+ if @encoding == Constant::PositionEncodingKind::UTF16
187
+ requested_position -= utf_16_character_position_correction(@pos, requested_position)
188
+ end
189
+
131
190
  requested_position
132
191
  end
133
192
 
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # EventEmitter is an intermediary between our requests and Syntax Tree visitors. It's used to visit the document's AST
6
+ # and emit events that the requests can listen to for providing functionality. Usages:
7
+ #
8
+ # - For positional requests, locate the target node and use `emit_for_target` to fire events for each listener
9
+ # - For nonpositional requests, use `visit` to go through the AST, which will fire events for each listener as nodes
10
+ # are found
11
+ #
12
+ # # Example
13
+ #
14
+ # ```ruby
15
+ # target_node = document.locate_node(position)
16
+ # listener = Requests::Hover.new
17
+ # EventEmitter.new(listener).emit_for_target(target_node)
18
+ # listener.response
19
+ # ```
20
+ class EventEmitter < SyntaxTree::Visitor
21
+ extend T::Sig
22
+
23
+ sig { params(listeners: Listener[T.untyped]).void }
24
+ def initialize(*listeners)
25
+ @listeners = listeners
26
+
27
+ # Create a map of event name to listeners that have registered it, so that we avoid unnecessary invocations
28
+ @event_to_listener_map = T.let(
29
+ listeners.each_with_object(Hash.new { |h, k| h[k] = [] }) do |listener, hash|
30
+ listener.class.events&.each { |event| hash[event] << listener }
31
+ end,
32
+ T::Hash[Symbol, T::Array[Listener[T.untyped]]],
33
+ )
34
+
35
+ super()
36
+ end
37
+
38
+ # Emit events for a specific node. This is similar to the regular `visit` method, but avoids going deeper into the
39
+ # tree for performance
40
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
41
+ def emit_for_target(node)
42
+ case node
43
+ when SyntaxTree::Command
44
+ @event_to_listener_map[:on_command]&.each { |listener| T.unsafe(listener).on_command(node) }
45
+ when SyntaxTree::CallNode
46
+ @event_to_listener_map[:on_call]&.each { |listener| T.unsafe(listener).on_call(node) }
47
+ when SyntaxTree::ConstPathRef
48
+ @event_to_listener_map[:on_const_path_ref]&.each { |listener| T.unsafe(listener).on_const_path_ref(node) }
49
+ when SyntaxTree::Const
50
+ @event_to_listener_map[:on_const]&.each { |listener| T.unsafe(listener).on_const(node) }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -11,7 +11,7 @@ module RubyLsp
11
11
  # Requests that mutate the store must be run sequentially! Parallel requests only receive a temporary copy of the
12
12
  # store
13
13
  @store = store
14
- @notifications = T.let([], T::Array[Notification])
14
+ @messages = T.let([], T::Array[Message])
15
15
  end
16
16
 
17
17
  sig { params(request: T::Hash[Symbol, T.untyped]).returns(Result) }
@@ -25,7 +25,7 @@ module RubyLsp
25
25
  error = e
26
26
  end
27
27
 
28
- Result.new(response: response, error: error, request_time: request_time, notifications: @notifications)
28
+ Result.new(response: response, error: error, request_time: request_time, messages: @messages)
29
29
  end
30
30
 
31
31
  private
@@ -38,6 +38,22 @@ module RubyLsp
38
38
  when "initialize"
39
39
  initialize_request(request.dig(:params))
40
40
  when "initialized"
41
+ Extension.load_extensions
42
+
43
+ errored_extensions = Extension.extensions.select(&:error?)
44
+
45
+ if errored_extensions.any?
46
+ @messages << Notification.new(
47
+ message: "window/showMessage",
48
+ params: Interface::ShowMessageParams.new(
49
+ type: Constant::MessageType::WARNING,
50
+ message: "Error loading extensions:\n\n#{errored_extensions.map(&:formatted_errors).join("\n\n")}",
51
+ ),
52
+ )
53
+
54
+ warn(errored_extensions.map(&:backtraces).join("\n\n"))
55
+ end
56
+
41
57
  warn("Ruby LSP is ready")
42
58
  VOID
43
59
  when "textDocument/didOpen"
@@ -47,7 +63,7 @@ module RubyLsp
47
63
  request.dig(:params, :textDocument, :version),
48
64
  )
49
65
  when "textDocument/didClose"
50
- @notifications << Notification.new(
66
+ @messages << Notification.new(
51
67
  message: "textDocument/publishDiagnostics",
52
68
  params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: []),
53
69
  )
@@ -75,7 +91,7 @@ module RubyLsp
75
91
  begin
76
92
  formatting(uri)
77
93
  rescue Requests::Formatting::InvalidFormatter => error
78
- @notifications << Notification.new(
94
+ @messages << Notification.new(
79
95
  message: "window/showMessage",
80
96
  params: Interface::ShowMessageParams.new(
81
97
  type: Constant::MessageType::ERROR,
@@ -85,7 +101,7 @@ module RubyLsp
85
101
 
86
102
  nil
87
103
  rescue StandardError => error
88
- @notifications << Notification.new(
104
+ @messages << Notification.new(
89
105
  message: "window/showMessage",
90
106
  params: Interface::ShowMessageParams.new(
91
107
  type: Constant::MessageType::ERROR,
@@ -111,7 +127,7 @@ module RubyLsp
111
127
  begin
112
128
  diagnostic(uri)
113
129
  rescue StandardError => error
114
- @notifications << Notification.new(
130
+ @messages << Notification.new(
115
131
  message: "window/showMessage",
116
132
  params: Interface::ShowMessageParams.new(
117
133
  type: Constant::MessageType::ERROR,
@@ -123,6 +139,8 @@ module RubyLsp
123
139
  end
124
140
  when "textDocument/completion"
125
141
  completion(uri, request.dig(:params, :position))
142
+ when "textDocument/codeLens"
143
+ code_lens(uri)
126
144
  end
127
145
  end
128
146
 
@@ -133,6 +151,13 @@ module RubyLsp
133
151
  end
134
152
  end
135
153
 
154
+ sig { params(uri: String).returns(T::Array[Interface::CodeLens]) }
155
+ def code_lens(uri)
156
+ @store.cache_fetch(uri, :code_lens) do |document|
157
+ Requests::CodeLens.new(document).run
158
+ end
159
+ end
160
+
136
161
  sig do
137
162
  params(
138
163
  uri: String,
@@ -140,7 +165,27 @@ module RubyLsp
140
165
  ).returns(T.nilable(Interface::Hover))
141
166
  end
142
167
  def hover(uri, position)
143
- RubyLsp::Requests::Hover.new(@store.get(uri), position).run
168
+ document = @store.get(uri)
169
+ document.parse
170
+ return if document.syntax_error?
171
+
172
+ target, parent = document.locate_node(position)
173
+
174
+ if !Requests::Hover::ALLOWED_TARGETS.include?(target.class) &&
175
+ Requests::Hover::ALLOWED_TARGETS.include?(parent.class)
176
+ target = parent
177
+ end
178
+
179
+ # Instantiate all listeners
180
+ base_listener = Requests::Hover.new
181
+ listeners = Requests::Hover.listeners.map(&:new)
182
+
183
+ # Emit events for all listeners
184
+ T.unsafe(EventEmitter).new(base_listener, *listeners).emit_for_target(target)
185
+
186
+ # Merge all responses into a single hover
187
+ listeners.each { |ext| base_listener.merge_response!(ext) }
188
+ base_listener.response
144
189
  end
145
190
 
146
191
  sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
@@ -269,7 +314,7 @@ module RubyLsp
269
314
 
270
315
  case result
271
316
  when Requests::CodeActionResolve::Error::EmptySelection
272
- @notifications << Notification.new(
317
+ @messages << Notification.new(
273
318
  message: "window/showMessage",
274
319
  params: Interface::ShowMessageParams.new(
275
320
  type: Constant::MessageType::ERROR,
@@ -278,7 +323,7 @@ module RubyLsp
278
323
  )
279
324
  raise Requests::CodeActionResolve::CodeActionError
280
325
  when Requests::CodeActionResolve::Error::InvalidTargetRange
281
- @notifications << Notification.new(
326
+ @messages << Notification.new(
282
327
  message: "window/showMessage",
283
328
  params: Interface::ShowMessageParams.new(
284
329
  type: Constant::MessageType::ERROR,
@@ -326,11 +371,21 @@ module RubyLsp
326
371
  sig { params(options: T::Hash[Symbol, T.untyped]).returns(Interface::InitializeResult) }
327
372
  def initialize_request(options)
328
373
  @store.clear
329
- @store.encoding = options.dig(:capabilities, :general, :positionEncodings)
374
+
375
+ encodings = options.dig(:capabilities, :general, :positionEncodings)
376
+ @store.encoding = if encodings.nil? || encodings.empty?
377
+ Constant::PositionEncodingKind::UTF16
378
+ elsif encodings.include?(Constant::PositionEncodingKind::UTF8)
379
+ Constant::PositionEncodingKind::UTF8
380
+ else
381
+ encodings.first
382
+ end
383
+
330
384
  formatter = options.dig(:initializationOptions, :formatter)
331
385
  @store.formatter = formatter unless formatter.nil?
332
386
 
333
387
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
388
+ experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled)
334
389
 
335
390
  enabled_features = case configured_features
336
391
  when Array
@@ -359,6 +414,10 @@ module RubyLsp
359
414
  Interface::DocumentLinkOptions.new(resolve_provider: false)
360
415
  end
361
416
 
417
+ code_lens_provider = if experimental_features
418
+ Interface::CodeLensOptions.new(resolve_provider: false)
419
+ end
420
+
362
421
  hover_provider = if enabled_features["hover"]
363
422
  Interface::HoverClientCapabilities.new(dynamic_registration: false)
364
423
  end
@@ -414,6 +473,7 @@ module RubyLsp
414
473
  change: Constant::TextDocumentSyncKind::INCREMENTAL,
415
474
  open_close: true,
416
475
  ),
476
+ position_encoding: @store.encoding,
417
477
  selection_range_provider: enabled_features["selectionRanges"],
418
478
  hover_provider: hover_provider,
419
479
  document_symbol_provider: document_symbol_provider,
@@ -427,6 +487,7 @@ module RubyLsp
427
487
  diagnostic_provider: diagnostics_provider,
428
488
  inlay_hint_provider: inlay_hint_provider,
429
489
  completion_provider: completion_provider,
490
+ code_lens_provider: code_lens_provider,
430
491
  ),
431
492
  )
432
493
  end
@@ -0,0 +1,104 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # To register an extension, inherit from this class and implement both `name` and `activate`
6
+ #
7
+ # # Example
8
+ #
9
+ # ```ruby
10
+ # module MyGem
11
+ # class MyExtension < Extension
12
+ # def activate
13
+ # # Perform any relevant initialization
14
+ # end
15
+ #
16
+ # def name
17
+ # "My extension name"
18
+ # end
19
+ # end
20
+ # end
21
+ # ```
22
+ class Extension
23
+ extend T::Sig
24
+ extend T::Helpers
25
+
26
+ abstract!
27
+
28
+ class << self
29
+ extend T::Sig
30
+
31
+ # Automatically track and instantiate extension classes
32
+ sig { params(child_class: T.class_of(Extension)).void }
33
+ def inherited(child_class)
34
+ extensions << child_class.new
35
+ super
36
+ end
37
+
38
+ sig { returns(T::Array[Extension]) }
39
+ def extensions
40
+ @extensions ||= T.let([], T.nilable(T::Array[Extension]))
41
+ end
42
+
43
+ # Discovers and loads all extensions. Returns the list of activated extensions
44
+ sig { returns(T::Array[Extension]) }
45
+ def load_extensions
46
+ # Require all extensions entry points, which should be placed under
47
+ # `some_gem/lib/ruby_lsp/your_gem_name/extension.rb`
48
+ Gem.find_files("ruby_lsp/**/extension.rb").each do |extension|
49
+ require File.expand_path(extension)
50
+ rescue => e
51
+ warn(e.message)
52
+ warn(e.backtrace.to_s) # rubocop:disable Lint/RedundantStringCoercion
53
+ end
54
+
55
+ # Activate each one of the discovered extensions. If any problems occur in the extensions, we don't want to
56
+ # fail to boot the server
57
+ extensions.each do |extension|
58
+ extension.activate
59
+ nil
60
+ rescue => e
61
+ extension.add_error(e)
62
+ end
63
+ end
64
+ end
65
+
66
+ sig { void }
67
+ def initialize
68
+ @errors = T.let([], T::Array[StandardError])
69
+ end
70
+
71
+ sig { params(error: StandardError).returns(T.self_type) }
72
+ def add_error(error)
73
+ @errors << error
74
+ self
75
+ end
76
+
77
+ sig { returns(T::Boolean) }
78
+ def error?
79
+ @errors.any?
80
+ end
81
+
82
+ sig { returns(String) }
83
+ def formatted_errors
84
+ <<~ERRORS
85
+ #{name}:
86
+ #{@errors.map(&:message).join("\n")}
87
+ ERRORS
88
+ end
89
+
90
+ sig { returns(String) }
91
+ def backtraces
92
+ @errors.filter_map(&:backtrace).join("\n\n")
93
+ end
94
+
95
+ # Each extension should implement `MyExtension#activate` and use to perform any sort of initialization, such as
96
+ # reading information into memory or even spawning a separate process
97
+ sig { abstract.void }
98
+ def activate; end
99
+
100
+ # Extensions should override the `name` method to return the extension name
101
+ sig { abstract.returns(String) }
102
+ def name; end
103
+ end
104
+ end
@@ -11,5 +11,8 @@ require "ruby-lsp"
11
11
  require "ruby_lsp/utils"
12
12
  require "ruby_lsp/server"
13
13
  require "ruby_lsp/executor"
14
+ require "ruby_lsp/event_emitter"
14
15
  require "ruby_lsp/requests"
16
+ require "ruby_lsp/listener"
15
17
  require "ruby_lsp/store"
18
+ require "ruby_lsp/extension"
@@ -0,0 +1,49 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # Listener is an abstract class to be used by requests for listening to events emitted when visiting an AST using the
6
+ # EventEmitter.
7
+ class Listener
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ extend T::Generic
11
+ include Requests::Support::Common
12
+
13
+ ResponseType = type_member
14
+
15
+ abstract!
16
+
17
+ class << self
18
+ extend T::Sig
19
+
20
+ sig { returns(T.nilable(T::Array[Symbol])) }
21
+ attr_reader :events
22
+
23
+ sig { returns(T::Array[T.class_of(Listener)]) }
24
+ def listeners
25
+ @listeners ||= T.let([], T.nilable(T::Array[T.class_of(Listener)]))
26
+ end
27
+
28
+ sig { params(listener: T.class_of(Listener)).void }
29
+ def add_listener(listener)
30
+ listeners << listener
31
+ end
32
+
33
+ # All listener events must be defined inside of a `listener_events` block. This is to ensure we know which events
34
+ # have been registered. Defining an event outside of this block will simply not register it and it'll never be
35
+ # invoked
36
+ sig { params(block: T.proc.void).void }
37
+ def listener_events(&block)
38
+ current_methods = instance_methods
39
+ block.call
40
+ @events = T.let(instance_methods - current_methods, T.nilable(T::Array[Symbol]))
41
+ end
42
+ end
43
+
44
+ # Override this method with an attr_reader that returns the response of your listener. The listener should
45
+ # accumulate results in a @response variable and then provide the reader so that it is accessible
46
+ sig { abstract.returns(ResponseType) }
47
+ def response; end
48
+ end
49
+ end
@@ -7,6 +7,7 @@ module RubyLsp
7
7
  class BaseRequest < SyntaxTree::Visitor
8
8
  extend T::Sig
9
9
  extend T::Helpers
10
+ include Support::Common
10
11
 
11
12
  abstract!
12
13
 
@@ -31,94 +32,10 @@ module RubyLsp
31
32
  # Syntax Tree implements `visit_all` using `map` instead of `each` for users who want to use the pattern
32
33
  # `result = visitor.visit(tree)`. However, we don't use that pattern and should avoid producing a new array for
33
34
  # every single node visited
34
- sig { params(nodes: T::Array[SyntaxTree::Node]).void }
35
+ sig { params(nodes: T::Array[T.nilable(SyntaxTree::Node)]).void }
35
36
  def visit_all(nodes)
36
37
  nodes.each { |node| visit(node) }
37
38
  end
38
-
39
- sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
40
- def range_from_syntax_tree_node(node)
41
- loc = node.location
42
-
43
- Interface::Range.new(
44
- start: Interface::Position.new(
45
- line: loc.start_line - 1,
46
- character: loc.start_column,
47
- ),
48
- end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
49
- )
50
- end
51
-
52
- sig do
53
- params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
54
- end
55
- def full_constant_name(node)
56
- name = +node.constant.value
57
- constant = T.let(node, SyntaxTree::Node)
58
-
59
- while constant.is_a?(SyntaxTree::ConstPathRef)
60
- constant = constant.parent
61
-
62
- case constant
63
- when SyntaxTree::ConstPathRef
64
- name.prepend("#{constant.constant.value}::")
65
- when SyntaxTree::VarRef
66
- name.prepend("#{constant.value.value}::")
67
- end
68
- end
69
-
70
- name
71
- end
72
-
73
- sig do
74
- params(
75
- node: SyntaxTree::Node,
76
- position: Integer,
77
- node_types: T::Array[T.class_of(SyntaxTree::Node)],
78
- ).returns([T.nilable(SyntaxTree::Node), T.nilable(SyntaxTree::Node)])
79
- end
80
- def locate(node, position, node_types: [])
81
- queue = T.let(node.child_nodes.compact, T::Array[T.nilable(SyntaxTree::Node)])
82
- closest = node
83
-
84
- until queue.empty?
85
- candidate = queue.shift
86
-
87
- # Skip nil child nodes
88
- next if candidate.nil?
89
-
90
- # Add the next child_nodes to the queue to be processed
91
- queue.concat(candidate.child_nodes)
92
-
93
- # Skip if the current node doesn't cover the desired position
94
- loc = candidate.location
95
- next unless (loc.start_char...loc.end_char).cover?(position)
96
-
97
- # If the node's start character is already past the position, then we should've found the closest node already
98
- break if position < loc.start_char
99
-
100
- # If there are node types to filter by, and the current node is not one of those types, then skip it
101
- next if node_types.any? && node_types.none? { |type| candidate.is_a?(type) }
102
-
103
- # If the current node is narrower than or equal to the previous closest node, then it is more precise
104
- closest_loc = closest.location
105
- if loc.end_char - loc.start_char <= closest_loc.end_char - closest_loc.start_char
106
- parent = T.let(closest, SyntaxTree::Node)
107
- closest = candidate
108
- end
109
- end
110
-
111
- [closest, parent]
112
- end
113
-
114
- sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
115
- def visible?(node, range)
116
- return true if range.nil?
117
- return false if node.nil?
118
-
119
- loc = node.location
120
- range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
121
- end
122
39
  end
123
40
  end
124
41
  end
@@ -54,7 +54,8 @@ module RubyLsp
54
54
  extracted_source = T.must(@document.source[start_index...end_index])
55
55
 
56
56
  # Find the closest statements node, so that we place the refactor in a valid position
57
- closest_statements = locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements]).first
57
+ closest_statements, parent_statements = @document
58
+ .locate(T.must(@document.tree), start_index, node_types: [SyntaxTree::Statements])
58
59
  return Error::InvalidTargetRange if closest_statements.nil?
59
60
 
60
61
  # Find the node with the end line closest to the requested position, so that we can place the refactor
@@ -64,25 +65,51 @@ module RubyLsp
64
65
  distance <= 0 ? Float::INFINITY : distance
65
66
  end
66
67
 
67
- # When trying to extract the first node inside of a statements block, then we can just select one line above it
68
- target_line = if closest_node == closest_statements.child_nodes.first
69
- closest_node.location.start_line - 1
68
+ # Find the parent expression of the closest node
69
+ parent_expression = parent_statements.child_nodes.compact.find do |node|
70
+ loc = node.location
71
+ loc.start_line - 1 <= source_range.dig(:start, :line) && loc.end_line - 1 >= source_range.dig(:end, :line)
72
+ end if parent_statements
73
+
74
+ closest_node_loc = closest_node.location
75
+ # If the parent expression is a single line block, then we have to extract it inside of the oneline block
76
+ if parent_expression.is_a?(SyntaxTree::MethodAddBlock) &&
77
+ parent_expression.location.start_line == parent_expression.location.end_line
78
+
79
+ variable_source = " #{NEW_VARIABLE_NAME} = #{extracted_source};"
80
+ character = source_range.dig(:start, :character) - 1
81
+ target_range = {
82
+ start: { line: closest_node_loc.end_line - 1, character: character },
83
+ end: { line: closest_node_loc.end_line - 1, character: character },
84
+ }
70
85
  else
71
- closest_node.location.end_line
72
- end
73
-
74
- lines = @document.source.lines
75
- indentation = T.must(T.must(lines[target_line - 1])[/\A */]).size
76
-
77
- target_range = {
78
- start: { line: target_line, character: indentation },
79
- end: { line: target_line, character: indentation },
80
- }
81
-
82
- variable_source = if T.must(lines[target_line]).strip.empty?
83
- "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
84
- else
85
- "#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
86
+ # If the closest node covers the requested location, then we're extracting a statement nested inside of it. In
87
+ # that case, we want to place the extraction at the start of the closest node (one line above). Otherwise, we
88
+ # want to place the extract right below the closest node
89
+ if closest_node_loc.start_line - 1 <= source_range.dig(
90
+ :start,
91
+ :line,
92
+ ) && closest_node_loc.end_line - 1 >= source_range.dig(:end, :line)
93
+ indentation_line = closest_node_loc.start_line - 1
94
+ target_line = indentation_line
95
+ else
96
+ target_line = closest_node_loc.end_line
97
+ indentation_line = closest_node_loc.end_line - 1
98
+ end
99
+
100
+ lines = @document.source.lines
101
+ indentation = T.must(T.must(lines[indentation_line])[/\A */]).size
102
+
103
+ target_range = {
104
+ start: { line: target_line, character: indentation },
105
+ end: { line: target_line, character: indentation },
106
+ }
107
+
108
+ variable_source = if T.must(lines[target_line]).strip.empty?
109
+ "\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
110
+ else
111
+ "#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
112
+ end
86
113
  end
87
114
 
88
115
  Interface::CodeAction.new(
@@ -0,0 +1,135 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Code lens demo](../../misc/code_lens.gif)
7
+ #
8
+ # This feature is currently experimental. Clients will need to pass `experimentalFeaturesEnabled`
9
+ # in the initialization options to enable it.
10
+ #
11
+ # The
12
+ # [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
13
+ # request informs the editor of runnable commands such as tests
14
+ #
15
+ # # Example
16
+ #
17
+ # ```ruby
18
+ # # Run
19
+ # class Test < Minitest::Test
20
+ # end
21
+ # ```
22
+
23
+ class CodeLens < BaseRequest
24
+ BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
25
+ ACCESS_MODIFIERS = T.let(["public", "private", "protected"], T::Array[String])
26
+
27
+ sig do
28
+ params(
29
+ document: Document,
30
+ ).void
31
+ end
32
+ def initialize(document)
33
+ super(document)
34
+ @results = T.let([], T::Array[Interface::CodeLens])
35
+ @path = T.let(document.uri.delete_prefix("file://"), String)
36
+ @modifier = T.let("public", String)
37
+ end
38
+
39
+ sig { override.returns(T.all(T::Array[Interface::CodeLens], Object)) }
40
+ def run
41
+ visit(@document.tree) if @document.parsed?
42
+ @results
43
+ end
44
+
45
+ sig { override.params(node: SyntaxTree::ClassDeclaration).void }
46
+ def visit_class(node)
47
+ class_name = node.constant.constant.value
48
+ if class_name.end_with?("Test")
49
+ add_code_lens(node, name: class_name, command: BASE_COMMAND + @path)
50
+ end
51
+ visit(node.bodystmt)
52
+ end
53
+
54
+ sig { override.params(node: SyntaxTree::DefNode).void }
55
+ def visit_def(node)
56
+ if @modifier == "public"
57
+ method_name = node.name.value
58
+ if method_name.start_with?("test_")
59
+ add_code_lens(
60
+ node,
61
+ name: method_name,
62
+ command: BASE_COMMAND + @path + " --name " + method_name,
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ sig { override.params(node: SyntaxTree::Command).void }
69
+ def visit_command(node)
70
+ if node.message.value == "public"
71
+ with_visiblity("public", node)
72
+ end
73
+ end
74
+
75
+ sig { override.params(node: SyntaxTree::CallNode).void }
76
+ def visit_call(node)
77
+ ident = node.message if node.message.is_a?(SyntaxTree::Ident)
78
+
79
+ if ident
80
+ if T.cast(ident, SyntaxTree::Ident).value == "public"
81
+ with_visiblity("public", node)
82
+ end
83
+ end
84
+ end
85
+
86
+ sig { override.params(node: SyntaxTree::VCall).void }
87
+ def visit_vcall(node)
88
+ vcall_value = node.value.value
89
+
90
+ if ACCESS_MODIFIERS.include?(vcall_value)
91
+ @modifier = vcall_value
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ sig do
98
+ params(
99
+ visibility: String,
100
+ node: T.any(SyntaxTree::CallNode, SyntaxTree::Command),
101
+ ).void
102
+ end
103
+ def with_visiblity(visibility, node)
104
+ current_visibility = @modifier
105
+ @modifier = visibility
106
+ visit(node.arguments)
107
+ ensure
108
+ @modifier = T.must(current_visibility)
109
+ end
110
+
111
+ sig { params(node: SyntaxTree::Node, name: String, command: String).void }
112
+ def add_code_lens(node, name:, command:)
113
+ @results << Interface::CodeLens.new(
114
+ range: range_from_syntax_tree_node(node),
115
+ command: Interface::Command.new(
116
+ title: "Run",
117
+ command: "rubyLsp.runTest",
118
+ arguments: [
119
+ @path,
120
+ name,
121
+ command,
122
+ {
123
+ start_line: node.location.start_line - 1,
124
+ start_column: node.location.start_column,
125
+ end_line: node.location.end_line - 1,
126
+ end_column: node.location.end_column,
127
+ },
128
+ ],
129
+ ),
130
+ data: { type: "test" },
131
+ )
132
+ end
133
+ end
134
+ end
135
+ end
@@ -32,8 +32,7 @@ module RubyLsp
32
32
  @highlights = T.let([], T::Array[Interface::DocumentHighlight])
33
33
  return unless document.parsed?
34
34
 
35
- position = document.create_scanner.find_char_position(position)
36
- @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
35
+ @target = T.let(find(position), T.nilable(Support::HighlightTarget))
37
36
  end
38
37
 
39
38
  sig { override.returns(T.all(T::Array[Interface::DocumentHighlight], Object)) }
@@ -68,12 +67,11 @@ module RubyLsp
68
67
 
69
68
  sig do
70
69
  params(
71
- node: SyntaxTree::Node,
72
- position: Integer,
70
+ position: Document::PositionShape,
73
71
  ).returns(T.nilable(Support::HighlightTarget))
74
72
  end
75
- def find(node, position)
76
- matched, parent = locate(node, position)
73
+ def find(position)
74
+ matched, parent = @document.locate_node(position)
77
75
 
78
76
  return unless matched && parent
79
77
  return unless matched.is_a?(SyntaxTree::Ident) || DIRECT_HIGHLIGHTS.include?(matched.class)
@@ -17,8 +17,11 @@ module RubyLsp
17
17
  # before_save :do_something # when hovering on before_save, the link will be rendered
18
18
  # end
19
19
  # ```
20
- class Hover < BaseRequest
20
+ class Hover < Listener
21
21
  extend T::Sig
22
+ extend T::Generic
23
+
24
+ ResponseType = type_member { { fixed: T.nilable(Interface::Hover) } }
22
25
 
23
26
  ALLOWED_TARGETS = T.let(
24
27
  [
@@ -29,53 +32,60 @@ module RubyLsp
29
32
  T::Array[T.class_of(SyntaxTree::Node)],
30
33
  )
31
34
 
32
- sig { params(document: Document, position: Document::PositionShape).void }
33
- def initialize(document, position)
34
- super(document)
35
+ sig { override.returns(ResponseType) }
36
+ attr_reader :response
37
+
38
+ sig { void }
39
+ def initialize
40
+ @response = T.let(nil, ResponseType)
41
+ super()
42
+ end
43
+
44
+ # Merges responses from other hover listeners
45
+ sig { params(other: Listener[ResponseType]).returns(T.self_type) }
46
+ def merge_response!(other)
47
+ other_response = other.response
48
+ return self unless other_response
49
+
50
+ if @response.nil?
51
+ @response = other.response
52
+ else
53
+ @response.contents.value << other_response.contents.value << "\n\n"
54
+ end
35
55
 
36
- @position = T.let(document.create_scanner.find_char_position(position), Integer)
56
+ self
37
57
  end
38
58
 
39
- sig { override.returns(T.nilable(Interface::Hover)) }
40
- def run
41
- return unless @document.parsed?
59
+ listener_events do
60
+ sig { params(node: SyntaxTree::Command).void }
61
+ def on_command(node)
62
+ message = node.message
63
+ @response = generate_rails_document_link_hover(message.value, message)
64
+ end
42
65
 
43
- target, parent = locate(T.must(@document.tree), @position)
44
- target = parent if !ALLOWED_TARGETS.include?(target.class) && ALLOWED_TARGETS.include?(parent.class)
66
+ sig { params(node: SyntaxTree::ConstPathRef).void }
67
+ def on_const_path_ref(node)
68
+ @response = generate_rails_document_link_hover(full_constant_name(node), node)
69
+ end
45
70
 
46
- case target
47
- when SyntaxTree::Command
48
- message = target.message
49
- generate_rails_document_link_hover(message.value, message)
50
- when SyntaxTree::CallNode
51
- message = target.message
71
+ sig { params(node: SyntaxTree::CallNode).void }
72
+ def on_call(node)
73
+ message = node.message
52
74
  return if message.is_a?(Symbol)
53
75
 
54
- generate_rails_document_link_hover(message.value, message)
55
- when SyntaxTree::ConstPathRef
56
- constant_name = full_constant_name(target)
57
- generate_rails_document_link_hover(constant_name, target)
76
+ @response = generate_rails_document_link_hover(message.value, message)
58
77
  end
59
78
  end
60
79
 
61
80
  private
62
81
 
63
- sig do
64
- params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover))
65
- end
82
+ sig { params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover)) }
66
83
  def generate_rails_document_link_hover(name, node)
67
84
  urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
68
-
69
85
  return if urls.empty?
70
86
 
71
- contents = Interface::MarkupContent.new(
72
- kind: "markdown",
73
- value: urls.join("\n\n"),
74
- )
75
- Interface::Hover.new(
76
- range: range_from_syntax_tree_node(node),
77
- contents: contents,
78
- )
87
+ contents = Interface::MarkupContent.new(kind: "markdown", value: urls.join("\n\n"))
88
+ Interface::Hover.new(range: range_from_syntax_tree_node(node), contents: contents)
79
89
  end
80
90
  end
81
91
  end
@@ -80,6 +80,10 @@ module RubyLsp
80
80
 
81
81
  sig { void }
82
82
  def handle_statement_end
83
+ # If a keyword occurs in a line which appears be a comment or a string, we will not try to format it, since
84
+ # it could be a coincidental match. This approach is not perfect, but it should cover most cases.
85
+ return if @previous_line.start_with?(/["'#]/)
86
+
83
87
  return unless END_REGEXES.any? { |regex| regex.match?(@previous_line) }
84
88
 
85
89
  indents = " " * @indentation
@@ -29,8 +29,7 @@ module RubyLsp
29
29
  # We can't verify if we're inside a require when there are syntax errors
30
30
  return [] if @document.syntax_error?
31
31
 
32
- char_position = @document.create_scanner.find_char_position(@position)
33
- target = T.let(find(char_position), T.nilable(SyntaxTree::TStringContent))
32
+ target = T.let(find, T.nilable(SyntaxTree::TStringContent))
34
33
  # no target means the we are not inside a `require` call
35
34
  return [] unless target
36
35
 
@@ -51,11 +50,12 @@ module RubyLsp
51
50
  end
52
51
  end
53
52
 
54
- sig { params(position: Integer).returns(T.nilable(SyntaxTree::TStringContent)) }
55
- def find(position)
56
- matched, parent = locate(
53
+ sig { returns(T.nilable(SyntaxTree::TStringContent)) }
54
+ def find
55
+ char_position = @document.create_scanner.find_char_position(@position)
56
+ matched, parent = @document.locate(
57
57
  T.must(@document.tree),
58
- position,
58
+ char_position,
59
59
  node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
60
60
  )
61
61
 
@@ -76,7 +76,7 @@ module RubyLsp
76
76
 
77
77
  path_node = argument.parts.first
78
78
  return unless path_node.is_a?(SyntaxTree::TStringContent)
79
- return unless (path_node.location.start_char..path_node.location.end_char).cover?(position)
79
+ return unless (path_node.location.start_char..path_node.location.end_char).cover?(char_position)
80
80
 
81
81
  path_node
82
82
  end
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ module Common
8
+ extend T::Sig
9
+
10
+ sig { params(node: SyntaxTree::Node).returns(Interface::Range) }
11
+ def range_from_syntax_tree_node(node)
12
+ loc = node.location
13
+
14
+ Interface::Range.new(
15
+ start: Interface::Position.new(
16
+ line: loc.start_line - 1,
17
+ character: loc.start_column,
18
+ ),
19
+ end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
20
+ )
21
+ end
22
+
23
+ sig do
24
+ params(node: T.any(SyntaxTree::ConstPathRef, SyntaxTree::ConstRef, SyntaxTree::TopConstRef)).returns(String)
25
+ end
26
+ def full_constant_name(node)
27
+ name = +node.constant.value
28
+ constant = T.let(node, SyntaxTree::Node)
29
+
30
+ while constant.is_a?(SyntaxTree::ConstPathRef)
31
+ constant = constant.parent
32
+
33
+ case constant
34
+ when SyntaxTree::ConstPathRef
35
+ name.prepend("#{constant.constant.value}::")
36
+ when SyntaxTree::VarRef
37
+ name.prepend("#{constant.value.value}::")
38
+ end
39
+ end
40
+
41
+ name
42
+ end
43
+
44
+ sig { params(node: T.nilable(SyntaxTree::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
45
+ def visible?(node, range)
46
+ return true if range.nil?
47
+ return false if node.nil?
48
+
49
+ loc = node.location
50
+ range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -18,6 +18,7 @@ module RubyLsp
18
18
  # - {RubyLsp::Requests::DocumentHighlight}
19
19
  # - {RubyLsp::Requests::InlayHints}
20
20
  # - {RubyLsp::Requests::PathCompletion}
21
+ # - {RubyLsp::Requests::CodeLens}
21
22
 
22
23
  module Requests
23
24
  autoload :BaseRequest, "ruby_lsp/requests/base_request"
@@ -35,6 +36,7 @@ module RubyLsp
35
36
  autoload :DocumentHighlight, "ruby_lsp/requests/document_highlight"
36
37
  autoload :InlayHints, "ruby_lsp/requests/inlay_hints"
37
38
  autoload :PathCompletion, "ruby_lsp/requests/path_completion"
39
+ autoload :CodeLens, "ruby_lsp/requests/code_lens"
38
40
 
39
41
  # :nodoc:
40
42
  module Support
@@ -46,6 +48,7 @@ module RubyLsp
46
48
  autoload :HighlightTarget, "ruby_lsp/requests/support/highlight_target"
47
49
  autoload :RailsDocumentClient, "ruby_lsp/requests/support/rails_document_client"
48
50
  autoload :PrefixTree, "ruby_lsp/requests/support/prefix_tree"
51
+ autoload :Common, "ruby_lsp/requests/support/common"
49
52
  end
50
53
  end
51
54
  end
@@ -23,6 +23,7 @@ module RubyLsp
23
23
  @jobs = T.let({}, T::Hash[T.any(String, Integer), Job])
24
24
  @mutex = T.let(Mutex.new, Mutex)
25
25
  @worker = T.let(new_worker, Thread)
26
+ @current_request_id = T.let(1, Integer)
26
27
 
27
28
  Thread.main.priority = 1
28
29
  end
@@ -54,7 +55,7 @@ module RubyLsp
54
55
  @worker.join
55
56
  @store.clear
56
57
 
57
- finalize_request(Result.new(response: nil, notifications: []), request)
58
+ finalize_request(Result.new(response: nil, messages: []), request)
58
59
  when "exit"
59
60
  # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
60
61
  # https://microsoft.github.io/language-server-protocol/specification/#exit
@@ -88,7 +89,7 @@ module RubyLsp
88
89
 
89
90
  result = if job.cancelled
90
91
  # We need to return nil to the client even if the request was cancelled
91
- Result.new(response: nil, notifications: [])
92
+ Result.new(response: nil, messages: [])
92
93
  else
93
94
  Executor.new(@store).execute(request)
94
95
  end
@@ -105,9 +106,6 @@ module RubyLsp
105
106
  error = result.error
106
107
  response = result.response
107
108
 
108
- # If the response include any notifications, go through them and publish each one
109
- result.notifications.each { |n| @writer.write(method: n.message, params: n.params) }
110
-
111
109
  if error
112
110
  @writer.write(
113
111
  id: request[:id],
@@ -121,6 +119,17 @@ module RubyLsp
121
119
  @writer.write(id: request[:id], result: response)
122
120
  end
123
121
 
122
+ # If the response include any messages, go through them and publish each one
123
+ result.messages.each do |n|
124
+ case n
125
+ when Notification
126
+ @writer.write(method: n.message, params: n.params)
127
+ when Request
128
+ @writer.write(id: @current_request_id, method: n.message, params: n.params)
129
+ @current_request_id += 1
130
+ end
131
+ end
132
+
124
133
  request_time = result.request_time
125
134
  if request_time
126
135
  @writer.write(method: "telemetry/event", params: telemetry_params(request, request_time, error))
@@ -9,8 +9,8 @@ module RubyLsp
9
9
  class Store
10
10
  extend T::Sig
11
11
 
12
- sig { params(encoding: String).void }
13
- attr_writer :encoding
12
+ sig { returns(String) }
13
+ attr_accessor :encoding
14
14
 
15
15
  sig { returns(String) }
16
16
  attr_accessor :formatter
@@ -18,7 +18,7 @@ module RubyLsp
18
18
  sig { void }
19
19
  def initialize
20
20
  @state = T.let({}, T::Hash[String, Document])
21
- @encoding = T.let("utf-8", String)
21
+ @encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
22
22
  @formatter = T.let("auto", String)
23
23
  end
24
24
 
@@ -9,8 +9,11 @@ module RubyLsp
9
9
  WORKSPACE_URI = T.let("file://#{Dir.pwd}".freeze, String) # rubocop:disable Style/RedundantFreeze
10
10
 
11
11
  # A notification to be sent to the client
12
- class Notification
12
+ class Message
13
13
  extend T::Sig
14
+ extend T::Helpers
15
+
16
+ abstract!
14
17
 
15
18
  sig { returns(String) }
16
19
  attr_reader :message
@@ -25,6 +28,9 @@ module RubyLsp
25
28
  end
26
29
  end
27
30
 
31
+ class Notification < Message; end
32
+ class Request < Message; end
33
+
28
34
  # The final result of running a request before its IO is finalized
29
35
  class Result
30
36
  extend T::Sig
@@ -32,8 +38,8 @@ module RubyLsp
32
38
  sig { returns(T.untyped) }
33
39
  attr_reader :response
34
40
 
35
- sig { returns(T::Array[Notification]) }
36
- attr_reader :notifications
41
+ sig { returns(T::Array[Message]) }
42
+ attr_reader :messages
37
43
 
38
44
  sig { returns(T.nilable(Exception)) }
39
45
  attr_reader :error
@@ -44,14 +50,14 @@ module RubyLsp
44
50
  sig do
45
51
  params(
46
52
  response: T.untyped,
47
- notifications: T::Array[Notification],
53
+ messages: T::Array[Message],
48
54
  error: T.nilable(Exception),
49
55
  request_time: T.nilable(Float),
50
56
  ).void
51
57
  end
52
- def initialize(response:, notifications:, error: nil, request_time: nil)
58
+ def initialize(response:, messages:, error: nil, request_time: nil)
53
59
  @response = response
54
- @notifications = notifications
60
+ @messages = messages
55
61
  @error = error
56
62
  @request_time = request_time
57
63
  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.4.3
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-30 00:00:00.000000000 Z
11
+ date: 2023-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -73,12 +73,16 @@ files:
73
73
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
74
74
  - lib/ruby-lsp.rb
75
75
  - lib/ruby_lsp/document.rb
76
+ - lib/ruby_lsp/event_emitter.rb
76
77
  - lib/ruby_lsp/executor.rb
78
+ - lib/ruby_lsp/extension.rb
77
79
  - lib/ruby_lsp/internal.rb
80
+ - lib/ruby_lsp/listener.rb
78
81
  - lib/ruby_lsp/requests.rb
79
82
  - lib/ruby_lsp/requests/base_request.rb
80
83
  - lib/ruby_lsp/requests/code_action_resolve.rb
81
84
  - lib/ruby_lsp/requests/code_actions.rb
85
+ - lib/ruby_lsp/requests/code_lens.rb
82
86
  - lib/ruby_lsp/requests/diagnostics.rb
83
87
  - lib/ruby_lsp/requests/document_highlight.rb
84
88
  - lib/ruby_lsp/requests/document_link.rb
@@ -92,6 +96,7 @@ files:
92
96
  - lib/ruby_lsp/requests/selection_ranges.rb
93
97
  - lib/ruby_lsp/requests/semantic_highlighting.rb
94
98
  - lib/ruby_lsp/requests/support/annotation.rb
99
+ - lib/ruby_lsp/requests/support/common.rb
95
100
  - lib/ruby_lsp/requests/support/highlight_target.rb
96
101
  - lib/ruby_lsp/requests/support/prefix_tree.rb
97
102
  - lib/ruby_lsp/requests/support/rails_document_client.rb
@@ -127,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
132
  - !ruby/object:Gem::Version
128
133
  version: '0'
129
134
  requirements: []
130
- rubygems_version: 3.4.9
135
+ rubygems_version: 3.4.12
131
136
  signing_key:
132
137
  specification_version: 4
133
138
  summary: An opinionated language server for Ruby