ruby-lsp 0.4.3 → 0.4.5

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