3
3
import logging
4
4
import uuid
5
5
6
- import jsonrpc
6
+ from jsonrpc import jsonrpc2 , JSONRPCResponseManager
7
7
8
8
log = logging .getLogger (__name__ )
9
9
@@ -14,6 +14,8 @@ class JSONRPCServer(object):
14
14
def __init__ (self , rfile , wfile ):
15
15
self .rfile = rfile
16
16
self .wfile = wfile
17
+
18
+ self ._callbacks = {}
17
19
self ._shutdown = False
18
20
19
21
def exit (self ):
@@ -27,56 +29,68 @@ def shutdown(self):
27
29
log .debug ("Server shut down, awaiting exit notification" )
28
30
29
31
def handle (self ):
30
- # VSCode wants us to keep the connection open, so let's handle messages in a loop
31
32
while True :
32
33
try :
33
34
data = self ._read_message ()
34
35
log .debug ("Got message: %s" , data )
35
36
36
37
if self ._shutdown :
37
38
# 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 })
39
40
break
40
41
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' ])
45
59
except Exception :
46
60
log .exception ("Language server exiting due to uncaught exception" )
47
61
break
48
62
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 )
54
77
self ._write_message (req .data )
55
78
56
79
def notify (self , method , params = None ):
57
80
""" Send a notification to the client, expects no response. """
58
81
log .debug ("Sending notification %s: %s" , method , params )
59
- req = jsonrpc . jsonrpc2 .JSONRPC20Request (
82
+ req = jsonrpc2 .JSONRPC20Request (
60
83
method = method , params = params , is_notification = True
61
84
)
62
85
self ._write_message (req .data )
63
86
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
-
73
87
def _read_message (self ):
74
88
line = self .rfile .readline ()
75
89
76
90
if not line :
77
91
raise EOFError ()
78
92
79
- content_length = self . _content_length (line )
93
+ content_length = _content_length (line )
80
94
81
95
# Blindly consume all header lines
6293
82
96
while line and line .strip ():
@@ -98,3 +112,14 @@ def _write_message(self, msg):
98
112
)
99
113
self .wfile .write (response .encode ('utf-8' ))
100
114
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