Skip to content

Commit cae9dce

Browse files
author
John Towers
committed
Merge branch 'develop'
2 parents 9df8694 + 67b9419 commit cae9dce

File tree

6 files changed

+707
-0
lines changed

6 files changed

+707
-0
lines changed

lib/client.py

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#MIT License
2+
#
3+
#Copyright (c) 2017 Tom van Ommeren
4+
#
5+
#Permission is hereby granted, free of charge, to any person obtaining a copy
6+
#of this software and associated documentation files (the "Software"), to deal
7+
#in the Software without restriction, including without limitation the rights
8+
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
#copies of the Software, and to permit persons to whom the Software is
10+
#furnished to do so, subject to the following conditions:
11+
12+
#The above copyright notice and this permission notice shall be included in all
13+
#copies or substantial portions of the Software.
14+
15+
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
#SOFTWARE.
22+
23+
from .request import *
24+
from .notification import *
25+
import sublime
26+
import threading
27+
class Client(object):
28+
29+
def __init__(self, process):
30+
self.process = process
31+
self.stdout_thread = threading.Thread(target=self.read_stdout)
32+
self.stdout_thread.start()
33+
self.stderr_thread = threading.Thread(target=self.read_stderr)
34+
self.stderr_thread.start()
35+
self.request_id = 0
36+
self.handlers = {} # type: Dict[int, Callable]
37+
self.capabilities = {} # type: Dict[str, Any]
38+
39+
def set_capabilities(self, capabilities):
40+
self.capabilities = capabilities
41+
42+
def has_capability(self, capability):
43+
return capability in self.capabilities
44+
45+
def get_capability(self, capability):
46+
return self.capabilities.get(capability)
47+
48+
def send_request(self, request: Request, handler: 'Callable'):
49+
self.request_id += 1
50+
if handler is not None:
51+
self.handlers[self.request_id] = handler
52+
self.send_payload(request.to_payload(self.request_id))
53+
54+
def send_notification(self, notification: Notification):
55+
debug('notify: ' + notification.method)
56+
self.send_payload(notification.to_payload())
57+
58+
def kill(self):
59+
self.process.kill()
60+
61+
def send_payload(self, payload):
62+
try:
63+
message = format_request(payload)
64+
self.process.stdin.write(bytes(message, 'UTF-8'))
65+
self.process.stdin.flush()
66+
except BrokenPipeError as e:
67+
debug("client unexpectedly died:", e)
68+
69+
def read_stdout(self):
70+
"""
71+
Reads JSON responses from process and dispatch them to response_handler
72+
"""
73+
ContentLengthHeader = b"Content-Length: "
74+
75+
while self.process.poll() is None:
76+
try:
77+
78+
in_headers = True
79+
content_length = 0
80+
while in_headers:
81+
header = self.process.stdout.readline().strip()
82+
if (len(header) == 0):
83+
in_headers = False
84+
85+
if header.startswith(ContentLengthHeader):
86+
content_length = int(header[len(ContentLengthHeader):])
87+
88+
if (content_length > 0):
89+
content = self.process.stdout.read(content_length).decode(
90+
"UTF-8")
91+
92+
payload = None
93+
try:
94+
payload = json.loads(content)
95+
limit = min(len(content), 200)
96+
if payload.get("method") != "window/logMessage":
97+
debug("got json: ", content[0:limit])
98+
except IOError:
99+
debug("Got a non-JSON payload: ", content)
100+
continue
101+
102+
try:
103+
if "error" in payload:
104+
error = payload['error']
105+
debug("got error: ", error)
106+
sublime.status_message(error.get('message'))
107+
elif "method" in payload:
108+
if "id" in payload:
109+
self.request_handler(payload)
110+
else:
111+
self.notification_handler(payload)
112+
elif "id" in payload:
113+
self.response_handler(payload)
114+
else:
115+
debug("Unknown payload type: ", payload)
116+
except Exception as err:
117+
debug("Error handling server content:", err)
118+
119+
except IOError:
120+
printf("LSP stdout process ending due to exception: ",
121+
sys.exc_info())
122+
self.process.terminate()
123+
self.process = None
124+
return
125+
126+
debug("LSP stdout process ended.")
127+
128+
def read_stderr(self):
129+
"""
130+
Reads any errors from the LSP process.
131+
"""
132+
while self.process.poll() is None:
133+
try:
134+
content = self.process.stderr.readline()
135+
debug("(stderr): ", content.strip())
136+
except IOError:
137+
utl.debug("LSP stderr process ending due to exception: ",
138+
sys.exc_info())
139+
return
140+
141+
debug("LSP stderr process ended.")
142+
143+
def response_handler(self, response):
144+
try:
145+
handler_id = int(response.get("id")) # dotty sends strings back :(
146+
result = response.get('result', None)
147+
if (self.handlers[handler_id]):
148+
self.handlers[handler_id](result)
149+
else:
150+
debug("No handler found for id" + response.get("id"))
151+
except Exception as e:
152+
debug("error handling response", handler_id)
153+
raise
154+
155+
def request_handler(self, request):
156+
method = request.get("method")
157+
if method == "workspace/applyEdit":
158+
apply_workspace_edit(sublime.active_window(),
159+
request.get("params"))
160+
else:
161+
debug("Unhandled request", method)
162+
163+
def notification_handler(self, response):
164+
method = response.get("method")
165+
if method == "textDocument/publishDiagnostics":
166+
EventHub.publish("document.diagnostics", response.get("params"))
167+
elif method == "window/showMessage":
168+
sublime.active_window().message_dialog(
169+
response.get("params").get("message"))
170+
elif method == "window/logMessage" and log_server:
171+
server_log(self.process.args[0],
172+
response.get("params").get("message"))
173+
else:
174+
debug("Unhandled notification:", method)
175+
176+
def initialize_on_open(view: sublime.View):
177+
global didopen_after_initialize
178+
config = config_for_scope(view)
179+
if config:
180+
if config.name not in window_clients(view.window()):
181+
didopen_after_initialize.append(view)
182+
get_window_client(view, config)

0 commit comments

Comments
 (0)