Пример #1
0
    def __init__(self, read_stream, write_stream):
        from robocorp_ls_core.lsp import LSPMessages

        self._config: IConfig = self._create_config()
        self._workspace: Optional[IWorkspace] = None
        self.root_uri = None
        self.watching_thread = None
        self.uri_workspace_mapper = {}

        self._jsonrpc_stream_reader = JsonRpcStreamReader(read_stream)
        self._jsonrpc_stream_writer = JsonRpcStreamWriter(write_stream)
        self._endpoint = Endpoint(self, self._jsonrpc_stream_writer.write)
        self._lsp_messages = LSPMessages(self._endpoint)

        self._shutdown = False
    def _initialize_reader_and_writer(self):
        assert self.writer is None
        from robocorp_ls_core.jsonrpc.streams import JsonRpcStreamWriter
        from robocorp_ls_core.jsonrpc.streams import JsonRpcStreamReader

        s = socket_module.socket(socket_module.AF_INET,
                                 socket_module.SOCK_STREAM)
        s.connect(("127.0.0.1", self.port))
        self._socket = s
        write_to = s.makefile("wb")
        read_from = s.makefile("rb")

        w = JsonRpcStreamWriter(write_to, sort_keys=True)
        r = JsonRpcStreamReader(read_from)
        self.writer = w
        self.reader = r

        self.reader_thread = threading.Thread(target=self.reader.listen,
                                              args=(self._on_read, ))
        self.reader_thread.daemon = True
        self.reader_thread.start()
Пример #3
0
def communicate_lang_server(write_to,
                            read_from,
                            language_server_client_class=None,
                            kwargs={}):
    if language_server_client_class is None:
        from robocorp_ls_core.unittest_tools.language_server_client import (
            LanguageServerClient, )

        language_server_client_class = LanguageServerClient

    from robocorp_ls_core.jsonrpc.streams import JsonRpcStreamWriter
    from robocorp_ls_core.jsonrpc.streams import JsonRpcStreamReader

    w = JsonRpcStreamWriter(write_to, sort_keys=True)
    r = JsonRpcStreamReader(read_from)

    language_server = language_server_client_class(w, r, **kwargs)
    try:
        yield language_server
    finally:
        if language_server.require_exit_messages:
            language_server.shutdown()
            language_server.exit()
