Skip to content

Commit 7e06926

Browse files
committed
Replace LanguageServer::Protocol::Transport with our own implementation
1 parent 23a7084 commit 7e06926

File tree

5 files changed

+81
-11
lines changed

5 files changed

+81
-11
lines changed

exe/ruby-lsp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ if ENV["BUNDLE_GEMFILE"].nil?
8888
exit exec(env, "#{base_command} exec ruby-lsp #{original_args.join(" ")}".strip)
8989
end
9090

91+
$stdin.sync = true
92+
$stdout.sync = true
93+
$stderr.sync = true
94+
$stdin.binmode
95+
$stdout.binmode
96+
$stderr.binmode
97+
9198
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
9299

93100
require "ruby_lsp/load_sorbet"
@@ -147,8 +154,10 @@ if options[:doctor]
147154
return
148155
end
149156

157+
server = RubyLsp::Server.new
158+
150159
# Ensure all output goes out stderr by default to allow puts/p/pp to work
151160
# without specifying output device.
152161
$> = $stderr
153162

154-
RubyLsp::Server.new.start
163+
server.start

exe/ruby-lsp-launcher

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
# composed bundle
77
# !!!!!!!
88

9+
$stdin.sync = true
10+
$stdout.sync = true
11+
$stderr.sync = true
12+
$stdin.binmode
13+
$stdout.binmode
14+
$stderr.binmode
15+
916
setup_error = nil
1017
install_error = nil
1118
reboot = false
@@ -28,7 +35,6 @@ else
2835
# Read the initialize request before even starting the server. We need to do this to figure out the workspace URI.
2936
# Editors are not required to spawn the language server process on the same directory as the workspace URI, so we need
3037
# to ensure that we're setting up the bundle in the right place
31-
$stdin.binmode
3238
headers = $stdin.gets("\r\n\r\n")
3339
content_length = headers[/Content-Length: (\d+)/i, 1].to_i
3440
$stdin.read(content_length)
@@ -138,22 +144,28 @@ if ARGV.include?("--debug")
138144
end
139145
end
140146

141-
# Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
142-
$> = $stderr
143-
144147
initialize_request = JSON.parse(raw_initialize, symbolize_names: true) if raw_initialize
145148

146149
begin
147-
RubyLsp::Server.new(
150+
server = RubyLsp::Server.new(
148151
install_error: install_error,
149152
setup_error: setup_error,
150153
initialize_request: initialize_request,
151-
).start
154+
)
155+
156+
# Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
157+
$> = $stderr
158+
159+
server.start
152160
rescue ArgumentError
153161
# If the launcher is booting an outdated version of the server, then the initializer doesn't accept a keyword splat
154162
# and we already read the initialize request from the stdin pipe. In this case, we need to process the initialize
155163
# request manually and then start the main loop
156164
server = RubyLsp::Server.new
165+
166+
# Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
167+
$> = $stderr
168+
157169
server.process_message(initialize_request)
158170
server.start
159171
end

lib/ruby_lsp/base_server.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ class BaseServer
1010

1111
#: (**untyped options) -> void
1212
def initialize(**options)
13+
@reader = MessageReader.new(options[:reader] || $stdin) #: MessageReader
14+
@writer = MessageWriter.new(options[:writer] || $stdout) #: MessageWriter
1315
@test_mode = options[:test_mode] #: bool?
1416
@setup_error = options[:setup_error] #: StandardError?
1517
@install_error = options[:install_error] #: StandardError?
16-
@writer = Transport::Stdio::Writer.new #: Transport::Stdio::Writer
17-
@reader = Transport::Stdio::Reader.new #: Transport::Stdio::Reader
1818
@incoming_queue = Thread::Queue.new #: Thread::Queue
1919
@outgoing_queue = Thread::Queue.new #: Thread::Queue
2020
@cancelled_requests = [] #: Array[Integer]
@@ -40,7 +40,7 @@ def initialize(**options)
4040

4141
#: -> void
4242
def start
43-
@reader.read do |message|
43+
@reader.each_message do |message|
4444
method = message[:method]
4545

4646
# We must parse the document under a mutex lock or else we might switch threads and accept text edits in the

lib/ruby_lsp/utils.rb

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ module RubyLsp
55
# rubocop:disable RubyLsp/UseLanguageServerAliases
66
Interface = LanguageServer::Protocol::Interface
77
Constant = LanguageServer::Protocol::Constant
8-
Transport = LanguageServer::Protocol::Transport
98
# rubocop:enable RubyLsp/UseLanguageServerAliases
109

1110
# Used to indicate that a request shouldn't return a response
@@ -302,4 +301,37 @@ def none? = @level == :none
302301
#: -> bool
303302
def true_or_higher? = @level == :true || @level == :strict
304303
end
304+
305+
# Reads JSON RPC messages from the given IO in a loop
306+
class MessageReader
307+
#: (IO) -> void
308+
def initialize(io)
309+
@io = io
310+
end
311+
312+
#: () { (Hash[Symbol, untyped]) -> void } -> void
313+
def each_message(&block)
314+
while (headers = @io.gets("\r\n\r\n"))
315+
raw_message = @io.read(headers[/Content-Length: (\d+)/i, 1].to_i) #: as !nil
316+
block.call(JSON.parse(raw_message, symbolize_names: true))
317+
end
318+
end
319+
end
320+
321+
# Writes JSON RPC messages to the given IO
322+
class MessageWriter
323+
#: (IO) -> void
324+
def initialize(io)
325+
@io = io
326+
end
327+
328+
#: (Hash[Symbol, untyped]) -> void
329+
def write(message)
330+
message[:jsonrpc] = "2.0"
331+
json_message = message.to_json
332+
333+
@io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
334+
@io.flush
335+
end
336+
end
305337
end

test/integration_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,23 @@ def test_launch_mode_retries_if_setup_failed_after_successful_install
314314
end
315315
end
316316

317+
def test_launching_an_older_server_version
318+
in_temp_dir do |dir|
319+
File.write(File.join(dir, "Gemfile"), <<~RUBY)
320+
source "https://rubygems.org"
321+
gem "ruby-lsp", "0.23.0"
322+
RUBY
323+
324+
Bundler.with_unbundled_env do
325+
capture_subprocess_io do
326+
system("bundle", "install")
327+
end
328+
329+
launch(dir)
330+
end
331+
end
332+
end
333+
317334
private
318335

319336
def launch(workspace_path, exec = "ruby-lsp-launcher", extra_env = {})

0 commit comments

Comments
 (0)