<NME> langserver.py
<BEF> import sys
import json
import jedi

class JSONRPC2Error(Exception):
    pass

def log(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def path_from_uri(uri):
    _, path = uri.split("file://", 1)
    return path

def read_header_content_length(line):
    if len(line) < 2 or line[-2:] != "\r\n":
        raise JSONRPC2Error("Line endings must be \\r\\n")
    if line.startswith("Content-Length: "):
        _, value = line.split("Content-Length: ")
        value = value.strip()
        try:
            return int(value)
        except ValueError:
            raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))

def write_response(id, result):
    body = {
        "jsonrpc": "2.0",
        "id": id,
        "result": result,
    }
    body = json.dumps(body, separators=(",", ":"))
    content_length = len(body)
    response = (
        "Content-Length: {}\r\n"
        "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n"
        "{}".format(content_length, body))
    sys.stdout.write(response)
    sys.stdout.flush()
    log("RESPONSE: ", response)

def read_messages():
    while True:
        line = sys.stdin.readline()
        length = read_header_content_length(line)
        body = sys.stdin.read(length+2) # TODO(renfred): why the off-by-two?
        request = json.loads(body)
        log("REQUEST: ", length, request)
        if request["method"] == "initialize":
            write_response(request["id"], {
                "capabilities": {
                    # "textDocumentSync": 1,
                    "hoverProvider": True,
            *args, **kwargs)
                    "referencesProvider": True,
                    # "workspaceSymbolProvider": True TODO
                }
            })

        elif request["method"] == "textDocument/hover":
            write_response(request["id"], handle_hover(request))

        elif request["method"] == "textDocument/definition":
            write_response(request["id"], handle_definition(request))

        elif request["method"] == "textDocument/references":
            write_response(request["id"], handle_references(request))

def handle_hover(request):
    params = request["params"]
    pos = params["position"]
    path = path_from_uri(params["textDocument"]["uri"])
    source = open(path).read()
    if len(source.split("\n")[pos["line"]]) < pos["character"]:
        return {}
    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
                             column=pos["character"])

    defs, error = [], None
    try:
        defs = script.goto_definitions()
    except Exception as e:
        # TODO return these errors using JSONRPC properly. Doing it this way
        # initially for debugging purposes.
        error = "ERROR {}: {}".format(type(e), e)
    d = defs[0] if len(defs) > 0 else None

    # TODO(renfred): better failure mode
    if d is None:
        value = error or "404 Not Found"
        return {
            "contents": [{"language": "markdown", "value": value}],
        }

    hover_info = d.docstring() or d.description
    return {
        # TODO(renfred): convert reStructuredText docstrings to markdown.
        "contents": [{"language": "markdown", "value": hover_info}],
    }

def handle_definition(request):
    params = request["params"]
    pos = params["position"]
    path = path_from_uri(params["textDocument"]["uri"])
    source = open(path).read()
    if len(source.split("\n")[pos["line"]]) < pos["character"]:
        return {}
    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
                             column=pos["character"])

    defs = script.goto_definitions()
    assigns = script.goto_assignments()
    d = None
    if len(defs) > 0:
        d = defs[0]
    elif len(assigns) > 0:
        # TODO(renfred): figure out if this works in all cases.
        d = assigns[0]
    if d is None: return {}

    return {
        "uri": "file://" + d.module_path,
        "range": {
            "start": {
                "line": d.line-1,
                "character": d.column,
            },
            "end": {
                "line": d.line-1,
                "character": d.column,
            }
        }
    }

def handle_references(request):
    params = request["params"]
    pos = params["position"]
    path = path_from_uri(params["textDocument"]["uri"])
    source = open(path).read()
    if len(source.split("\n")[pos["line"]]) < pos["character"]:
        return {}
    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
                             column=pos["character"])

    usages = script.usages()
    if len(usages) == 0:
        return {}

    refs = []
    for u in usages:
        if u.is_definition():
            continue
        refs.append({
            "uri": "file://" + u.module_path,
            "range": {
                "start": {
                    "line": u.line-1,
                    "character": u.column,
                },
                "end": {
                    "line": u.line-1,
                    "character": u.column,
                }
            }
        })
    return refs

def main():
    log("Reading on stdin, writing on stdout")
    read_messages()