Пример #4
0
    def get_robotframework_api_client(
            self) -> Optional[IRobotFrameworkApiClient]:
        self._check_in_main_thread()
        workspace = self.workspace
        assert (
            workspace
        ), "The workspace must be already set when getting the server api."

        server_process = self._server_process

        if server_process is not None:
            # If someone killed it, dispose of internal references
            # and create a new process.
            if not is_process_alive(server_process.pid):
                server_process = None
                self._dispose_server_process()

        if server_process is None:
            try:
                from robotframework_ls.options import Setup
                from robotframework_ls.server_api.client import RobotFrameworkApiClient
                from robotframework_ls.server_api.server__main__ import (
                    start_server_process, )
                from robocorp_ls_core.jsonrpc.streams import (
                    JsonRpcStreamWriter,
                    JsonRpcStreamReader,
                )

                args = []
                if Setup.options.verbose:
                    args.append("-" + "v" * int(Setup.options.verbose))
                if Setup.options.log_file:
                    log_id = _next_id()
                    # i.e.: use a log id in case we create more than one in the
                    # same session.
                    if log_id == 0:
                        args.append("--log-file=" + Setup.options.log_file +
                                    self._log_extension)
                    else:
                        args.append("--log-file=" + Setup.options.log_file +
                                    (".%s" % (log_id, )) + self._log_extension)

                python_exe = self._get_python_executable()
                environ = self._get_environ()

                self._used_python_executable = python_exe
                self._used_environ = environ

                server_process = start_server_process(args=args,
                                                      python_exe=python_exe,
                                                      env=environ)

                self._server_process = server_process

                write_to = server_process.stdin
                read_from = server_process.stdout
                w = JsonRpcStreamWriter(write_to, sort_keys=True)
                r = JsonRpcStreamReader(read_from)

                api = self._robotframework_api_client = RobotFrameworkApiClient(
                    w, r, server_process)

                log.debug(
                    "Initializing api... (this pid: %s, api pid: %s).",
                    os.getpid(),
                    server_process.pid,
                )
                api.initialize(
                    process_id=os.getpid(),
                    root_uri=workspace.root_uri,
                    workspace_folders=list({
                        "uri": folder.uri,
                        "name": folder.name
                    } for folder in workspace.iter_folders()),
                )

                config = self._config
                log.debug("Forwarding config to api...")
                if config is not None:
                    api.forward(
                        "workspace/didChangeConfiguration",
                        {"settings": config.get_full_settings()},
                    )

                # Open existing documents in the API.
                source: Optional[str]
                for document in workspace.iter_documents():
                    log.debug("Forwarding doc: %s to api...", document.uri)
                    try:
                        source = document.source
                    except Exception:
                        source = None

                    api.forward(
                        "textDocument/didOpen",
                        {
                            "textDocument": {
                                "uri": document.uri,
                                "version": document.version,
                                "text": source,
                            }
                        },
                    )

            except Exception as e:
                if server_process is None:
                    log.exception(
                        "Error starting robotframework server api (server_process=None)."
                    )
                else:
                    exitcode = server_process.poll()
                    if exitcode is not None:
                        # Note: only read() if the process exited.
                        log.exception(
                            "Error starting robotframework server api. Exit code: %s Base exception: %s. Stderr: %s",
                            exitcode,
                            e,
                            server_process.stderr.read(),
                        )
                    else:
                        log.exception(
                            "Error (%s) starting robotframework server api (still running). Base exception: %s.",
                            exitcode,
                            e,
                        )
                self._dispose_server_process()
            finally:
                if server_process is not None:
                    log.debug("Server api (%s) created pid: %s", self,
                              server_process.pid)
                else:
                    log.debug(
                        "server_process == None in get_robotframework_api_client()"
                    )

        return self._robotframework_api_client
    def _get_api_client(self) -> Any:
        self._check_thread()
        server_process = self._server_process

        if server_process is not None:
            # If someone killed it, dispose of internal references
            # and create a new process.
            if not is_process_alive(server_process.pid):
                server_process = None
                self._dispose_server_process()

        if server_process is None:
            try:
                from robocorp_code.options import Setup
                from robocorp_code.locators.server.locator__main__ import (
                    start_server_process, )
                from robocorp_ls_core.jsonrpc.streams import (
                    JsonRpcStreamWriter,
                    JsonRpcStreamReader,
                )
                from robocorp_code.locators.server.locator_client import (
                    LocatorsApiClient, )

                args = []
                if Setup.options.verbose:
                    args.append("-" + "v" * int(Setup.options.verbose))
                if Setup.options.log_file:
                    log_id = _next_id()
                    # i.e.: use a log id in case we create more than one in the
                    # same session.
                    if log_id == 0:
                        args.append("--log-file=" + Setup.options.log_file +
                                    self._log_extension)
                    else:
                        args.append("--log-file=" + Setup.options.log_file +
                                    (".%s" % (log_id, )) + self._log_extension)

                python_exe = sys.executable

                server_process = start_server_process(args=args,
                                                      python_exe=python_exe)

                self._server_process = server_process

                write_to = server_process.stdin
                read_from = server_process.stdout
                w = JsonRpcStreamWriter(write_to, sort_keys=True)
                r = JsonRpcStreamReader(read_from)

                api = self._locators_api_client = LocatorsApiClient(
                    w, r, server_process)

                log.debug(
                    "Initializing locators api... (this pid: %s, api pid: %s).",
                    os.getpid(),
                    server_process.pid,
                )
                api.initialize(process_id=os.getpid())

            except Exception as e:
                if server_process is None:
                    log.exception(
                        "Error starting locators server api (server_process=None)."
                    )
                else:
                    exitcode = server_process.poll()
                    if exitcode is not None:
                        # Note: only read() if the process exited.
                        log.exception(
                            "Error starting locators server api. Exit code: %s Base exception: %s. Stderr: %s",
                            exitcode,
                            e,
                            server_process.stderr.read(),
                        )
                    else:
                        log.exception(
                            "Error (%s) starting locators server api (still running). Base exception: %s.",
                            exitcode,
                            e,
                        )
                self._dispose_server_process()
            finally:
                if server_process is not None:
                    log.debug("Server api (%s) created pid: %s", self,
                              server_process.pid)
                else:
                    log.debug("server_process == None in _get_api_client()")

        return self._locators_api_client
