ruby-lsp-rails 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -20
- data/lib/ruby-lsp-rails.rb +0 -3
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +26 -7
- data/lib/ruby_lsp/ruby_lsp_rails/code_lens.rb +3 -19
- data/lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb +42 -0
- data/lib/ruby_lsp/ruby_lsp_rails/hover.rb +1 -1
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +73 -7
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +59 -39
- data/lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb +38 -0
- data/lib/ruby_lsp_rails/railtie.rb +4 -21
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +6 -6
- 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: 75f1c942c624b415b9ac1207d42c3cd0d32fe5403ef49626c3be6f18f8cb3682
|
4
|
+
data.tar.gz: 7a6740caeae1fd86c55f917bd3ae59c1b1fc4d0080875ee37875e04ad3dc8c55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a86197ecdea2653c87dfb3d79b092681504391fd76e1a0849b754aca21b60fdcc8855f6c4d8fab4ee8e9a1816a1e39d896311bf30f558fb8a9b195ac6de3711
|
7
|
+
data.tar.gz: f295e7756887b52128eafd56a1cbb70017c48f0128c1f82d521b81098e50c894ae1c32f97c66dddd3752a62a5a9171f35dd7368ef6444a13acb467a368cd5cee
|
data/README.md
CHANGED
@@ -15,19 +15,6 @@ group :development do
|
|
15
15
|
gem "ruby-lsp-rails"
|
16
16
|
end
|
17
17
|
```
|
18
|
-
Some features rely on server introspection, and use a Rack server which is automatically mounted by using a Railtie.
|
19
|
-
|
20
|
-
For applications with specialized routing requirements, such as custom sharding, this may not be compatible. It can
|
21
|
-
be disabled with:
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
# config/environments/development.rb
|
25
|
-
Rails.application.configure do
|
26
|
-
# ...
|
27
|
-
config.ruby_lsp_rails.server = false
|
28
|
-
# ...
|
29
|
-
end
|
30
|
-
```
|
31
18
|
|
32
19
|
## Usage
|
33
20
|
|
@@ -52,16 +39,15 @@ See the [documentation](https://shopify.github.io/ruby-lsp-rails) for more in-de
|
|
52
39
|
|
53
40
|
## How It Works
|
54
41
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
1. A Ruby LSP addon that connects to the exposed APIs to fetch runtime information from the Rails server
|
42
|
+
When Ruby LSP Rails starts, it spawns a `rails runner` instance which runs
|
43
|
+
[`server.rb`](https://github.com/Shopify/ruby-lsp-rails/blob/main/lib/ruby_lsp/ruby_lsp_rails/server.rb).
|
44
|
+
The addon communicates with this process over a pipe (i.e. `stdin` and `stdout`) to fetch runtime information about the application.
|
59
45
|
|
60
|
-
|
46
|
+
When extension is stopped (e.g. by quitting the editor), the server instance is shut down.
|
61
47
|
|
62
48
|
> [!NOTE]
|
63
|
-
>
|
64
|
-
>
|
49
|
+
> Prior to v0.3, `ruby-lsp-rails` used a different approach which involved mounting a Rack application within the Rails app.
|
50
|
+
> That approach was brittle and susceptible to the application's configuration, such as routing and middleware.
|
65
51
|
|
66
52
|
## Contributing
|
67
53
|
|
data/lib/ruby-lsp-rails.rb
CHANGED
@@ -3,27 +3,36 @@
|
|
3
3
|
|
4
4
|
require "ruby_lsp/addon"
|
5
5
|
|
6
|
-
require_relative "
|
6
|
+
require_relative "support/active_support_test_case_helper"
|
7
|
+
require_relative "runner_client"
|
7
8
|
require_relative "hover"
|
8
9
|
require_relative "code_lens"
|
10
|
+
require_relative "document_symbol"
|
9
11
|
|
10
12
|
module RubyLsp
|
11
13
|
module Rails
|
12
14
|
class Addon < ::RubyLsp::Addon
|
13
15
|
extend T::Sig
|
14
16
|
|
15
|
-
sig {
|
16
|
-
def
|
17
|
-
|
17
|
+
sig { void }
|
18
|
+
def initialize
|
19
|
+
super
|
20
|
+
|
21
|
+
# We first initialize the client as a NullClient, so that we can start the server in a background thread. Until
|
22
|
+
# the real client is initialized, features that depend on it will not be blocked by using the NullClient
|
23
|
+
@client = T.let(NullClient.new, RunnerClient)
|
18
24
|
end
|
19
25
|
|
20
26
|
sig { override.params(message_queue: Thread::Queue).void }
|
21
27
|
def activate(message_queue)
|
22
|
-
client.
|
28
|
+
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
|
29
|
+
Thread.new { @client = RunnerClient.create_client }
|
23
30
|
end
|
24
31
|
|
25
32
|
sig { override.void }
|
26
|
-
def deactivate
|
33
|
+
def deactivate
|
34
|
+
@client.shutdown
|
35
|
+
end
|
27
36
|
|
28
37
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
29
38
|
sig do
|
@@ -46,7 +55,17 @@ module RubyLsp
|
|
46
55
|
).void
|
47
56
|
end
|
48
57
|
def create_hover_listener(response_builder, nesting, index, dispatcher)
|
49
|
-
Hover.new(client, response_builder, nesting, index, dispatcher)
|
58
|
+
Hover.new(@client, response_builder, nesting, index, dispatcher)
|
59
|
+
end
|
60
|
+
|
61
|
+
sig do
|
62
|
+
override.params(
|
63
|
+
response_builder: ResponseBuilders::DocumentSymbol,
|
64
|
+
dispatcher: Prism::Dispatcher,
|
65
|
+
).returns(Object)
|
66
|
+
end
|
67
|
+
def create_document_symbol_listener(response_builder, dispatcher)
|
68
|
+
DocumentSymbol.new(response_builder, dispatcher)
|
50
69
|
end
|
51
70
|
|
52
71
|
sig { override.returns(String) }
|
@@ -35,6 +35,7 @@ module RubyLsp
|
|
35
35
|
class CodeLens
|
36
36
|
extend T::Sig
|
37
37
|
include Requests::Support::Common
|
38
|
+
include ActiveSupportTestCaseHelper
|
38
39
|
|
39
40
|
BASE_COMMAND = "bin/rails test"
|
40
41
|
|
@@ -56,26 +57,9 @@ module RubyLsp
|
|
56
57
|
|
57
58
|
sig { params(node: Prism::CallNode).void }
|
58
59
|
def on_call_node_enter(node)
|
59
|
-
|
60
|
-
return unless message_value == "test"
|
60
|
+
content = extract_test_case_name(node)
|
61
61
|
|
62
|
-
|
63
|
-
return unless arguments&.any?
|
64
|
-
|
65
|
-
first_argument = arguments.first
|
66
|
-
|
67
|
-
content = case first_argument
|
68
|
-
when Prism::InterpolatedStringNode
|
69
|
-
parts = first_argument.parts
|
70
|
-
|
71
|
-
if parts.all? { |part| part.is_a?(Prism::StringNode) }
|
72
|
-
T.cast(parts, T::Array[Prism::StringNode]).map(&:content).join
|
73
|
-
end
|
74
|
-
when Prism::StringNode
|
75
|
-
first_argument.content
|
76
|
-
end
|
77
|
-
|
78
|
-
return unless content && !content.empty?
|
62
|
+
return unless content
|
79
63
|
|
80
64
|
line_number = node.location.start_line
|
81
65
|
command = "#{BASE_COMMAND} #{@path}:#{line_number}"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
# 
|
7
|
+
#
|
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 VS Code's "Go to Symbol" feature.
|
10
|
+
class DocumentSymbol
|
11
|
+
extend T::Sig
|
12
|
+
include Requests::Support::Common
|
13
|
+
include ActiveSupportTestCaseHelper
|
14
|
+
|
15
|
+
sig do
|
16
|
+
params(
|
17
|
+
response_builder: ResponseBuilders::DocumentSymbol,
|
18
|
+
dispatcher: Prism::Dispatcher,
|
19
|
+
).void
|
20
|
+
end
|
21
|
+
def initialize(response_builder, dispatcher)
|
22
|
+
@response_builder = response_builder
|
23
|
+
|
24
|
+
dispatcher.register(self, :on_call_node_enter)
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(node: Prism::CallNode).void }
|
28
|
+
def on_call_node_enter(node)
|
29
|
+
content = extract_test_case_name(node)
|
30
|
+
|
31
|
+
return unless content
|
32
|
+
|
33
|
+
@response_builder.last.children << RubyLsp::Interface::DocumentSymbol.new(
|
34
|
+
name: content,
|
35
|
+
kind: LanguageServer::Protocol::Constant::SymbolKind::METHOD,
|
36
|
+
selection_range: range_from_node(node),
|
37
|
+
range: range_from_node(node),
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -4,17 +4,38 @@
|
|
4
4
|
require "json"
|
5
5
|
require "open3"
|
6
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
7
|
module RubyLsp
|
12
8
|
module Rails
|
13
9
|
class RunnerClient
|
10
|
+
class << self
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
sig { returns(RunnerClient) }
|
14
|
+
def create_client
|
15
|
+
new
|
16
|
+
rescue Errno::ENOENT, StandardError => e # rubocop:disable Lint/ShadowedException
|
17
|
+
warn("Ruby LSP Rails failed to initialize server: #{e.message}\n#{e.backtrace&.join("\n")}")
|
18
|
+
warn("Server dependent features will not be available")
|
19
|
+
NullClient.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class InitializationError < StandardError; end
|
24
|
+
class IncompleteMessageError < StandardError; end
|
25
|
+
|
14
26
|
extend T::Sig
|
15
27
|
|
16
28
|
sig { void }
|
17
29
|
def initialize
|
30
|
+
# Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
|
31
|
+
# parent ends, the spring process ends as well. If this is not set, Spring will throw an error while trying to
|
32
|
+
# set its own session ID
|
33
|
+
begin
|
34
|
+
Process.setsid
|
35
|
+
rescue Errno::EPERM
|
36
|
+
# If we can't set the session ID, continue
|
37
|
+
end
|
38
|
+
|
18
39
|
stdin, stdout, stderr, wait_thread = Open3.popen3(
|
19
40
|
"bin/rails",
|
20
41
|
"runner",
|
@@ -27,11 +48,20 @@ module RubyLsp
|
|
27
48
|
@wait_thread = T.let(wait_thread, Process::Waiter)
|
28
49
|
@stdin.binmode # for Windows compatibility
|
29
50
|
@stdout.binmode # for Windows compatibility
|
51
|
+
|
52
|
+
warn("Ruby LSP Rails booting server")
|
53
|
+
read_response
|
54
|
+
warn("Finished booting Ruby LSP Rails server")
|
55
|
+
rescue Errno::EPIPE, IncompleteMessageError
|
56
|
+
raise InitializationError, @stderr.read
|
30
57
|
end
|
31
58
|
|
32
59
|
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
33
60
|
def model(name)
|
34
61
|
make_request("model", name: name)
|
62
|
+
rescue IncompleteMessageError
|
63
|
+
warn("Ruby LSP Rails failed to get model information: #{@stderr.read}")
|
64
|
+
nil
|
35
65
|
end
|
36
66
|
|
37
67
|
sig { void }
|
@@ -48,13 +78,18 @@ module RubyLsp
|
|
48
78
|
|
49
79
|
private
|
50
80
|
|
51
|
-
sig
|
81
|
+
sig do
|
82
|
+
params(
|
83
|
+
request: String,
|
84
|
+
params: T.nilable(T::Hash[Symbol, T.untyped]),
|
85
|
+
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
86
|
+
end
|
52
87
|
def make_request(request, params = nil)
|
53
88
|
send_message(request, params)
|
54
89
|
read_response
|
55
90
|
end
|
56
91
|
|
57
|
-
sig { params(request:
|
92
|
+
sig { params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
58
93
|
def send_message(request, params = nil)
|
59
94
|
message = { method: request }
|
60
95
|
message[:params] = params if params
|
@@ -68,8 +103,9 @@ module RubyLsp
|
|
68
103
|
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
69
104
|
def read_response
|
70
105
|
headers = @stdout.gets("\r\n\r\n")
|
71
|
-
|
106
|
+
raise IncompleteMessageError unless headers
|
72
107
|
|
108
|
+
raw_response = @stdout.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
73
109
|
response = JSON.parse(T.must(raw_response), symbolize_names: true)
|
74
110
|
|
75
111
|
if response[:error]
|
@@ -80,5 +116,35 @@ module RubyLsp
|
|
80
116
|
response.fetch(:result)
|
81
117
|
end
|
82
118
|
end
|
119
|
+
|
120
|
+
class NullClient < RunnerClient
|
121
|
+
extend T::Sig
|
122
|
+
|
123
|
+
sig { void }
|
124
|
+
def initialize # rubocop:disable Lint/MissingSuper
|
125
|
+
end
|
126
|
+
|
127
|
+
sig { override.void }
|
128
|
+
def shutdown
|
129
|
+
# no-op
|
130
|
+
end
|
131
|
+
|
132
|
+
sig { override.returns(T::Boolean) }
|
133
|
+
def stopped?
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
sig { override.params(request: String, params: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
140
|
+
def send_message(request, params = nil)
|
141
|
+
# no-op
|
142
|
+
end
|
143
|
+
|
144
|
+
sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
145
|
+
def read_response
|
146
|
+
# no-op
|
147
|
+
end
|
148
|
+
end
|
83
149
|
end
|
84
150
|
end
|
@@ -19,6 +19,9 @@ rescue
|
|
19
19
|
nil
|
20
20
|
end
|
21
21
|
|
22
|
+
# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe from the
|
23
|
+
# client, so it will become full and eventually hang or crash. Instead, return a response with an `error` key.
|
24
|
+
|
22
25
|
module RubyLsp
|
23
26
|
module Rails
|
24
27
|
class Server
|
@@ -26,59 +29,76 @@ module RubyLsp
|
|
26
29
|
|
27
30
|
extend T::Sig
|
28
31
|
|
29
|
-
sig {
|
32
|
+
sig { void }
|
33
|
+
def initialize
|
34
|
+
$stdin.sync = true
|
35
|
+
$stdout.sync = true
|
36
|
+
@running = T.let(true, T::Boolean)
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { void }
|
40
|
+
def start
|
41
|
+
initialize_result = { result: { message: "ok" } }.to_json
|
42
|
+
$stdout.write("Content-Length: #{initialize_result.length}\r\n\r\n#{initialize_result}")
|
43
|
+
|
44
|
+
while @running
|
45
|
+
headers = $stdin.gets("\r\n\r\n")
|
46
|
+
json = $stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
|
47
|
+
|
48
|
+
request = JSON.parse(json, symbolize_names: true)
|
49
|
+
response = execute(request.fetch(:method), request[:params])
|
50
|
+
next if response == VOID
|
51
|
+
|
52
|
+
json_response = response.to_json
|
53
|
+
$stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
sig do
|
58
|
+
params(
|
59
|
+
request: String,
|
60
|
+
params: T::Hash[Symbol, T.untyped],
|
61
|
+
).returns(T.any(Object, T::Hash[Symbol, T.untyped]))
|
62
|
+
end
|
63
|
+
def execute(request, params = {})
|
64
|
+
case request
|
65
|
+
when "shutdown"
|
66
|
+
@running = false
|
67
|
+
VOID
|
68
|
+
when "model"
|
69
|
+
resolve_database_info_from_model(params.fetch(:name))
|
70
|
+
else
|
71
|
+
VOID
|
72
|
+
end
|
73
|
+
rescue => e
|
74
|
+
{ error: e.full_message(highlight: false) }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
sig { params(model_name: String).returns(T::Hash[Symbol, T.untyped]) }
|
30
80
|
def resolve_database_info_from_model(model_name)
|
31
81
|
const = ActiveSupport::Inflector.safe_constantize(model_name)
|
32
|
-
unless const && const < ActiveRecord::Base && !const.abstract_class?
|
82
|
+
unless const && defined?(ActiveRecord) && const < ActiveRecord::Base && !const.abstract_class?
|
33
83
|
return {
|
34
84
|
result: nil,
|
35
85
|
}
|
36
86
|
end
|
37
87
|
|
38
|
-
|
39
|
-
|
40
|
-
{
|
88
|
+
info = {
|
41
89
|
result: {
|
42
90
|
columns: const.columns.map { |column| [column.name, column.type] },
|
43
|
-
schema_file: ::Rails.root + schema_file,
|
44
91
|
},
|
45
92
|
}
|
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
93
|
|
77
|
-
|
94
|
+
if ActiveRecord::Tasks::DatabaseTasks.respond_to?(:schema_dump_path)
|
95
|
+
info[:result][:schema_file] =
|
96
|
+
ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
|
78
97
|
|
79
|
-
json_response = response.to_json
|
80
|
-
$stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
|
81
98
|
end
|
99
|
+
info
|
100
|
+
rescue => e
|
101
|
+
{ error: e.full_message(highlight: false) }
|
82
102
|
end
|
83
103
|
end
|
84
104
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Rails
|
6
|
+
module ActiveSupportTestCaseHelper
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(node: Prism::CallNode).returns(T.nilable(String)) }
|
10
|
+
def extract_test_case_name(node)
|
11
|
+
message_value = node.message
|
12
|
+
return unless message_value == "test" || message_value == "it"
|
13
|
+
|
14
|
+
return unless node.arguments
|
15
|
+
|
16
|
+
arguments = node.arguments&.arguments
|
17
|
+
return unless arguments&.any?
|
18
|
+
|
19
|
+
first_argument = arguments.first
|
20
|
+
|
21
|
+
content = case first_argument
|
22
|
+
when Prism::InterpolatedStringNode
|
23
|
+
parts = first_argument.parts
|
24
|
+
|
25
|
+
if parts.all? { |part| part.is_a?(Prism::StringNode) }
|
26
|
+
T.cast(parts, T::Array[Prism::StringNode]).map(&:content).join
|
27
|
+
end
|
28
|
+
when Prism::StringNode
|
29
|
+
first_argument.content
|
30
|
+
end
|
31
|
+
|
32
|
+
if content && !content.empty?
|
33
|
+
content
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.14.
|
61
|
+
version: 0.14.2
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: 0.15.0
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
requirements:
|
69
69
|
- - ">="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: 0.14.
|
71
|
+
version: 0.14.2
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: 0.15.0
|
@@ -99,12 +99,12 @@ 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/document_symbol.rb
|
102
103
|
- lib/ruby_lsp/ruby_lsp_rails/hover.rb
|
103
|
-
- lib/ruby_lsp/ruby_lsp_rails/rails_client.rb
|
104
104
|
- lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
|
105
105
|
- lib/ruby_lsp/ruby_lsp_rails/server.rb
|
106
|
+
- lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb
|
106
107
|
- lib/ruby_lsp/ruby_lsp_rails/support/rails_document_client.rb
|
107
|
-
- lib/ruby_lsp_rails/rack_app.rb
|
108
108
|
- lib/ruby_lsp_rails/railtie.rb
|
109
109
|
- lib/ruby_lsp_rails/version.rb
|
110
110
|
- lib/tasks/ruby_lsp_rails_tasks.rake
|
@@ -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 && !const.abstract_class?
|
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
|