if __name__ == "__main__":
    main()
            if completion.type in ['module'
                                   ] or not hasattr(completion, 'params'):
                return ''
            return '%s(%s)' % (completion.name,
                               ', '.join(p.description
                                         for p in completion.params if p))

        def get_definition_type(definition):
            definition.in_builtin_module
            try:
                if definition.type in ['statement'
                                       ] and definition.name.isupper():
                    return 'constant'
                basic_types = {
                    'module': 'import',
                    'instance': 'variable',
                    'statement': 'value',
                    'param': 'variable',
                }
                return basic_types.get(definition.type, definition.type)
            except Exception:
                return 'builtin'

        results = []
        with opentracing.start_child_span(
                parent_span, "accumulate_definitions"):
            for definition in defs:
                signature = definition.name
                description = None
                if definition.type in ('class', 'function'):
                    signature = generate_signature(definition)
                    try:
                        description = definition.docstring(raw=True).strip()
                    except Exception:
                        description = ''
                    if not description and not hasattr(definition,
                                                       'get_line_code'):
                        # jedi returns an empty string for compiled objects
                        description = definition.docstring().strip()
                if definition.type == 'module':
                    try:
                        signature = definition.full_name
                        description = definition.docstring(raw=True).strip()
                    except Exception:
                        description = ''
                    if not description and hasattr(definition,
                                                   'get_line_code'):
                        # jedi returns an empty string for compiled objects
                        description = definition.docstring().strip()

                def_type = get_definition_type(definition)
                if def_type in ('function', 'method'):
                    signature = 'def ' + signature
                elif def_type == 'class':
                    signature = 'class ' + signature
                else:
                    # TODO(keegan) vscode python uses the current word if
                    # definition.name is empty
                    signature = definition.name

                # TODO(keegan) implement the rest of
                # https://sourcegraph.com/github.com/DonJayamanne/pythonVSCode/-/blob/src/client/providers/hoverProvider.ts#L34
                results.append({
                    "language": "python",
                    "value": signature,
                })
                if description:
                    results.append(description)
                elif definition.type == "param":
                    results.append("parameter `" + definition.name + "`")
                elif definition.type == "statement":
                    results.append("variable `" + definition.name + "`")

        return {"contents": results}

    def serve_definition(self, request):
        return list(
            filter(None, (d["location"]
                          for d in self.serve_x_definition(request))))

    def serve_x_definition(self, request):
        params = request["params"]
        pos = params["position"]
        path = path_from_uri(params["textDocument"]["uri"])
        # will be useful for filtering out circular/useless definitions
        pos["path"] = path
        parent_span = request["span"]
        source = self.fs.open(path, parent_span)
        if len(source.split("\n")[pos["line"]]) < pos["character"]:
            return {}
        script = self.new_script(
            path=path,
            source=source,
            line=pos["line"] + 1,
            column=pos["character"],
            parent_span=parent_span)

        results = []
        defs = []
        defs.extend(LangServer.goto_definitions(script, request))
        defs.extend(LangServer.goto_assignments(script, request))
        if not defs:
            return results

        for d in defs:
            defining_module_path = d.module_path
            defining_module = self.workspace.get_module_by_path(
                defining_module_path)

            if not defining_module and (not d.is_definition() or
                                        d.line is None or d.column is None):
                continue

            symbol_locator = {"symbol": None, "location": None}

            if defining_module and not defining_module.is_stdlib:
                # the module path doesn't map onto the repository structure
                # because we're not fully installing
                # dependency packages, so don't include it in the symbol
                # descriptor
                filename = os.path.basename(defining_module_path)
                symbol_name = ""
                symbol_kind = ""
                if d.description:
                    symbol_name, symbol_kind = LangServer.name_and_kind(
                        d.description)
                symbol_locator["symbol"] = {
                    "package": {
                        "name": defining_module.qualified_name.split(".")[0],
                    },
                    "name": symbol_name,
                    "container": defining_module.qualified_name,
                    "kind": symbol_kind,
                    "file": filename
                }

            elif defining_module and defining_module.is_stdlib:
                rel_path = os.path.relpath(defining_module_path,
                                           self.workspace.PYTHON_PATH)
                filename = os.path.basename(defining_module_path)
                symbol_name = ""
                symbol_kind = ""
                if d.description:
                    symbol_name, symbol_kind = LangServer.name_and_kind(
                        d.description)
                symbol_locator["symbol"] = {
                    "package": {
                        "name": "cpython",
                    },
                    "name": symbol_name,
                    "container": defining_module.qualified_name,
                    "kind": symbol_kind,
                    "path": os.path.join(GlobalConfig.STDLIB_SRC_PATH,
                                         rel_path),
                    "file": filename
                }

            if (d.is_definition() and
                    d.line is not None and d.column is not None):
                location = {
                    # TODO(renfred) determine why d.module_path is empty.
                    "uri": "file://" + (d.module_path or path),
                    "range": {
                        "start": {
                            "line": d.line - 1,
                            "character": d.column,
                        },
                        "end": {
                            "line": d.line - 1,
                            "character": d.column + len(d.name),
                        },
                    },
                }
                # add a position hint in case this eventually gets passed to an
                # operation that could use it
                if symbol_locator["symbol"]:
                    symbol_locator["symbol"]["position"] = location["range"][
                        "start"]

                # set the full location if the definition is in this workspace
                if not defining_module or not defining_module.is_external:
                    symbol_locator["location"] = location

            results.append(symbol_locator)

        unique_results = []
        for result in results:
            if result not in unique_results:
                unique_results.append(result)

        # if there's more than one definition, go ahead and remove the ones
        # that are the same as the input position
        if len(unique_results) > 1:
            unique_results = [
                ur for ur in unique_results
                if not LangServer.is_circular(pos, ur["location"])
            ]
        return unique_results

    @staticmethod
    def is_circular(reference, definition):
        """Takes a reference location and a definition location, and determines
        whether they're the same (and hence useless).

        We need to do this because getting the definition of an import
        (using Jedi's goto_assignments method) sometimes returns that
        same import. We filter out such cases and fall back on
        goto_definitions.
        """
        if not reference or not definition:
            return False
        if reference["path"] != path_from_uri(definition["uri"]):
            return False
        if definition["range"]["start"]["line"] == 0 \
                and definition["range"]["end"]["line"] == 0 \
                and definition["range"]["start"]["character"] == 0:
            # if the definition is at the very beginning of the same file,
            # then we've almost certainly jumped to the beginning of the
            # module that we're already inside of
            return True
        if reference["line"] < definition["range"]["start"]["line"] \
                or reference["line"] > definition["range"]["end"]["line"]:
            return False
        if reference["character"] < definition["range"]["start"]["character"] \
                or reference["character"] > definition["range"]["end"]["character"]:
            return False
        return True

    @staticmethod
    def name_and_kind(description: str):
        parts = description.split(" ")
        if "=" in description:
            name = parts[0]
            kind = "="
        else:
            name = parts[1]
            kind = parts[0]
        return name, kind

    def serve_references(self, request):
        params = request["params"]
        limit = params.get("limit", 200)
        pos = params["position"]
        path = path_from_uri(params["textDocument"]["uri"])
        parent_span = request["span"]
        source = self.fs.open(path, parent_span)
        if len(source.split("\n")[pos["line"]]) < pos["character"]:
            return {}
        script = self.new_script(
            path=path,
            source=source,
            line=pos["line"] + 1,
            column=pos["character"],
            parent_span=parent_span)

        usages = LangServer.usages(script, parent_span)
        if len(usages) == 0:
            return {}
        if len(usages) > limit:
            usages = usages[:limit]

        refs = []
        partial_initializer = {
            "id": request["id"],
            "patch": [{
                "op": "add",
                "path": "",
                "value": []
            }]
        }
        self.conn.send_notification(
            "$/partialResult", partial_initializer) if self.streaming else None
        json_patch = []
        package_cache_path = os.path.abspath(self.workspace.PACKAGES_PATH)
        for u in usages:
            if u.is_definition():
                continue
            # filter out any results from files that are cached on the local fs
            if u.module_path.startswith(package_cache_path):
                continue
            if u.module_path.startswith(self.workspace.PYTHON_PATH):
                continue
            location = {
                "uri": "file://" + u.module_path,
                "range": {
                    "start": {
                        "line": u.line - 1,
                        "character": u.column,
                    },
                    "end": {
                        "line": u.line - 1,
                        "character": u.column + len(u.name),
                    }
                }
            }
            refs.append(location)
            patch_op = {
                "op": "add",
                "path": "/-",
                "value": location,
            }
            json_patch.append(patch_op)
        partial_result = {
            "id": request["id"],
            "patch": json_patch,
        }
        self.conn.send_notification("$/partialResult",
                                    partial_result) if self.streaming else None
        return refs

    def serve_x_references(self, request):
        parent_span = request["span"]
        params = request["params"]
        limit = params.get("limit", 200)
        symbol = params["query"]
        if not symbol:
            return []

        package_name = symbol["container"].split(".")[0]
        symbol_name = symbol["name"]

        refs = []
        partial_initializer = {
            "id": request["id"],
            "patch": [{
                "op": "add",
                "path": "",
                "value": []
            }]
        }
        self.conn.send_notification(
            "$/partialResult", partial_initializer) if self.streaming else None
        # We can't use Jedi to get x-refs because we only have a symbol descriptor,
        # not a source location and source file. I tried fetching the package that's
        # mentioned in the symbol descriptor and providing the source of the
        # definition for Jedi, but that didn't seem to work, maybe because the fetched
        # package is in the local FS cache, whereas the project files are handled by the remote FS.
        # Anyway, unless we want to dig further into Jedi or rewrite the FS abstraction, it's
        # easier to manually parse the source files and search the ASTs. We can still use Jedi to
        # eliminate false positives by ensuring that each returned reference has a definition that
        # matches the symbol descriptor.
        for ref_batch in get_references(package_name, symbol_name, self.fs,
                                        self.root_path, parent_span):
            json_patch = []
            for r in ref_batch:
                location = {
                    "uri": "file://" + r["path"],
                    "range": {
                        "start": {
                            "line": r["line"],
                            "character": r["character"],
                        },
                        "end": {
                            "line": r["line"],
                            "character": r["character"] + len(symbol_name)
                        }
                    }
                }
                ref_info = {
                    "reference": location,
                    "symbol": symbol,
                }
                refs.append(ref_info)
                patch_op = {
                    "op": "add",
                    "path": "/-",
                    "value": ref_info,
                }
                json_patch.append(patch_op)
                if len(refs) >= limit:
                    break
            partial_result = {
                "id": request["id"],
                "patch": json_patch,
            }
            self.conn.send_notification(
                "$/partialResult", partial_result) if self.streaming else None
            if len(refs) >= limit:
                break

        return refs

    def serve_symbols(self, request):
        parent_span = request["span"]
        params = request["params"]

        if "symbol" in params:
            return targeted_symbol(params["symbol"], self.fs, self.root_path,
                                   parent_span)

        if self.all_symbols is None:
            self.all_symbols = workspace_symbols(self.fs, self.root_path,
                                                 parent_span)

        q, limit = params.get("query"), params.get("limit", 50)
        symbols = ((sym.score(q), sym) for sym in self.all_symbols)
        symbols = ((score, sym) for (score, sym) in symbols if score >= 0)
        symbols = sorted(symbols, reverse=True, key=lambda x: x[0])[:limit]

        result = [s.json_object() for (_, s) in symbols]
        return result

    def serve_document_symbols(self, request):
        params = request["params"]
        path = path_from_uri(params["textDocument"]["uri"])
        parent_span = request["span"]
        source = self.fs.open(path, parent_span)
        return [s.json_object() for s in extract_symbols(source, path)]

    def serve_x_packages(self, request):
        return self.workspace.get_package_information(request["span"])

    def serve_x_dependencies(self, request):
        return self.workspace.get_dependencies(request["span"])

    def serve_exit(self, request):
        self.workspace.cleanup()
        self.running = False

    def serve_default(self, request):
        raise JSONRPC2Error(
            code=-32601,
            message="method {} not found".format(request["method"]))


class JSONRPC2Error(Exception):
    def __init__(self, code, message, data=None):
        self.code = code
        self.message = message
        self.data = data


class ForkingTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass


class LangserverTCPTransport(socketserver.StreamRequestHandler):
    def handle(self):
        conn = JSONRPC2Connection(TCPReadWriter(self.rfile, self.wfile))
        s = LangServer(conn)
        s.run()


def main():
    import argparse

    parser = argparse.ArgumentParser(description="")
    parser.add_argument(
        "--mode", default="stdio", help="communication (stdio|tcp)")
    parser.add_argument(
        "--addr", default=4389, help="server listen (tcp)", type=int)
    parser.add_argument("--debug", action="store_true")
    parser.add_argument("--lightstep_token", default=os.environ.get("LIGHTSTEP_ACCESS_TOKEN"))
    parser.add_argument("--python_path")

    args = parser.parse_args()

    logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))

    if args.python_path:
        GlobalConfig.PYTHON_PATH = args.python_path

    log.info("Setting Python path to %s", GlobalConfig.PYTHON_PATH)

    # if args.lightstep_token isn't set, we'll fall back on the default no-op
    # opentracing implementation
    if args.lightstep_token:
        opentracing.tracer = lightstep.Tracer(
            component_name="python-langserver",
            access_token=args.lightstep_token)

    if args.mode == "stdio":
        logging.info("Reading on stdin, writing on stdout")
        s = LangServer(conn=ReadWriter(sys.stdin, sys.stdout))
        s.run()
    elif args.mode == "tcp":
        host, addr = "0.0.0.0", args.addr
        logging.info("Accepting TCP connections on %s:%s", host, addr)
        ForkingTCPServer.allow_reuse_address = True
        ForkingTCPServer.daemon_threads = True
        s = ForkingTCPServer((host, addr), LangserverTCPTransport)
        try:
            s.serve_forever()
        finally:
            s.shutdown()


if __name__ == "__main__":
    main()

<MSG> Refactor code, stub tcp implmentation

<DFF> @@ -1,53 +1,115 @@
 import sys
 import json
 import jedi
-
-class JSONRPC2Error(Exception):
-    pass
+import argparse
+from abc import ABC, abstractmethod
 
 def log(*args, **kwargs):
     print(*args, file=sys.stderr, **kwargs)
 
