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 +4 -4
- data/README.md +1 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -1
- data/lib/ruby_lsp/event_emitter.rb +3 -1
- data/lib/ruby_lsp/executor.rb +34 -11
- data/lib/ruby_lsp/extension.rb +104 -0
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listener.rb +10 -0
- data/lib/ruby_lsp/requests/hover.rb +15 -0
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -0
- data/lib/ruby_lsp/server.rb +14 -5
- data/lib/ruby_lsp/utils.rb +12 -6
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 437c2048862bed450fd70057489636117c99943dd7bb68df35c4e161a29f8385
|
4
|
+
data.tar.gz: 30d26e8455f9086bc934ffe23b5e37816f18f5904e3422495c316a980a860cfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
-
#
|
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
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -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
|
-
@
|
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,
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
164
|
-
|
165
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
data/lib/ruby_lsp/internal.rb
CHANGED
data/lib/ruby_lsp/listener.rb
CHANGED
@@ -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
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -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,
|
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,
|
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))
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -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
|
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[
|
36
|
-
attr_reader :
|
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
|
-
|
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:,
|
58
|
+
def initialize(response:, messages:, error: nil, request_time: nil)
|
53
59
|
@response = response
|
54
|
-
@
|
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
|
+
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
|
+
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.
|
135
|
+
rubygems_version: 3.4.12
|
135
136
|
signing_key:
|
136
137
|
specification_version: 4
|
137
138
|
summary: An opinionated language server for Ruby
|