3
3
import socketserver
4
4
import traceback
5
5
6
- from .jsonrpc import JSONRPC2Connection , ReadWriter , TCPReadWriter
6
+ from pyls .jsonrpc .endpoint import Endpoint
7
+ from pyls .jsonrpc .dispatchers import MethodDispatcher
8
+ from pyls .jsonrpc .streams import JsonRpcStreamReader
9
+ from pyls .jsonrpc .streams import JsonRpcStreamWriter
10
+ from coala_utils .decorators import enforce_signature
7
11
from .log import log
8
12
from .coalashim import run_coala_with_specific_file
9
13
from .uri import path_from_uri
10
14
from .diagnostic import output_to_diagnostics
11
15
12
16
13
- class ThreadingTCPServer (socketserver .ThreadingMixIn , socketserver .TCPServer ):
14
- pass
17
+ class _StreamHandlerWrapper (socketserver .StreamRequestHandler , object ):
18
+ """
19
+ A wrapper class that is used to construct a custom handler class.
20
+ """
15
21
22
+ delegate = None
16
23
17
- class LangserverTCPTransport (socketserver .StreamRequestHandler ):
24
+ def setup (self ):
25
+ super (_StreamHandlerWrapper , self ).setup ()
26
+ self .delegate = self .DELEGATE_CLASS (self .rfile , self .wfile )
18
27
19
28
def handle (self ):
20
- s = LangServer (conn = TCPReadWriter (self .rfile , self .wfile ))
21
- try :
22
- s .listen ()
23
- except Exception as e :
24
- tb = traceback .format_exc ()
25
- log ('ERROR: {} {}' .format (e , tb ))
29
+ self .delegate .start ()
26
30
27
31
28
- class LangServer (JSONRPC2Connection ):
32
+ class LangServer (MethodDispatcher ):
29
33
"""
30
34
Language server for coala base on JSON RPC.
31
35
"""
32
36
33
- def __init__ (self , conn = None ):
34
- super ().__init__ (conn = conn )
37
+ def __init__ (self , rx , tx ):
35
38
self .root_path = None
39
+ self ._jsonrpc_stream_reader = JsonRpcStreamReader (rx )
40
+ self ._jsonrpc_stream_writer = JsonRpcStreamWriter (tx )
41
+ self ._endpoint = Endpoint (self , self ._jsonrpc_stream_writer .write )
42
+ self ._dispatchers = []
43
+ self ._shutdown = False
36
44
37
- def handle (self , _id , request ):
38
- """
39
- Handle the request from language client.
40
- """
41
- log ('REQUEST: ' , request )
42
- resp = None
43
-
44
- if request ['method' ] == 'initialize' :
45
- resp = self .serve_initialize (request )
46
- # TODO: Support did_change and did_change_watched_files.
47
- # elif request["method"] == "textDocument/didChange":
48
- # resp = self.serve_change(request)
49
- # elif request["method"] == "workspace/didChangeWatchedFiles":
50
- # resp = self.serve_did_change_watched_files(request)
51
- elif request ['method' ] == 'textDocument/didSave' :
52
- self .serve_did_save (request )
53
-
54
- if resp is not None :
55
- self .write_response (request ['id' ], resp )
56
-
57
- def serve_initialize (self , request ):
45
+ def start (self ):
46
+ self ._jsonrpc_stream_reader .listen (self ._endpoint .consume )
47
+
48
+ def m_initialize (self , ** params ):
58
49
"""
59
50
Serve for the initialization request.
60
51
"""
61
- params = request ['params' ]
62
52
# Notice that the root_path could be None.
63
53
if 'rootUri' in params :
64
54
self .root_path = path_from_uri (params ['rootUri' ])
@@ -70,17 +60,19 @@ def serve_initialize(self, request):
70
60
}
71
61
}
72
62
73
- def serve_did_save (self , request ):
63
+ def m_text_document__did_save (self , ** params ):
74
64
"""
75
65
Serve for did_change request.
76
66
"""
77
- params = request ['params' ]
78
67
uri = params ['textDocument' ]['uri' ]
79
68
path = path_from_uri (uri )
80
69
diagnostics = output_to_diagnostics (
81
70
run_coala_with_specific_file (self .root_path , path ))
82
71
self .send_diagnostics (path , diagnostics )
83
72
73
+ def m_shutdown (self , ** _kwargs ):
74
+ self ._shutdown = True
75
+
84
76
# TODO: Support did_change and did_change_watched_files.
85
77
# def serve_change(self, request):
86
78
# '""Serve for the request of documentation changed.""'
@@ -110,7 +102,38 @@ def send_diagnostics(self, path, diagnostics):
110
102
'uri' : 'file://{0}' .format (path ),
111
103
'diagnostics' : _diagnostics ,
112
104
}
113
- self .send_notification ('textDocument/publishDiagnostics' , params )
105
+ self ._endpoint .notify ('textDocument/publishDiagnostics' , params = params )
106
+
107
+
108
+ @enforce_signature
109
+ def start_tcp_lang_server (handler_class : LangServer , bind_addr , port ):
110
+ # Construct a custom wrapper class around the user's handler_class
111
+ wrapper_class = type (
112
+ handler_class .__name__ + 'Handler' ,
113
+ (_StreamHandlerWrapper ,),
114
+ {'DELEGATE_CLASS' : handler_class },
115
+ )
116
+
117
+ try :
118
+ server = socketserver .TCPServer ((bind_addr , port ), wrapper_class )
119
+ except Exception as e :
120
+ log ('Fatal Exception: {}' .format (e ))
121
+ sys .exit (1 )
122
+
123
+ log ('Serving {} on ({}, {})' .format (
124
+ handler_class .__name__ , bind_addr , port ))
125
+ try :
126
+ server .serve_forever ()
127
+ finally :
128
+ log ('Shutting down' )
129
+ server .server_close ()
130
+
131
+
132
+ @enforce_signature
133
+ def start_io_lang_server (handler_class : LangServer , rstream , wstream ):
134
+ log ('Starting {} IO language server' .format (handler_class .__name__ ))
135
+ server = handler_class (rstream , wstream )
136
+ server .start ()
114
137
115
138
116
139
def main ():
@@ -123,18 +146,10 @@ def main():
123
146
args = parser .parse_args ()
124
147
125
148
if args .mode == 'stdio' :
126
- log ('Reading on stdin, writing on stdout' )
127
- s = LangServer (conn = ReadWriter (sys .stdin , sys .stdout ))
128
- s .listen ()
149
+ start_io_lang_server (LangServer , sys .stdin .buffer , sys .stdout .buffer )
129
150
elif args .mode == 'tcp' :
130
151
host , addr = '0.0.0.0' , args .addr
131
- log ('Accepting TCP connections on {}:{}' .format (host , addr ))
132
- ThreadingTCPServer .allow_reuse_address = True
133
- s = ThreadingTCPServer ((host , addr ), LangserverTCPTransport )
134
- try :
135
- s .serve_forever ()
136
- finally :
137
- s .shutdown ()
152
+ start_tcp_lang_server (LangServer , host , addr )
138
153
139
154
140
155
if __name__ == '__main__' :
0 commit comments