ruby-lsp 0.4.4 → 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: 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