1 # Python Tools for Visual Studio
\r
2 # Copyright(c) Microsoft Corporation
\r
3 # All rights reserved.
\r
5 # Licensed under the Apache License, Version 2.0 (the License); you may not use
\r
6 # this file except in compliance with the License. You may obtain a copy of the
\r
7 # License at http://www.apache.org/licenses/LICENSE-2.0
\r
9 # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
\r
10 # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
\r
11 # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
\r
12 # MERCHANTABILITY OR NON-INFRINGEMENT.
\r
14 # See the Apache Version 2.0 License for specific language governing
\r
15 # permissions and limitations under the License.
\r
17 # Supported Python versions: 2.7, 3.5+
\r
18 # https://devguide.python.org/#status-of-python-branches
\r
20 from __future__ import print_function
\r
29 # Uncomment to send stderr somewhere readable.
\r
30 # sys.stderr = open(os.path.join(os.path.expanduser("~"), "log.txt"), "a")
\r
32 if sys.version_info >= (3,):
\r
33 stdout = sys.stdout.buffer
\r
34 stdin = sys.stdin.buffer
\r
39 if sys.platform == "win32":
\r
42 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
\r
43 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
\r
50 line += stdin.readline()
\r
55 if line.endswith(b"\r\n"):
\r
59 def write_stdout(data):
\r
60 if not isinstance(data, bytes):
\r
61 data = data.encode("utf-8")
\r
67 METHOD_NOT_FOUND = -32601
\r
68 INTERNAL_ERROR = -32603
\r
80 key, _, value = line.partition(b": ")
\r
81 headers[key] = value
\r
83 length = int(headers[b"Content-Length"])
\r
87 chunk = stdin.read(length)
\r
89 length -= len(chunk)
\r
91 request = json.loads(body)
\r
92 return Request(request)
\r
98 request = read_request()
\r
105 def write_response(id, d):
\r
106 response = {"jsonrpc": "2.0", "id": id}
\r
109 s = json.dumps(response)
\r
111 data = "Content-Length: {}\r\n\r\n".format(len(s)) + s
\r
115 class Request(object):
\r
116 def __init__(self, request):
\r
117 self.id = request["id"]
\r
118 self.method = request["method"]
\r
119 self.params = request.get("params", None)
\r
121 def write_result(self, result):
\r
122 write_response(self.id, {"result": result})
\r
124 def write_error(self, code, message):
\r
125 write_response(self.id, {"error": {"code": code, "message": message}})
\r
129 def __init__(self):
\r
132 def handler(self, method):
\r
133 def decorator(func):
\r
134 self.handlers[method] = func
\r
139 def handle(self, request):
\r
140 handler = self.handlers.get(request.method, None)
\r
143 request.write_error(
\r
144 METHOD_NOT_FOUND, "method {} not found".format(request.method)
\r
149 result = handler(*request.params)
\r
150 except Exception as e:
\r
151 request.write_error(INTERNAL_ERROR, str(e))
\r
153 request.write_result(result)
\r
159 def do_not_inspect(v):
\r
160 # https://github.com/Microsoft/python-language-server/issues/740
\r
161 # https://github.com/cython/cython/issues/1470
\r
162 if type(v).__name__ != "fused_cython_function":
\r
165 # If a fused function has __defaults__, then attempting to access
\r
166 # __kwdefaults__ will fail if generated before cython 0.29.6.
\r
167 return bool(getattr(v, "__defaults__", False))
\r
170 KNOWN_DIST_PREFIXES = {"PyQt5": ["PyQt5"], "PyQt5-sip": ["PyQt5.sip"]}
\r
173 def build_dist_prefixes():
\r
174 import pkg_resources
\r
178 # This iterates in the order things were added; no need to reverse.
\r
179 for d in pkg_resources.WorkingSet():
\r
183 top_level = d.get_metadata("top_level.txt")
\r
187 module_prefixes = None
\r
189 module_prefixes = top_level.splitlines()
\r
190 elif d.project_name:
\r
191 module_prefixes = KNOWN_DIST_PREFIXES.get(d.project_name, None)
\r
193 if not module_prefixes:
\r
196 for prefix in module_prefixes:
\r
197 prefix = prefix.strip()
\r
199 prefixes[prefix] = d
\r
204 DIST_PREFIXES = None
\r
207 def find_dist(module_name):
\r
208 global DIST_PREFIXES
\r
209 if DIST_PREFIXES is None:
\r
210 DIST_PREFIXES = build_dist_prefixes()
\r
213 if not module_name:
\r
216 d = DIST_PREFIXES.get(module_name, None)
\r
220 split = module_name.split(".")
\r
224 module_name = ".".join(split[:-1])
\r
227 @mux.handler("$/cancelRequest")
\r
228 def cancel_request(params):
\r
232 @mux.handler("moduleMemberNames")
\r
233 def module_member_names(module_name):
\r
235 module = importlib.import_module(module_name)
\r
239 members = inspect.getmembers(module)
\r
242 "members": [name for name, _ in members],
\r
243 "all": getattr(module, "__all__", None),
\r
247 @mux.handler("moduleVersion")
\r
248 def module_version(module_name):
\r
251 # TODO: iterate as in find_dist and import looking for __version__?
\r
252 module = importlib.import_module(module_name)
\r
253 version = getattr(module, "__version__", None)
\r
259 d = find_dist(module_name)
\r
261 version = d.version
\r
269 for request in requests():
\r
270 mux.handle(request)
\r
273 if __name__ == "__main__":
\r