diff options
author | Tristan Gingold <tgingold@free.fr> | 2020-03-09 18:19:38 +0100 |
---|---|---|
committer | Tristan Gingold <tgingold@free.fr> | 2020-03-09 18:19:38 +0100 |
commit | 85627172aea75430ccd809ea0a13f3c4ed3ea8a0 (patch) | |
tree | 39a78d01afe31d9450ff316eaa9b923b91091af6 /python/vhdl_langserver/lsp.py | |
parent | 23935c8f2849fcb36bd69bbcadd4a0660912663f (diff) | |
download | ghdl-85627172aea75430ccd809ea0a13f3c4ed3ea8a0.tar.gz ghdl-85627172aea75430ccd809ea0a13f3c4ed3ea8a0.tar.bz2 ghdl-85627172aea75430ccd809ea0a13f3c4ed3ea8a0.zip |
Import vhdl_langserver from ghdl-language-server
Diffstat (limited to 'python/vhdl_langserver/lsp.py')
-rw-r--r-- | python/vhdl_langserver/lsp.py | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/python/vhdl_langserver/lsp.py b/python/vhdl_langserver/lsp.py new file mode 100644 index 000000000..983b3005e --- /dev/null +++ b/python/vhdl_langserver/lsp.py @@ -0,0 +1,288 @@ +import os +import logging +import json +import attr +from attr.validators import instance_of + +try: + from urllib.parse import unquote, quote +except ImportError: + from urllib2 import quote + from urlparse import unquote + +log = logging.getLogger('ghdl-ls') + +class ProtocolError(Exception): + pass + +class LSPConn: + def __init__(self, reader, writer): + self.reader = reader + self.writer = writer + + def readline(self): + data = self.reader.readline() + return data.decode("utf-8") + + def read(self, size): + data = self.reader.read(size) + return data.decode("utf-8") + + def write(self, out): + self.writer.write(out.encode()) + self.writer.flush() + +def path_from_uri(uri): + # Convert file uri to path (strip html like head part) + if not uri.startswith("file://"): + return uri + if os.name == "nt": + _, path = uri.split("file:///", 1) + else: + _, path = uri.split("file://", 1) + return os.path.normpath(unquote(path)) + + +def path_to_uri(path): + # Convert path to file uri (add html like head part) + if os.name == "nt": + return "file:///" + quote(path.replace('\\', '/')) + else: + return "file://" + quote(path) + + +class LanguageProtocolServer(object): + def __init__(self, handler, conn): + self.conn = conn + self.handler = handler + if handler is not None: + handler.set_lsp(self) + self.running = True + self._next_id = 0 + + def read_request(self): + headers = {} + while True: + # Read a line + line = self.conn.readline() + # Return on EOF. + if not line: + return None + if line[-2:] != '\r\n': + raise ProtocolError("invalid end of line in header") + line = line[:-2] + if not line: + # End of headers. + log.debug('Headers: %r', headers) + length = headers.get('Content-Length', None) + if length is not None: + body = self.conn.read(int(length)) + return body + else: + raise ProtocolError("missing Content-Length in header") + else: + key, value = line.split(': ', 1) + headers[key] = value + + def run(self): + while self.running: + body = self.read_request() + if body is None: + # EOF + break + + # Text to JSON + msg = json.loads(body) + log.debug('Read msg: %s', msg) + + reply = self.handle(msg) + if reply is not None: + self.write_output(reply) + + def handle(self, msg): + if msg.get('jsonrpc', None) != '2.0': + raise ProtocolError("invalid jsonrpc version") + tid = msg.get('id', None) + method = msg.get('method', None) + if method is None: + # This is a reply. + log.error('Unexpected reply for %s', tid) + return + params = msg.get('params', None) + fmethod = self.handler.dispatcher.get(method, None) + if fmethod: + if params is None: + params = {} + response = fmethod(**params) + if tid is None: + # If this was just a notification, discard it + return None + log.debug('Response: %s', response) + rbody = { + "jsonrpc": "2.0", + "id": tid, + "result": response, + } + else: + # Unknown method. + log.error('Unknown method %s', method) + # If this was just a notification, discard it + if tid is None: + return None + # Otherwise create an error. + rbody = { + "jsonrpc": "2.0", + "id": tid, + "error": { + "code": JSONErrorCodes.MethodNotFound, + "message": "unknown method {}".format(method) + } + } + return rbody + + def write_output(self, body): + output = json.dumps(body, separators=(",", ":")) + self.conn.write('Content-Length: {}\r\n'.format(len(output))) + self.conn.write('\r\n') + self.conn.write(output) + + def notify(self, method, params): + """Send a notification""" + body = { + "jsonrpc": "2.0", + "method": method, + "params": params, + } + self.write_output(body) + + def send_request(self, method, params): + """Send a request""" + self._next_id += 1 + body = { + "jsonrpc": "2.0", + "id": self._next_id, + "method": method, + "params": params, + } + self.write_output(body) + + def shutdown(self): + """Prepare to shutdown the server""" + self.running = False + + def show_message(self, typ, message): + self.notify('window/showMessage', {'type': typ, 'message': message}) + + def configuration(self, items): + return self.send_request("workspace/configuration", {'items': items}) + +#---------------------------------------------------------------------- +# Standard defines and object types +# + +class JSONErrorCodes(object): + # Defined by JSON RPC + ParseError = -32700 + InvalidRequest = -32600 + MethodNotFound = -32601 + InvalidParams = -32602 + InternalError = -32603 + serverErrorStart = -32099 + serverErrorEnd = -32000 + ServerNotInitialized = -32002 + UnknownErrorCode = -32001 + + # Defined by the protocol. + RequestCancelled = -32800 + ContentModified = -32801 + + +class CompletionKind(object): + Text = 1 + Method = 2 + Function = 3 + Constructor = 4 + Field = 5 + Variable = 6 + Class = 7 + Interface = 8 + Module = 9 + Property = 10 + Unit = 11 + Value = 12 + Enum = 13 + Keyword = 14 + Snippet = 15 + Color = 16 + File = 17 + Reference = 18 + + +class DiagnosticSeverity(object): + Error = 1 + Warning = 2 + Information = 3 + Hint = 4 + +class TextDocumentSyncKind(object): + NONE = 0, + FULL = 1 + INCREMENTAL = 2 + +class MessageType(object): + Error = 1 + Warning = 2 + Info = 3 + Log = 4 + +class SymbolKind(object): + File = 1 + Module = 2 + Namespace = 3 + Package = 4 + Class = 5 + Method = 6 + Property = 7 + Field = 8 + Constructor = 9 + Enum = 10 + Interface = 11 + Function = 12 + Variable = 13 + Constant = 14 + String = 15 + Number = 16 + Boolean = 17 + Array = 18 + +@attr.s +class HoverInfo(object): + language = attr.ib() + value = attr.ib() + + +@attr.s +class Completion(object): + label = attr.ib() + kind = attr.ib() + detail = attr.ib() + documentation = attr.ib() + + +@attr.s +class Position(object): + line = attr.ib() + character = attr.ib() + +@attr.s +class Range(object): + start = attr.ib(validator=instance_of(Position)) + end = attr.ib(validator=instance_of(Position)) + + +@attr.s +class Diagnostic(object): + range = attr.ib(validator=instance_of(Range)) + severity = attr.ib() + source = attr.ib() + message = attr.ib() |