11# frozen_string_literal: true
22
33require 'json'
4+ require 'socket'
45
56module RubyLanguageServer
67 class IO
8+ attr_reader :using_socket
9+
710 def initialize ( server , mutex )
811 @server = server
912 @mutex = mutex
1013 server . io = self
14+
15+ configure_io
16+
1117 loop do
12- ( id , response ) = process_request ( $stdin )
13- return_response ( id , response , $stdout ) unless id . nil?
18+ ( id , response ) = process_request
19+ return_response ( id , response ) unless id . nil?
1420 rescue SignalException => e
1521 RubyLanguageServer . logger . error "We received a signal. Let's bail: #{ e } "
1622 exit
@@ -19,38 +25,66 @@ def initialize(server, mutex)
1925 backtrace = e . backtrace * "\n "
2026 RubyLanguageServer . logger . error "Backtrace:\n #{ backtrace } "
2127 end
28+ return unless @using_socket
29+
30+ begin
31+ @in &.close
32+ rescue StandardError => e
33+ RubyLanguageServer . logger . error "Error closing socket: #{ e } "
34+ end
2235 end
2336
24- def return_response ( id , response , io = $stdout)
37+ def send_notification ( message , params )
38+ io ||= out
2539 full_response = {
2640 jsonrpc : '2.0' ,
27- id : ,
28- result : response
41+ method : message ,
42+ params :
2943 }
30- response_body = JSON . unparse ( full_response )
31- RubyLanguageServer . logger . info "return_response body: #{ response_body } "
32- io . write "Content-Length: #{ response_body . length + 0 } \r \n "
44+ body = JSON . generate ( full_response )
45+ RubyLanguageServer . logger . info "send_notification body: #{ body } "
46+ io . write "Content-Length: #{ body . length } \r \n "
3347 io . write "\r \n "
34- io . write response_body
35- io . flush
48+ io . write body
49+ io . flush if io . respond_to? ( :flush )
50+ end
51+
52+ private
53+
54+ attr_accessor :in , :out
55+
56+ def configure_io
57+ if ENV [ 'LSP_PORT' ]
58+ @tcp_server = TCPServer . new ( ENV [ 'LSP_PORT' ] . to_i )
59+ RubyLanguageServer . logger . info "Listening on TCP port #{ ENV [ 'LSP_PORT' ] } for LSP connections"
60+ self . in = @tcp_server . accept
61+ self . out = self . in
62+ @using_socket = true
63+ RubyLanguageServer . logger . info 'Accepted LSP socket connection'
64+ else
65+ self . in = $stdin
66+ self . out = $stdout
67+ @using_socket = false
68+ end
3669 end
3770
38- def send_notification ( message , params , io = $stdout)
71+ def return_response ( id , response )
72+ io ||= out
3973 full_response = {
4074 jsonrpc : '2.0' ,
41- method : message ,
42- params :
75+ id : ,
76+ result : response
4377 }
44- body = JSON . unparse ( full_response )
45- RubyLanguageServer . logger . info "send_notification body: #{ body } "
46- io . write "Content-Length: #{ body . length + 0 } \r \n "
78+ response_body = JSON . generate ( full_response )
79+ RubyLanguageServer . logger . info "return_response body: #{ response_body } "
80+ io . write "Content-Length: #{ response_body . length } \r \n "
4781 io . write "\r \n "
48- io . write body
49- io . flush
82+ io . write response_body
83+ io . flush if io . respond_to? ( :flush )
5084 end
5185
52- def process_request ( io = $stdin )
53- request_body = get_request ( io )
86+ def process_request
87+ request_body = get_request
5488 # RubyLanguageServer.logger.debug "request_body: #{request_body}"
5589 request_json = JSON . parse request_body
5690 id = request_json [ 'id' ]
@@ -77,14 +111,14 @@ def process_request(io = $stdin)
77111 end
78112 end
79113
80- def get_request ( io = $stdin )
81- initial_line = get_initial_request_line ( io )
114+ def get_request # rubocop:disable Naming/AccessorMethodName
115+ initial_line = get_initial_request_line
82116 RubyLanguageServer . logger . debug "initial_line: #{ initial_line } "
83117 length = get_length ( initial_line )
84118 content = ''
85119 while content . length < length + 2
86120 begin
87- content += get_content ( length + 2 , io ) # Why + 2? CRLF?
121+ content += get_content ( length + 2 ) # Why + 2? CRLF?
88122 rescue Exception => e
89123 RubyLanguageServer . logger . error e
90124 # We have almost certainly been disconnected from the server
@@ -95,8 +129,8 @@ def get_request(io = $stdin)
95129 content
96130 end
97131
98- def get_initial_request_line ( io = $stdin )
99- io . gets
132+ def get_initial_request_line # rubocop:disable Naming/AccessorMethodName
133+ self . in . gets
100134 end
101135
102136 def get_length ( string )
@@ -105,37 +139,8 @@ def get_length(string)
105139 string . match ( /Content-Length: (\d +)/ ) [ 1 ] . to_i
106140 end
107141
108- def get_content ( size , io = $stdin )
109- io . read ( size )
142+ def get_content ( size )
143+ self . in . read ( size )
110144 end
111-
112- # http://www.alecjacobson.com/weblog/?p=75
113- # def stdin_read_char
114- # begin
115- # # save previous state of stty
116- # old_state = `stty -g`
117- # # disable echoing and enable raw (not having to press enter)
118- # system "stty raw -echo"
119- # c = STDIN.getc.chr
120- # # gather next two characters of special keys
121- # if(c=="\e")
122- # extra_thread = Thread.new{
123- # c = c + STDIN.getc.chr
124- # c = c + STDIN.getc.chr
125- # }
126- # # wait just long enough for special keys to get swallowed
127- # extra_thread.join(0.00001)
128- # # kill thread so not-so-long special keys don't wait on getc
129- # extra_thread.kill
130- # end
131- # rescue Exception => ex
132- # puts "#{ex.class}: #{ex.message}"
133- # puts ex.backtrace
134- # ensure
135- # # restore previous state of stty
136- # system "stty #{old_state}"
137- # end
138- # return c
139- # end
140145 end # class
141146end # module
0 commit comments