Пример #6
0
class PythonLanguageServer(MethodDispatcher):
    """ Implementation of the Microsoft VSCode Language Server Protocol
    https://github.com/Microsoft/language-server-protocol/blob/master/versions/protocol-1-x.md
    
    Based on: https://github.com/palantir/python-language-server/blob/develop/pyls/python_ls.py
    """
    def __init__(self, read_stream, write_stream):
        from robocorp_ls_core.lsp import LSPMessages

        self._config: IConfig = self._create_config()
        self._workspace: Optional[IWorkspace] = None
        self.root_uri = None
        self.watching_thread = None
        self.uri_workspace_mapper = {}

        self._jsonrpc_stream_reader = JsonRpcStreamReader(read_stream)
        self._jsonrpc_stream_writer = JsonRpcStreamWriter(write_stream)
        self._endpoint = Endpoint(self, self._jsonrpc_stream_writer.write)
        self._lsp_messages = LSPMessages(self._endpoint)

        self._shutdown = False

    @property
    def workspace(self) -> Optional[IWorkspace]:
        return self._workspace

    @workspace.setter
    def workspace(self, workspace: IWorkspace) -> None:
        self._workspace = workspace
        self._config.set_workspace_dir(workspace.root_path)
        self._on_workspace_set(workspace)

    def _on_workspace_set(self, workspace: IWorkspace):
        pass

    @property  # i.e.: read-only
    def config(self) -> IConfig:
        return self._config

    def start(self):
        """Entry point for the server."""
        self._jsonrpc_stream_reader.listen(self._endpoint.consume)

    def m_shutdown(self, **_kwargs):
        self._shutdown = True

    def m_exit(self, **_kwargs):
        self._endpoint.shutdown()
        # If there's someone reading, we could deadlock here.
        self._jsonrpc_stream_reader.close()
        self._jsonrpc_stream_writer.close()

    def capabilities(self):
        return {}  # Subclasses should override for capabilities.

    def m_initialize(
        self,
        processId=None,
        rootUri=None,
        rootPath=None,
        initializationOptions=None,
        workspaceFolders=None,
        **_kwargs,
    ) -> dict:
        from robocorp_ls_core.basic import exit_when_pid_exists
        from robocorp_ls_core.lsp import WorkspaceFolder

        log.debug(
            "Language server initialized with:\n    processId: %s\n    rootUri: %s\n    rootPath: %s\n    initializationOptions: %s\n    workspaceFolders: %s",
            processId,
            rootUri,
            rootPath,
            initializationOptions,
            workspaceFolders,
        )
        if rootUri is None:
            rootUri = uris.from_fs_path(
                rootPath) if rootPath is not None else ""

        self.root_uri = rootUri
        if workspaceFolders:
            workspaceFolders = [WorkspaceFolder(**w) for w in workspaceFolders]

        self.workspace = self._create_workspace(rootUri,
                                                self._obtain_fs_observer(),
                                                workspaceFolders or [])

        if processId not in (None, -1, 0):
            exit_when_pid_exists(processId)

        # Get our capabilities
        return {"capabilities": self.capabilities()}

    def _obtain_fs_observer(self) -> IFSObserver:
        """
        The FSObserver is needed to keep the list of files updated in the
        Workspace (_VirtualFS).
        """
        try:
            return self._observer
        except AttributeError:
            from robocorp_ls_core import watchdog_wrapper

            self._observer = watchdog_wrapper.create_observer("dummy", None)
            return self._observer

    def _create_config(self) -> IConfig:
        raise NotImplementedError(f"Not implemented in: {self.__class__}")

    def _create_workspace(self, root_uri: str, fs_observer: IFSObserver,
                          workspace_folders) -> IWorkspace:
        from robocorp_ls_core.workspace import Workspace

        return Workspace(root_uri, fs_observer, workspace_folders)

    def m_initialized(self, **_kwargs):
        pass

    def lint(self, doc_uri, is_saved):
        raise NotImplementedError(
            "Subclasses must override (current class: %s)." %
            (self.__class__, ))

    def cancel_lint(self, doc_uri):
        raise NotImplementedError(
            "Subclasses must override (current class: %s)." %
            (self.__class__, ))

    def m_text_document__did_close(self, textDocument=None, **_kwargs) -> None:
        ws = self.workspace
        doc_uri = textDocument["uri"]
        if ws is not None:
            ws.remove_document(doc_uri)
        self.cancel_lint(doc_uri)

    def m_text_document__did_open(self, textDocument=None, **_kwargs) -> None:
        from robocorp_ls_core.lsp import TextDocumentItem

        ws = self.workspace
        if ws is not None:
            ws.put_document(TextDocumentItem(**textDocument))
        self.lint(textDocument["uri"], is_saved=True)

    def m_text_document__did_change(self,
                                    contentChanges=None,
                                    textDocument=None,
                                    **_kwargs):
        from robocorp_ls_core.lsp import TextDocumentItem
        from robocorp_ls_core.lsp import TextDocumentContentChangeEvent

        if contentChanges:
            text_document_item = TextDocumentItem(**textDocument)
            for change in contentChanges:
                try:
                    range = change.get("range", None)
                    range_length = change.get("rangeLength", 0)
                    text = change.get("text", "")
                    self.workspace.update_document(
                        text_document_item,
                        TextDocumentContentChangeEvent(
                            range=range, rangeLength=range_length, text=text),
                    )
                except:
                    log.exception(
                        "Error updating document: %s with changes: %s" %
                        (textDocument, contentChanges))
        self.lint(textDocument["uri"], is_saved=False)

    def m_text_document__did_save(self, textDocument=None, **_kwargs):
        self.lint(textDocument["uri"], is_saved=True)

    def m_workspace__did_change_configuration(self, settings=None) -> None:
        self.config.update(settings or {})

    def m_workspace__did_change_workspace_folders(self, event=None):
        """Adds/Removes folders from the workspace."""
        from robocorp_ls_core.lsp import WorkspaceFolder

        log.info(f"Workspace folders changed: {event}")

        added_folders = []
        removed_folders = []
        if event:
            added_folders = event.get("added", [])
            removed_folders = event.get("removed", [])

        for f_add in added_folders:
            self.workspace.add_folder(WorkspaceFolder(**f_add))

        for f_remove in removed_folders:
            self.workspace.remove_folder(f_remove["uri"])

    def m_workspace__did_change_watched_files(self, changes=None, **_kwargs):
        pass
