ruby-lsp 0.4.4 → 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: 61c8fe2a55057823533a8a17b4472309e3d463ec3d63302fa3393f046ec4b339
4
- data.tar.gz: fb9636b8aafac9d5002a872d0f34f7f9852e9348009cdc2934369637a4cadb4c
3
+ metadata.gz: 437c2048862bed450fd70057489636117c99943dd7bb68df35c4e161a29f8385
4
+ data.tar.gz: 30d26e8455f9086bc934ffe23b5e37816f18f5904e3422495c316a980a860cfc
5
5
  SHA512:
6
- metadata.gz: db51fd5526431b773125c0b27bfe8ca53b2861759457f9a36e7a681bcfbffb0f58026b4f8cc4dc991be2380309959aacde7c8cc2cd0a0f0373dfb313d0d28db9
7
- data.tar.gz: 13cc3991bffd1a18dd1e2580c8d1051161b03a171a25e8ce681f46fb8d5baedb1de0f9f96304556e08f3db95c2262e29693d22bf3659ba45eff620157adc035c
6
+ metadata.gz: 366dd4b756ded58762b598545e398059fe0eae7578b13c92cc0a90dcc9aba2d58106dbf2dc0e9867a9db7de5007e6114ebbbf7cf191bcdebb843b077de945ba9
7
+ data.tar.gz: 1f798c1462825cccafd48bff23baecb8ba8ef36f9b5733502d79c5d8e6e52550c7ab722a99678eec04ce2a8b42c4d84aab912acb2100ff7d26e422b02f2c185b
data/README.md CHANGED
@@ -123,7 +123,7 @@ To add a new expectations test runner for a new request handler:
123
123
  3. [`vscode-ruby-lsp`] Select `Run Extension` and click the green triangle (or press F5).
124
124
  4. [`vscode-ruby-lsp`] Now VS Code will:
125
125
  - Open another workspace as the `Extension Development Host`.
126
- - 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.
127
127
  5. Open `ruby-lsp` in VS Code.
128
128
  6. [`ruby-lsp`] Run `bin/rdbg -A` to connect to the running `ruby-lsp` process.
129
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.4
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
@@ -9,7 +9,7 @@ module RubyLsp
9
9
  # - For nonpositional requests, use `visit` to go through the AST, which will fire events for each listener as nodes
10
10
  # are found
11
11
  #
12
- # = Example
12
+ # # Example
13
13
  #
14
14
  # ```ruby
15
15
  # target_node = document.locate_node(position)
@@ -46,6 +46,8 @@ module RubyLsp
46
46
  @event_to_listener_map[:on_call]&.each { |listener| T.unsafe(listener).on_call(node) }
47
47
  when SyntaxTree::ConstPathRef
48
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) }
49
51
  end
50
52
  end
51
53
  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,
@@ -160,9 +176,16 @@ module RubyLsp
160
176
  target = parent
161
177
  end
162
178
 
163
- listener = RubyLsp::Requests::Hover.new
164
- EventEmitter.new(listener).emit_for_target(target)
165
- listener.response
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
166
189
  end
167
190
 
168
191
  sig { params(uri: String).returns(T::Array[Interface::DocumentLink]) }
@@ -291,7 +314,7 @@ module RubyLsp
291
314
 
292
315
  case result
293
316
  when Requests::CodeActionResolve::Error::EmptySelection
294
- @notifications << Notification.new(
317
+ @messages << Notification.new(
295
318
  message: "window/showMessage",
296
319
  params: Interface::ShowMessageParams.new(
297
320
  type: Constant::MessageType::ERROR,
@@ -300,7 +323,7 @@ module RubyLsp
300
323
  )
301
324
  raise Requests::CodeActionResolve::CodeActionError
302
325
  when Requests::CodeActionResolve::Error::InvalidTargetRange
303
- @notifications << Notification.new(
326
+ @messages << Notification.new(
304
327
  message: "window/showMessage",
305
328
  params: Interface::ShowMessageParams.new(
306
329
  type: Constant::MessageType::ERROR,
@@ -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
@@ -15,3 +15,4 @@ require "ruby_lsp/event_emitter"
15
15
  require "ruby_lsp/requests"
16
16
  require "ruby_lsp/listener"
17
17
  require "ruby_lsp/store"
18
+ require "ruby_lsp/extension"
@@ -20,6 +20,16 @@ module RubyLsp
20
20
  sig { returns(T.nilable(T::Array[Symbol])) }
21
21
  attr_reader :events
22
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
+
23
33
  # All listener events must be defined inside of a `listener_events` block. This is to ensure we know which events
24
34
  # have been registered. Defining an event outside of this block will simply not register it and it'll never be
25
35
  # invoked
@@ -41,6 +41,21 @@ module RubyLsp
41
41
  super()
42
42
  end
43
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
55
+
56
+ self
57
+ end
58
+
44
59
  listener_events do
45
60
  sig { params(node: SyntaxTree::Command).void }
46
61
  def on_command(node)
@@ -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
@@ -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,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.4
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-04-11 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
@@ -75,6 +75,7 @@ files:
75
75
  - lib/ruby_lsp/document.rb
76
76
  - lib/ruby_lsp/event_emitter.rb
77
77
  - lib/ruby_lsp/executor.rb
78
+ - lib/ruby_lsp/extension.rb
78
79
  - lib/ruby_lsp/internal.rb
79
80
  - lib/ruby_lsp/listener.rb
80
81
  - lib/ruby_lsp/requests.rb
@@ -131,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
132
  - !ruby/object:Gem::Version
132
133
  version: '0'
133
134
  requirements: []
134
- rubygems_version: 3.4.10
135
+ rubygems_version: 3.4.12
135
136
  signing_key:
136
137
  specification_version: 4
137
138
  summary: An opinionated language server for Ruby