ruby-lsp-rails 0.3.8 → 0.3.13
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/README.md +2 -0
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +7 -4
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +155 -9
- data/lib/ruby_lsp/ruby_lsp_rails/definition.rb +4 -2
- data/lib/ruby_lsp/ruby_lsp_rails/indexing_enhancement.rb +113 -0
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +50 -13
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +29 -1
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2543aa7d39f0a6b231112f9ddb365692c7e8d06eb14681da551ab55fd8f5a4d
|
4
|
+
data.tar.gz: e466ccd6d90934e083e638efdcedfb5d6b8f85fbce70b139e1542eeb2ab8a139
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f7c82462682d93fd6b944c2705c8b62b424b4abc3636d1f2540eeee1ab9f7e2896eb77d338697bb65a0a64752529085f38e49bd5a75bfc668fa9d31dd5b0d10
|
7
|
+
data.tar.gz: a8978fdebb07ba8b76dd10a725b3b3c736961f103cefa806fbc0302b494516e0a196105bf7fac9d114c963a62c8a706959d53f1509a4ed343e2e9d24633c4372
|
data/README.md
CHANGED
@@ -9,6 +9,8 @@ Ruby LSP Rails is a [Ruby LSP](https://github.com/Shopify/ruby-lsp) addon for ex
|
|
9
9
|
* Navigate to associations, validations, callbacks and test cases using your editor's "Go to Symbol" feature, or outline view.
|
10
10
|
* Jump to the definition of callbacks using your editor's "Go to Definition" feature.
|
11
11
|
* Jump to the declaration of a route.
|
12
|
+
* Code Lens allowing fast-forwarding or rewinding of migrations.
|
13
|
+
* Code Lens showing the path that a route action corresponds to.
|
12
14
|
|
13
15
|
## Installation
|
14
16
|
|
@@ -13,6 +13,7 @@ require_relative "hover"
|
|
13
13
|
require_relative "code_lens"
|
14
14
|
require_relative "document_symbol"
|
15
15
|
require_relative "definition"
|
16
|
+
require_relative "indexing_enhancement"
|
16
17
|
|
17
18
|
module RubyLsp
|
18
19
|
module Rails
|
@@ -35,6 +36,8 @@ module RubyLsp
|
|
35
36
|
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
|
36
37
|
Thread.new { @client = RunnerClient.create_client }
|
37
38
|
register_additional_file_watchers(global_state: global_state, message_queue: message_queue)
|
39
|
+
|
40
|
+
T.must(@global_state).index.register_enhancement(IndexingEnhancement.new)
|
38
41
|
end
|
39
42
|
|
40
43
|
sig { override.void }
|
@@ -51,9 +54,7 @@ module RubyLsp
|
|
51
54
|
).void
|
52
55
|
end
|
53
56
|
def create_code_lens_listener(response_builder, uri, dispatcher)
|
54
|
-
|
55
|
-
|
56
|
-
CodeLens.new(response_builder, uri, dispatcher)
|
57
|
+
CodeLens.new(@client, T.must(@global_state), response_builder, uri, dispatcher)
|
57
58
|
end
|
58
59
|
|
59
60
|
sig do
|
@@ -79,7 +80,9 @@ module RubyLsp
|
|
79
80
|
|
80
81
|
sig do
|
81
82
|
override.params(
|
82
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[
|
83
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
|
84
|
+
Interface::Location, Interface::LocationLink
|
85
|
+
)],
|
83
86
|
uri: URI::Generic,
|
84
87
|
node_context: NodeContext,
|
85
88
|
dispatcher: Prism::Dispatcher,
|
@@ -5,11 +5,22 @@ module RubyLsp
|
|
5
5
|
module Rails
|
6
6
|
# 
|
7
7
|
#
|
8
|
-
# This feature adds
|
8
|
+
# This feature adds Code Lens features for Rails applications.
|
9
|
+
#
|
10
|
+
# For Active Support test cases:
|
9
11
|
#
|
10
12
|
# - Run tests in the VS Terminal
|
11
13
|
# - Run tests in the VS Code Test Explorer
|
12
14
|
# - Debug tests
|
15
|
+
# - Run migrations in the VS Terminal
|
16
|
+
#
|
17
|
+
# For Rails controllers:
|
18
|
+
#
|
19
|
+
# - See the path corresponding to an action
|
20
|
+
# - Click on the action's Code Lens to jump to its declaration in the routes.
|
21
|
+
#
|
22
|
+
# Note: This depends on a support for the `rubyLsp.openFile` command.
|
23
|
+
# For the VS Code extension this is built-in, but for other editors this may require some custom configuration.
|
13
24
|
#
|
14
25
|
# The
|
15
26
|
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
|
@@ -32,12 +43,34 @@ module RubyLsp
|
|
32
43
|
# # ...
|
33
44
|
# end
|
34
45
|
# end
|
35
|
-
#
|
46
|
+
# ```
|
47
|
+
#
|
48
|
+
# # Example:
|
49
|
+
# ```ruby
|
50
|
+
# Run
|
51
|
+
# class AddFirstNameToUsers < ActiveRecord::Migration[7.1]
|
52
|
+
# # ...
|
53
|
+
# end
|
54
|
+
# ```
|
36
55
|
#
|
37
56
|
# The code lenses will be displayed above the class and above each test method.
|
38
57
|
#
|
39
58
|
# Note: When using the Test Explorer view, if your code contains a statement to pause execution (e.g. `debugger`) it
|
40
59
|
# will cause the test runner to hang.
|
60
|
+
#
|
61
|
+
# For the following code, assuming the routing contains `resources :users`, a Code Lens will be seen above each
|
62
|
+
# action.
|
63
|
+
#
|
64
|
+
# ```ruby
|
65
|
+
# class UsersController < ApplicationController
|
66
|
+
# GET /users(.:format)
|
67
|
+
# def index # <- Will show code lens above for the path
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
# ```
|
71
|
+
#
|
72
|
+
# Note: Complex routing configurations may not be supported.
|
73
|
+
#
|
41
74
|
class CodeLens
|
42
75
|
extend T::Sig
|
43
76
|
include Requests::Support::Common
|
@@ -45,18 +78,31 @@ module RubyLsp
|
|
45
78
|
|
46
79
|
sig do
|
47
80
|
params(
|
81
|
+
client: RunnerClient,
|
82
|
+
global_state: GlobalState,
|
48
83
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
49
84
|
uri: URI::Generic,
|
50
85
|
dispatcher: Prism::Dispatcher,
|
51
86
|
).void
|
52
87
|
end
|
53
|
-
def initialize(response_builder, uri, dispatcher)
|
88
|
+
def initialize(client, global_state, response_builder, uri, dispatcher)
|
89
|
+
@client = client
|
90
|
+
@global_state = global_state
|
54
91
|
@response_builder = response_builder
|
55
92
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
56
93
|
@group_id = T.let(1, Integer)
|
57
94
|
@group_id_stack = T.let([], T::Array[Integer])
|
95
|
+
@constant_name_stack = T.let([], T::Array[[String, T.nilable(String)]])
|
58
96
|
|
59
|
-
dispatcher.register(
|
97
|
+
dispatcher.register(
|
98
|
+
self,
|
99
|
+
:on_call_node_enter,
|
100
|
+
:on_class_node_enter,
|
101
|
+
:on_def_node_enter,
|
102
|
+
:on_class_node_leave,
|
103
|
+
:on_module_node_enter,
|
104
|
+
:on_module_node_leave,
|
105
|
+
)
|
60
106
|
end
|
61
107
|
|
62
108
|
sig { params(node: Prism::CallNode).void }
|
@@ -74,46 +120,146 @@ module RubyLsp
|
|
74
120
|
sig { params(node: Prism::DefNode).void }
|
75
121
|
def on_def_node_enter(node)
|
76
122
|
method_name = node.name.to_s
|
123
|
+
|
77
124
|
if method_name.start_with?("test_")
|
78
125
|
line_number = node.location.start_line
|
79
126
|
command = "#{test_command} #{@path}:#{line_number}"
|
80
127
|
add_test_code_lens(node, name: method_name, command: command, kind: :example)
|
81
128
|
end
|
129
|
+
|
130
|
+
if controller?
|
131
|
+
add_route_code_lens_to_action(node)
|
132
|
+
add_jump_to_view(node)
|
133
|
+
end
|
82
134
|
end
|
83
135
|
|
84
136
|
sig { params(node: Prism::ClassNode).void }
|
85
137
|
def on_class_node_enter(node)
|
86
138
|
class_name = node.constant_path.slice
|
139
|
+
superclass_name = node.superclass&.slice
|
140
|
+
|
87
141
|
if class_name.end_with?("Test")
|
88
142
|
command = "#{test_command} #{@path}"
|
89
143
|
add_test_code_lens(node, name: class_name, command: command, kind: :group)
|
90
144
|
@group_id_stack.push(@group_id)
|
91
145
|
@group_id += 1
|
92
146
|
end
|
147
|
+
|
148
|
+
if superclass_name&.start_with?("ActiveRecord::Migration")
|
149
|
+
command = "#{migrate_command} VERSION=#{migration_version}"
|
150
|
+
add_migrate_code_lens(node, name: class_name, command: command)
|
151
|
+
end
|
152
|
+
|
153
|
+
# We need to use a stack because someone could define a nested class
|
154
|
+
# inside a controller. When we exit that nested class declaration, we are
|
155
|
+
# back in a controller context. This part is used in other places in the LSP
|
156
|
+
@constant_name_stack << [class_name, superclass_name]
|
93
157
|
end
|
94
158
|
|
95
159
|
sig { params(node: Prism::ClassNode).void }
|
96
160
|
def on_class_node_leave(node)
|
97
161
|
class_name = node.constant_path.slice
|
162
|
+
|
98
163
|
if class_name.end_with?("Test")
|
99
164
|
@group_id_stack.pop
|
100
165
|
end
|
166
|
+
|
167
|
+
@constant_name_stack.pop
|
168
|
+
end
|
169
|
+
|
170
|
+
sig { params(node: Prism::ModuleNode).void }
|
171
|
+
def on_module_node_enter(node)
|
172
|
+
@constant_name_stack << [node.constant_path.slice, nil]
|
173
|
+
end
|
174
|
+
|
175
|
+
sig { params(node: Prism::ModuleNode).void }
|
176
|
+
def on_module_node_leave(node)
|
177
|
+
@constant_name_stack.pop
|
101
178
|
end
|
102
179
|
|
103
180
|
private
|
104
181
|
|
182
|
+
sig { returns(T.nilable(T::Boolean)) }
|
183
|
+
def controller?
|
184
|
+
class_name, superclass_name = @constant_name_stack.last
|
185
|
+
return false unless class_name && superclass_name
|
186
|
+
|
187
|
+
class_name.end_with?("Controller") && superclass_name.end_with?("Controller")
|
188
|
+
end
|
189
|
+
|
190
|
+
sig { params(node: Prism::DefNode).void }
|
191
|
+
def add_jump_to_view(node)
|
192
|
+
class_name = @constant_name_stack.map(&:first).join("::")
|
193
|
+
action_name = node.name
|
194
|
+
controller_name = class_name
|
195
|
+
.delete_suffix("Controller")
|
196
|
+
.gsub(/([a-z])([A-Z])/, "\\1_\\2")
|
197
|
+
.gsub("::", "/")
|
198
|
+
.downcase
|
199
|
+
|
200
|
+
view_uris = Dir.glob("#{@client.rails_root}/app/views/#{controller_name}/#{action_name}*").map! do |path|
|
201
|
+
URI::Generic.from_path(path: path).to_s
|
202
|
+
end
|
203
|
+
return if view_uris.empty?
|
204
|
+
|
205
|
+
@response_builder << create_code_lens(
|
206
|
+
node,
|
207
|
+
title: "Jump to view",
|
208
|
+
command_name: "rubyLsp.openFile",
|
209
|
+
arguments: [view_uris],
|
210
|
+
data: { type: "file" },
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
sig { params(node: Prism::DefNode).void }
|
215
|
+
def add_route_code_lens_to_action(node)
|
216
|
+
class_name, _ = T.must(@constant_name_stack.last)
|
217
|
+
route = @client.route(controller: class_name, action: node.name.to_s)
|
218
|
+
return unless route
|
219
|
+
|
220
|
+
file_path, line = route[:source_location]
|
221
|
+
|
222
|
+
@response_builder << create_code_lens(
|
223
|
+
node,
|
224
|
+
title: "#{route[:verb]} #{route[:path]}",
|
225
|
+
command_name: "rubyLsp.openFile",
|
226
|
+
arguments: [["file://#{file_path}#L#{line}"]],
|
227
|
+
data: { type: "file" },
|
228
|
+
)
|
229
|
+
end
|
230
|
+
|
105
231
|
sig { returns(String) }
|
106
232
|
def test_command
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
233
|
+
"#{RbConfig.ruby} bin/rails test"
|
234
|
+
end
|
235
|
+
|
236
|
+
sig { returns(String) }
|
237
|
+
def migrate_command
|
238
|
+
"#{RbConfig.ruby} bin/rails db:migrate"
|
239
|
+
end
|
240
|
+
|
241
|
+
sig { returns(T.nilable(String)) }
|
242
|
+
def migration_version
|
243
|
+
File.basename(T.must(@path)).split("_").first
|
244
|
+
end
|
245
|
+
|
246
|
+
sig { params(node: Prism::Node, name: String, command: String).void }
|
247
|
+
def add_migrate_code_lens(node, name:, command:)
|
248
|
+
return unless @path
|
249
|
+
|
250
|
+
@response_builder << create_code_lens(
|
251
|
+
node,
|
252
|
+
title: "Run",
|
253
|
+
command_name: "rubyLsp.runTask",
|
254
|
+
arguments: [command],
|
255
|
+
data: { type: "migrate" },
|
256
|
+
)
|
112
257
|
end
|
113
258
|
|
114
259
|
sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
|
115
260
|
def add_test_code_lens(node, name:, command:, kind:)
|
116
261
|
return unless @path
|
262
|
+
return unless @global_state.test_library == "rails"
|
117
263
|
|
118
264
|
arguments = [
|
119
265
|
@path,
|
@@ -17,7 +17,7 @@ module RubyLsp
|
|
17
17
|
# # Example
|
18
18
|
#
|
19
19
|
# ```ruby
|
20
|
-
# before_action :foo # <- Go to definition on this symbol will jump to the method
|
20
|
+
# before_action :foo # <- Go to definition on this symbol will jump to the method
|
21
21
|
# ```
|
22
22
|
#
|
23
23
|
# Notes for named routes:
|
@@ -34,7 +34,9 @@ module RubyLsp
|
|
34
34
|
sig do
|
35
35
|
params(
|
36
36
|
client: RunnerClient,
|
37
|
-
response_builder: ResponseBuilders::CollectionResponseBuilder[
|
37
|
+
response_builder: RubyLsp::ResponseBuilders::CollectionResponseBuilder[T.any(
|
38
|
+
Interface::Location, Interface::LocationLink
|
39
|
+
)],
|
38
40
|
node_context: NodeContext,
|
39
41
|
index: RubyIndexer::Index,
|
40
42
|
dispatcher: Prism::Dispatcher,
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
class IndexingEnhancement
|
7
|
+
extend T::Sig
|
8
|
+
include RubyIndexer::Enhancement
|
9
|
+
|
10
|
+
sig do
|
11
|
+
override.params(
|
12
|
+
index: RubyIndexer::Index,
|
13
|
+
owner: T.nilable(RubyIndexer::Entry::Namespace),
|
14
|
+
node: Prism::CallNode,
|
15
|
+
file_path: String,
|
16
|
+
).void
|
17
|
+
end
|
18
|
+
def on_call_node(index, owner, node, file_path)
|
19
|
+
return unless owner
|
20
|
+
|
21
|
+
name = node.name
|
22
|
+
|
23
|
+
case name
|
24
|
+
when :extend
|
25
|
+
handle_concern_extend(index, owner, node)
|
26
|
+
when :has_one, :has_many, :belongs_to, :has_and_belongs_to_many
|
27
|
+
handle_association(index, owner, node, file_path)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
sig do
|
34
|
+
params(
|
35
|
+
index: RubyIndexer::Index,
|
36
|
+
owner: RubyIndexer::Entry::Namespace,
|
37
|
+
node: Prism::CallNode,
|
38
|
+
file_path: String,
|
39
|
+
).void
|
40
|
+
end
|
41
|
+
def handle_association(index, owner, node, file_path)
|
42
|
+
arguments = node.arguments&.arguments
|
43
|
+
return unless arguments
|
44
|
+
|
45
|
+
name_arg = arguments.first
|
46
|
+
|
47
|
+
name = case name_arg
|
48
|
+
when Prism::StringNode
|
49
|
+
name_arg.content
|
50
|
+
when Prism::SymbolNode
|
51
|
+
name_arg.value
|
52
|
+
end
|
53
|
+
|
54
|
+
return unless name
|
55
|
+
|
56
|
+
# Reader
|
57
|
+
index.add(RubyIndexer::Entry::Method.new(
|
58
|
+
name,
|
59
|
+
file_path,
|
60
|
+
name_arg.location,
|
61
|
+
name_arg.location,
|
62
|
+
[],
|
63
|
+
[RubyIndexer::Entry::Signature.new([])],
|
64
|
+
RubyIndexer::Entry::Visibility::PUBLIC,
|
65
|
+
owner,
|
66
|
+
))
|
67
|
+
|
68
|
+
# Writer
|
69
|
+
index.add(RubyIndexer::Entry::Method.new(
|
70
|
+
"#{name}=",
|
71
|
+
file_path,
|
72
|
+
name_arg.location,
|
73
|
+
name_arg.location,
|
74
|
+
[],
|
75
|
+
[RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: name.to_sym)])],
|
76
|
+
RubyIndexer::Entry::Visibility::PUBLIC,
|
77
|
+
owner,
|
78
|
+
))
|
79
|
+
end
|
80
|
+
|
81
|
+
sig do
|
82
|
+
params(
|
83
|
+
index: RubyIndexer::Index,
|
84
|
+
owner: RubyIndexer::Entry::Namespace,
|
85
|
+
node: Prism::CallNode,
|
86
|
+
).void
|
87
|
+
end
|
88
|
+
def handle_concern_extend(index, owner, node)
|
89
|
+
arguments = node.arguments&.arguments
|
90
|
+
return unless arguments
|
91
|
+
|
92
|
+
arguments.each do |node|
|
93
|
+
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
|
94
|
+
|
95
|
+
module_name = node.full_name
|
96
|
+
next unless module_name == "ActiveSupport::Concern"
|
97
|
+
|
98
|
+
index.register_included_hook(owner.name) do |index, base|
|
99
|
+
class_methods_name = "#{owner.name}::ClassMethods"
|
100
|
+
|
101
|
+
if index.indexed?(class_methods_name)
|
102
|
+
singleton = index.existing_or_new_singleton_class(base.name)
|
103
|
+
singleton.mixin_operations << RubyIndexer::Entry::Include.new(class_methods_name)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
107
|
+
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
108
|
+
# Do nothing
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -36,8 +36,12 @@ module RubyLsp
|
|
36
36
|
|
37
37
|
extend T::Sig
|
38
38
|
|
39
|
+
sig { returns(String) }
|
40
|
+
attr_reader :rails_root
|
41
|
+
|
39
42
|
sig { void }
|
40
43
|
def initialize
|
44
|
+
@mutex = T.let(Mutex.new, Mutex)
|
41
45
|
# Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
|
42
46
|
# parent ends, the spring process ends as well. If this is not set, Spring will throw an error while trying to
|
43
47
|
# set its own session ID
|
@@ -67,7 +71,8 @@ module RubyLsp
|
|
67
71
|
|
68
72
|
begin
|
69
73
|
count += 1
|
70
|
-
read_response
|
74
|
+
initialize_response = T.must(read_response)
|
75
|
+
@rails_root = T.let(initialize_response[:root], String)
|
71
76
|
rescue EmptyMessageError
|
72
77
|
$stderr.puts("Ruby LSP Rails is retrying initialize (#{count})")
|
73
78
|
retry if count < MAX_RETRIES
|
@@ -80,9 +85,7 @@ module RubyLsp
|
|
80
85
|
if @wait_thread.alive?
|
81
86
|
$stderr.puts("Ruby LSP Rails is force killing the server")
|
82
87
|
sleep(0.5) # give the server a bit of time if we already issued a shutdown notification
|
83
|
-
|
84
|
-
# Windows does not support the `TERM` signal, so we're forced to use `KILL` here
|
85
|
-
Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
|
88
|
+
force_kill
|
86
89
|
end
|
87
90
|
end
|
88
91
|
end
|
@@ -122,6 +125,14 @@ module RubyLsp
|
|
122
125
|
nil
|
123
126
|
end
|
124
127
|
|
128
|
+
sig { params(controller: String, action: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
129
|
+
def route(controller:, action:)
|
130
|
+
make_request("route_info", controller: controller, action: action)
|
131
|
+
rescue IncompleteMessageError
|
132
|
+
$stderr.puts("Ruby LSP Rails failed to get route information: #{@stderr.read}")
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
125
136
|
sig { void }
|
126
137
|
def trigger_reload
|
127
138
|
$stderr.puts("Reloading Rails application")
|
@@ -137,6 +148,9 @@ module RubyLsp
|
|
137
148
|
send_message("shutdown")
|
138
149
|
sleep(0.5) # give the server a bit of time to shutdown
|
139
150
|
[@stdin, @stdout, @stderr].each(&:close)
|
151
|
+
rescue IOError
|
152
|
+
# The server connection may have died
|
153
|
+
force_kill
|
140
154
|
end
|
141
155
|
|
142
156
|
sig { returns(T::Boolean) }
|
@@ -157,26 +171,35 @@ module RubyLsp
|
|
157
171
|
read_response
|
158
172
|
end
|
159
173
|
|
160
|
-
sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
174
|
+
sig { overridable.params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
161
175
|
def send_message(request, params = nil)
|
162
176
|
message = { method: request }
|
163
177
|
message[:params] = params if params
|
164
178
|
json = message.to_json
|
165
179
|
|
166
|
-
@
|
180
|
+
@mutex.synchronize do
|
181
|
+
@stdin.write("Content-Length: #{json.length}\r\n\r\n", json)
|
182
|
+
end
|
183
|
+
rescue Errno::EPIPE
|
184
|
+
# The server connection died
|
167
185
|
end
|
168
186
|
|
169
|
-
|
187
|
+
# Notifications are like messages, but one-way, with no response sent back.
|
188
|
+
sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
189
|
+
def send_notification(request, params = nil) = send_message(request, params)
|
170
190
|
|
171
|
-
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
191
|
+
sig { overridable.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
172
192
|
def read_response
|
173
|
-
|
174
|
-
|
193
|
+
raw_response = @mutex.synchronize do
|
194
|
+
headers = @stdout.gets("\r\n\r\n")
|
195
|
+
raise IncompleteMessageError unless headers
|
175
196
|
|
176
|
-
|
177
|
-
|
197
|
+
content_length = headers[/Content-Length: (\d+)/i, 1].to_i
|
198
|
+
raise EmptyMessageError if content_length.zero?
|
199
|
+
|
200
|
+
@stdout.read(content_length)
|
201
|
+
end
|
178
202
|
|
179
|
-
raw_response = @stdout.read(content_length)
|
180
203
|
response = JSON.parse(T.must(raw_response), symbolize_names: true)
|
181
204
|
|
182
205
|
if response[:error]
|
@@ -185,6 +208,15 @@ module RubyLsp
|
|
185
208
|
end
|
186
209
|
|
187
210
|
response.fetch(:result)
|
211
|
+
rescue Errno::EPIPE
|
212
|
+
# The server connection died
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
|
216
|
+
sig { void }
|
217
|
+
def force_kill
|
218
|
+
# Windows does not support the `TERM` signal, so we're forced to use `KILL` here
|
219
|
+
Process.kill(T.must(Signal.list["KILL"]), @wait_thread.pid)
|
188
220
|
end
|
189
221
|
end
|
190
222
|
|
@@ -205,6 +237,11 @@ module RubyLsp
|
|
205
237
|
true
|
206
238
|
end
|
207
239
|
|
240
|
+
sig { override.returns(String) }
|
241
|
+
def rails_root
|
242
|
+
Dir.pwd
|
243
|
+
end
|
244
|
+
|
208
245
|
private
|
209
246
|
|
210
247
|
sig { override.params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
@@ -24,7 +24,7 @@ module RubyLsp
|
|
24
24
|
routes_reloader = ::Rails.application.routes_reloader
|
25
25
|
routes_reloader.execute_unless_loaded if routes_reloader&.respond_to?(:execute_unless_loaded)
|
26
26
|
|
27
|
-
initialize_result = { result: { message: "ok" } }.to_json
|
27
|
+
initialize_result = { result: { message: "ok", root: ::Rails.root.to_s } }.to_json
|
28
28
|
$stdout.write("Content-Length: #{initialize_result.length}\r\n\r\n#{initialize_result}")
|
29
29
|
|
30
30
|
while @running
|
@@ -54,6 +54,8 @@ module RubyLsp
|
|
54
54
|
VOID
|
55
55
|
when "route_location"
|
56
56
|
route_location(params.fetch(:name))
|
57
|
+
when "route_info"
|
58
|
+
resolve_route_info(params)
|
57
59
|
else
|
58
60
|
VOID
|
59
61
|
end
|
@@ -63,6 +65,32 @@ module RubyLsp
|
|
63
65
|
|
64
66
|
private
|
65
67
|
|
68
|
+
def resolve_route_info(requirements)
|
69
|
+
if requirements[:controller]
|
70
|
+
requirements[:controller] = requirements.fetch(:controller).underscore.delete_suffix("_controller")
|
71
|
+
end
|
72
|
+
|
73
|
+
# In Rails 7.2 we can use `from_requirements, otherwise we fall back to a private API
|
74
|
+
route = if ::Rails.application.routes.respond_to?(:from_requirements)
|
75
|
+
::Rails.application.routes.from_requirements(requirements)
|
76
|
+
else
|
77
|
+
::Rails.application.routes.routes.find { |route| route.requirements == requirements }
|
78
|
+
end
|
79
|
+
|
80
|
+
if route&.source_location
|
81
|
+
file, _, line = route.source_location.rpartition(":")
|
82
|
+
body = {
|
83
|
+
source_location: [::Rails.root.join(file).to_s, line],
|
84
|
+
verb: route.verb,
|
85
|
+
path: route.path.spec.to_s,
|
86
|
+
}
|
87
|
+
|
88
|
+
{ result: body }
|
89
|
+
else
|
90
|
+
{ result: nil }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
66
94
|
# Older versions of Rails don't support `route_source_locations`.
|
67
95
|
# We also check that it's enabled.
|
68
96
|
if ActionDispatch::Routing::Mapper.respond_to?(:route_source_locations) &&
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-lsp
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.17.
|
19
|
+
version: 0.17.12
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 0.18.0
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.17.
|
29
|
+
version: 0.17.12
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 0.18.0
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- lib/ruby_lsp/ruby_lsp_rails/definition.rb
|
47
47
|
- lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb
|
48
48
|
- lib/ruby_lsp/ruby_lsp_rails/hover.rb
|
49
|
+
- lib/ruby_lsp/ruby_lsp_rails/indexing_enhancement.rb
|
49
50
|
- lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
|
50
51
|
- lib/ruby_lsp/ruby_lsp_rails/server.rb
|
51
52
|
- lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb
|
@@ -79,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
80
|
- !ruby/object:Gem::Version
|
80
81
|
version: '0'
|
81
82
|
requirements: []
|
82
|
-
rubygems_version: 3.5.
|
83
|
+
rubygems_version: 3.5.17
|
83
84
|
signing_key:
|
84
85
|
specification_version: 4
|
85
86
|
summary: A Ruby LSP addon for Rails
|