lib/theme_check/language_server/server.rb in theme-check-1.6.2 vs lib/theme_check/language_server/server.rb in theme-check-1.7.0
- old
+ new
@@ -14,11 +14,12 @@
def initialize(
in_stream: STDIN,
out_stream: STDOUT,
err_stream: STDERR,
- should_raise_errors: false
+ should_raise_errors: false,
+ number_of_threads: 2
)
validate!([in_stream, out_stream, err_stream])
@handler = Handler.new(self)
@in = in_stream
@@ -35,37 +36,94 @@
@out.binmode
@out.sync = true # do not buffer
@err.sync = true # do not buffer
+ # The queue holds the JSON RPC messages
+ @queue = Queue.new
+
+ # The JSON RPC thread pushes messages onto the queue
+ @json_rpc_thread = nil
+
+ # The handler threads read messages from the queue
+ @number_of_threads = number_of_threads
+ @handlers = []
+
+ # The messenger permits requests to be made from the handler
+ # to the language client and for those messages to be resolved in place.
+ @messenger = Messenger.new
+
+ # The error queue holds blocks the main thread. When filled, we exit the program.
+ @error = SizedQueue.new(1)
+
@should_raise_errors = should_raise_errors
end
def listen
- loop do
- process_request
+ start_handler_threads
+ start_json_rpc_thread
+ status_code_from_error(@error.pop)
+ rescue SignalException
+ 0
+ ensure
+ cleanup
+ end
- # support ctrl+c and stuff
- rescue SignalException, DoneStreaming
- cleanup
- return 0
+ def start_json_rpc_thread
+ @json_rpc_thread = Thread.new do
+ loop do
+ message = read_json_rpc_message
+ if message['method'] == 'initialize'
+ handle_message(message)
+ else
+ @queue << message
+ end
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ break @error << e
+ end
+ end
+ end
- rescue Exception => e # rubocop:disable Lint/RescueException
- raise e if should_raise_errors
- log(e)
- log(e.backtrace)
- return 1
+ def start_handler_threads
+ @number_of_threads.times do
+ @handlers << Thread.new do
+ loop do
+ message = @queue.pop
+ break if @queue.closed? && @queue.empty?
+ handle_message(message)
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ break @error << e
+ end
+ end
end
end
- def send_response(response)
- response_body = JSON.dump(response)
- log(JSON.pretty_generate(response)) if $DEBUG
+ def status_code_from_error(e)
+ raise e
- @out.write("Content-Length: #{response_body.bytesize}\r\n")
+ # support ctrl+c and stuff
+ rescue SignalException, DoneStreaming
+ 0
+
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ raise e if should_raise_errors
+ log(e)
+ log(e.backtrace)
+ 2
+ end
+
+ def request(&block)
+ @messenger.request(&block)
+ end
+
+ def send_message(message)
+ message_body = JSON.dump(message)
+ log(JSON.pretty_generate(message)) if $DEBUG
+
+ @out.write("Content-Length: #{message_body.bytesize}\r\n")
@out.write("\r\n")
- @out.write(response_body)
+ @out.write(message_body)
@out.flush
end
def log(message)
@err.puts(message)
@@ -89,21 +147,27 @@
def incompatible_stream_message
'if provided, in_stream, out_stream, and err_stream must be a kind of '\
"one of the following: #{supported_io_classes.join(', ')}"
end
- def process_request
- request_body = read_new_content
- request_json = JSON.parse(request_body)
- log(JSON.pretty_generate(request_json)) if $DEBUG
+ def read_json_rpc_message
+ message_body = read_new_content
+ message_json = JSON.parse(message_body)
+ log(JSON.pretty_generate(message_json)) if $DEBUG
+ message_json
+ end
- id = request_json['id']
- method_name = request_json['method']
- params = request_json['params']
- method_name = "on_#{to_snake_case(method_name)}"
+ def handle_message(message)
+ id = message['id']
+ method_name = message['method']
+ method_name &&= "on_#{to_snake_case(method_name)}"
+ params = message['params']
+ result = message['result']
- if @handler.respond_to?(method_name)
+ if message.key?('result')
+ @messenger.respond(id, result)
+ elsif @handler.respond_to?(method_name)
@handler.send(method_name, id, params)
end
end
def to_snake_case(method_name)
@@ -126,29 +190,30 @@
def read_new_content
length = initial_line.match(/Content-Length: (\d+)/)[1].to_i
content = ''
while content.length < length + 2
- begin
- # Why + 2? Because \r\n
- content += @in.read(length + 2)
- rescue => e
- log(e)
- log(e.backtrace)
- # We have almost certainly been disconnected from the server
- cleanup
- raise DoneStreaming
- end
+ # Why + 2? Because \r\n
+ content += @in.read(length + 2)
+ raise DoneStreaming if @in.closed?
end
content
end
def cleanup
+ # Stop listenting to RPC calls
+ @in.close unless @in.closed?
+ # Wait for rpc loop to close
+ @json_rpc_thread&.join if @json_rpc_thread&.alive?
+ # Close the queue
+ @queue.close unless @queue.closed?
+ # Give 10 seconds for the handlers to wrap up what they were
+ # doing/emptying the queue. 👀 unit tests.
+ @handlers.each { |thread| thread.join(10) if thread.alive? }
+ ensure
@err.close
@out.close
- rescue
- # I did my best
end
end
end
end