ruby-lsp-rails 0.3.31 → 0.4.1
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 +4 -4
- data/Rakefile +11 -2
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +68 -61
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +15 -24
- data/lib/ruby_lsp/ruby_lsp_rails/completion.rb +6 -14
- data/lib/ruby_lsp/ruby_lsp_rails/definition.rb +9 -20
- data/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb +12 -24
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +39 -27
- data/lib/ruby_lsp/ruby_lsp_rails/indexing_enhancement.rb +7 -20
- data/lib/ruby_lsp/ruby_lsp_rails/rails_test_style.rb +150 -0
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +90 -59
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +174 -17
- data/lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb +1 -3
- data/lib/ruby_lsp/ruby_lsp_rails/support/location_builder.rb +1 -3
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +6 -5
@@ -15,18 +15,9 @@ module RubyLsp
|
|
15
15
|
# # ^ hovering here will show information about the User model
|
16
16
|
# ```
|
17
17
|
class Hover
|
18
|
-
extend T::Sig
|
19
18
|
include Requests::Support::Common
|
20
19
|
|
21
|
-
|
22
|
-
params(
|
23
|
-
client: RunnerClient,
|
24
|
-
response_builder: ResponseBuilders::Hover,
|
25
|
-
node_context: NodeContext,
|
26
|
-
global_state: GlobalState,
|
27
|
-
dispatcher: Prism::Dispatcher,
|
28
|
-
).void
|
29
|
-
end
|
20
|
+
#: (RunnerClient client, ResponseBuilders::Hover response_builder, NodeContext node_context, GlobalState global_state, Prism::Dispatcher dispatcher) -> void
|
30
21
|
def initialize(client, response_builder, node_context, global_state, dispatcher)
|
31
22
|
@client = client
|
32
23
|
@response_builder = response_builder
|
@@ -35,26 +26,26 @@ module RubyLsp
|
|
35
26
|
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter)
|
36
27
|
end
|
37
28
|
|
38
|
-
|
29
|
+
#: (Prism::ConstantPathNode node) -> void
|
39
30
|
def on_constant_path_node_enter(node)
|
40
31
|
entries = @index.resolve(node.slice, @nesting)
|
41
32
|
return unless entries
|
42
33
|
|
43
|
-
name =
|
34
|
+
name = entries.first.name
|
44
35
|
generate_column_content(name)
|
45
36
|
end
|
46
37
|
|
47
|
-
|
38
|
+
#: (Prism::ConstantReadNode node) -> void
|
48
39
|
def on_constant_read_node_enter(node)
|
49
40
|
entries = @index.resolve(node.name.to_s, @nesting)
|
50
41
|
return unless entries
|
51
42
|
|
52
|
-
generate_column_content(
|
43
|
+
generate_column_content(entries.first.name)
|
53
44
|
end
|
54
45
|
|
55
46
|
private
|
56
47
|
|
57
|
-
|
48
|
+
#: (String name) -> void
|
58
49
|
def generate_column_content(name)
|
59
50
|
model = @client.model(name)
|
60
51
|
return if model.nil?
|
@@ -66,20 +57,41 @@ module RubyLsp
|
|
66
57
|
category: :documentation,
|
67
58
|
) if schema_file
|
68
59
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
60
|
+
if model[:columns].any?
|
61
|
+
@response_builder.push(
|
62
|
+
"### Columns",
|
63
|
+
category: :documentation,
|
64
|
+
)
|
65
|
+
@response_builder.push(
|
66
|
+
model[:columns].map do |name, type, default_value, nullable|
|
67
|
+
primary_key_suffix = " (PK)" if model[:primary_keys].include?(name)
|
68
|
+
foreign_key_suffix = " (FK)" if model[:foreign_keys].include?(name)
|
69
|
+
suffixes = []
|
70
|
+
suffixes << "default: #{format_default(default_value, type)}" if default_value
|
71
|
+
suffixes << "not null" unless nullable || primary_key_suffix
|
72
|
+
suffix_string = " - #{suffixes.join(" - ")}" if suffixes.any?
|
73
|
+
"- **#{name}**: #{type}#{primary_key_suffix}#{foreign_key_suffix}#{suffix_string}\n"
|
74
|
+
end.join("\n"),
|
75
|
+
category: :documentation,
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
if model[:indexes].any?
|
80
|
+
@response_builder.push(
|
81
|
+
"### Indexes",
|
82
|
+
category: :documentation,
|
83
|
+
)
|
84
|
+
@response_builder.push(
|
85
|
+
model[:indexes].map do |index|
|
86
|
+
uniqueness = index[:unique] ? " (unique)" : ""
|
87
|
+
"- **#{index[:name]}** (#{index[:columns].join(",")})#{uniqueness}"
|
88
|
+
end.join("\n"),
|
89
|
+
category: :documentation,
|
90
|
+
)
|
91
|
+
end
|
80
92
|
end
|
81
93
|
|
82
|
-
|
94
|
+
#: (String default_value, String type) -> String
|
83
95
|
def format_default(default_value, type)
|
84
96
|
case type
|
85
97
|
when "boolean"
|
@@ -4,13 +4,8 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module Rails
|
6
6
|
class IndexingEnhancement < RubyIndexer::Enhancement
|
7
|
-
|
8
|
-
|
9
|
-
sig do
|
10
|
-
override.params(
|
11
|
-
call_node: Prism::CallNode,
|
12
|
-
).void
|
13
|
-
end
|
7
|
+
# @override
|
8
|
+
#: (Prism::CallNode call_node) -> void
|
14
9
|
def on_call_node_enter(call_node)
|
15
10
|
owner = @listener.current_owner
|
16
11
|
return unless owner
|
@@ -26,11 +21,8 @@ module RubyLsp
|
|
26
21
|
end
|
27
22
|
end
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
call_node: Prism::CallNode,
|
32
|
-
).void
|
33
|
-
end
|
24
|
+
# @override
|
25
|
+
#: (Prism::CallNode call_node) -> void
|
34
26
|
def on_call_node_leave(call_node)
|
35
27
|
if call_node.name == :class_methods && call_node.block
|
36
28
|
@listener.pop_namespace_stack
|
@@ -39,12 +31,7 @@ module RubyLsp
|
|
39
31
|
|
40
32
|
private
|
41
33
|
|
42
|
-
|
43
|
-
params(
|
44
|
-
owner: RubyIndexer::Entry::Namespace,
|
45
|
-
call_node: Prism::CallNode,
|
46
|
-
).void
|
47
|
-
end
|
34
|
+
#: (RubyIndexer::Entry::Namespace owner, Prism::CallNode call_node) -> void
|
48
35
|
def handle_association(owner, call_node)
|
49
36
|
arguments = call_node.arguments&.arguments
|
50
37
|
return unless arguments
|
@@ -73,7 +60,7 @@ module RubyLsp
|
|
73
60
|
@listener.add_method("#{name}=", loc, writer_signatures)
|
74
61
|
end
|
75
62
|
|
76
|
-
|
63
|
+
#: (RubyIndexer::Entry::Namespace owner, Prism::CallNode call_node) -> void
|
77
64
|
def handle_concern_extend(owner, call_node)
|
78
65
|
arguments = call_node.arguments&.arguments
|
79
66
|
return unless arguments
|
@@ -98,7 +85,7 @@ module RubyLsp
|
|
98
85
|
end
|
99
86
|
end
|
100
87
|
|
101
|
-
|
88
|
+
#: (RubyIndexer::Entry::Namespace owner, Prism::CallNode call_node) -> void
|
102
89
|
def handle_class_methods(owner, call_node)
|
103
90
|
return unless call_node.block
|
104
91
|
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
class RailsTestStyle < Listeners::TestDiscovery
|
7
|
+
BASE_COMMAND = "#{RbConfig.ruby} bin/rails test" #: String
|
8
|
+
|
9
|
+
class << self
|
10
|
+
#: (Array[Hash[Symbol, untyped]]) -> Array[String]
|
11
|
+
def resolve_test_commands(items)
|
12
|
+
commands = []
|
13
|
+
queue = items.dup
|
14
|
+
|
15
|
+
full_files = []
|
16
|
+
|
17
|
+
until queue.empty?
|
18
|
+
item = T.must(queue.shift)
|
19
|
+
tags = Set.new(item[:tags])
|
20
|
+
next unless tags.include?("framework:rails")
|
21
|
+
|
22
|
+
children = item[:children]
|
23
|
+
uri = URI(item[:uri])
|
24
|
+
path = uri.full_path
|
25
|
+
next unless path
|
26
|
+
|
27
|
+
if tags.include?("test_dir")
|
28
|
+
if children.empty?
|
29
|
+
full_files.concat(Dir.glob(
|
30
|
+
"#{path}/**/{*_test,test_*}.rb",
|
31
|
+
File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME,
|
32
|
+
))
|
33
|
+
end
|
34
|
+
elsif tags.include?("test_file")
|
35
|
+
full_files << path if children.empty?
|
36
|
+
elsif tags.include?("test_group")
|
37
|
+
commands << "#{BASE_COMMAND} #{path} --name \"/#{Shellwords.escape(item[:id])}(#|::)/\""
|
38
|
+
else
|
39
|
+
full_files << "#{path}:#{item.dig(:range, :start, :line) + 1}"
|
40
|
+
end
|
41
|
+
|
42
|
+
queue.concat(children)
|
43
|
+
end
|
44
|
+
|
45
|
+
unless full_files.empty?
|
46
|
+
commands << "#{BASE_COMMAND} #{full_files.join(" ")}"
|
47
|
+
end
|
48
|
+
|
49
|
+
commands
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#: (RunnerClient client, ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
|
54
|
+
def initialize(client, response_builder, global_state, dispatcher, uri)
|
55
|
+
super(response_builder, global_state, dispatcher, uri)
|
56
|
+
|
57
|
+
dispatcher.register(
|
58
|
+
self,
|
59
|
+
:on_class_node_enter,
|
60
|
+
:on_call_node_enter,
|
61
|
+
:on_def_node_enter,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
#: (Prism::ClassNode node) -> void
|
66
|
+
def on_class_node_enter(node)
|
67
|
+
with_test_ancestor_tracking(node) do |name, ancestors|
|
68
|
+
if declarative_minitest?(ancestors, name)
|
69
|
+
test_item = Requests::Support::TestItem.new(
|
70
|
+
name,
|
71
|
+
name,
|
72
|
+
@uri,
|
73
|
+
range_from_node(node),
|
74
|
+
framework: :rails,
|
75
|
+
)
|
76
|
+
|
77
|
+
@response_builder.add(test_item)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#: (Prism::CallNode node) -> void
|
83
|
+
def on_call_node_enter(node)
|
84
|
+
return unless node.name == :test
|
85
|
+
return unless node.block
|
86
|
+
|
87
|
+
arguments = node.arguments&.arguments
|
88
|
+
first_arg = arguments&.first
|
89
|
+
return unless first_arg.is_a?(Prism::StringNode)
|
90
|
+
|
91
|
+
test_name = first_arg.content
|
92
|
+
test_name = "<empty test name>" if test_name.empty?
|
93
|
+
|
94
|
+
# Rails' `test "foo bar"` helper defines a method `def test_foo_bar`. We normalize test names
|
95
|
+
# the same way (spaces to underscores, prefix with `test_`) to match the actual method names
|
96
|
+
# Rails uses at runtime, ensuring proper test discovery and execution.
|
97
|
+
rails_normalized_name = "test_#{test_name.gsub(/\s+/, "_")}"
|
98
|
+
|
99
|
+
add_test_item(node, rails_normalized_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
#: (Prism::DefNode node) -> void
|
103
|
+
def on_def_node_enter(node)
|
104
|
+
return if @visibility_stack.last != :public
|
105
|
+
|
106
|
+
name = node.name.to_s
|
107
|
+
return unless name.start_with?("test_")
|
108
|
+
|
109
|
+
add_test_item(node, name)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
#: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
|
115
|
+
def declarative_minitest?(attached_ancestors, fully_qualified_name)
|
116
|
+
# The declarative test style is present as long as the class extends
|
117
|
+
# ActiveSupport::Testing::Declarative
|
118
|
+
name_parts = fully_qualified_name.split("::")
|
119
|
+
singleton_name = "#{name_parts.join("::")}::<Class:#{name_parts.last}>"
|
120
|
+
@index.linearized_ancestors_of(singleton_name).include?("ActiveSupport::Testing::Declarative")
|
121
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
#: (Prism::Node node, String test_name) -> void
|
126
|
+
def add_test_item(node, test_name)
|
127
|
+
test_item = group_test_item
|
128
|
+
return unless test_item
|
129
|
+
|
130
|
+
test_item.add(Requests::Support::TestItem.new(
|
131
|
+
"#{test_item.id}##{test_name}",
|
132
|
+
test_name,
|
133
|
+
@uri,
|
134
|
+
range_from_node(node),
|
135
|
+
framework: :rails,
|
136
|
+
))
|
137
|
+
end
|
138
|
+
|
139
|
+
#: -> Requests::Support::TestItem?
|
140
|
+
def group_test_item
|
141
|
+
current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
|
142
|
+
|
143
|
+
# If we're finding a test method, but for the wrong framework, then the group test item will not have been
|
144
|
+
# previously pushed and thus we return early and avoid adding items for a framework this listener is not
|
145
|
+
# interested in
|
146
|
+
@response_builder[current_group_name]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -8,12 +8,10 @@ module RubyLsp
|
|
8
8
|
module Rails
|
9
9
|
class RunnerClient
|
10
10
|
class << self
|
11
|
-
|
12
|
-
|
13
|
-
sig { params(outgoing_queue: Thread::Queue).returns(RunnerClient) }
|
14
|
-
def create_client(outgoing_queue)
|
11
|
+
#: (Thread::Queue outgoing_queue, RubyLsp::GlobalState global_state) -> RunnerClient
|
12
|
+
def create_client(outgoing_queue, global_state)
|
15
13
|
if File.exist?("bin/rails")
|
16
|
-
new(outgoing_queue)
|
14
|
+
new(outgoing_queue, global_state)
|
17
15
|
else
|
18
16
|
unless outgoing_queue.closed?
|
19
17
|
outgoing_queue << RubyLsp::Notification.window_log_message(
|
@@ -46,13 +44,11 @@ module RubyLsp
|
|
46
44
|
class MessageError < StandardError; end
|
47
45
|
class EmptyMessageError < MessageError; end
|
48
46
|
|
49
|
-
|
50
|
-
|
51
|
-
sig { returns(String) }
|
47
|
+
#: String
|
52
48
|
attr_reader :rails_root
|
53
49
|
|
54
|
-
|
55
|
-
def initialize(outgoing_queue)
|
50
|
+
#: (Thread::Queue outgoing_queue, RubyLsp::GlobalState global_state) -> void
|
51
|
+
def initialize(outgoing_queue, global_state)
|
56
52
|
@outgoing_queue = T.let(outgoing_queue, Thread::Queue)
|
57
53
|
@mutex = T.let(Mutex.new, Mutex)
|
58
54
|
# Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
|
@@ -71,7 +67,15 @@ module RubyLsp
|
|
71
67
|
log_message("Ruby LSP Rails booting server")
|
72
68
|
|
73
69
|
stdin, stdout, stderr, wait_thread = Bundler.with_original_env do
|
74
|
-
Open3.popen3(
|
70
|
+
Open3.popen3(
|
71
|
+
"bundle",
|
72
|
+
"exec",
|
73
|
+
"rails",
|
74
|
+
"runner",
|
75
|
+
"#{__dir__}/server.rb",
|
76
|
+
"start",
|
77
|
+
server_relevant_capabilities(global_state),
|
78
|
+
)
|
75
79
|
end
|
76
80
|
|
77
81
|
@stdin = T.let(stdin, IO)
|
@@ -100,10 +104,16 @@ module RubyLsp
|
|
100
104
|
end
|
101
105
|
end
|
102
106
|
|
103
|
-
|
107
|
+
# Responsible for transmitting notifications coming from the server to the outgoing queue, so that we can do
|
108
|
+
# things such as showing progress notifications initiated by the server
|
109
|
+
@notifier_thread = T.let(
|
104
110
|
Thread.new do
|
105
|
-
|
106
|
-
|
111
|
+
until @stderr.closed?
|
112
|
+
notification = read_notification
|
113
|
+
|
114
|
+
unless @outgoing_queue.closed? || !notification
|
115
|
+
@outgoing_queue << notification
|
116
|
+
end
|
107
117
|
end
|
108
118
|
rescue IOError
|
109
119
|
# The server was shutdown and stderr is already closed
|
@@ -114,7 +124,7 @@ module RubyLsp
|
|
114
124
|
raise InitializationError, @stderr.read
|
115
125
|
end
|
116
126
|
|
117
|
-
|
127
|
+
#: (String server_addon_path) -> void
|
118
128
|
def register_server_addon(server_addon_path)
|
119
129
|
send_notification("server_addon/register", server_addon_path: server_addon_path)
|
120
130
|
rescue MessageError
|
@@ -125,7 +135,7 @@ module RubyLsp
|
|
125
135
|
nil
|
126
136
|
end
|
127
137
|
|
128
|
-
|
138
|
+
#: (String name) -> Hash[Symbol, untyped]?
|
129
139
|
def model(name)
|
130
140
|
make_request("model", name: name)
|
131
141
|
rescue MessageError
|
@@ -136,12 +146,7 @@ module RubyLsp
|
|
136
146
|
nil
|
137
147
|
end
|
138
148
|
|
139
|
-
|
140
|
-
params(
|
141
|
-
model_name: String,
|
142
|
-
association_name: String,
|
143
|
-
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
144
|
-
end
|
149
|
+
#: (model_name: String, association_name: String) -> Hash[Symbol, untyped]?
|
145
150
|
def association_target_location(model_name:, association_name:)
|
146
151
|
make_request(
|
147
152
|
"association_target_location",
|
@@ -156,7 +161,7 @@ module RubyLsp
|
|
156
161
|
nil
|
157
162
|
end
|
158
163
|
|
159
|
-
|
164
|
+
#: (String name) -> Hash[Symbol, untyped]?
|
160
165
|
def route_location(name)
|
161
166
|
make_request("route_location", name: name)
|
162
167
|
rescue MessageError
|
@@ -167,7 +172,7 @@ module RubyLsp
|
|
167
172
|
nil
|
168
173
|
end
|
169
174
|
|
170
|
-
|
175
|
+
#: (controller: String, action: String) -> Hash[Symbol, untyped]?
|
171
176
|
def route(controller:, action:)
|
172
177
|
make_request("route_info", controller: controller, action: action)
|
173
178
|
rescue MessageError
|
@@ -179,7 +184,7 @@ module RubyLsp
|
|
179
184
|
end
|
180
185
|
|
181
186
|
# Delegates a notification to a server add-on
|
182
|
-
|
187
|
+
#: (server_addon_name: String, request_name: String, **untyped params) -> void
|
183
188
|
def delegate_notification(server_addon_name:, request_name:, **params)
|
184
189
|
send_notification(
|
185
190
|
"server_addon/delegate",
|
@@ -189,7 +194,7 @@ module RubyLsp
|
|
189
194
|
)
|
190
195
|
end
|
191
196
|
|
192
|
-
|
197
|
+
#: -> String?
|
193
198
|
def pending_migrations_message
|
194
199
|
response = make_request("pending_migrations_message")
|
195
200
|
response[:pending_migrations_message] if response
|
@@ -201,7 +206,7 @@ module RubyLsp
|
|
201
206
|
nil
|
202
207
|
end
|
203
208
|
|
204
|
-
|
209
|
+
#: -> Hash[Symbol, untyped]?
|
205
210
|
def run_migrations
|
206
211
|
make_request("run_migrations")
|
207
212
|
rescue MessageError
|
@@ -213,13 +218,7 @@ module RubyLsp
|
|
213
218
|
end
|
214
219
|
|
215
220
|
# Delegates a request to a server add-on
|
216
|
-
|
217
|
-
params(
|
218
|
-
server_addon_name: String,
|
219
|
-
request_name: String,
|
220
|
-
params: T.untyped,
|
221
|
-
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
222
|
-
end
|
221
|
+
#: (server_addon_name: String, request_name: String, **untyped params) -> Hash[Symbol, untyped]?
|
223
222
|
def delegate_request(server_addon_name:, request_name:, **params)
|
224
223
|
make_request(
|
225
224
|
"server_addon/delegate",
|
@@ -231,7 +230,7 @@ module RubyLsp
|
|
231
230
|
nil
|
232
231
|
end
|
233
232
|
|
234
|
-
|
233
|
+
#: -> void
|
235
234
|
def trigger_reload
|
236
235
|
log_message("Reloading Rails application")
|
237
236
|
send_notification("reload")
|
@@ -243,7 +242,7 @@ module RubyLsp
|
|
243
242
|
nil
|
244
243
|
end
|
245
244
|
|
246
|
-
|
245
|
+
#: -> void
|
247
246
|
def shutdown
|
248
247
|
log_message("Ruby LSP Rails shutting down server")
|
249
248
|
send_message("shutdown")
|
@@ -254,29 +253,30 @@ module RubyLsp
|
|
254
253
|
force_kill
|
255
254
|
end
|
256
255
|
|
257
|
-
|
256
|
+
#: -> bool
|
258
257
|
def stopped?
|
259
258
|
[@stdin, @stdout, @stderr].all?(&:closed?) && !@wait_thread.alive?
|
260
259
|
end
|
261
260
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
params: T.untyped,
|
266
|
-
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
261
|
+
#: -> bool
|
262
|
+
def connected?
|
263
|
+
true
|
267
264
|
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
#: (String request, **untyped params) -> Hash[Symbol, untyped]?
|
268
269
|
def make_request(request, **params)
|
269
270
|
send_message(request, **params)
|
270
271
|
read_response
|
271
272
|
end
|
272
273
|
|
273
274
|
# Notifications are like messages, but one-way, with no response sent back.
|
274
|
-
|
275
|
+
#: (String request, **untyped params) -> void
|
275
276
|
def send_notification(request, **params) = send_message(request, **params)
|
276
277
|
|
277
|
-
|
278
|
-
|
279
|
-
sig { overridable.params(request: String, params: T.untyped).void }
|
278
|
+
# @overridable
|
279
|
+
#: (String request, **untyped params) -> void
|
280
280
|
def send_message(request, **params)
|
281
281
|
message = { method: request }
|
282
282
|
message[:params] = params
|
@@ -289,7 +289,8 @@ module RubyLsp
|
|
289
289
|
# The server connection died
|
290
290
|
end
|
291
291
|
|
292
|
-
|
292
|
+
# @overridable
|
293
|
+
#: -> Hash[Symbol, untyped]?
|
293
294
|
def read_response
|
294
295
|
raw_response = @mutex.synchronize do
|
295
296
|
content_length = read_content_length
|
@@ -315,20 +316,20 @@ module RubyLsp
|
|
315
316
|
nil
|
316
317
|
end
|
317
318
|
|
318
|
-
|
319
|
+
#: -> void
|
319
320
|
def force_kill
|
320
321
|
# Windows does not support the `TERM` signal, so we're forced to use `KILL` here
|
321
322
|
Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
|
322
323
|
end
|
323
324
|
|
324
|
-
|
325
|
+
#: (::String message, ?type: ::Integer) -> void
|
325
326
|
def log_message(message, type: RubyLsp::Constant::MessageType::LOG)
|
326
327
|
return if @outgoing_queue.closed?
|
327
328
|
|
328
329
|
@outgoing_queue << RubyLsp::Notification.window_log_message(message, type: type)
|
329
330
|
end
|
330
331
|
|
331
|
-
|
332
|
+
#: -> Integer?
|
332
333
|
def read_content_length
|
333
334
|
headers = @stdout.gets("\r\n\r\n")
|
334
335
|
return unless headers
|
@@ -338,43 +339,73 @@ module RubyLsp
|
|
338
339
|
|
339
340
|
length.to_i
|
340
341
|
end
|
342
|
+
|
343
|
+
# Read a server notification from stderr. Only intended to be used by notifier thread
|
344
|
+
#: -> Hash[Symbol, untyped]?
|
345
|
+
def read_notification
|
346
|
+
headers = @stderr.gets("\r\n\r\n")
|
347
|
+
return unless headers
|
348
|
+
|
349
|
+
length = headers[/Content-Length: (\d+)/i, 1]
|
350
|
+
return unless length
|
351
|
+
|
352
|
+
raw_content = @stderr.read(length.to_i)
|
353
|
+
return unless raw_content
|
354
|
+
|
355
|
+
JSON.parse(raw_content, symbolize_names: true)
|
356
|
+
end
|
357
|
+
|
358
|
+
#: (GlobalState global_state) -> String
|
359
|
+
def server_relevant_capabilities(global_state)
|
360
|
+
{
|
361
|
+
supports_progress: global_state.client_capabilities.supports_progress,
|
362
|
+
}.to_json
|
363
|
+
end
|
341
364
|
end
|
342
365
|
|
343
366
|
class NullClient < RunnerClient
|
344
|
-
|
345
|
-
|
346
|
-
sig { void }
|
367
|
+
#: -> void
|
347
368
|
def initialize # rubocop:disable Lint/MissingSuper
|
348
369
|
end
|
349
370
|
|
350
|
-
|
371
|
+
# @override
|
372
|
+
#: -> void
|
351
373
|
def shutdown
|
352
374
|
# no-op
|
353
375
|
end
|
354
376
|
|
355
|
-
|
377
|
+
# @override
|
378
|
+
#: -> bool
|
356
379
|
def stopped?
|
357
380
|
true
|
358
381
|
end
|
359
382
|
|
360
|
-
|
383
|
+
# @override
|
384
|
+
#: -> String
|
361
385
|
def rails_root
|
362
386
|
Dir.pwd
|
363
387
|
end
|
364
388
|
|
389
|
+
#: -> bool
|
390
|
+
def connected?
|
391
|
+
false
|
392
|
+
end
|
393
|
+
|
365
394
|
private
|
366
395
|
|
367
|
-
|
396
|
+
#: (::String message, ?type: ::Integer) -> void
|
368
397
|
def log_message(message, type: RubyLsp::Constant::MessageType::LOG)
|
369
398
|
# no-op
|
370
399
|
end
|
371
400
|
|
372
|
-
|
401
|
+
# @override
|
402
|
+
#: (String request, **untyped params) -> void
|
373
403
|
def send_message(request, **params)
|
374
404
|
# no-op
|
375
405
|
end
|
376
406
|
|
377
|
-
|
407
|
+
# @override
|
408
|
+
#: -> Hash[Symbol, untyped]?
|
378
409
|
def read_response
|
379
410
|
# no-op
|
380
411
|
end
|