ruby-lsp-rails 0.2.9 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -9
- data/lib/ruby-lsp-rails.rb +0 -3
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +15 -13
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +14 -14
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +25 -34
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +84 -0
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +87 -0
- data/lib/ruby_lsp_rails/railtie.rb +4 -21
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +9 -9
- data/lib/ruby_lsp/ruby_lsp_rails/rails_client.rb +0 -77
- data/lib/ruby_lsp_rails/rack_app.rb +0 -58
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecc8c1b4cd4569bdb9c816f842a90f0dfb1122931adcd9c96d1e2eb8a3799471
|
4
|
+
data.tar.gz: 3c1bcc4f69a361ae6a26b635331f6475413fed38bac9a88c7ed45ffea02129eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eff545775dfa463c99b301a94ab450070f683d3f70a3c974683ac1b311a53d10da57acbd051a6fd0e4c8b005629c1d18bd8d61e33c94548e2fcf2b22b70df606
|
7
|
+
data.tar.gz: 927c991dc2d499be19c626b4a64cfcc11064f2bbadad41abb71ebea13e1e33592810c7fe6135393d7a1c2140d760e8518eee085d3a2d596942af5e031ff8f272
|
data/README.md
CHANGED
@@ -36,8 +36,6 @@ end
|
|
36
36
|
1. Start your Rails server
|
37
37
|
1. Hover over an ActiveRecord model to see its details
|
38
38
|
|
39
|
-
Nested models (e.g. `Admin::User`) are not yet supported.
|
40
|
-
|
41
39
|
### Documentation
|
42
40
|
|
43
41
|
See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-depth details about the
|
@@ -54,16 +52,15 @@ See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-de
|
|
54
52
|
|
55
53
|
## How It Works
|
56
54
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
1. A Ruby LSP addon that connects to the exposed APIs to fetch runtime information from the Rails server
|
55
|
+
When Ruby LSP Rails starts, it spawns a `rails runner` instance which runs
|
56
|
+
`[server.rb](https://github.com/Shopify/ruby-lsp-rails/blob/main/lib/ruby_lsp/ruby_lsp_rails/server.rb)`.
|
57
|
+
The addon communicates with this process over a pipe (i.e. `stdin` and `stdout`) to fetch runtime information about the application.
|
61
58
|
|
62
|
-
|
59
|
+
When extension is stopped (e.g. by quitting the editor), the server instance is shut down.
|
63
60
|
|
64
61
|
> [!NOTE]
|
65
|
-
>
|
66
|
-
>
|
62
|
+
> Prior to v0.3, `ruby-lsp-rails` used a different approach which involved mounting a Rack application within the Rails app.
|
63
|
+
> That approach was brittle and susceptible to the application's configuration, such as routing and middleware.
|
67
64
|
|
68
65
|
## Contributing
|
69
66
|
|
data/lib/ruby-lsp-rails.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
require "ruby_lsp/addon"
|
5
5
|
|
6
|
-
require_relative "
|
6
|
+
require_relative "runner_client"
|
7
7
|
require_relative "hover"
|
8
8
|
require_relative "code_lens"
|
9
9
|
|
@@ -12,39 +12,41 @@ module RubyLsp
|
|
12
12
|
class Addon < ::RubyLsp::Addon
|
13
13
|
extend T::Sig
|
14
14
|
|
15
|
-
sig { returns(
|
15
|
+
sig { returns(RunnerClient) }
|
16
16
|
def client
|
17
|
-
@client ||= T.let(
|
17
|
+
@client ||= T.let(RunnerClient.new, T.nilable(RunnerClient))
|
18
18
|
end
|
19
19
|
|
20
20
|
sig { override.params(message_queue: Thread::Queue).void }
|
21
|
-
def activate(message_queue)
|
22
|
-
client.check_if_server_is_running!
|
23
|
-
end
|
21
|
+
def activate(message_queue); end
|
24
22
|
|
25
23
|
sig { override.void }
|
26
|
-
def deactivate
|
24
|
+
def deactivate
|
25
|
+
client.shutdown
|
26
|
+
end
|
27
27
|
|
28
28
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
29
29
|
sig do
|
30
30
|
override.params(
|
31
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
31
32
|
uri: URI::Generic,
|
32
33
|
dispatcher: Prism::Dispatcher,
|
33
|
-
).
|
34
|
+
).void
|
34
35
|
end
|
35
|
-
def create_code_lens_listener(uri, dispatcher)
|
36
|
-
CodeLens.new(uri, dispatcher)
|
36
|
+
def create_code_lens_listener(response_builder, uri, dispatcher)
|
37
|
+
CodeLens.new(response_builder, uri, dispatcher)
|
37
38
|
end
|
38
39
|
|
39
40
|
sig do
|
40
41
|
override.params(
|
42
|
+
response_builder: ResponseBuilders::Hover,
|
41
43
|
nesting: T::Array[String],
|
42
44
|
index: RubyIndexer::Index,
|
43
45
|
dispatcher: Prism::Dispatcher,
|
44
|
-
).
|
46
|
+
).void
|
45
47
|
end
|
46
|
-
def create_hover_listener(nesting, index, dispatcher)
|
47
|
-
Hover.new(client, nesting, index, dispatcher)
|
48
|
+
def create_hover_listener(response_builder, nesting, index, dispatcher)
|
49
|
+
Hover.new(client, response_builder, nesting, index, dispatcher)
|
48
50
|
end
|
49
51
|
|
50
52
|
sig { override.returns(String) }
|
@@ -32,26 +32,26 @@ module RubyLsp
|
|
32
32
|
# ````
|
33
33
|
#
|
34
34
|
# The code lenses will be displayed above the class and above each test method.
|
35
|
-
class CodeLens
|
35
|
+
class CodeLens
|
36
36
|
extend T::Sig
|
37
|
-
|
37
|
+
include Requests::Support::Common
|
38
38
|
|
39
|
-
ResponseType = type_member { { fixed: T::Array[::RubyLsp::Interface::CodeLens] } }
|
40
39
|
BASE_COMMAND = "bin/rails test"
|
41
40
|
|
42
|
-
sig
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
sig do
|
42
|
+
params(
|
43
|
+
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
|
44
|
+
uri: URI::Generic,
|
45
|
+
dispatcher: Prism::Dispatcher,
|
46
|
+
).void
|
47
|
+
end
|
48
|
+
def initialize(response_builder, uri, dispatcher)
|
49
|
+
@response_builder = response_builder
|
48
50
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
49
51
|
@group_id = T.let(1, Integer)
|
50
52
|
@group_id_stack = T.let([], T::Array[Integer])
|
51
53
|
|
52
54
|
dispatcher.register(self, :on_call_node_enter, :on_class_node_enter, :on_def_node_enter, :on_class_node_leave)
|
53
|
-
|
54
|
-
super(dispatcher)
|
55
55
|
end
|
56
56
|
|
57
57
|
sig { params(node: Prism::CallNode).void }
|
@@ -131,7 +131,7 @@ module RubyLsp
|
|
131
131
|
grouping_data = { group_id: @group_id_stack.last, kind: kind }
|
132
132
|
grouping_data[:id] = @group_id if kind == :group
|
133
133
|
|
134
|
-
@
|
134
|
+
@response_builder << create_code_lens(
|
135
135
|
node,
|
136
136
|
title: "Run",
|
137
137
|
command_name: "rubyLsp.runTest",
|
@@ -139,7 +139,7 @@ module RubyLsp
|
|
139
139
|
data: { type: "test", **grouping_data },
|
140
140
|
)
|
141
141
|
|
142
|
-
@
|
142
|
+
@response_builder << create_code_lens(
|
143
143
|
node,
|
144
144
|
title: "Run In Terminal",
|
145
145
|
command_name: "rubyLsp.runTestInTerminal",
|
@@ -147,7 +147,7 @@ module RubyLsp
|
|
147
147
|
data: { type: "test_in_terminal", **grouping_data },
|
148
148
|
)
|
149
149
|
|
150
|
-
@
|
150
|
+
@response_builder << create_code_lens(
|
151
151
|
node,
|
152
152
|
title: "Debug",
|
153
153
|
command_name: "rubyLsp.debugTest",
|
@@ -16,28 +16,22 @@ module RubyLsp
|
|
16
16
|
# User.all
|
17
17
|
# # ^ hovering here will show information about the User model
|
18
18
|
# ```
|
19
|
-
class Hover
|
19
|
+
class Hover
|
20
20
|
extend T::Sig
|
21
|
-
|
22
|
-
|
23
|
-
ResponseType = type_member { { fixed: T.nilable(::RubyLsp::Interface::Hover) } }
|
24
|
-
|
25
|
-
sig { override.returns(ResponseType) }
|
26
|
-
attr_reader :_response
|
21
|
+
include Requests::Support::Common
|
27
22
|
|
28
23
|
sig do
|
29
24
|
params(
|
30
|
-
client:
|
25
|
+
client: RunnerClient,
|
26
|
+
response_builder: ResponseBuilders::Hover,
|
31
27
|
nesting: T::Array[String],
|
32
28
|
index: RubyIndexer::Index,
|
33
29
|
dispatcher: Prism::Dispatcher,
|
34
30
|
).void
|
35
31
|
end
|
36
|
-
def initialize(client, nesting, index, dispatcher)
|
37
|
-
super(dispatcher)
|
38
|
-
|
39
|
-
@_response = T.let(nil, ResponseType)
|
32
|
+
def initialize(client, response_builder, nesting, index, dispatcher)
|
40
33
|
@client = client
|
34
|
+
@response_builder = response_builder
|
41
35
|
@nesting = nesting
|
42
36
|
@index = index
|
43
37
|
dispatcher.register(self, :on_constant_path_node_enter, :on_constant_read_node_enter, :on_call_node_enter)
|
@@ -49,16 +43,10 @@ module RubyLsp
|
|
49
43
|
return unless entries
|
50
44
|
|
51
45
|
name = T.must(entries.first).name
|
52
|
-
content = +""
|
53
|
-
column_info = generate_column_content(name)
|
54
|
-
content << column_info if column_info
|
55
46
|
|
56
|
-
|
57
|
-
content << urls.join("\n\n") unless urls.empty?
|
58
|
-
return if content.empty?
|
47
|
+
generate_column_content(name)
|
59
48
|
|
60
|
-
|
61
|
-
@_response = RubyLsp::Interface::Hover.new(range: range_from_location(node.location), contents: contents)
|
49
|
+
generate_rails_document_link_hover(name, node.location)
|
62
50
|
end
|
63
51
|
|
64
52
|
sig { params(node: Prism::ConstantReadNode).void }
|
@@ -66,11 +54,7 @@ module RubyLsp
|
|
66
54
|
entries = @index.resolve(node.name.to_s, @nesting)
|
67
55
|
return unless entries
|
68
56
|
|
69
|
-
|
70
|
-
return unless content
|
71
|
-
|
72
|
-
contents = RubyLsp::Interface::MarkupContent.new(kind: "markdown", value: content)
|
73
|
-
@_response = RubyLsp::Interface::Hover.new(range: range_from_location(node.location), contents: contents)
|
57
|
+
generate_column_content(T.must(entries.first).name)
|
74
58
|
end
|
75
59
|
|
76
60
|
sig { params(node: Prism::CallNode).void }
|
@@ -80,30 +64,37 @@ module RubyLsp
|
|
80
64
|
|
81
65
|
return unless message_value && message_loc
|
82
66
|
|
83
|
-
|
67
|
+
generate_rails_document_link_hover(message_value, message_loc)
|
84
68
|
end
|
85
69
|
|
86
70
|
private
|
87
71
|
|
88
|
-
sig { params(name: String).
|
72
|
+
sig { params(name: String).void }
|
89
73
|
def generate_column_content(name)
|
90
74
|
model = @client.model(name)
|
91
75
|
return if model.nil?
|
92
76
|
|
93
77
|
schema_file = model[:schema_file]
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
78
|
+
|
79
|
+
@response_builder.push(
|
80
|
+
"[Schema](#{URI::Generic.build(scheme: "file", path: schema_file)})",
|
81
|
+
category: :links,
|
82
|
+
) if schema_file
|
83
|
+
|
84
|
+
@response_builder.push(
|
85
|
+
model[:columns].map do |name, type|
|
86
|
+
"**#{name}**: #{type}\n"
|
87
|
+
end.join("\n"),
|
88
|
+
category: :documentation,
|
89
|
+
)
|
98
90
|
end
|
99
91
|
|
100
|
-
sig { params(name: String, location: Prism::Location).
|
92
|
+
sig { params(name: String, location: Prism::Location).void }
|
101
93
|
def generate_rails_document_link_hover(name, location)
|
102
94
|
urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
|
103
95
|
return if urls.empty?
|
104
96
|
|
105
|
-
|
106
|
-
RubyLsp::Interface::Hover.new(range: range_from_location(location), contents: contents)
|
97
|
+
@response_builder.push(urls.join("\n\n"), category: :links)
|
107
98
|
end
|
108
99
|
end
|
109
100
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "json"
|
5
|
+
require "open3"
|
6
|
+
|
7
|
+
# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe
|
8
|
+
# from the client, so it will become full and eventually hang or crash.
|
9
|
+
# Instead, return a response with an `error` key.
|
10
|
+
|
11
|
+
module RubyLsp
|
12
|
+
module Rails
|
13
|
+
class RunnerClient
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { void }
|
17
|
+
def initialize
|
18
|
+
stdin, stdout, stderr, wait_thread = Open3.popen3(
|
19
|
+
"bin/rails",
|
20
|
+
"runner",
|
21
|
+
"#{__dir__}/server.rb",
|
22
|
+
"start",
|
23
|
+
)
|
24
|
+
@stdin = T.let(stdin, IO)
|
25
|
+
@stdout = T.let(stdout, IO)
|
26
|
+
@stderr = T.let(stderr, IO)
|
27
|
+
@wait_thread = T.let(wait_thread, Process::Waiter)
|
28
|
+
@stdin.binmode # for Windows compatibility
|
29
|
+
@stdout.binmode # for Windows compatibility
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
33
|
+
def model(name)
|
34
|
+
make_request("model", name: name)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { void }
|
38
|
+
def shutdown
|
39
|
+
send_notification("shutdown")
|
40
|
+
Thread.pass while @wait_thread.alive?
|
41
|
+
[@stdin, @stdout, @stderr].each(&:close)
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { returns(T::Boolean) }
|
45
|
+
def stopped?
|
46
|
+
[@stdin, @stdout, @stderr].all?(&:closed?) && !@wait_thread.alive?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
sig { params(request: T.untyped, params: T.untyped).returns(T.untyped) }
|
52
|
+
def make_request(request, params = nil)
|
53
|
+
send_message(request, params)
|
54
|
+
read_response
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { params(request: T.untyped, params: T.untyped).void }
|
58
|
+
def send_message(request, params = nil)
|
59
|
+
message = { method: request }
|
60
|
+
message[:params] = params if params
|
61
|
+
json = message.to_json
|
62
|
+
|
63
|
+
@stdin.write("Content-Length: #{json.length}\r\n\r\n", json)
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :send_notification, :send_message
|
67
|
+
|
68
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
69
|
+
def read_response
|
70
|
+
headers = @stdout.gets("\r\n\r\n")
|
71
|
+
raw_response = @stdout.read(T.must(headers)[/Content-Length: (\d+)/i, 1].to_i)
|
72
|
+
|
73
|
+
response = JSON.parse(T.must(raw_response), symbolize_names: true)
|
74
|
+
|
75
|
+
if response[:error]
|
76
|
+
warn("Ruby LSP Rails error: " + response[:error])
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
response.fetch(:result)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
begin
|
8
|
+
T::Configuration.default_checked_level = :never
|
9
|
+
# Suppresses call validation errors
|
10
|
+
T::Configuration.call_validation_error_handler = ->(*) {}
|
11
|
+
# Suppresses errors caused by T.cast, T.let, T.must, etc.
|
12
|
+
T::Configuration.inline_type_error_handler = ->(*) {}
|
13
|
+
# Suppresses errors caused by incorrect parameter ordering
|
14
|
+
T::Configuration.sig_validation_error_handler = ->(*) {}
|
15
|
+
rescue
|
16
|
+
# Need this rescue so that if another gem has
|
17
|
+
# already set the checked level by the time we
|
18
|
+
# get to it, we don't fail outright.
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
module RubyLsp
|
23
|
+
module Rails
|
24
|
+
class Server
|
25
|
+
VOID = Object.new
|
26
|
+
|
27
|
+
extend T::Sig
|
28
|
+
|
29
|
+
sig { params(model_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
30
|
+
def resolve_database_info_from_model(model_name)
|
31
|
+
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
32
|
+
unless const && const < ActiveRecord::Base && !const.abstract_class?
|
33
|
+
return {
|
34
|
+
result: nil,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
schema_file = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
|
39
|
+
|
40
|
+
{
|
41
|
+
result: {
|
42
|
+
columns: const.columns.map { |column| [column.name, column.type] },
|
43
|
+
schema_file: ::Rails.root + schema_file,
|
44
|
+
},
|
45
|
+
}
|
46
|
+
rescue => e
|
47
|
+
{
|
48
|
+
error: e.message,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { void }
|
53
|
+
def start
|
54
|
+
$stdin.sync = true
|
55
|
+
$stdout.sync = true
|
56
|
+
|
57
|
+
running = T.let(true, T::Boolean)
|
58
|
+
|
59
|
+
while running
|
60
|
+
headers = $stdin.gets("\r\n\r\n")
|
61
|
+
request = $stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
62
|
+
|
63
|
+
json = JSON.parse(request, symbolize_names: true)
|
64
|
+
request_method = json.fetch(:method)
|
65
|
+
params = json[:params]
|
66
|
+
|
67
|
+
response = case request_method
|
68
|
+
when "shutdown"
|
69
|
+
running = false
|
70
|
+
VOID
|
71
|
+
when "model"
|
72
|
+
resolve_database_info_from_model(params.fetch(:name))
|
73
|
+
else
|
74
|
+
VOID
|
75
|
+
end
|
76
|
+
|
77
|
+
next if response == VOID
|
78
|
+
|
79
|
+
json_response = response.to_json
|
80
|
+
$stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
RubyLsp::Rails::Server.new.start if ARGV.first == "start"
|
@@ -2,34 +2,17 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "rails"
|
5
|
-
require "ruby_lsp_rails/rack_app"
|
6
5
|
|
7
6
|
module RubyLsp
|
8
7
|
module Rails
|
9
8
|
class Railtie < ::Rails::Railtie
|
10
9
|
config.ruby_lsp_rails = ActiveSupport::OrderedOptions.new
|
11
|
-
config.ruby_lsp_rails.server = true
|
12
10
|
|
13
11
|
initializer "ruby_lsp_rails.setup" do |_app|
|
14
|
-
config.after_initialize do |
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
T.bind(self, ActionDispatch::Routing::Mapper)
|
19
|
-
mount(RackApp.new => RackApp::BASE_PATH)
|
20
|
-
end
|
21
|
-
|
22
|
-
ssl_enable, host, port = ::Rails::Server::Options.new.parse!(ARGV).values_at(:SSLEnable, :Host, :Port)
|
23
|
-
app_uri = "#{ssl_enable ? "https" : "http"}://#{host}:#{port}"
|
24
|
-
app_uri_path = ::Rails.root.join("tmp", "app_uri.txt")
|
25
|
-
app_uri_path.write(app_uri)
|
26
|
-
|
27
|
-
at_exit do
|
28
|
-
# The app_uri.txt file should only exist when the server is running. The addon uses its presence to
|
29
|
-
# report if the server is running or not. If the server is not running, some of the addon features
|
30
|
-
# will not be available.
|
31
|
-
File.delete(app_uri_path) if File.exist?(app_uri_path)
|
32
|
-
end
|
12
|
+
config.after_initialize do |_app|
|
13
|
+
unless config.ruby_lsp_rails.server.nil?
|
14
|
+
ActiveSupport::Deprecation.new.warn("The `ruby_lsp_rails.server` configuration option is no longer " \
|
15
|
+
"needed and will be removed in a future release.")
|
33
16
|
end
|
34
17
|
end
|
35
18
|
end
|
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.
|
4
|
+
version: 0.3.0
|
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-02-15 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.14.0
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: 0.
|
64
|
+
version: 0.15.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.14.0
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: 0.
|
74
|
+
version: 0.15.0
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: sorbet-runtime
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,9 +100,9 @@ files:
|
|
100
100
|
- lib/ruby_lsp/ruby_lsp_rails/addon.rb
|
101
101
|
- lib/ruby_lsp/ruby_lsp_rails/code_lens.rb
|
102
102
|
- lib/ruby_lsp/ruby_lsp_rails/hover.rb
|
103
|
-
- lib/ruby_lsp/ruby_lsp_rails/
|
103
|
+
- lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
|
104
|
+
- lib/ruby_lsp/ruby_lsp_rails/server.rb
|
104
105
|
- lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
|
105
|
-
- lib/ruby_lsp_rails/rack_app.rb
|
106
106
|
- lib/ruby_lsp_rails/railtie.rb
|
107
107
|
- lib/ruby_lsp_rails/version.rb
|
108
108
|
- lib/tasks/ruby_lsp_rails_tasks.rake
|
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: '0'
|
131
131
|
requirements: []
|
132
|
-
rubygems_version: 3.5.
|
132
|
+
rubygems_version: 3.5.6
|
133
133
|
signing_key:
|
134
134
|
specification_version: 4
|
135
135
|
summary: A Ruby LSP addon for Rails
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "net/http"
|
5
|
-
|
6
|
-
module RubyLsp
|
7
|
-
module Rails
|
8
|
-
class RailsClient
|
9
|
-
class ServerAddressUnknown < StandardError; end
|
10
|
-
|
11
|
-
extend T::Sig
|
12
|
-
|
13
|
-
SERVER_NOT_RUNNING_MESSAGE = "Rails server is not running. " \
|
14
|
-
"To get Rails features in the editor, boot the Rails server"
|
15
|
-
|
16
|
-
sig { returns(Pathname) }
|
17
|
-
attr_reader :root
|
18
|
-
|
19
|
-
sig { void }
|
20
|
-
def initialize
|
21
|
-
project_root = T.let(Bundler.with_unbundled_env { Bundler.default_gemfile }.dirname, Pathname)
|
22
|
-
dummy_path = project_root.join("test", "dummy")
|
23
|
-
|
24
|
-
@root = T.let(dummy_path.exist? ? dummy_path : project_root, Pathname)
|
25
|
-
app_uri_path = @root.join("tmp", "app_uri.txt")
|
26
|
-
|
27
|
-
if app_uri_path.exist?
|
28
|
-
url = URI(app_uri_path.read.chomp)
|
29
|
-
|
30
|
-
@ssl = T.let(url.scheme == "https", T::Boolean)
|
31
|
-
@address = T.let(
|
32
|
-
[url.host, url.path].reject { |component| component.nil? || component.empty? }.join("/"),
|
33
|
-
T.nilable(String),
|
34
|
-
)
|
35
|
-
@port = T.let(T.must(url.port).to_i, Integer)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
40
|
-
def model(name)
|
41
|
-
response = request("models/#{name}")
|
42
|
-
return unless response.code == "200"
|
43
|
-
|
44
|
-
JSON.parse(response.body.chomp, symbolize_names: true)
|
45
|
-
rescue Errno::ECONNREFUSED,
|
46
|
-
Errno::EADDRNOTAVAIL,
|
47
|
-
Errno::ECONNRESET,
|
48
|
-
Net::ReadTimeout,
|
49
|
-
Net::OpenTimeout,
|
50
|
-
ServerAddressUnknown
|
51
|
-
nil
|
52
|
-
end
|
53
|
-
|
54
|
-
sig { void }
|
55
|
-
def check_if_server_is_running!
|
56
|
-
request("activate", 0.2)
|
57
|
-
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL, Errno::ECONNRESET, ServerAddressUnknown
|
58
|
-
warn(SERVER_NOT_RUNNING_MESSAGE)
|
59
|
-
rescue Net::ReadTimeout, Net::OpenTimeout
|
60
|
-
# If the server is running, but the initial request is taking too long, we don't want to block the
|
61
|
-
# initialization of the Ruby LSP
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
sig { params(path: String, timeout: T.nilable(Float)).returns(Net::HTTPResponse) }
|
67
|
-
def request(path, timeout = nil)
|
68
|
-
raise ServerAddressUnknown unless @address
|
69
|
-
|
70
|
-
http = Net::HTTP.new(@address, @port)
|
71
|
-
http.use_ssl = @ssl
|
72
|
-
http.read_timeout = timeout if timeout
|
73
|
-
http.get("/ruby_lsp_rails/#{path}")
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
module RubyLsp
|
5
|
-
module Rails
|
6
|
-
class RackApp
|
7
|
-
extend T::Sig
|
8
|
-
|
9
|
-
BASE_PATH = "/ruby_lsp_rails/"
|
10
|
-
|
11
|
-
sig { params(env: T::Hash[T.untyped, T.untyped]).returns(T::Array[T.untyped]) }
|
12
|
-
def call(env)
|
13
|
-
request = ActionDispatch::Request.new(env)
|
14
|
-
path = request.path
|
15
|
-
|
16
|
-
route, argument = path.delete_prefix(BASE_PATH).split("/")
|
17
|
-
|
18
|
-
case route
|
19
|
-
when "activate"
|
20
|
-
[200, { "Content-Type" => "application/json" }, []]
|
21
|
-
when "models"
|
22
|
-
resolve_database_info_from_model(argument)
|
23
|
-
else
|
24
|
-
not_found
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
sig { params(model_name: String).returns(T::Array[T.untyped]) }
|
31
|
-
def resolve_database_info_from_model(model_name)
|
32
|
-
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
33
|
-
|
34
|
-
if const && const < ActiveRecord::Base
|
35
|
-
begin
|
36
|
-
schema_file = ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
|
37
|
-
rescue => e
|
38
|
-
warn("Could not locate schema: #{e.message}")
|
39
|
-
end
|
40
|
-
|
41
|
-
body = JSON.dump({
|
42
|
-
columns: const.columns.map { |column| [column.name, column.type] },
|
43
|
-
schema_file: schema_file,
|
44
|
-
})
|
45
|
-
|
46
|
-
[200, { "Content-Type" => "application/json" }, [body]]
|
47
|
-
else
|
48
|
-
not_found
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
sig { returns(T::Array[T.untyped]) }
|
53
|
-
def not_found
|
54
|
-
[404, { "Content-Type" => "text/plain" }, ["Not Found"]]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|