33import logging
44import uuid
55
6- import jsonrpc
6+ from jsonrpc import jsonrpc2 , JSONRPCResponseManager
77
88log = logging .getLogger (__name__ )
99
@@ -14,6 +14,8 @@ class JSONRPCServer(object):
1414 def __init__ (self , rfile , wfile ):
1515 self .rfile = rfile
1616 self .wfile = wfile
17+
18+ self ._callbacks = {}
1719 self ._shutdown = False
1820
1921 def exit (self ):
@@ -27,56 +29,68 @@ def shutdown(self):
2729 log .debug ("Server shut down, awaiting exit notification" )
2830
2931 def handle (self ):
30- # VSCode wants us to keep the connection open, so let's handle messages in a loop
3132 while True :
3233 try :
3334 data = self ._read_message ()
3435 log .debug ("Got message: %s" , data )
3536
3637 if self ._shutdown :
3738 # Handle only the exit notification when we're shut down
38- jsonrpc . JSONRPCResponseManager .handle (data , {'exit' : self .exit })
39+ JSONRPCResponseManager .handle (data , {'exit' : self .exit })
3940 break
4041
41- response = jsonrpc .JSONRPCResponseManager .handle (data , self )
42-
43- if response is not None :
44- self ._write_message (response .data )
42+ if isinstance (data , bytes ):
43+ data = data .decode ("utf-8" )
44+
45+ msg = json .loads (data )
46+ if 'method' in msg :
47+ # It's a notification or request
48+ # Dispatch to the thread pool for handling
49+ response = JSONRPCResponseManager .handle (data , self )
50+ if response is not None :
51+ self ._write_message (response .data )
52+ else :
53+ # Otherwise, it's a response message
54+ on_result , on_error = self ._callbacks .pop (msg ['id' ])
55+ if 'result' in msg and on_result :
56+ on_result (msg ['result' ])
57+ elif 'error' in msg and on_error :
58+ on_error (msg ['error' ])
4559 except Exception :
4660 log .exception ("Language server exiting due to uncaught exception" )
4761 break
4862
49- def call (self , method , params = None ):
50- """ Call a method on the client. TODO: return the result. """
51- log .debug ("Sending request %s: %s" , method , params )
52- req = jsonrpc .jsonrpc2 .JSONRPC20Request (method = method , params = params )
53- req ._id = str (uuid .uuid4 ())
63+ def call (self , method , params = None , on_result = None , on_error = None ):
64+ """Call a method on the client."""
65+ msg_id = str (uuid .uuid4 ())
66+ log .debug ("Sending request %s: %s: %s" , msg_id , method , params )
67+ req = jsonrpc2 .JSONRPC20Request (method = method , params = params )
68+ req ._id = msg_id
69+
70+ def _default_on_error (error ):
71+ log .error ("Call to %s failed with %s" , method , error )
72+
73+ if not on_error :
74+ on_error = _default_on_error
75+
76+ self ._callbacks [msg_id ] = (on_result , on_error )
5477 self ._write_message (req .data )
5578
5679 def notify (self , method , params = None ):
5780 """ Send a notification to the client, expects no response. """
5881 log .debug ("Sending notification %s: %s" , method , params )
59- req = jsonrpc . jsonrpc2 .JSONRPC20Request (
82+ req = jsonrpc2 .JSONRPC20Request (
6083 method = method , params = params , is_notification = True
6184 )
6285 self ._write_message (req .data )
6386
64- def _content_length (self , line ):
65- if line .startswith (b'Content-Length: ' ):
66- _ , value = line .split (b'Content-Length: ' )
67- value = value .strip ()
68- try :
69- return int (value )
70- except ValueError :
71- raise ValueError ("Invalid Content-Length header: {}" .format (value ))
72-
7387 def _read_message (self ):
7488 line = self .rfile .readline ()
7589
7690 if not line :
7791 raise EOFError ()
7892
79- content_length = self . _content_length (line )
93+ content_length = _content_length (line )
8094
8195 # Blindly consume all header lines
8296 while line and line .strip ():
@@ -98,3 +112,14 @@ def _write_message(self, msg):
98112 )
99113 self .wfile .write (response .encode ('utf-8' ))
100114 self .wfile .flush ()
115+
116+
117+ def _content_length (line ):
118+ """Extract the content length from an input line."""
119+ if line .startswith (b'Content-Length: ' ):
120+ _ , value = line .split (b'Content-Length: ' )
121+ value = value .strip ()
122+ try :
123+ return int (value )
124+ except ValueError :
125+ raise ValueError ("Invalid Content-Length header: {}" .format (value ))
0 commit comments