-def path_from_uri(uri):
-    _, path = uri.split("file://", 1)
-    return path
+class JSONRPC2Error(Exception):
+    pass
+
+class ReadWriter(ABC):
+    @abstractmethod
+    def readline(self, size):
+        pass
+
+    @abstractmethod
+    def read(self, size):
+        pass
+
+    @abstractmethod
+    def write(self, size):
+        pass
+
+class StdIOReadWriter(ReadWriter):
+    def readline(self, *args):
+        return sys.stdin.readline(*args)
+
+    def read(self, *args):
+        return sys.stdin.read(*args)
+
+    def write(self, out):
+        sys.stdout.write(out)
+        sys.stdout.flush()
+
+class SocketReadWriter(ReadWriter):
+    def __init__(self, socket):
+        self.socket = socket
+        self.buffer = ""
+
+    def readline(self, size=None):
+        buffering = True
+        while buffering:
+            if "\n" in self.buffer:
+                line, self.buffer = buffer.split("\n", 1)
+                if size:
+                    line, self.buffer = line[:size], self.buffer + line[size:]
+                return line
+            else:
+                next = socket.recv(4096)
+                if not next:
+                    buffering = False
+                else:
+                    self.buffer += more
+
+    def read(self, size):
+        if len(self.buffer) >= size:
+            out, self.buffer = self.buffer[:size], self.buffer[size:]
+        recv_size = size - len(self.buffer)
+        next = socket.recv(recv_size)
+        out, self.buffer = self.buffer + next, self.buffer
+        return out
+
+class JSONRPC2Server:
+    def __init__(self, conn=None):
+        self.conn = conn
+
+    def handle(self, id, request):
+        pass
+
+    def _read_header_content_length(self, line):
+        if len(line) < 2 or line[-2:] != "\r\n":
+            raise JSONRPC2Error("Line endings must be \\r\\n")
+        if line.startswith("Content-Length: "):
+            _, value = line.split("Content-Length: ")
+            value = value.strip()
+            try:
+                return int(value)
+            except ValueError:
+                raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))
+
+    def write_response(self, id, result):
+        body = {
+            "jsonrpc": "2.0",
+            "id": id,
+            "result": result,
+        }
+        body = json.dumps(body, separators=(",", ":"))
+        content_length = len(body)
+        response = (
+            "Content-Length: {}\r\n"
+            "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n"
+            "{}".format(content_length, body))
+        self.conn.write(response)
+        log("RESPONSE: ", response)
+
+    def serve(self):
+        while True:
+            line = self.conn.readline()
+            length = self._read_header_content_length(line)
+            body = self.conn.read(length+2) # TODO(renfred): why the off-by-two?
+            request = json.loads(body)
+            self.handle(id, request)
+
+class LangServer(JSONRPC2Server):
+    def handle(self, id, request):
+        log("REQUEST: ", id, request)
+        resp = ""
 
-def read_header_content_length(line):
-    if len(line) < 2 or line[-2:] != "\r\n":
-        raise JSONRPC2Error("Line endings must be \\r\\n")
-    if line.startswith("Content-Length: "):
-        _, value = line.split("Content-Length: ")
-        value = value.strip()
-        try:
-            return int(value)
-        except ValueError:
-            raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))
-
-def write_response(id, result):
-    body = {
-        "jsonrpc": "2.0",
-        "id": id,
-        "result": result,
-    }
-    body = json.dumps(body, separators=(",", ":"))
-    content_length = len(body)
-    response = (
-        "Content-Length: {}\r\n"
-        "Content-Type: application/vscode-jsonrpc; charset=utf8\r\n\r\n"
-        "{}".format(content_length, body))
-    sys.stdout.write(response)
-    sys.stdout.flush()
-    log("RESPONSE: ", response)
-
-def read_messages():
-    while True:
-        line = sys.stdin.readline()
-        length = read_header_content_length(line)
-        body = sys.stdin.read(length+2) # TODO(renfred): why the off-by-two?
-        request = json.loads(body)
-        log("REQUEST: ", length, request)
         if request["method"] == "initialize":
-            write_response(request["id"], {
+            resp = {
                 "capabilities": {
                     # "textDocumentSync": 1,
                     "hoverProvider": True,
@@ -55,119 +117,133 @@ def read_messages():
                     "referencesProvider": True,
                     # "workspaceSymbolProvider": True TODO
                 }
-            })
-
+            }
         elif request["method"] == "textDocument/hover":
-            write_response(request["id"], handle_hover(request))
-
+            resp = self.serve_hover(request)
         elif request["method"] == "textDocument/definition":
-            write_response(request["id"], handle_definition(request))
-
+            resp = self.serve_definition(request)
         elif request["method"] == "textDocument/references":
-            write_response(request["id"], handle_references(request))
-
-def handle_hover(request):
-    params = request["params"]
-    pos = params["position"]
-    path = path_from_uri(params["textDocument"]["uri"])
-    source = open(path).read()
-    if len(source.split("\n")[pos["line"]]) < pos["character"]:
-        return {}
-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
-                             column=pos["character"])
-
-    defs, error = [], None
-    try:
-        defs = script.goto_definitions()
-    except Exception as e:
-        # TODO return these errors using JSONRPC properly. Doing it this way
-        # initially for debugging purposes.
-        error = "ERROR {}: {}".format(type(e), e)
-    d = defs[0] if len(defs) > 0 else None
-
-    # TODO(renfred): better failure mode
-    if d is None:
-        value = error or "404 Not Found"
+            resp = self.serve_references(request)
+
+        if resp:
+            self.write_response(request["id"], resp)
+
+    def path_from_uri(self, uri):
+        _, path = uri.split("file://", 1)
+        return path
+
+    def serve_hover(self, request):
+        params = request["params"]
+        pos = params["position"]
+        path = self.path_from_uri(params["textDocument"]["uri"])
+        source = open(path).read()
+        if len(source.split("\n")[pos["line"]]) < pos["character"]:
+            return {}
+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
+                                 column=pos["character"])
+
+        defs, error = [], None
+        try:
+            defs = script.goto_definitions()
+        except Exception as e:
+            # TODO return these errors using JSONRPC properly. Doing it this way
+            # initially for debugging purposes.
+            error = "ERROR {}: {}".format(type(e), e)
+        d = defs[0] if len(defs) > 0 else None
+
+        # TODO(renfred): better failure mode
+        if d is None:
+            value = error or "404 Not Found"
+            return {
+                "contents": [{"language": "markdown", "value": value}],
+            }
+
+        hover_info = d.docstring() or d.description
         return {
-            "contents": [{"language": "markdown", "value": value}],
+            # TODO(renfred): convert reStructuredText docstrings to markdown.
+            "contents": [{"language": "markdown", "value": hover_info}],
         }
 
-    hover_info = d.docstring() or d.description
-    return {
-        # TODO(renfred): convert reStructuredText docstrings to markdown.
-        "contents": [{"language": "markdown", "value": hover_info}],
-    }
-
-def handle_definition(request):
-    params = request["params"]
-    pos = params["position"]
-    path = path_from_uri(params["textDocument"]["uri"])
-    source = open(path).read()
-    if len(source.split("\n")[pos["line"]]) < pos["character"]:
-        return {}
-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
-                             column=pos["character"])
-
-    defs = script.goto_definitions()
-    assigns = script.goto_assignments()
-    d = None
-    if len(defs) > 0:
-        d = defs[0]
-    elif len(assigns) > 0:
-        # TODO(renfred): figure out if this works in all cases.
-        d = assigns[0]
-    if d is None: return {}
-
-    return {
-        "uri": "file://" + d.module_path,
-        "range": {
-            "start": {
-                "line": d.line-1,
-                "character": d.column,
-            },
-            "end": {
-                "line": d.line-1,
-                "character": d.column,
-            }
-        }
-    }
-
-def handle_references(request):
-    params = request["params"]
-    pos = params["position"]
-    path = path_from_uri(params["textDocument"]["uri"])
-    source = open(path).read()
-    if len(source.split("\n")[pos["line"]]) < pos["character"]:
-        return {}
-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
-                             column=pos["character"])
-
-    usages = script.usages()
-    if len(usages) == 0:
-        return {}
-
-    refs = []
-    for u in usages:
-        if u.is_definition():
-            continue
-        refs.append({
-            "uri": "file://" + u.module_path,
+    def serve_definition(self, request):
+        params = request["params"]
+        pos = params["position"]
+        path = self.path_from_uri(params["textDocument"]["uri"])
+        source = open(path).read()
+        if len(source.split("\n")[pos["line"]]) < pos["character"]:
+            return {}
+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
+                                 column=pos["character"])
+
+        defs = script.goto_definitions()
+        assigns = script.goto_assignments()
+        d = None
+        if len(defs) > 0:
+            d = defs[0]
+        elif len(assigns) > 0:
+            # TODO(renfred): figure out if this works in all cases.
+            d = assigns[0]
+        if d is None: return {}
+
+        return {
+            "uri": "file://" + d.module_path,
             "range": {
                 "start": {
-                    "line": u.line-1,
-                    "character": u.column,
+                    "line": d.line-1,
+                    "character": d.column,
                 },
                 "end": {
-                    "line": u.line-1,
-                    "character": u.column,
+                    "line": d.line-1,
+                    "character": d.column,
                 }
             }
-        })
-    return refs
+        }
+
+    def serve_references(self, request):
+        params = request["params"]
+        pos = params["position"]
+        path = self.path_from_uri(params["textDocument"]["uri"])
+        source = open(path).read()
+        if len(source.split("\n")[pos["line"]]) < pos["character"]:
+            return {}
+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,
+                                 column=pos["character"])
+
+        usages = script.usages()
+        if len(usages) == 0:
+            return {}
+
+        refs = []
+        for u in usages:
+            if u.is_definition():
+                continue
+            refs.append({
+                "uri": "file://" + u.module_path,
+                "range": {
+                    "start": {
+                        "line": u.line-1,
+                        "character": u.column,
+                    },
+                    "end": {
+                        "line": u.line-1,
+                        "character": u.column,
+                    }
+                }
+            })
+        return refs
 
 def main():
