-
Notifications
You must be signed in to change notification settings - Fork 28
/
server.rb
125 lines (105 loc) · 3.63 KB
/
server.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# typed: false
# frozen_string_literal: true
require "json"
# NOTE: We should avoid printing to stderr since it causes problems. We never read the standard error pipe from the
# client, so it will become full and eventually hang or crash. Instead, return a response with an `error` key.
module RubyLsp
module Rails
class Server
VOID = Object.new
def initialize
$stdin.sync = true
$stdout.sync = true
$stdin.binmode
$stdout.binmode
@running = true
end
def start
initialize_result = { result: { message: "ok" } }.to_json
$stdout.write("Content-Length: #{initialize_result.length}\r\n\r\n#{initialize_result}")
while @running
headers = $stdin.gets("\r\n\r\n")
json = $stdin.read(headers[/Content-Length: (\d+)/i, 1].to_i)
request = JSON.parse(json, symbolize_names: true)
response = execute(request.fetch(:method), request[:params])
next if response == VOID
json_response = response.to_json
$stdout.write("Content-Length: #{json_response.length}\r\n\r\n#{json_response}")
end
end
def execute(request, params)
case request
when "shutdown"
@running = false
VOID
when "model"
resolve_database_info_from_model(params.fetch(:name))
when "reload"
::Rails.application.reloader.reload!
VOID
when "route_location"
route_location(params.fetch(:name))
else
VOID
end
rescue => e
{ error: e.full_message(highlight: false) }
end
private
# Older versions of Rails don't support `route_source_locations`.
# We also check that it's enabled.
if ActionDispatch::Routing::Mapper.respond_to?(:route_source_locations) &&
ActionDispatch::Routing::Mapper.route_source_locations
def route_location(name)
match_data = name.match(/^(.+)(_path|_url)$/)
return { result: nil } unless match_data
key = match_data[1]
# A token could match the _path or _url pattern, but not be an actual route.
route = ::Rails.application.routes.named_routes.get(key)
return { result: nil } unless route&.source_location
{
result: {
location: ::Rails.root.join(route.source_location).to_s,
},
}
rescue => e
{ error: e.full_message(highlight: false) }
end
else
def route_location(name)
{ result: nil }
end
end
def resolve_database_info_from_model(model_name)
const = ActiveSupport::Inflector.safe_constantize(model_name)
unless active_record_model?(const)
return {
result: nil,
}
end
info = {
result: {
columns: const.columns.map { |column| [column.name, column.type] },
primary_keys: Array(const.primary_key),
},
}
if ActiveRecord::Tasks::DatabaseTasks.respond_to?(:schema_dump_path)
info[:result][:schema_file] =
ActiveRecord::Tasks::DatabaseTasks.schema_dump_path(const.connection.pool.db_config)
end
info
rescue => e
{ error: e.full_message(highlight: false) }
end
def active_record_model?(const)
!!(
const &&
defined?(ActiveRecord) &&
ActiveRecord::Base > const && # We do this 'backwards' in case the class overwrites `<`
!const.abstract_class?
)
end
end
end
end
RubyLsp::Rails::Server.new.start if ARGV.first == "start"