ruby-lsp-rails 0.3.2 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
# ![Definition demo](../../definition.gif)
|
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
|
# ![Document Symbol demo](../../document_symbol.gif)
|
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
|