-    log("Reading on stdin, writing on stdout")
-    read_messages()
+    parser = argparse.ArgumentParser(description="")
+    parser.add_argument("--mode", default="stdio", help="communication (stdio|tcp)")
+
+    args = parser.parse_args()
+    rw = None
+    if args.mode == "stdio":
+        log("Reading on stdin, writing on stdout")
+        rw = StdIOReadWriter()
+    elif args.mode == "tcp":
+        log("Accepting TCP connections on {}")
+    server = LangServer(conn=rw)
+    server.serve()
 
 if __name__ == "__main__":
     main()

{'commit': 'diff --git a/langserver/langserver.py b/langserver/langserver.py\nindex f5d1960..0e1eb7b 100644\n--- a/langserver/langserver.py\n+++ b/langserver/langserver.py\n@@ -1,53 +1,115 @@\n import sys\n import json\n import jedi\n-\n-class JSONRPC2Error(Exception):\n-    pass\n+import argparse\n+from abc import ABC, abstractmethod\n \n def log(*args, **kwargs):\n     print(*args, file=sys.stderr, **kwargs)\n \n-def path_from_uri(uri):\n-    _, path = uri.split("file://", 1)\n-    return path\n+class JSONRPC2Error(Exception):\n+    pass\n+\n+class ReadWriter(ABC):\n+    @abstractmethod\n+    def readline(self, size):\n+        pass\n+\n+    @abstractmethod\n+    def read(self, size):\n+        pass\n+\n+    @abstractmethod\n+    def write(self, size):\n+        pass\n+\n+class StdIOReadWriter(ReadWriter):\n+    def readline(self, *args):\n+        return sys.stdin.readline(*args)\n+\n+    def read(self, *args):\n+        return sys.stdin.read(*args)\n+\n+    def write(self, out):\n+        sys.stdout.write(out)\n+        sys.stdout.flush()\n+\n+class SocketReadWriter(ReadWriter):\n+    def __init__(self, socket):\n+        self.socket = socket\n+        self.buffer = ""\n+\n+    def readline(self, size=None):\n+        buffering = True\n+        while buffering:\n+            if "\\n" in self.buffer:\n+                line, self.buffer = buffer.split("\\n", 1)\n+                if size:\n+                    line, self.buffer = line[:size], self.buffer + line[size:]\n+                return line\n+            else:\n+                next = socket.recv(4096)\n+                if not next:\n+                    buffering = False\n+                else:\n+                    self.buffer += more\n+\n+    def read(self, size):\n+        if len(self.buffer) >= size:\n+            out, self.buffer = self.buffer[:size], self.buffer[size:]\n+        recv_size = size - len(self.buffer)\n+        next = socket.recv(recv_size)\n+        out, self.buffer = self.buffer + next, self.buffer\n+        return out\n+\n+class JSONRPC2Server:\n+    def __init__(self, conn=None):\n+        self.conn = conn\n+\n+    def handle(self, id, request):\n+        pass\n+\n+    def _read_header_content_length(self, line):\n+        if len(line) < 2 or line[-2:] != "\\r\\n":\n+            raise JSONRPC2Error("Line endings must be \\\\r\\\\n")\n+        if line.startswith("Content-Length: "):\n+            _, value = line.split("Content-Length: ")\n+            value = value.strip()\n+            try:\n+                return int(value)\n+            except ValueError:\n+                raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))\n+\n+    def write_response(self, id, result):\n+        body = {\n+            "jsonrpc": "2.0",\n+            "id": id,\n+            "result": result,\n+        }\n+        body = json.dumps(body, separators=(",", ":"))\n+        content_length = len(body)\n+        response = (\n+            "Content-Length: {}\\r\\n"\n+            "Content-Type: application/vscode-jsonrpc; charset=utf8\\r\\n\\r\\n"\n+            "{}".format(content_length, body))\n+        self.conn.write(response)\n+        log("RESPONSE: ", response)\n+\n+    def serve(self):\n+        while True:\n+            line = self.conn.readline()\n+            length = self._read_header_content_length(line)\n+            body = self.conn.read(length+2) # TODO(renfred): why the off-by-two?\n+            request = json.loads(body)\n+            self.handle(id, request)\n+\n+class LangServer(JSONRPC2Server):\n+    def handle(self, id, request):\n+        log("REQUEST: ", id, request)\n+        resp = ""\n \n-def read_header_content_length(line):\n-    if len(line) < 2 or line[-2:] != "\\r\\n":\n-        raise JSONRPC2Error("Line endings must be \\\\r\\\\n")\n-    if line.startswith("Content-Length: "):\n-        _, value = line.split("Content-Length: ")\n-        value = value.strip()\n-        try:\n-            return int(value)\n-        except ValueError:\n-            raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))\n-\n-def write_response(id, result):\n-    body = {\n-        "jsonrpc": "2.0",\n-        "id": id,\n-        "result": result,\n-    }\n-    body = json.dumps(body, separators=(",", ":"))\n-    content_length = len(body)\n-    response = (\n-        "Content-Length: {}\\r\\n"\n-        "Content-Type: application/vscode-jsonrpc; charset=utf8\\r\\n\\r\\n"\n-        "{}".format(content_length, body))\n-    sys.stdout.write(response)\n-    sys.stdout.flush()\n-    log("RESPONSE: ", response)\n-\n-def read_messages():\n-    while True:\n-        line = sys.stdin.readline()\n-        length = read_header_content_length(line)\n-        body = sys.stdin.read(length+2) # TODO(renfred): why the off-by-two?\n-        request = json.loads(body)\n-        log("REQUEST: ", length, request)\n         if request["method"] == "initialize":\n-            write_response(request["id"], {\n+            resp = {\n                 "capabilities": {\n                     # "textDocumentSync": 1,\n                     "hoverProvider": True,\n@@ -55,119 +117,133 @@ def read_messages():\n                     "referencesProvider": True,\n                     # "workspaceSymbolProvider": True TODO\n                 }\n-            })\n-\n+            }\n         elif request["method"] == "textDocument/hover":\n-            write_response(request["id"], handle_hover(request))\n-\n+            resp = self.serve_hover(request)\n         elif request["method"] == "textDocument/definition":\n-            write_response(request["id"], handle_definition(request))\n-\n+            resp = self.serve_definition(request)\n         elif request["method"] == "textDocument/references":\n-            write_response(request["id"], handle_references(request))\n-\n-def handle_hover(request):\n-    params = request["params"]\n-    pos = params["position"]\n-    path = path_from_uri(params["textDocument"]["uri"])\n-    source = open(path).read()\n-    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n-        return {}\n-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n-                             column=pos["character"])\n-\n-    defs, error = [], None\n-    try:\n-        defs = script.goto_definitions()\n-    except Exception as e:\n-        # TODO return these errors using JSONRPC properly. Doing it this way\n-        # initially for debugging purposes.\n-        error = "ERROR {}: {}".format(type(e), e)\n-    d = defs[0] if len(defs) > 0 else None\n-\n-    # TODO(renfred): better failure mode\n-    if d is None:\n-        value = error or "404 Not Found"\n+            resp = self.serve_references(request)\n+\n+        if resp:\n+            self.write_response(request["id"], resp)\n+\n+    def path_from_uri(self, uri):\n+        _, path = uri.split("file://", 1)\n+        return path\n+\n+    def serve_hover(self, request):\n+        params = request["params"]\n+        pos = params["position"]\n+        path = self.path_from_uri(params["textDocument"]["uri"])\n+        source = open(path).read()\n+        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n+            return {}\n+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n+                                 column=pos["character"])\n+\n+        defs, error = [], None\n+        try:\n+            defs = script.goto_definitions()\n+        except Exception as e:\n+            # TODO return these errors using JSONRPC properly. Doing it this way\n+            # initially for debugging purposes.\n+            error = "ERROR {}: {}".format(type(e), e)\n+        d = defs[0] if len(defs) > 0 else None\n+\n+        # TODO(renfred): better failure mode\n+        if d is None:\n+            value = error or "404 Not Found"\n+            return {\n+                "contents": [{"language": "markdown", "value": value}],\n+            }\n+\n+        hover_info = d.docstring() or d.description\n         return {\n-            "contents": [{"language": "markdown", "value": value}],\n+            # TODO(renfred): convert reStructuredText docstrings to markdown.\n+            "contents": [{"language": "markdown", "value": hover_info}],\n         }\n \n-    hover_info = d.docstring() or d.description\n-    return {\n-        # TODO(renfred): convert reStructuredText docstrings to markdown.\n-        "contents": [{"language": "markdown", "value": hover_info}],\n-    }\n-\n-def handle_definition(request):\n-    params = request["params"]\n-    pos = params["position"]\n-    path = path_from_uri(params["textDocument"]["uri"])\n-    source = open(path).read()\n-    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n-        return {}\n-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n-                             column=pos["character"])\n-\n-    defs = script.goto_definitions()\n-    assigns = script.goto_assignments()\n-    d = None\n-    if len(defs) > 0:\n-        d = defs[0]\n-    elif len(assigns) > 0:\n-        # TODO(renfred): figure out if this works in all cases.\n-        d = assigns[0]\n-    if d is None: return {}\n-\n-    return {\n-        "uri": "file://" + d.module_path,\n-        "range": {\n-            "start": {\n-                "line": d.line-1,\n-                "character": d.column,\n-            },\n-            "end": {\n-                "line": d.line-1,\n-                "character": d.column,\n-            }\n-        }\n-    }\n-\n-def handle_references(request):\n-    params = request["params"]\n-    pos = params["position"]\n-    path = path_from_uri(params["textDocument"]["uri"])\n-    source = open(path).read()\n-    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n-        return {}\n-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n-                             column=pos["character"])\n-\n-    usages = script.usages()\n-    if len(usages) == 0:\n-        return {}\n-\n-    refs = []\n-    for u in usages:\n-        if u.is_definition():\n-            continue\n-        refs.append({\n-            "uri": "file://" + u.module_path,\n+    def serve_definition(self, request):\n+        params = request["params"]\n+        pos = params["position"]\n+        path = self.path_from_uri(params["textDocument"]["uri"])\n+        source = open(path).read()\n+        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n+            return {}\n+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n+                                 column=pos["character"])\n+\n+        defs = script.goto_definitions()\n+        assigns = script.goto_assignments()\n+        d = None\n+        if len(defs) > 0:\n+            d = defs[0]\n+        elif len(assigns) > 0:\n+            # TODO(renfred): figure out if this works in all cases.\n+            d = assigns[0]\n+        if d is None: return {}\n+\n+        return {\n+            "uri": "file://" + d.module_path,\n             "range": {\n                 "start": {\n-                    "line": u.line-1,\n-                    "character": u.column,\n+                    "line": d.line-1,\n+                    "character": d.column,\n                 },\n                 "end": {\n-                    "line": u.line-1,\n-                    "character": u.column,\n+                    "line": d.line-1,\n+                    "character": d.column,\n                 }\n             }\n-        })\n-    return refs\n+        }\n+\n+    def serve_references(self, request):\n+        params = request["params"]\n+        pos = params["position"]\n+        path = self.path_from_uri(params["textDocument"]["uri"])\n+        source = open(path).read()\n+        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n+            return {}\n+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n+                                 column=pos["character"])\n+\n+        usages = script.usages()\n+        if len(usages) == 0:\n+            return {}\n+\n+        refs = []\n+        for u in usages:\n+            if u.is_definition():\n+                continue\n+            refs.append({\n+                "uri": "file://" + u.module_path,\n+                "range": {\n+                    "start": {\n+                        "line": u.line-1,\n+                        "character": u.column,\n+                    },\n+                    "end": {\n+                        "line": u.line-1,\n+                        "character": u.column,\n+                    }\n+                }\n+            })\n+        return refs\n \n def main():\n-    log("Reading on stdin, writing on stdout")\n-    read_messages()\n+    parser = argparse.ArgumentParser(description="")\n+    parser.add_argument("--mode", default="stdio", help="communication (stdio|tcp)")\n+\n+    args = parser.parse_args()\n+    rw = None\n+    if args.mode == "stdio":\n+        log("Reading on stdin, writing on stdout")\n+        rw = StdIOReadWriter()\n+    elif args.mode == "tcp":\n+        log("Accepting TCP connections on {}")\n+    server = LangServer(conn=rw)\n+    server.serve()\n \n if __name__ == "__main__":\n     main()\n', 'commit_subject': 'Refactor code, stub tcp implmentation', 'repo_name': 'sourcegraph/python-langserver', 'committer': {'name': 'Renfred Harper', 'email': '74a8a29e55eab83eeae0cf4070dc14740104f854@gmail.com', 'time_sec': '1478817896', 'tz_offset': '-480', 'date': {'seconds': '1478817896'}}, 'commit_tree': '66a2361944403505faf501b22823a20b84d985a4', 'commit_message': 'Refactor code, stub tcp implmentation\n', 'language_name': 'Shell', 'license': 'mit', 'hunks': '@@ -1,53 +1,115 @@\n import sys\n import json\n import jedi\n-\n-class JSONRPC2Error(Exception):\n-    pass\n+import argparse\n+from abc import ABC, abstractmethod\n \n def log(*args, **kwargs):\n     print(*args, file=sys.stderr, **kwargs)\n \n-def path_from_uri(uri):\n-    _, path = uri.split("file://", 1)\n-    return path\n+class JSONRPC2Error(Exception):\n+    pass\n+\n+class ReadWriter(ABC):\n+    @abstractmethod\n+    def readline(self, size):\n+        pass\n+\n+    @abstractmethod\n+    def read(self, size):\n+        pass\n+\n+    @abstractmethod\n+    def write(self, size):\n+        pass\n+\n+class StdIOReadWriter(ReadWriter):\n+    def readline(self, *args):\n+        return sys.stdin.readline(*args)\n+\n+    def read(self, *args):\n+        return sys.stdin.read(*args)\n+\n+    def write(self, out):\n+        sys.stdout.write(out)\n+        sys.stdout.flush()\n+\n+class SocketReadWriter(ReadWriter):\n+    def __init__(self, socket):\n+        self.socket = socket\n+        self.buffer = ""\n+\n+    def readline(self, size=None):\n+        buffering = True\n+        while buffering:\n+            if "\\n" in self.buffer:\n+                line, self.buffer = buffer.split("\\n", 1)\n+                if size:\n+                    line, self.buffer = line[:size], self.buffer + line[size:]\n+                return line\n+            else:\n+                next = socket.recv(4096)\n+                if not next:\n+                    buffering = False\n+                else:\n+                    self.buffer += more\n+\n+    def read(self, size):\n+        if len(self.buffer) >= size:\n+            out, self.buffer = self.buffer[:size], self.buffer[size:]\n+        recv_size = size - len(self.buffer)\n+        next = socket.recv(recv_size)\n+        out, self.buffer = self.buffer + next, self.buffer\n+        return out\n+\n+class JSONRPC2Server:\n+    def __init__(self, conn=None):\n+        self.conn = conn\n+\n+    def handle(self, id, request):\n+        pass\n+\n+    def _read_header_content_length(self, line):\n+        if len(line) < 2 or line[-2:] != "\\r\\n":\n+            raise JSONRPC2Error("Line endings must be \\\\r\\\\n")\n+        if line.startswith("Content-Length: "):\n+            _, value = line.split("Content-Length: ")\n+            value = value.strip()\n+            try:\n+                return int(value)\n+            except ValueError:\n+                raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))\n+\n+    def write_response(self, id, result):\n+        body = {\n+            "jsonrpc": "2.0",\n+            "id": id,\n+            "result": result,\n+        }\n+        body = json.dumps(body, separators=(",", ":"))\n+        content_length = len(body)\n+        response = (\n+            "Content-Length: {}\\r\\n"\n+            "Content-Type: application/vscode-jsonrpc; charset=utf8\\r\\n\\r\\n"\n+            "{}".format(content_length, body))\n+        self.conn.write(response)\n+        log("RESPONSE: ", response)\n+\n+    def serve(self):\n+        while True:\n+            line = self.conn.readline()\n+            length = self._read_header_content_length(line)\n+            body = self.conn.read(length+2) # TODO(renfred): why the off-by-two?\n+            request = json.loads(body)\n+            self.handle(id, request)\n+\n+class LangServer(JSONRPC2Server):\n+    def handle(self, id, request):\n+        log("REQUEST: ", id, request)\n+        resp = ""\n \n-def read_header_content_length(line):\n-    if len(line) < 2 or line[-2:] != "\\r\\n":\n-        raise JSONRPC2Error("Line endings must be \\\\r\\\\n")\n-    if line.startswith("Content-Length: "):\n-        _, value = line.split("Content-Length: ")\n-        value = value.strip()\n-        try:\n-            return int(value)\n-        except ValueError:\n-            raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))\n-\n-def write_response(id, result):\n-    body = {\n-        "jsonrpc": "2.0",\n-        "id": id,\n-        "result": result,\n-    }\n-    body = json.dumps(body, separators=(",", ":"))\n-    content_length = len(body)\n-    response = (\n-        "Content-Length: {}\\r\\n"\n-        "Content-Type: application/vscode-jsonrpc; charset=utf8\\r\\n\\r\\n"\n-        "{}".format(content_length, body))\n-    sys.stdout.write(response)\n-    sys.stdout.flush()\n-    log("RESPONSE: ", response)\n-\n-def read_messages():\n-    while True:\n-        line = sys.stdin.readline()\n-        length = read_header_content_length(line)\n-        body = sys.stdin.read(length+2) # TODO(renfred): why the off-by-two?\n-        request = json.loads(body)\n-        log("REQUEST: ", length, request)\n         if request["method"] == "initialize":\n-            write_response(request["id"], {\n+            resp = {\n                 "capabilities": {\n                     # "textDocumentSync": 1,\n                     "hoverProvider": True,\n@@ -55,119 +117,133 @@ def read_messages():\n                     "referencesProvider": True,\n                     # "workspaceSymbolProvider": True TODO\n                 }\n-            })\n-\n+            }\n         elif request["method"] == "textDocument/hover":\n-            write_response(request["id"], handle_hover(request))\n-\n+            resp = self.serve_hover(request)\n         elif request["method"] == "textDocument/definition":\n-            write_response(request["id"], handle_definition(request))\n-\n+            resp = self.serve_definition(request)\n         elif request["method"] == "textDocument/references":\n-            write_response(request["id"], handle_references(request))\n-\n-def handle_hover(request):\n-    params = request["params"]\n-    pos = params["position"]\n-    path = path_from_uri(params["textDocument"]["uri"])\n-    source = open(path).read()\n-    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n-        return {}\n-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n-                             column=pos["character"])\n-\n-    defs, error = [], None\n-    try:\n-        defs = script.goto_definitions()\n-    except Exception as e:\n-        # TODO return these errors using JSONRPC properly. Doing it this way\n-        # initially for debugging purposes.\n-        error = "ERROR {}: {}".format(type(e), e)\n-    d = defs[0] if len(defs) > 0 else None\n-\n-    # TODO(renfred): better failure mode\n-    if d is None:\n-        value = error or "404 Not Found"\n+            resp = self.serve_references(request)\n+\n+        if resp:\n+            self.write_response(request["id"], resp)\n+\n+    def path_from_uri(self, uri):\n+        _, path = uri.split("file://", 1)\n+        return path\n+\n+    def serve_hover(self, request):\n+        params = request["params"]\n+        pos = params["position"]\n+        path = self.path_from_uri(params["textDocument"]["uri"])\n+        source = open(path).read()\n+        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n+            return {}\n+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n+                                 column=pos["character"])\n+\n+        defs, error = [], None\n+        try:\n+            defs = script.goto_definitions()\n+        except Exception as e:\n+            # TODO return these errors using JSONRPC properly. Doing it this way\n+            # initially for debugging purposes.\n+            error = "ERROR {}: {}".format(type(e), e)\n+        d = defs[0] if len(defs) > 0 else None\n+\n+        # TODO(renfred): better failure mode\n+        if d is None:\n+            value = error or "404 Not Found"\n+            return {\n+                "contents": [{"language": "markdown", "value": value}],\n+            }\n+\n+        hover_info = d.docstring() or d.description\n         return {\n-            "contents": [{"language": "markdown", "value": value}],\n+            # TODO(renfred): convert reStructuredText docstrings to markdown.\n+            "contents": [{"language": "markdown", "value": hover_info}],\n         }\n \n-    hover_info = d.docstring() or d.description\n-    return {\n-        # TODO(renfred): convert reStructuredText docstrings to markdown.\n-        "contents": [{"language": "markdown", "value": hover_info}],\n-    }\n-\n-def handle_definition(request):\n-    params = request["params"]\n-    pos = params["position"]\n-    path = path_from_uri(params["textDocument"]["uri"])\n-    source = open(path).read()\n-    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n-        return {}\n-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n-                             column=pos["character"])\n-\n-    defs = script.goto_definitions()\n-    assigns = script.goto_assignments()\n-    d = None\n-    if len(defs) > 0:\n-        d = defs[0]\n-    elif len(assigns) > 0:\n-        # TODO(renfred): figure out if this works in all cases.\n-        d = assigns[0]\n-    if d is None: return {}\n-\n-    return {\n-        "uri": "file://" + d.module_path,\n-        "range": {\n-            "start": {\n-                "line": d.line-1,\n-                "character": d.column,\n-            },\n-            "end": {\n-                "line": d.line-1,\n-                "character": d.column,\n-            }\n-        }\n-    }\n-\n-def handle_references(request):\n-    params = request["params"]\n-    pos = params["position"]\n-    path = path_from_uri(params["textDocument"]["uri"])\n-    source = open(path).read()\n-    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n-        return {}\n-    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n-                             column=pos["character"])\n-\n-    usages = script.usages()\n-    if len(usages) == 0:\n-        return {}\n-\n-    refs = []\n-    for u in usages:\n-        if u.is_definition():\n-            continue\n-        refs.append({\n-            "uri": "file://" + u.module_path,\n+    def serve_definition(self, request):\n+        params = request["params"]\n+        pos = params["position"]\n+        path = self.path_from_uri(params["textDocument"]["uri"])\n+        source = open(path).read()\n+        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n+            return {}\n+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n+                                 column=pos["character"])\n+\n+        defs = script.goto_definitions()\n+        assigns = script.goto_assignments()\n+        d = None\n+        if len(defs) > 0:\n+            d = defs[0]\n+        elif len(assigns) > 0:\n+            # TODO(renfred): figure out if this works in all cases.\n+            d = assigns[0]\n+        if d is None: return {}\n+\n+        return {\n+            "uri": "file://" + d.module_path,\n             "range": {\n                 "start": {\n-                    "line": u.line-1,\n-                    "character": u.column,\n+                    "line": d.line-1,\n+                    "character": d.column,\n                 },\n                 "end": {\n-                    "line": u.line-1,\n-                    "character": u.column,\n+                    "line": d.line-1,\n+                    "character": d.column,\n                 }\n             }\n-        })\n-    return refs\n+        }\n+\n+    def serve_references(self, request):\n+        params = request["params"]\n+        pos = params["position"]\n+        path = self.path_from_uri(params["textDocument"]["uri"])\n+        source = open(path).read()\n+        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n+            return {}\n+        script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n+                                 column=pos["character"])\n+\n+        usages = script.usages()\n+        if len(usages) == 0:\n+            return {}\n+\n+        refs = []\n+        for u in usages:\n+            if u.is_definition():\n+                continue\n+            refs.append({\n+                "uri": "file://" + u.module_path,\n+                "range": {\n+                    "start": {\n+                        "line": u.line-1,\n+                        "character": u.column,\n+                    },\n+                    "end": {\n+                        "line": u.line-1,\n+                        "character": u.column,\n+                    }\n+                }\n+            })\n+        return refs\n \n def main():\n-    log("Reading on stdin, writing on stdout")\n-    read_messages()\n+    parser = argparse.ArgumentParser(description="")\n+    parser.add_argument("--mode", default="stdio", help="communication (stdio|tcp)")\n+\n+    args = parser.parse_args()\n+    rw = None\n+    if args.mode == "stdio":\n+        log("Reading on stdin, writing on stdout")\n+        rw = StdIOReadWriter()\n+    elif args.mode == "tcp":\n+        log("Accepting TCP connections on {}")\n+    server = LangServer(conn=rw)\n+    server.serve()\n \n if __name__ == "__main__":\n     main()\n', 'addition_count': 216, 'deletion_count': 140, 'src_file': 'a/langserver/langserver.py', 'tgt_file': 'b/langserver/langserver.py', 'file_extension': '.py', 'before_file': 'import sys\nimport json\nimport jedi\n\nclass JSONRPC2Error(Exception):\n    pass\n\ndef log(*args, **kwargs):\n    print(*args, file=sys.stderr, **kwargs)\n\ndef path_from_uri(uri):\n    _, path = uri.split("file://", 1)\n    return path\n\ndef read_header_content_length(line):\n    if len(line) < 2 or line[-2:] != "\\r\\n":\n        raise JSONRPC2Error("Line endings must be \\\\r\\\\n")\n    if line.startswith("Content-Length: "):\n        _, value = line.split("Content-Length: ")\n        value = value.strip()\n        try:\n            return int(value)\n        except ValueError:\n            raise JSONRPC2Error("Invalid Content-Length header: {}".format(value))\n\ndef write_response(id, result):\n    body = {\n        "jsonrpc": "2.0",\n        "id": id,\n        "result": result,\n    }\n    body = json.dumps(body, separators=(",", ":"))\n    content_length = len(body)\n    response = (\n        "Content-Length: {}\\r\\n"\n        "Content-Type: application/vscode-jsonrpc; charset=utf8\\r\\n\\r\\n"\n        "{}".format(content_length, body))\n    sys.stdout.write(response)\n    sys.stdout.flush()\n    log("RESPONSE: ", response)\n\ndef read_messages():\n    while True:\n        line = sys.stdin.readline()\n        length = read_header_content_length(line)\n        body = sys.stdin.read(length+2) # TODO(renfred): why the off-by-two?\n        request = json.loads(body)\n        log("REQUEST: ", length, request)\n        if request["method"] == "initialize":\n            write_response(request["id"], {\n                "capabilities": {\n                    # "textDocumentSync": 1,\n                    "hoverProvider": True,\n            *args, **kwargs)\n                    "referencesProvider": True,\n                    # "workspaceSymbolProvider": True TODO\n                }\n            })\n\n        elif request["method"] == "textDocument/hover":\n            write_response(request["id"], handle_hover(request))\n\n        elif request["method"] == "textDocument/definition":\n            write_response(request["id"], handle_definition(request))\n\n        elif request["method"] == "textDocument/references":\n            write_response(request["id"], handle_references(request))\n\ndef handle_hover(request):\n    params = request["params"]\n    pos = params["position"]\n    path = path_from_uri(params["textDocument"]["uri"])\n    source = open(path).read()\n    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n        return {}\n    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n                             column=pos["character"])\n\n    defs, error = [], None\n    try:\n        defs = script.goto_definitions()\n    except Exception as e:\n        # TODO return these errors using JSONRPC properly. Doing it this way\n        # initially for debugging purposes.\n        error = "ERROR {}: {}".format(type(e), e)\n    d = defs[0] if len(defs) > 0 else None\n\n    # TODO(renfred): better failure mode\n    if d is None:\n        value = error or "404 Not Found"\n        return {\n            "contents": [{"language": "markdown", "value": value}],\n        }\n\n    hover_info = d.docstring() or d.description\n    return {\n        # TODO(renfred): convert reStructuredText docstrings to markdown.\n        "contents": [{"language": "markdown", "value": hover_info}],\n    }\n\ndef handle_definition(request):\n    params = request["params"]\n    pos = params["position"]\n    path = path_from_uri(params["textDocument"]["uri"])\n    source = open(path).read()\n    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n        return {}\n    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n                             column=pos["character"])\n\n    defs = script.goto_definitions()\n    assigns = script.goto_assignments()\n    d = None\n    if len(defs) > 0:\n        d = defs[0]\n    elif len(assigns) > 0:\n        # TODO(renfred): figure out if this works in all cases.\n        d = assigns[0]\n    if d is None: return {}\n\n    return {\n        "uri": "file://" + d.module_path,\n        "range": {\n            "start": {\n                "line": d.line-1,\n                "character": d.column,\n            },\n            "end": {\n                "line": d.line-1,\n                "character": d.column,\n            }\n        }\n    }\n\ndef handle_references(request):\n    params = request["params"]\n    pos = params["position"]\n    path = path_from_uri(params["textDocument"]["uri"])\n    source = open(path).read()\n    if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n        return {}\n    script = jedi.api.Script(path=path, source=source, line=pos["line"]+1,\n                             column=pos["character"])\n\n    usages = script.usages()\n    if len(usages) == 0:\n        return {}\n\n    refs = []\n    for u in usages:\n        if u.is_definition():\n            continue\n        refs.append({\n            "uri": "file://" + u.module_path,\n            "range": {\n                "start": {\n                    "line": u.line-1,\n                    "character": u.column,\n                },\n                "end": {\n                    "line": u.line-1,\n                    "character": u.column,\n                }\n            }\n        })\n    return refs\n\ndef main():\n    log("Reading on stdin, writing on stdout")\n    read_messages()\n\nif __name__ == "__main__":\n    main()\n            if completion.type in [\'module\'\n                                   ] or not hasattr(completion, \'params\'):\n                return \'\'\n            return \'%s(%s)\' % (completion.name,\n                               \', \'.join(p.description\n                                         for p in completion.params if p))\n\n        def get_definition_type(definition):\n            definition.in_builtin_module\n            try:\n                if definition.type in [\'statement\'\n                                       ] and definition.name.isupper():\n                    return \'constant\'\n                basic_types = {\n                    \'module\': \'import\',\n                    \'instance\': \'variable\',\n                    \'statement\': \'value\',\n                    \'param\': \'variable\',\n                }\n                return basic_types.get(definition.type, definition.type)\n            except Exception:\n                return \'builtin\'\n\n        results = []\n        with opentracing.start_child_span(\n                parent_span, "accumulate_definitions"):\n            for definition in defs:\n                signature = definition.name\n                description = None\n                if definition.type in (\'class\', \'function\'):\n                    signature = generate_signature(definition)\n                    try:\n                        description = definition.docstring(raw=True).strip()\n                    except Exception:\n                        description = \'\'\n                    if not description and not hasattr(definition,\n                                                       \'get_line_code\'):\n                        # jedi returns an empty string for compiled objects\n                        description = definition.docstring().strip()\n                if definition.type == \'module\':\n                    try:\n                        signature = definition.full_name\n                        description = definition.docstring(raw=True).strip()\n                    except Exception:\n                        description = \'\'\n                    if not description and hasattr(definition,\n                                                   \'get_line_code\'):\n                        # jedi returns an empty string for compiled objects\n                        description = definition.docstring().strip()\n\n                def_type = get_definition_type(definition)\n                if def_type in (\'function\', \'method\'):\n                    signature = \'def \' + signature\n                elif def_type == \'class\':\n                    signature = \'class \' + signature\n                else:\n                    # TODO(keegan) vscode python uses the current word if\n                    # definition.name is empty\n                    signature = definition.name\n\n                # TODO(keegan) implement the rest of\n                # https://sourcegraph.com/github.com/DonJayamanne/pythonVSCode/-/blob/src/client/providers/hoverProvider.ts#L34\n                results.append({\n                    "language": "python",\n                    "value": signature,\n                })\n                if description:\n                    results.append(description)\n                elif definition.type == "param":\n                    results.append("parameter `" + definition.name + "`")\n                elif definition.type == "statement":\n                    results.append("variable `" + definition.name + "`")\n\n        return {"contents": results}\n\n    def serve_definition(self, request):\n        return list(\n            filter(None, (d["location"]\n                          for d in self.serve_x_definition(request))))\n\n    def serve_x_definition(self, request):\n        params = request["params"]\n        pos = params["position"]\n        path = path_from_uri(params["textDocument"]["uri"])\n        # will be useful for filtering out circular/useless definitions\n        pos["path"] = path\n        parent_span = request["span"]\n        source = self.fs.open(path, parent_span)\n        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n            return {}\n        script = self.new_script(\n            path=path,\n            source=source,\n            line=pos["line"] + 1,\n            column=pos["character"],\n            parent_span=parent_span)\n\n        results = []\n        defs = []\n        defs.extend(LangServer.goto_definitions(script, request))\n        defs.extend(LangServer.goto_assignments(script, request))\n        if not defs:\n            return results\n\n        for d in defs:\n            defining_module_path = d.module_path\n            defining_module = self.workspace.get_module_by_path(\n                defining_module_path)\n\n            if not defining_module and (not d.is_definition() or\n                                        d.line is None or d.column is None):\n                continue\n\n            symbol_locator = {"symbol": None, "location": None}\n\n            if defining_module and not defining_module.is_stdlib:\n                # the module path doesn\'t map onto the repository structure\n                # because we\'re not fully installing\n                # dependency packages, so don\'t include it in the symbol\n                # descriptor\n                filename = os.path.basename(defining_module_path)\n                symbol_name = ""\n                symbol_kind = ""\n                if d.description:\n                    symbol_name, symbol_kind = LangServer.name_and_kind(\n                        d.description)\n                symbol_locator["symbol"] = {\n                    "package": {\n                        "name": defining_module.qualified_name.split(".")[0],\n                    },\n                    "name": symbol_name,\n                    "container": defining_module.qualified_name,\n                    "kind": symbol_kind,\n                    "file": filename\n                }\n\n            elif defining_module and defining_module.is_stdlib:\n                rel_path = os.path.relpath(defining_module_path,\n                                           self.workspace.PYTHON_PATH)\n                filename = os.path.basename(defining_module_path)\n                symbol_name = ""\n                symbol_kind = ""\n                if d.description:\n                    symbol_name, symbol_kind = LangServer.name_and_kind(\n                        d.description)\n                symbol_locator["symbol"] = {\n                    "package": {\n                        "name": "cpython",\n                    },\n                    "name": symbol_name,\n                    "container": defining_module.qualified_name,\n                    "kind": symbol_kind,\n                    "path": os.path.join(GlobalConfig.STDLIB_SRC_PATH,\n                                         rel_path),\n                    "file": filename\n                }\n\n            if (d.is_definition() and\n                    d.line is not None and d.column is not None):\n                location = {\n                    # TODO(renfred) determine why d.module_path is empty.\n                    "uri": "file://" + (d.module_path or path),\n                    "range": {\n                        "start": {\n                            "line": d.line - 1,\n                            "character": d.column,\n                        },\n                        "end": {\n                            "line": d.line - 1,\n                            "character": d.column + len(d.name),\n                        },\n                    },\n                }\n                # add a position hint in case this eventually gets passed to an\n                # operation that could use it\n                if symbol_locator["symbol"]:\n                    symbol_locator["symbol"]["position"] = location["range"][\n                        "start"]\n\n                # set the full location if the definition is in this workspace\n                if not defining_module or not defining_module.is_external:\n                    symbol_locator["location"] = location\n\n            results.append(symbol_locator)\n\n        unique_results = []\n        for result in results:\n            if result not in unique_results:\n                unique_results.append(result)\n\n        # if there\'s more than one definition, go ahead and remove the ones\n        # that are the same as the input position\n        if len(unique_results) > 1:\n            unique_results = [\n                ur for ur in unique_results\n                if not LangServer.is_circular(pos, ur["location"])\n            ]\n        return unique_results\n\n    @staticmethod\n    def is_circular(reference, definition):\n        """Takes a reference location and a definition location, and determines\n        whether they\'re the same (and hence useless).\n\n        We need to do this because getting the definition of an import\n        (using Jedi\'s goto_assignments method) sometimes returns that\n        same import. We filter out such cases and fall back on\n        goto_definitions.\n        """\n        if not reference or not definition:\n            return False\n        if reference["path"] != path_from_uri(definition["uri"]):\n            return False\n        if definition["range"]["start"]["line"] == 0 \\\n                and definition["range"]["end"]["line"] == 0 \\\n                and definition["range"]["start"]["character"] == 0:\n            # if the definition is at the very beginning of the same file,\n            # then we\'ve almost certainly jumped to the beginning of the\n            # module that we\'re already inside of\n            return True\n        if reference["line"] < definition["range"]["start"]["line"] \\\n                or reference["line"] > definition["range"]["end"]["line"]:\n            return False\n        if reference["character"] < definition["range"]["start"]["character"] \\\n                or reference["character"] > definition["range"]["end"]["character"]:\n            return False\n        return True\n\n    @staticmethod\n    def name_and_kind(description: str):\n        parts = description.split(" ")\n        if "=" in description:\n            name = parts[0]\n            kind = "="\n        else:\n            name = parts[1]\n            kind = parts[0]\n        return name, kind\n\n    def serve_references(self, request):\n        params = request["params"]\n        limit = params.get("limit", 200)\n        pos = params["position"]\n        path = path_from_uri(params["textDocument"]["uri"])\n        parent_span = request["span"]\n        source = self.fs.open(path, parent_span)\n        if len(source.split("\\n")[pos["line"]]) < pos["character"]:\n            return {}\n        script = self.new_script(\n            path=path,\n            source=source,\n            line=pos["line"] + 1,\n            column=pos["character"],\n            parent_span=parent_span)\n\n        usages = LangServer.usages(script, parent_span)\n        if len(usages) == 0:\n            return {}\n        if len(usages) > limit:\n            usages = usages[:limit]\n\n        refs = []\n        partial_initializer = {\n            "id": request["id"],\n            "patch": [{\n                "op": "add",\n                "path": "",\n                "value": []\n            }]\n        }\n        self.conn.send_notification(\n            "$/partialResult", partial_initializer) if self.streaming else None\n        json_patch = []\n        package_cache_path = os.path.abspath(self.workspace.PACKAGES_PATH)\n        for u in usages:\n            if u.is_definition():\n                continue\n            # filter out any results from files that are cached on the local fs\n            if u.module_path.startswith(package_cache_path):\n                continue\n            if u.module_path.startswith(self.workspace.PYTHON_PATH):\n                continue\n            location = {\n                "uri": "file://" + u.module_path,\n                "range": {\n                    "start": {\n                        "line": u.line - 1,\n                        "character": u.column,\n                    },\n                    "end": {\n                        "line": u.line - 1,\n                        "character": u.column + len(u.name),\n                    }\n                }\n            }\n            refs.append(location)\n            patch_op = {\n                "op": "add",\n                "path": "/-",\n                "value": location,\n            }\n            json_patch.append(patch_op)\n        partial_result = {\n            "id": request["id"],\n            "patch": json_patch,\n        }\n        self.conn.send_notification("$/partialResult",\n                                    partial_result) if self.streaming else None\n        return refs\n\n    def serve_x_references(self, request):\n        parent_span = request["span"]\n        params = request["params"]\n        limit = params.get("limit", 200)\n        symbol = params["query"]\n        if not symbol:\n            return []\n\n        package_name = symbol["container"].split(".")[0]\n        symbol_name = symbol["name"]\n\n        refs = []\n        partial_initializer = {\n            "id": request["id"],\n            "patch": [{\n                "op": "add",\n                "path": "",\n                "value": []\n            }]\n        }\n        self.conn.send_notification(\n            "$/partialResult", partial_initializer) if self.streaming else None\n        # We can\'t use Jedi to get x-refs because we only have a symbol descriptor,\n        # not a source location and source file. I tried fetching the package that\'s\n        # mentioned in the symbol descriptor and providing the source of the\n        # definition for Jedi, but that didn\'t seem to work, maybe because the fetched\n        # package is in the local FS cache, whereas the project files are handled by the remote FS.\n        # Anyway, unless we want to dig further into Jedi or rewrite the FS abstraction, it\'s\n        # easier to manually parse the source files and search the ASTs. We can still use Jedi to\n        # eliminate false positives by ensuring that each returned reference has a definition that\n        # matches the symbol descriptor.\n        for ref_batch in get_references(package_name, symbol_name, self.fs,\n                                        self.root_path, parent_span):\n            json_patch = []\n            for r in ref_batch:\n                location = {\n                    "uri": "file://" + r["path"],\n                    "range": {\n                        "start": {\n                            "line": r["line"],\n                            "character": r["character"],\n                        },\n                        "end": {\n                            "line": r["line"],\n                            "character": r["character"] + len(symbol_name)\n                        }\n                    }\n                }\n                ref_info = {\n                    "reference": location,\n                    "symbol": symbol,\n                }\n                refs.append(ref_info)\n                patch_op = {\n                    "op": "add",\n                    "path": "/-",\n                    "value": ref_info,\n                }\n                json_patch.append(patch_op)\n                if len(refs) >= limit:\n                    break\n            partial_result = {\n                "id": request["id"],\n                "patch": json_patch,\n            }\n            self.conn.send_notification(\n                "$/partialResult", partial_result) if self.streaming else None\n            if len(refs) >= limit:\n                break\n\n        return refs\n\n    def serve_symbols(self, request):\n        parent_span = request["span"]\n        params = request["params"]\n\n        if "symbol" in params:\n            return targeted_symbol(params["symbol"], self.fs, self.root_path,\n                                   parent_span)\n\n        if self.all_symbols is None:\n            self.all_symbols = workspace_symbols(self.fs, self.root_path,\n                                                 parent_span)\n\n        q, limit = params.get("query"), params.get("limit", 50)\n        symbols = ((sym.score(q), sym) for sym in self.all_symbols)\n        symbols = ((score, sym) for (score, sym) in symbols if score >= 0)\n        symbols = sorted(symbols, reverse=True, key=lambda x: x[0])[:limit]\n\n        result = [s.json_object() for (_, s) in symbols]\n        return result\n\n    def serve_document_symbols(self, request):\n        params = request["params"]\n        path = path_from_uri(params["textDocument"]["uri"])\n        parent_span = request["span"]\n        source = self.fs.open(path, parent_span)\n        return [s.json_object() for s in extract_symbols(source, path)]\n\n    def serve_x_packages(self, request):\n        return self.workspace.get_package_information(request["span"])\n\n    def serve_x_dependencies(self, request):\n        return self.workspace.get_dependencies(request["span"])\n\n    def serve_exit(self, request):\n        self.workspace.cleanup()\n        self.running = False\n\n    def serve_default(self, request):\n        raise JSONRPC2Error(\n            code=-32601,\n            message="method {} not found".format(request["method"]))\n\n\nclass JSONRPC2Error(Exception):\n    def __init__(self, code, message, data=None):\n        self.code = code\n        self.message = message\n        self.data = data\n\n\nclass ForkingTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):\n    pass\n\n\nclass LangserverTCPTransport(socketserver.StreamRequestHandler):\n    def handle(self):\n        conn = JSONRPC2Connection(TCPReadWriter(self.rfile, self.wfile))\n        s = LangServer(conn)\n        s.run()\n\n\ndef main():\n    import argparse\n\n    parser = argparse.ArgumentParser(description="")\n    parser.add_argument(\n        "--mode", default="stdio", help="communication (stdio|tcp)")\n    parser.add_argument(\n        "--addr", default=4389, help="server listen (tcp)", type=int)\n    parser.add_argument("--debug", action="store_true")\n    parser.add_argument("--lightstep_token", default=os.environ.get("LIGHTSTEP_ACCESS_TOKEN"))\n    parser.add_argument("--python_path")\n\n    args = parser.parse_args()\n\n    logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))\n\n    if args.python_path:\n        GlobalConfig.PYTHON_PATH = args.python_path\n\n    log.info("Setting Python path to %s", GlobalConfig.PYTHON_PATH)\n\n    # if args.lightstep_token isn\'t set, we\'ll fall back on the default no-op\n    # opentracing implementation\n    if args.lightstep_token:\n        opentracing.tracer = lightstep.Tracer(\n            component_name="python-langserver",\n            access_token=args.lightstep_token)\n\n    if args.mode == "stdio":\n        logging.info("Reading on stdin, writing on stdout")\n        s = LangServer(conn=ReadWriter(sys.stdin, sys.stdout))\n        s.run()\n    elif args.mode == "tcp":\n        host, addr = "0.0.0.0", args.addr\n        logging.info("Accepting TCP connections on %s:%s", host, addr)\n        ForkingTCPServer.allow_reuse_address = True\n        ForkingTCPServer.daemon_threads = True\n        s = ForkingTCPServer((host, addr), LangserverTCPTransport)\n        try:\n            s.serve_forever()\n        finally:\n            s.shutdown()\n\n\nif __name__ == "__main__":\n    main()\n', 'message': 'Refactor code, stub tcp implmentation\n', 'author_name': 'Renfred Harper', 'lang': 'py'}