Пример #7
0
    def _get_api_client(self) -> Any:
        with self._lock_api_client:
            server_process = self._server_process

            if server_process is not None:
                # If someone killed it, dispose of internal references
                # and create a new process.
                if not is_process_alive(server_process.pid):
                    self._dispose_server_process()

            if self._disposed:
                log.info("Robot Framework Interpreter server already disposed.")
                return None

            if server_process is None:
                try:
                    from robotframework_interactive.server.rf_interpreter__main__ import (
                        start_server_process,
                    )
                    from robocorp_ls_core.jsonrpc.streams import (
                        JsonRpcStreamWriter,
                        JsonRpcStreamReader,
                    )
                    from robotframework_interactive.server.rf_interpreter_client import (
                        RfInterpreterApiClient,
                    )

                    args = []
                    if self._verbose:
                        args.append("-" + "v" * int(self._verbose))
                    if self._base_log_file:
                        log_id = _next_id()
                        # i.e.: use a log id in case we create more than one in the
                        # same session.
                        if log_id == 0:
                            args.append(
                                "--log-file="
                                + self._base_log_file
                                + self._log_extension
                            )
                        else:
                            args.append(
                                "--log-file="
                                + self._base_log_file
                                + (".%s" % (log_id,))
                                + self._log_extension
                            )

                    python_exe = self._get_python_executable()
                    environ = self._get_environ()
                    connect_event = threading.Event()

                    s = create_server_socket(host="127.0.0.1", port=0)
                    import socket as socket_module

                    new_socket: Optional[socket_module.socket] = None
                    connect_event = threading.Event()

                    def wait_for_connection():
                        nonlocal new_socket
                        try:
                            s.settimeout(
                                DEFAULT_TIMEOUT if USE_TIMEOUTS else NO_TIMEOUT
                            )
                            s.listen(1)
                            new_socket, _addr = s.accept()
                            log.info("Connection accepted")
                        except:
                            log.exception("Server did not connect.")
                        finally:
                            connect_event.set()
                            s.close()

                    t = threading.Thread(target=wait_for_connection)
                    t.start()

                    # Now, we're listening, let's start up the interpreter to connect back.
                    _, port = s.getsockname()
                    args.append("--tcp")
                    args.append("--host")
                    args.append("127.0.0.1")
                    args.append("--port")
                    args.append(str(port))

                    cwd = os.path.dirname(self._filename)
                    if not os.path.isdir(cwd):
                        raise AssertionError(f"CWD passed is not a directory: {cwd}")

                    server_process = start_server_process(
                        args=args, python_exe=python_exe, env=environ, cwd=cwd
                    )

                    self._server_process = server_process

                    connect_event.wait()
                    if new_socket is None:
                        raise RuntimeError(
                            "Timed out while waiting for interpreter server to connect."
                        )

                    read_from = new_socket.makefile("rb")
                    write_to = new_socket.makefile("wb")

                    w = JsonRpcStreamWriter(write_to, sort_keys=True)
                    r = JsonRpcStreamReader(read_from)

                    api = self._rf_interpreter_api_client = RfInterpreterApiClient(
                        w,
                        r,
                        server_process,
                        on_interpreter_message=self._on_interpreter_message,
                    )

                    log.debug(
                        "Initializing rf interpreter api... (this pid: %s, api pid: %s).",
                        os.getpid(),
                        server_process.pid,
                    )
                    api.initialize(process_id=os.getpid())

                except Exception as e:
                    if server_process is None:
                        log.exception(
                            "Error starting rf interpreter server api (server_process=None)."
                        )
                    else:
                        exitcode = server_process.poll()
                        if exitcode is not None:
                            # Note: only read() if the process exited.
                            log.exception(
                                "Error starting rf interpreter server api. Exit code: %s Base exception: %s. Stderr: %s",
                                exitcode,
                                e,
                                server_process.stderr.read(),
                            )
                        else:
                            log.exception(
                                "Error (%s) starting rf interpreter server api (still running). Base exception: %s.",
                                exitcode,
                                e,
                            )
                    self._dispose_server_process()
                finally:
                    if server_process is not None:
                        log.debug(
                            "Server api (%s) created pid: %s", self, server_process.pid
                        )
                    else:
                        log.debug("server_process == None in _get_api_client()")

            return self._rf_interpreter_api_client
Пример #8
0
def writer(wfile):
    return JsonRpcStreamWriter(wfile, sort_keys=True)