ruby-lsp-rails 0.3.2 → 0.3.4
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 +13 -23
- data/Rakefile +1 -0
- data/lib/ruby-lsp-rails.rb +2 -0
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +32 -5
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +6 -1
- data/lib/ruby_lsp/ruby_lsp_rails/definition.rb +87 -0
- data/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb +155 -6
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +3 -3
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +43 -11
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +6 -3
- data/lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb +0 -2
- data/lib/ruby_lsp/ruby_lsp_rails/support/callbacks.rb +67 -0
- data/lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb +3 -3
- data/lib/ruby_lsp_rails/version.rb +1 -1
- data/lib/tasks/ruby_lsp_rails_tasks.rake +1 -0
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 463c37a3e7c9fef9c7b5433d2ef9519f41a29867a0089fe6508fc701a6cf1871
|
4
|
+
data.tar.gz: 0f3e7a164235e85b610f9e0855a0d57e9a25125999ff1c15ed31acc54880cad2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85cbe926939cac7051877d168de8164099265329ccb0cbe4ff6dbe9d1508ae64ce52b168a816d6cd538c79c384f04a1873654b692c98592cb032bc9c1470f318
|
7
|
+
data.tar.gz: c1023803f769de1d701788a85e17906b9626aa9fee89a7d36cabb849826ca68021c00402d8a3d7043cb3128ac7966195fb9ef5b0714109fff96ad907359d7ed5
|
data/README.md
CHANGED
@@ -7,37 +7,27 @@ Ruby LSP Rails is a [Ruby LSP](https://github.com/Shopify/ruby-lsp) addon for ex
|
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
10
|
-
|
10
|
+
If you haven't already done so, you'll need to first [set up Ruby LSP](https://github.com/Shopify/ruby-lsp#usage).
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
group :development do
|
15
|
-
gem "ruby-lsp-rails"
|
16
|
-
end
|
17
|
-
```
|
12
|
+
As of v0.3.0, Ruby LSP will automatically include the Ruby LSP Rails addon in its custom bundle when a Rails app is detected.
|
13
|
+
There is no need to add the gem to your bundle.
|
18
14
|
|
19
|
-
##
|
15
|
+
## Features
|
20
16
|
|
21
|
-
|
17
|
+
* Hover over an ActiveRecord model to reveal its schema.
|
18
|
+
* Run or debug a test by clicking on the code lens which appears above the test class, or an individual test.
|
19
|
+
* Navigate to associations, validations, callbacks and test cases using your editor's "Go to Symbol" feature, or outline view.
|
20
|
+
* Jump to the definition of callbacks using your editor's "Go to Definition" feature.
|
22
21
|
|
23
|
-
|
24
|
-
1. Hover over an ActiveRecord model to see its details
|
25
|
-
|
26
|
-
### Documentation
|
22
|
+
## Documentation
|
27
23
|
|
28
24
|
See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-depth details about the
|
29
25
|
[supported features](https://shopify.github.io/ruby-lsp-rails/RubyLsp/Rails.html).
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
1. Open a test which inherits from `ActiveSupport::TestCase` or one of its descendants, such as `ActionDispatch::IntegrationTest`.
|
34
|
-
2. Click on the "Run", "Run in Terminal" or "Debug" code lens which appears above the test class, or an individual test.
|
35
|
-
|
36
|
-
> [!NOTE]
|
37
|
-
> When using the Test Explorer view, if your code contains a statement to pause execution (e.g. `debugger`) it will
|
38
|
-
> cause the test runner to hang.
|
27
|
+
## How Runtime Introspection Works
|
39
28
|
|
40
|
-
|
29
|
+
LSP tooling is typically based on static analysis, but `ruby-lsp-rails` actually communicates with your Rails app for
|
30
|
+
some features.
|
41
31
|
|
42
32
|
When Ruby LSP Rails starts, it spawns a `rails runner` instance which runs
|
43
33
|
[`server.rb`](https://github.com/Shopify/ruby-lsp-rails/blob/main/lib/ruby_lsp/ruby_lsp_rails/server.rb).
|
@@ -46,7 +36,7 @@ The addon communicates with this process over a pipe (i.e. `stdin` and `stdout`)
|
|
46
36
|
When extension is stopped (e.g. by quitting the editor), the server instance is shut down.
|
47
37
|
|
48
38
|
> [!NOTE]
|
49
|
-
> Prior to v0.3, `ruby-lsp-rails` used a different approach which involved mounting a Rack application within the Rails app.
|
39
|
+
> Prior to v0.3.0, `ruby-lsp-rails` used a different approach which involved mounting a Rack application within the Rails app.
|
50
40
|
> That approach was brittle and susceptible to the application's configuration, such as routing and middleware.
|
51
41
|
|
52
42
|
## Contributing
|
data/Rakefile
CHANGED
@@ -23,6 +23,7 @@ RDoc::Task.new do |rdoc|
|
|
23
23
|
rdoc.rdoc_files.include("*.md", "lib/**/*.rb")
|
24
24
|
rdoc.rdoc_dir = "docs"
|
25
25
|
rdoc.markup = "markdown"
|
26
|
+
rdoc.generator = "snapper"
|
26
27
|
rdoc.options.push("--copy-files", "misc")
|
27
28
|
rdoc.options.push("--copy-files", "LICENSE.txt")
|
28
29
|
end
|
data/lib/ruby-lsp-rails.rb
CHANGED
@@ -9,6 +9,8 @@ module RubyLsp
|
|
9
9
|
#
|
10
10
|
# - [Hover](rdoc-ref:RubyLsp::Rails::Hover)
|
11
11
|
# - [CodeLens](rdoc-ref:RubyLsp::Rails::CodeLens)
|
12
|
+
# - [DocumentSymbol](rdoc-ref:RubyLsp::Rails::DocumentSymbol)
|
13
|
+
# - [Definition](rdoc-ref:RubyLsp::Rails::Definition)
|
12
14
|
module Rails
|
13
15
|
end
|
14
16
|
end
|
@@ -4,10 +4,12 @@
|
|
4
4
|
require "ruby_lsp/addon"
|
5
5
|
|
6
6
|
require_relative "support/active_support_test_case_helper"
|
7
|
+
require_relative "support/callbacks"
|
7
8
|
require_relative "runner_client"
|
8
9
|
require_relative "hover"
|
9
10
|
require_relative "code_lens"
|
10
11
|
require_relative "document_symbol"
|
12
|
+
require_relative "definition"
|
11
13
|
|
12
14
|
module RubyLsp
|
13
15
|
module Rails
|
@@ -23,8 +25,10 @@ module RubyLsp
|
|
23
25
|
@client = T.let(NullClient.new, RunnerClient)
|
24
26
|
end
|
25
27
|
|
26
|
-
sig { override.params(message_queue: Thread::Queue).void }
|
27
|
-
def activate(message_queue)
|
28
|
+
sig { override.params(global_state: GlobalState, message_queue: Thread::Queue).void }
|
29
|
+
def activate(global_state, message_queue)
|
30
|
+
@global_state = T.let(global_state, T.nilable(RubyLsp::GlobalState))
|
31
|
+
$stderr.puts("Activating Ruby LSP Rails addon v#{VERSION}")
|
28
32
|
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
|
29
33
|
Thread.new { @client = RunnerClient.create_client }
|
30
34
|
end
|
@@ -43,6 +47,8 @@ module RubyLsp
|
|
43
47
|
).void
|
44
48
|
end
|
45
49
|
def create_code_lens_listener(response_builder, uri, dispatcher)
|
50
|
+
return unless T.must(@global_state).test_library == "rails"
|
51
|
+
|
46
52
|
CodeLens.new(response_builder, uri, dispatcher)
|
47
53
|
end
|
48
54
|
|
@@ -50,12 +56,11 @@ module RubyLsp
|
|
50
56
|
override.params(
|
51
57
|
response_builder: ResponseBuilders::Hover,
|
52
58
|
nesting: T::Array[String],
|
53
|
-
index: RubyIndexer::Index,
|
54
59
|
dispatcher: Prism::Dispatcher,
|
55
60
|
).void
|
56
61
|
end
|
57
|
-
def create_hover_listener(response_builder, nesting,
|
58
|
-
Hover.new(@client, response_builder, nesting,
|
62
|
+
def create_hover_listener(response_builder, nesting, dispatcher)
|
63
|
+
Hover.new(@client, response_builder, nesting, T.must(@global_state), dispatcher)
|
59
64
|
end
|
60
65
|
|
61
66
|
sig do
|
@@ -68,6 +73,28 @@ module RubyLsp
|
|
68
73
|
DocumentSymbol.new(response_builder, dispatcher)
|
69
74
|
end
|
70
75
|
|
76
|
+
sig do
|
77
|
+
override.params(
|
78
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
79
|
+
uri: URI::Generic,
|
80
|
+
nesting: T::Array[String],
|
81
|
+
dispatcher: Prism::Dispatcher,
|
82
|
+
).void
|
83
|
+
end
|
84
|
+
def create_definition_listener(response_builder, uri, nesting, dispatcher)
|
85
|
+
index = T.must(@global_state).index
|
86
|
+
Definition.new(response_builder, nesting, index, dispatcher)
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { params(changes: T::Array[{ uri: String, type: Integer }]).void }
|
90
|
+
def workspace_did_change_watched_files(changes)
|
91
|
+
if changes.any? do |change|
|
92
|
+
change[:uri].end_with?("db/schema.rb") || change[:uri].end_with?("structure.sql")
|
93
|
+
end
|
94
|
+
@client.trigger_reload
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
71
98
|
sig { override.returns(String) }
|
72
99
|
def name
|
73
100
|
"Ruby LSP Rails"
|
@@ -12,7 +12,9 @@ module RubyLsp
|
|
12
12
|
#
|
13
13
|
# The
|
14
14
|
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
|
15
|
-
# request informs the editor of runnable commands such as tests
|
15
|
+
# request informs the editor of runnable commands such as tests.
|
16
|
+
# It's available for tests which inherit from `ActiveSupport::TestCase` or one of its descendants, such as
|
17
|
+
# `ActionDispatch::IntegrationTest`.
|
16
18
|
#
|
17
19
|
# # Example:
|
18
20
|
#
|
@@ -32,6 +34,9 @@ module RubyLsp
|
|
32
34
|
# ````
|
33
35
|
#
|
34
36
|
# The code lenses will be displayed above the class and above each test method.
|
37
|
+
#
|
38
|
+
# Note: When using the Test Explorer view, if your code contains a statement to pause execution (e.g. `debugger`) it
|
39
|
+
# will cause the test runner to hang.
|
35
40
|
class CodeLens
|
36
41
|
extend T::Sig
|
37
42
|
include Requests::Support::Common
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
# 
|
7
|
+
#
|
8
|
+
# The [definition
|
9
|
+
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
|
10
|
+
# definition of the symbol under the cursor.
|
11
|
+
#
|
12
|
+
# Currently supported targets:
|
13
|
+
# - Callbacks
|
14
|
+
#
|
15
|
+
# # Example
|
16
|
+
#
|
17
|
+
# ```ruby
|
18
|
+
# before_action :foo # <- Go to definition on this symbol will jump to the method if it is defined in the same class
|
19
|
+
# ```
|
20
|
+
class Definition
|
21
|
+
extend T::Sig
|
22
|
+
include Requests::Support::Common
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(
|
26
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
27
|
+
nesting: T::Array[String],
|
28
|
+
index: RubyIndexer::Index,
|
29
|
+
dispatcher: Prism::Dispatcher,
|
30
|
+
).void
|
31
|
+
end
|
32
|
+
def initialize(response_builder, nesting, index, dispatcher)
|
33
|
+
@response_builder = response_builder
|
34
|
+
@nesting = nesting
|
35
|
+
@index = index
|
36
|
+
|
37
|
+
dispatcher.register(self, :on_call_node_enter)
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { params(node: Prism::CallNode).void }
|
41
|
+
def on_call_node_enter(node)
|
42
|
+
return unless self_receiver?(node)
|
43
|
+
|
44
|
+
message = node.message
|
45
|
+
|
46
|
+
return unless message && Support::Callbacks::ALL.include?(message)
|
47
|
+
|
48
|
+
arguments = node.arguments&.arguments
|
49
|
+
return unless arguments&.any?
|
50
|
+
|
51
|
+
arguments.each do |argument|
|
52
|
+
name = case argument
|
53
|
+
when Prism::SymbolNode
|
54
|
+
argument.value
|
55
|
+
when Prism::StringNode
|
56
|
+
argument.content
|
57
|
+
end
|
58
|
+
|
59
|
+
next unless name
|
60
|
+
|
61
|
+
collect_definitions(name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
sig { params(name: String).void }
|
68
|
+
def collect_definitions(name)
|
69
|
+
methods = @index.resolve_method(name, @nesting.join("::"))
|
70
|
+
return unless methods
|
71
|
+
|
72
|
+
methods.each do |target_method|
|
73
|
+
location = target_method.location
|
74
|
+
file_path = target_method.file_path
|
75
|
+
|
76
|
+
@response_builder << Interface::Location.new(
|
77
|
+
uri: URI::Generic.from_path(path: file_path).to_s,
|
78
|
+
range: Interface::Range.new(
|
79
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
80
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
81
|
+
),
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -6,7 +6,8 @@ module RubyLsp
|
|
6
6
|
# 
|
7
7
|
#
|
8
8
|
# The [document symbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol)
|
9
|
-
# request allows users to navigate between ActiveSupport test cases with
|
9
|
+
# request allows users to navigate between associations, validations, callbacks and ActiveSupport test cases with
|
10
|
+
# VS Code's "Go to Symbol" feature.
|
10
11
|
class DocumentSymbol
|
11
12
|
extend T::Sig
|
12
13
|
include Requests::Support::Common
|
@@ -28,13 +29,161 @@ module RubyLsp
|
|
28
29
|
def on_call_node_enter(node)
|
29
30
|
content = extract_test_case_name(node)
|
30
31
|
|
31
|
-
|
32
|
+
if content
|
33
|
+
append_document_symbol(
|
34
|
+
name: content,
|
35
|
+
selection_range: range_from_node(node),
|
36
|
+
range: range_from_node(node),
|
37
|
+
)
|
38
|
+
end
|
32
39
|
|
40
|
+
receiver = node.receiver
|
41
|
+
return if receiver && !receiver.is_a?(Prism::SelfNode)
|
42
|
+
|
43
|
+
message = node.message
|
44
|
+
case message
|
45
|
+
when *Support::Callbacks::ALL, "validate"
|
46
|
+
handle_all_arg_types(node, T.must(message))
|
47
|
+
when "validates", "validates!", "validates_each", "belongs_to", "has_one", "has_many", "has_and_belongs_to_many"
|
48
|
+
handle_symbol_and_string_arg_types(node, T.must(message))
|
49
|
+
when "validates_with"
|
50
|
+
handle_class_arg_types(node, T.must(message))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
sig { params(node: Prism::CallNode, message: String).void }
|
57
|
+
def handle_all_arg_types(node, message)
|
58
|
+
block = node.block
|
59
|
+
|
60
|
+
if block
|
61
|
+
append_document_symbol(
|
62
|
+
name: "#{message} <anonymous>",
|
63
|
+
range: range_from_location(node.location),
|
64
|
+
selection_range: range_from_location(block.location),
|
65
|
+
)
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
arguments = node.arguments&.arguments
|
70
|
+
return unless arguments&.any?
|
71
|
+
|
72
|
+
arguments.each do |argument|
|
73
|
+
case argument
|
74
|
+
when Prism::SymbolNode
|
75
|
+
name = argument.value
|
76
|
+
next unless name
|
77
|
+
|
78
|
+
append_document_symbol(
|
79
|
+
name: "#{message} :#{name}",
|
80
|
+
range: range_from_location(argument.location),
|
81
|
+
selection_range: range_from_location(T.must(argument.value_loc)),
|
82
|
+
)
|
83
|
+
when Prism::StringNode
|
84
|
+
name = argument.content
|
85
|
+
next if name.empty?
|
86
|
+
|
87
|
+
append_document_symbol(
|
88
|
+
name: "#{message} :#{name}",
|
89
|
+
range: range_from_location(argument.location),
|
90
|
+
selection_range: range_from_location(argument.content_loc),
|
91
|
+
)
|
92
|
+
when Prism::LambdaNode
|
93
|
+
append_document_symbol(
|
94
|
+
name: "#{message} <anonymous>",
|
95
|
+
range: range_from_location(node.location),
|
96
|
+
selection_range: range_from_location(argument.location),
|
97
|
+
)
|
98
|
+
when Prism::CallNode
|
99
|
+
next unless argument.name == :new
|
100
|
+
|
101
|
+
arg_receiver = argument.receiver
|
102
|
+
|
103
|
+
name = arg_receiver.full_name if arg_receiver.is_a?(Prism::ConstantReadNode) ||
|
104
|
+
arg_receiver.is_a?(Prism::ConstantPathNode)
|
105
|
+
next unless name
|
106
|
+
|
107
|
+
append_document_symbol(
|
108
|
+
name: "#{message} #{name}",
|
109
|
+
range: range_from_location(argument.location),
|
110
|
+
selection_range: range_from_location(argument.location),
|
111
|
+
)
|
112
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
113
|
+
name = argument.full_name
|
114
|
+
next if name.empty?
|
115
|
+
|
116
|
+
append_document_symbol(
|
117
|
+
name: "#{message} #{name}",
|
118
|
+
range: range_from_location(argument.location),
|
119
|
+
selection_range: range_from_location(argument.location),
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
sig { params(node: Prism::CallNode, message: String).void }
|
126
|
+
def handle_symbol_and_string_arg_types(node, message)
|
127
|
+
arguments = node.arguments&.arguments
|
128
|
+
return unless arguments&.any?
|
129
|
+
|
130
|
+
arguments.each do |argument|
|
131
|
+
case argument
|
132
|
+
when Prism::SymbolNode
|
133
|
+
name = argument.value
|
134
|
+
next unless name
|
135
|
+
|
136
|
+
append_document_symbol(
|
137
|
+
name: "#{message} :#{name}",
|
138
|
+
range: range_from_location(argument.location),
|
139
|
+
selection_range: range_from_location(T.must(argument.value_loc)),
|
140
|
+
)
|
141
|
+
when Prism::StringNode
|
142
|
+
name = argument.content
|
143
|
+
next if name.empty?
|
144
|
+
|
145
|
+
append_document_symbol(
|
146
|
+
name: "#{message} :#{name}",
|
147
|
+
range: range_from_location(argument.location),
|
148
|
+
selection_range: range_from_location(argument.content_loc),
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
sig { params(node: Prism::CallNode, message: String).void }
|
155
|
+
def handle_class_arg_types(node, message)
|
156
|
+
arguments = node.arguments&.arguments
|
157
|
+
return unless arguments&.any?
|
158
|
+
|
159
|
+
arguments.each do |argument|
|
160
|
+
case argument
|
161
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
162
|
+
name = argument.full_name
|
163
|
+
next if name.empty?
|
164
|
+
|
165
|
+
append_document_symbol(
|
166
|
+
name: "#{message} #{name}",
|
167
|
+
range: range_from_location(argument.location),
|
168
|
+
selection_range: range_from_location(argument.location),
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
sig do
|
175
|
+
params(
|
176
|
+
name: String,
|
177
|
+
range: RubyLsp::Interface::Range,
|
178
|
+
selection_range: RubyLsp::Interface::Range,
|
179
|
+
).void
|
180
|
+
end
|
181
|
+
def append_document_symbol(name:, range:, selection_range:)
|
33
182
|
@response_builder.last.children << RubyLsp::Interface::DocumentSymbol.new(
|
34
|
-
name:
|
35
|
-
kind:
|
36
|
-
|
37
|
-
|
183
|
+
name: name,
|
184
|
+
kind: RubyLsp::Constant::SymbolKind::METHOD,
|
185
|
+
range: range,
|
186
|
+
selection_range: selection_range,
|
38
187
|
)
|
39
188
|
end
|
40
189
|
end
|
@@ -25,15 +25,15 @@ module RubyLsp
|
|
25
25
|
client: RunnerClient,
|
26
26
|
response_builder: ResponseBuilders::Hover,
|
27
27
|
nesting: T::Array[String],
|
28
|
-
|
28
|
+
global_state: GlobalState,
|
29
29
|
dispatcher: Prism::Dispatcher,
|
30
30
|
).void
|
31
31
|
end
|
32
|
-
def initialize(client, response_builder, nesting,
|
32
|
+
def initialize(client, response_builder, nesting, global_state, dispatcher)
|
33
33
|
@client = client
|
34
34
|
@response_builder = response_builder
|
35
35
|
@nesting = nesting
|
36
|
-
@index = index
|
36
|
+
@index = T.let(global_state.index, RubyIndexer::Index)
|
37
37
|
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter, :on_call_node_enter)
|
38
38
|
end
|
39
39
|
|
@@ -12,16 +12,27 @@ module RubyLsp
|
|
12
12
|
|
13
13
|
sig { returns(RunnerClient) }
|
14
14
|
def create_client
|
15
|
-
|
15
|
+
if File.exist?("bin/rails")
|
16
|
+
new
|
17
|
+
else
|
18
|
+
$stderr.puts(<<~MSG)
|
19
|
+
Ruby LSP Rails failed to locate bin/rails in the current directory: #{Dir.pwd}"
|
20
|
+
MSG
|
21
|
+
$stderr.puts("Server dependent features will not be available")
|
22
|
+
NullClient.new
|
23
|
+
end
|
16
24
|
rescue Errno::ENOENT, StandardError => e # rubocop:disable Lint/ShadowedException
|
17
|
-
|
18
|
-
|
25
|
+
$stderr.puts("Ruby LSP Rails failed to initialize server: #{e.message}\n#{e.backtrace&.join("\n")}")
|
26
|
+
$stderr.puts("Server dependent features will not be available")
|
19
27
|
NullClient.new
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
23
31
|
class InitializationError < StandardError; end
|
24
32
|
class IncompleteMessageError < StandardError; end
|
33
|
+
class EmptyMessageError < StandardError; end
|
34
|
+
|
35
|
+
MAX_RETRIES = 5
|
25
36
|
|
26
37
|
extend T::Sig
|
27
38
|
|
@@ -50,14 +61,23 @@ module RubyLsp
|
|
50
61
|
@stdin.binmode # for Windows compatibility
|
51
62
|
@stdout.binmode # for Windows compatibility
|
52
63
|
|
53
|
-
|
54
|
-
|
55
|
-
|
64
|
+
$stderr.puts("Ruby LSP Rails booting server")
|
65
|
+
count = 0
|
66
|
+
|
67
|
+
begin
|
68
|
+
count += 1
|
69
|
+
read_response
|
70
|
+
rescue EmptyMessageError
|
71
|
+
$stderr.puts("Ruby LSP Rails is retrying initialize (#{count})")
|
72
|
+
retry if count < MAX_RETRIES
|
73
|
+
end
|
74
|
+
|
75
|
+
$stderr.puts("Finished booting Ruby LSP Rails server")
|
56
76
|
|
57
77
|
unless ENV["RAILS_ENV"] == "test"
|
58
78
|
at_exit do
|
59
79
|
if @wait_thread.alive?
|
60
|
-
|
80
|
+
$stderr.puts("Ruby LSP Rails is force killing the server")
|
61
81
|
sleep(0.5) # give the server a bit of time if we already issued a shutdown notification
|
62
82
|
Process.kill(T.must(Signal.list["TERM"]), @wait_thread.pid)
|
63
83
|
end
|
@@ -71,13 +91,22 @@ module RubyLsp
|
|
71
91
|
def model(name)
|
72
92
|
make_request("model", name: name)
|
73
93
|
rescue IncompleteMessageError
|
74
|
-
|
94
|
+
$stderr.puts("Ruby LSP Rails failed to get model information: #{@stderr.read}")
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
sig { void }
|
99
|
+
def trigger_reload
|
100
|
+
$stderr.puts("Reloading Rails application")
|
101
|
+
send_notification("reload")
|
102
|
+
rescue IncompleteMessageError
|
103
|
+
$stderr.puts("Ruby LSP Rails failed to trigger reload")
|
75
104
|
nil
|
76
105
|
end
|
77
106
|
|
78
107
|
sig { void }
|
79
108
|
def shutdown
|
80
|
-
|
109
|
+
$stderr.puts("Ruby LSP Rails shutting down server")
|
81
110
|
send_message("shutdown")
|
82
111
|
sleep(0.5) # give the server a bit of time to shutdown
|
83
112
|
[@stdin, @stdout, @stderr].each(&:close)
|
@@ -117,11 +146,14 @@ module RubyLsp
|
|
117
146
|
headers = @stdout.gets("\r\n\r\n")
|
118
147
|
raise IncompleteMessageError unless headers
|
119
148
|
|
120
|
-
|
149
|
+
content_length = headers[/Content-Length: (\d+)/i, 1].to_i
|
150
|
+
raise EmptyMessageError if content_length.zero?
|
151
|
+
|
152
|
+
raw_response = @stdout.read(content_length)
|
121
153
|
response = JSON.parse(T.must(raw_response), symbolize_names: true)
|
122
154
|
|
123
155
|
if response[:error]
|
124
|
-
|
156
|
+
$stderr.puts("Ruby LSP Rails error: " + response[:error])
|
125
157
|
return
|
126
158
|
end
|
127
159
|
|
@@ -57,16 +57,19 @@ module RubyLsp
|
|
57
57
|
sig do
|
58
58
|
params(
|
59
59
|
request: String,
|
60
|
-
params: T::Hash[Symbol, T.untyped],
|
60
|
+
params: T.nilable(T::Hash[Symbol, T.untyped]),
|
61
61
|
).returns(T.any(Object, T::Hash[Symbol, T.untyped]))
|
62
62
|
end
|
63
|
-
def execute(request, params
|
63
|
+
def execute(request, params)
|
64
64
|
case request
|
65
65
|
when "shutdown"
|
66
66
|
@running = false
|
67
67
|
VOID
|
68
68
|
when "model"
|
69
|
-
resolve_database_info_from_model(params.fetch(:name))
|
69
|
+
resolve_database_info_from_model(T.must(params).fetch(:name))
|
70
|
+
when "reload"
|
71
|
+
::Rails.application.reloader.reload!
|
72
|
+
VOID
|
70
73
|
else
|
71
74
|
VOID
|
72
75
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
module Support
|
7
|
+
module Callbacks
|
8
|
+
MODELS = T.let(
|
9
|
+
[
|
10
|
+
"before_validation",
|
11
|
+
"after_validation",
|
12
|
+
"before_save",
|
13
|
+
"around_save",
|
14
|
+
"after_save",
|
15
|
+
"before_create",
|
16
|
+
"around_create",
|
17
|
+
"after_create",
|
18
|
+
"after_commit",
|
19
|
+
"after_rollback",
|
20
|
+
"before_update",
|
21
|
+
"around_update",
|
22
|
+
"after_update",
|
23
|
+
"before_destroy",
|
24
|
+
"around_destroy",
|
25
|
+
"after_destroy",
|
26
|
+
"after_initialize",
|
27
|
+
"after_find",
|
28
|
+
"after_touch",
|
29
|
+
].freeze,
|
30
|
+
T::Array[String],
|
31
|
+
)
|
32
|
+
|
33
|
+
CONTROLLERS = T.let(
|
34
|
+
[
|
35
|
+
"after_action",
|
36
|
+
"append_after_action",
|
37
|
+
"append_around_action",
|
38
|
+
"append_before_action",
|
39
|
+
"around_action",
|
40
|
+
"before_action",
|
41
|
+
"prepend_after_action",
|
42
|
+
"prepend_around_action",
|
43
|
+
"prepend_before_action",
|
44
|
+
"skip_after_action",
|
45
|
+
"skip_around_action",
|
46
|
+
"skip_before_action",
|
47
|
+
].freeze,
|
48
|
+
T::Array[String],
|
49
|
+
)
|
50
|
+
|
51
|
+
JOBS = T.let(
|
52
|
+
[
|
53
|
+
"after_enqueue",
|
54
|
+
"after_perform",
|
55
|
+
"around_enqueue",
|
56
|
+
"around_perform",
|
57
|
+
"before_enqueue",
|
58
|
+
"before_perform",
|
59
|
+
].freeze,
|
60
|
+
T::Array[String],
|
61
|
+
)
|
62
|
+
|
63
|
+
ALL = T.let((MODELS + CONTROLLERS + JOBS).freeze, T::Array[String])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -66,7 +66,7 @@ module RubyLsp
|
|
66
66
|
private def build_search_index
|
67
67
|
return unless RAILTIES_VERSION
|
68
68
|
|
69
|
-
|
69
|
+
$stderr.puts("Fetching Rails Documents...")
|
70
70
|
|
71
71
|
response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/v#{RAILTIES_VERSION}/js/search_index.js"))
|
72
72
|
|
@@ -79,13 +79,13 @@ module RubyLsp
|
|
79
79
|
response = Net::HTTP.get_response(URI("#{RAILS_DOC_HOST}/js/search_index.js"))
|
80
80
|
response.body if response.is_a?(Net::HTTPSuccess)
|
81
81
|
else
|
82
|
-
|
82
|
+
$stderr.puts("Response failed: #{response.inspect}")
|
83
83
|
nil
|
84
84
|
end
|
85
85
|
|
86
86
|
process_search_index(body) if body
|
87
87
|
rescue StandardError => e
|
88
|
-
|
88
|
+
$stderr.puts("Exception occurred when fetching Rails document index: #{e.inspect}")
|
89
89
|
end
|
90
90
|
|
91
91
|
sig { params(js: String).returns(T::Hash[String, T::Array[T::Hash[Symbol, String]]]) }
|
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.4
|
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-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -58,20 +58,20 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
61
|
+
version: 0.16.0
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: 0.
|
64
|
+
version: 0.17.0
|
65
65
|
type: :runtime
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
69
|
- - ">="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: 0.
|
71
|
+
version: 0.16.0
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: 0.
|
74
|
+
version: 0.17.0
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: sorbet-runtime
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,11 +99,13 @@ files:
|
|
99
99
|
- lib/ruby-lsp-rails.rb
|
100
100
|
- lib/ruby_lsp/ruby_lsp_rails/addon.rb
|
101
101
|
- lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
|
102
|
+
- lib/ruby_lsp/ruby_lsp_rails/definition.rb
|
102
103
|
- lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb
|
103
104
|
- lib/ruby_lsp/ruby_lsp_rails/hover.rb
|
104
105
|
- lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
|
105
106
|
- lib/ruby_lsp/ruby_lsp_rails/server.rb
|
106
107
|
- lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb
|
108
|
+
- lib/ruby_lsp/ruby_lsp_rails/support/callbacks.rb
|
107
109
|
- lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
|
108
110
|
- lib/ruby_lsp_rails/railtie.rb
|
109
111
|
- lib/ruby_lsp_rails/version.rb
|
@@ -124,14 +126,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
126
|
requirements:
|
125
127
|
- - ">="
|
126
128
|
- !ruby/object:Gem::Version
|
127
|
-
version:
|
129
|
+
version: 3.0.0
|
128
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
131
|
requirements:
|
130
132
|
- - ">="
|
131
133
|
- !ruby/object:Gem::Version
|
132
134
|
version: '0'
|
133
135
|
requirements: []
|
134
|
-
rubygems_version: 3.5.
|
136
|
+
rubygems_version: 3.5.7
|
135
137
|
signing_key:
|
136
138
|
specification_version: 4
|
137
139
|
summary: A Ruby LSP addon for Rails
|