def __init__(self, rx, tx) -> None:
        from robocorp_ls_core.pluginmanager import PluginManager
        from robotframework_ls.server_manager import ServerManager
        from robotframework_ls.ep_providers import DefaultConfigurationProvider
        from robotframework_ls.ep_providers import DefaultEndPointProvider
        from robotframework_ls.ep_providers import DefaultDirCacheProvider

        PythonLanguageServer.__init__(self, rx, tx)

        from robocorp_ls_core.cache import DirCache

        from robotframework_ls import robot_config

        home = robot_config.get_robotframework_ls_home()
        cache_dir = os.path.join(home, ".cache")

        log.debug(f"Cache dir: {cache_dir}")

        self._dir_cache = DirCache(cache_dir)

        self._pm = PluginManager()
        self._config_provider = DefaultConfigurationProvider(self.config)
        self._pm.set_instance(EPConfigurationProvider, self._config_provider)
        self._pm.set_instance(EPDirCacheProvider,
                              DefaultDirCacheProvider(self._dir_cache))
        self._pm.set_instance(EPEndPointProvider,
                              DefaultEndPointProvider(self._endpoint))
        self._server_manager = ServerManager(self._pm, language_server=self)
        self._lint_manager = _LintManager(self._server_manager,
                                          self._lsp_messages)
Ejemplo n.º 2
0
def server_manager(pm, config):
    from robotframework_ls.server_manager import ServerManager

    return ServerManager(pm, config=config)
class RobotFrameworkLanguageServer(PythonLanguageServer):
    def __init__(self, rx, tx) -> None:
        from robocorp_ls_core.pluginmanager import PluginManager
        from robotframework_ls.server_manager import ServerManager
        from robotframework_ls.ep_providers import DefaultConfigurationProvider
        from robotframework_ls.ep_providers import DefaultEndPointProvider
        from robotframework_ls.ep_providers import DefaultDirCacheProvider

        PythonLanguageServer.__init__(self, rx, tx)

        from robocorp_ls_core.cache import DirCache

        from robotframework_ls import robot_config

        home = robot_config.get_robotframework_ls_home()
        cache_dir = os.path.join(home, ".cache")

        log.debug(f"Cache dir: {cache_dir}")

        self._dir_cache = DirCache(cache_dir)

        self._pm = PluginManager()
        self._config_provider = DefaultConfigurationProvider(self.config)
        self._pm.set_instance(EPConfigurationProvider, self._config_provider)
        self._pm.set_instance(EPDirCacheProvider,
                              DefaultDirCacheProvider(self._dir_cache))
        self._pm.set_instance(EPEndPointProvider,
                              DefaultEndPointProvider(self._endpoint))
        self._server_manager = ServerManager(self._pm, language_server=self)
        self._lint_manager = _LintManager(self._server_manager,
                                          self._lsp_messages)

    @overrides(PythonLanguageServer._create_config)
    def _create_config(self) -> IConfig:
        from robotframework_ls.robot_config import RobotConfig

        return RobotConfig()

    @overrides(PythonLanguageServer._on_workspace_set)
    def _on_workspace_set(self, workspace: IWorkspace):
        PythonLanguageServer._on_workspace_set(self, workspace)
        self._server_manager.set_workspace(workspace)

    @overrides(PythonLanguageServer._create_workspace)
    def _create_workspace(self, root_uri, workspace_folders):
        from robotframework_ls.impl.robot_workspace import RobotWorkspace

        return RobotWorkspace(root_uri, workspace_folders, generate_ast=False)

    def m_initialize(
        self,
        processId=None,
        rootUri=None,
        rootPath=None,
        initializationOptions=None,
        workspaceFolders=None,
        **_kwargs,
    ) -> dict:
        ret = PythonLanguageServer.m_initialize(
            self,
            processId=processId,
            rootUri=rootUri,
            rootPath=rootPath,
            initializationOptions=initializationOptions,
            workspaceFolders=workspaceFolders,
            **_kwargs,
        )

        initialization_options = initializationOptions
        if initialization_options:
            plugins_dir = initialization_options.get("pluginsDir")
            if isinstance(plugins_dir, str):
                if not os.path.isdir(plugins_dir):
                    log.critical(f"Expected: {plugins_dir} to be a directory.")
                else:
                    self._pm.load_plugins_from(Path(plugins_dir))

        return ret

    @overrides(PythonLanguageServer.capabilities)
    def capabilities(self):
        from robocorp_ls_core.lsp import TextDocumentSyncKind

        server_capabilities = {
            "codeActionProvider": False,
            "codeLensProvider": {
                "resolveProvider":
                True,  # We may need to make this configurable
            },
            "completionProvider": {
                "resolveProvider": False  # We know everything ahead of time
            },
            # "semanticTokensProvider": {
            #     "legend": {
            #         "tokenTypes": ["keyword", "comment", "string", "number", "operator"],
            #         "tokenModifiers": ["declaration", "definition", "documentation",]
            #     },
            #     "range": True,
            #     "full": True
            # },
            "documentFormattingProvider": True,
            "documentHighlightProvider": True,
            "documentRangeFormattingProvider": False,
            "documentSymbolProvider": False,
            "definitionProvider": True,
            "executeCommandProvider": {
                "commands":
                ["robot.addPluginsDir", "robot.resolveInterpreter"]
            },
            "hoverProvider": True,
            "referencesProvider": False,
            "renameProvider": False,
            "foldingRangeProvider": True,
            # Note that there are no auto-trigger characters (there's no good
            # character as there's no `(` for parameters and putting it as a
            # space becomes a bit too much).
            "signatureHelpProvider": {
                "triggerCharacters": []
            },
            "textDocumentSync": {
                "change": TextDocumentSyncKind.INCREMENTAL,
                "save": {
                    "includeText": False
                },
                "openClose": True,
            },
            "workspace": {
                "workspaceFolders": {
                    "supported": True,
                    "changeNotifications": True
                }
            },
            "workspaceSymbolProvider": {
                "workDoneProgress": False
            },
        }
        log.info("Server capabilities: %s", server_capabilities)
        return server_capabilities

    def m_workspace__symbol(self, query: Optional[str] = None) -> Any:
        api_client = self._server_manager.get_workspace_symbols_api_client()
        if api_client is not None:
            ret = partial(self._threaded_workspace_symbol, api_client, query)
            ret = require_monitor(ret)
            return ret

        log.info("Unable to search workspace symbols (no api available).")
        return None  # Unable to get the api.

    def _threaded_workspace_symbol(
        self,
        api_client: IRobotFrameworkApiClient,
        query: Optional[str],
        monitor: IMonitor,
    ):
        from robocorp_ls_core.client_base import wait_for_message_matcher

        # Asynchronous completion.
        message_matcher: Optional[
            IIdMessageMatcher] = api_client.request_workspace_symbols(query)
        if message_matcher is None:
            log.debug("Message matcher for workspace symbols returned None.")
            return None

        if wait_for_message_matcher(
                message_matcher,
                api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    def m_workspace__execute_command(self, command=None, arguments=()) -> Any:
        if command == "robot.addPluginsDir":
            directory: str = arguments[0]
            assert os.path.isdir(
                directory), f"Expected: {directory} to be a directory."
            self._pm.load_plugins_from(Path(directory))
            return True

        elif command == "robot.resolveInterpreter":
            try:
                from robocorp_ls_core import uris
                from robotframework_ls.ep_resolve_interpreter import (
                    EPResolveInterpreter, )
                from robotframework_ls.ep_resolve_interpreter import IInterpreterInfo

                target_robot: str = arguments[0]

                for ep in self._pm.get_implementations(EPResolveInterpreter):
                    interpreter_info: IInterpreterInfo = ep.get_interpreter_info_for_doc_uri(
                        uris.from_fs_path(target_robot))
                    if interpreter_info is not None:
                        return {
                            "pythonExe":
                            interpreter_info.get_python_exe(),
                            "environ":
                            interpreter_info.get_environ(),
                            "additionalPythonpathEntries":
                            interpreter_info.get_additional_pythonpath_entries(
                            ),
                        }
            except:
                log.exception(
                    f"Error resolving interpreter. Args: {arguments}")

    @overrides(PythonLanguageServer.m_workspace__did_change_configuration)
    @log_and_silence_errors(log)
    def m_workspace__did_change_configuration(self, **kwargs):
        PythonLanguageServer.m_workspace__did_change_configuration(
            self, **kwargs)
        self._server_manager.set_config(self.config)

    # --- Methods to forward to the api

    @overrides(PythonLanguageServer.m_shutdown)
    @log_and_silence_errors(log)
    def m_shutdown(self, **kwargs):
        self._server_manager.shutdown()

        PythonLanguageServer.m_shutdown(self, **kwargs)

    @overrides(PythonLanguageServer.m_exit)
    @log_and_silence_errors(log)
    def m_exit(self, **kwargs):
        self._server_manager.exit()

        PythonLanguageServer.m_exit(self, **kwargs)

    def m_text_document__document_highlight(self, *args,
                                            **kwargs) -> Optional[list]:
        return []

    def m_text_document__formatting(self,
                                    textDocument=None,
                                    options=None) -> Optional[list]:
        source_format_rf_api_client = (
            self._server_manager.get_source_format_rf_api_client())
        if source_format_rf_api_client is None:
            log.info("Unable to get API for source format.")
            return []

        message_matcher = source_format_rf_api_client.request_source_format(
            text_document=textDocument, options=options)
        if message_matcher is None:
            raise RuntimeError(
                "Error requesting code formatting (message_matcher==None).")
        curtime = time.time()
        maxtime = curtime + DEFAULT_COMPLETIONS_TIMEOUT

        # i.e.: wait X seconds for the code format and bail out if we
        # can't get it.
        available_time = maxtime - time.time()
        if available_time <= 0:
            raise RuntimeError(
                "Code formatting timed-out (available_time <= 0).")

        if message_matcher.event.wait(available_time):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result
                else:
                    return []
        raise RuntimeError("Code formatting timed-out.")

    @overrides(PythonLanguageServer.m_text_document__did_close)
    def m_text_document__did_close(self, textDocument=None, **_kwargs):
        self._server_manager.forward(("api", "lint"), "textDocument/didClose",
                                     {"textDocument": textDocument})
        PythonLanguageServer.m_text_document__did_close(
            self, textDocument=textDocument, **_kwargs)

    @overrides(PythonLanguageServer.m_text_document__did_open)
    def m_text_document__did_open(self, textDocument=None, **_kwargs):
        self._server_manager.forward(("api", "lint"), "textDocument/didOpen",
                                     {"textDocument": textDocument})
        PythonLanguageServer.m_text_document__did_open(
            self, textDocument=textDocument, **_kwargs)

    @overrides(PythonLanguageServer.m_text_document__did_change)
    def m_text_document__did_change(self,
                                    contentChanges=None,
                                    textDocument=None,
                                    **_kwargs):
        self._server_manager.forward(
            ("api", "lint"),
            "textDocument/didChange",
            {
                "contentChanges": contentChanges,
                "textDocument": textDocument
            },
        )
        PythonLanguageServer.m_text_document__did_change(
            self,
            contentChanges=contentChanges,
            textDocument=textDocument,
            **_kwargs)

    @overrides(PythonLanguageServer.m_workspace__did_change_workspace_folders)
    def m_workspace__did_change_workspace_folders(self, event=None, **_kwargs):
        self._server_manager.forward(
            ("api", "lint"), "workspace/didChangeWorkspaceFolders", event)
        PythonLanguageServer.m_workspace__did_change_workspace_folders(
            self, event=event, **_kwargs)

    # --- Customized implementation

    @overrides(PythonLanguageServer.lint)
    def lint(self, doc_uri, is_saved) -> None:
        self._lint_manager.schedule_lint(doc_uri, is_saved)

    @overrides(PythonLanguageServer.cancel_lint)
    def cancel_lint(self, doc_uri) -> None:
        self._lint_manager.cancel_lint(doc_uri)

    def m_text_document__definition(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            ret = partial(self._threaded_document_definition, rf_api_client,
                          doc_uri, line, col)
            ret = require_monitor(ret)
            return ret

        log.info("Unable to find definition (no api available).")
        return None  # Unable to get the api.

    @log_and_silence_errors(log)
    def _threaded_document_definition(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        line: int,
        col: int,
        monitor: IMonitor,
    ) -> Optional[list]:

        from robocorp_ls_core.client_base import wait_for_message_matchers

        workspace = self.workspace
        if not workspace:
            error_msg = "Workspace is closed."
            log.critical(error_msg)
            raise RuntimeError(error_msg)

        document = workspace.get_document(doc_uri, accept_from_file=True)
        if document is None:
            error_msg = "Unable to find document (%s) for definition." % (
                doc_uri, )
            log.critical(error_msg)
            raise RuntimeError(error_msg)

        message_matchers: List[Optional[IIdMessageMatcher]] = [
            rf_api_client.request_find_definition(doc_uri, line, col)
        ]
        accepted_message_matchers = wait_for_message_matchers(
            message_matchers,
            monitor,
            rf_api_client.request_cancel,
            DEFAULT_COMPLETIONS_TIMEOUT,
        )
        message_matcher: IMessageMatcher
        for message_matcher in accepted_message_matchers:
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    def m_text_document__completion(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(self._threaded_document_completion, rf_api_client,
                           doc_uri, line, col)
            func = require_monitor(func)
            return func

        log.info("Unable to get completions (no api available).")
        return []

    @log_and_silence_errors(log, return_on_error=[])
    def _threaded_document_completion(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        line: int,
        col: int,
        monitor: IMonitor,
    ) -> list:
        from robotframework_ls.impl.completion_context import CompletionContext
        from robotframework_ls.impl import section_completions
        from robotframework_ls.impl import snippets_completions
        from robocorp_ls_core.client_base import wait_for_message_matchers

        ws = self.workspace
        if not ws:
            log.critical("Workspace must be set before returning completions.")
            return []

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for completions." %
                         (doc_uri, ))
            return []

        ctx = CompletionContext(document, line, col, config=self.config)
        completions = []

        # Asynchronous completion.
        message_matchers: List[Optional[IIdMessageMatcher]] = []
        message_matchers.append(
            rf_api_client.request_complete_all(doc_uri, line, col))

        # These run locally (no need to get from the server).
        completions.extend(section_completions.complete(ctx))
        completions.extend(snippets_completions.complete(ctx))

        accepted_message_matchers = wait_for_message_matchers(
            message_matchers,
            monitor,
            rf_api_client.request_cancel,
            DEFAULT_COMPLETIONS_TIMEOUT,
        )
        for message_matcher in accepted_message_matchers:
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    completions.extend(result)

        return completions

    def m_text_document__signature_help(self, **kwargs):
        """
        "params": {
            "textDocument": {
                "uri": "file:///x%3A/vscode-robot/local_test/Basic/resources/keywords.robot"
            },
            "position": {"line": 7, "character": 22},
            "context": {
                "isRetrigger": False,
                "triggerCharacter": " ",
                "triggerKind": 2,
            },
        },
        """
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(self._signature_help, rf_api_client, doc_uri, line,
                           col)
            func = require_monitor(func)
            return func

        log.info("Unable to get signature (no api available).")
        return []

    @log_and_silence_errors(log)
    def _signature_help(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        line: int,
        col: int,
        monitor: Monitor,
    ) -> Optional[dict]:
        from robocorp_ls_core.client_base import wait_for_message_matcher

        ws = self.workspace
        if not ws:
            log.critical(
                "Workspace must be set before getting signature help.")
            return None

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for completions." %
                         (doc_uri, ))
            return None

        # Asynchronous completion.
        message_matcher: Optional[
            IIdMessageMatcher] = rf_api_client.request_signature_help(
                doc_uri, line, col)
        if message_matcher is None:
            log.debug("Message matcher for signature returned None.")
            return None

        if wait_for_message_matcher(
                message_matcher,
                rf_api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    def m_text_document__hover(self, **kwargs):
        """
        "params": {
            "textDocument": {
                "uri": "file:///x%3A/vscode-robot/local_test/Basic/resources/keywords.robot"
            },
            "position": {"line": 7, "character": 22},            
        },
        """
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(self._hover, rf_api_client, doc_uri, line, col)
            func = require_monitor(func)
            return func

        log.info("Unable to get hover (no api available).")
        return []

    @log_and_silence_errors(log)
    def _hover(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        line: int,
        col: int,
        monitor: Monitor,
    ) -> Optional[dict]:
        from robocorp_ls_core.client_base import wait_for_message_matcher

        ws = self.workspace
        if not ws:
            log.critical("Workspace must be set before getting hover.")
            return None

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for hover." %
                         (doc_uri, ))
            return None

        # Asynchronous completion.
        message_matcher: Optional[
            IIdMessageMatcher] = rf_api_client.request_hover(
                doc_uri, line, col)
        if message_matcher is None:
            log.debug("Message matcher for hover returned None.")
            return None

        if wait_for_message_matcher(
                message_matcher,
                rf_api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    def m_text_document__folding_range(self, *args,
                                       **kwargs) -> Optional[list]:
        """
        "params": {
            "textDocument": {
                "uri": "file:///x%3A/vscode-robot/local_test/Basic/resources/keywords.robot"
            }
        },
        """

        doc_uri = kwargs["textDocument"]["uri"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(self._folding_range, rf_api_client, doc_uri)
            func = require_monitor(func)
            return func

        log.info("Unable to get folding range (no api available).")
        return []

    @log_and_silence_errors(log)
    def _folding_range(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        monitor: Monitor,
    ) -> Optional[dict]:
        from robocorp_ls_core.client_base import wait_for_message_matcher

        ws = self.workspace
        if not ws:
            log.critical("Workspace must be set before getting hover.")
            return None

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for folding range." %
                         (doc_uri, ))
            return None

        # Asynchronous completion.
        message_matcher: Optional[
            IIdMessageMatcher] = rf_api_client.request_folding_range(doc_uri)
        if message_matcher is None:
            log.debug("Message matcher for folding range returned None.")
            return None

        if wait_for_message_matcher(
                message_matcher,
                rf_api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    def m_text_document__code_lens(self, *args, **kwargs) -> Optional[list]:
        """
        "params": {
            "textDocument": {
                "uri": "file:///x%3A/vscode-robot/local_test/Basic/resources/keywords.robot"
            }
        },
        """

        doc_uri = kwargs["textDocument"]["uri"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(self._code_lens, rf_api_client, doc_uri)
            func = require_monitor(func)
            return func

        log.info("Unable to get folding range (no api available).")
        return []

    @log_and_silence_errors(log)
    def _code_lens(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        monitor: Monitor,
    ) -> Optional[dict]:
        from robocorp_ls_core.client_base import wait_for_message_matcher

        ws = self.workspace
        if not ws:
            log.critical("Workspace must be set before getting hover.")
            return None

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for code lens." %
                         (doc_uri, ))
            return None

        # Asynchronous completion.
        message_matcher: Optional[
            IIdMessageMatcher] = rf_api_client.request_code_lens(doc_uri)
        if message_matcher is None:
            log.debug("Message matcher for code lens returned None.")
            return None

        if wait_for_message_matcher(
                message_matcher,
                rf_api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None
    def __init__(self, rx, tx) -> None:
        from robocorp_ls_core.pluginmanager import PluginManager
        from robotframework_ls.rf_interactive_integration import _RfInterpretersManager
        from robotframework_ls.server_manager import ServerManager
        from robotframework_ls.ep_providers import DefaultConfigurationProvider
        from robotframework_ls.ep_providers import DefaultEndPointProvider
        from robotframework_ls.ep_providers import DefaultDirCacheProvider
        from robocorp_ls_core import watchdog_wrapper
        from robocorp_ls_core.remote_fs_observer_impl import RemoteFSObserver
        from robocorp_ls_core.options import Setup

        PythonLanguageServer.__init__(self, rx, tx)

        from robocorp_ls_core.cache import DirCache

        from robotframework_ls import robot_config

        home = robot_config.get_robotframework_ls_home()
        cache_dir = os.path.join(home, ".cache")

        log.debug(f"Cache dir: {cache_dir}")

        self._dir_cache = DirCache(cache_dir)

        self._pm = PluginManager()
        self._config_provider = DefaultConfigurationProvider(self.config)
        self._pm.set_instance(EPConfigurationProvider, self._config_provider)
        self._pm.set_instance(EPDirCacheProvider,
                              DefaultDirCacheProvider(self._dir_cache))
        self._pm.set_instance(EPEndPointProvider,
                              DefaultEndPointProvider(self._endpoint))
        self._rf_interpreters_manager = _RfInterpretersManager(
            self._endpoint, self._pm)

        watch_impl = os.environ.get("ROBOTFRAMEWORK_LS_WATCH_IMPL", "auto")
        if watch_impl not in ("watchdog", "fsnotify", "auto"):
            log.info(
                f"ROBOTFRAMEWORK_LS_WATCH_IMPL should be 'auto', 'watchdog' or 'fsnotify'. Found: {watch_impl} (falling back to auto)"
            )
            watch_impl = "auto"

        if watch_impl == "auto":
            # In auto mode we use watchdog for windows and fsnotify (polling)
            # for Linux and Mac. The reason for that is that on Linux and Mac
            # if big folders are watched the system may complain due to the
            # lack of resources, which may prevent the extension from working
            # properly.
            #
            # If users want to opt-in, they can change to watchdog (and
            # ideally install it to their env to get native extensions).
            if sys.platform == "win32":
                watch_impl = "watchdog"
            else:
                watch_impl = "fsnotify"

        self._fs_observer = watchdog_wrapper.create_remote_observer(
            watch_impl, (".py", ".libspec", "robot", ".resource"))
        remote_observer = typing.cast(RemoteFSObserver, self._fs_observer)
        log_file = Setup.options.log_file
        if not isinstance(log_file, str):
            log_file = None
        remote_observer.start_server(log_file=log_file)

        self._server_manager = ServerManager(self._pm, language_server=self)
        self._lint_manager = _LintManager(self._server_manager,
                                          self._lsp_messages)
class RobotFrameworkLanguageServer(PythonLanguageServer):
    def __init__(self, rx, tx) -> None:
        from robocorp_ls_core.pluginmanager import PluginManager
        from robotframework_ls.rf_interactive_integration import _RfInterpretersManager
        from robotframework_ls.server_manager import ServerManager
        from robotframework_ls.ep_providers import DefaultConfigurationProvider
        from robotframework_ls.ep_providers import DefaultEndPointProvider
        from robotframework_ls.ep_providers import DefaultDirCacheProvider
        from robocorp_ls_core import watchdog_wrapper
        from robocorp_ls_core.remote_fs_observer_impl import RemoteFSObserver
        from robocorp_ls_core.options import Setup

        PythonLanguageServer.__init__(self, rx, tx)

        from robocorp_ls_core.cache import DirCache

        from robotframework_ls import robot_config

        home = robot_config.get_robotframework_ls_home()
        cache_dir = os.path.join(home, ".cache")

        log.debug(f"Cache dir: {cache_dir}")

        self._dir_cache = DirCache(cache_dir)

        self._pm = PluginManager()
        self._config_provider = DefaultConfigurationProvider(self.config)
        self._pm.set_instance(EPConfigurationProvider, self._config_provider)
        self._pm.set_instance(EPDirCacheProvider,
                              DefaultDirCacheProvider(self._dir_cache))
        self._pm.set_instance(EPEndPointProvider,
                              DefaultEndPointProvider(self._endpoint))
        self._rf_interpreters_manager = _RfInterpretersManager(
            self._endpoint, self._pm)

        watch_impl = os.environ.get("ROBOTFRAMEWORK_LS_WATCH_IMPL", "auto")
        if watch_impl not in ("watchdog", "fsnotify", "auto"):
            log.info(
                f"ROBOTFRAMEWORK_LS_WATCH_IMPL should be 'auto', 'watchdog' or 'fsnotify'. Found: {watch_impl} (falling back to auto)"
            )
            watch_impl = "auto"

        if watch_impl == "auto":
            # In auto mode we use watchdog for windows and fsnotify (polling)
            # for Linux and Mac. The reason for that is that on Linux and Mac
            # if big folders are watched the system may complain due to the
            # lack of resources, which may prevent the extension from working
            # properly.
            #
            # If users want to opt-in, they can change to watchdog (and
            # ideally install it to their env to get native extensions).
            if sys.platform == "win32":
                watch_impl = "watchdog"
            else:
                watch_impl = "fsnotify"

        self._fs_observer = watchdog_wrapper.create_remote_observer(
            watch_impl, (".py", ".libspec", "robot", ".resource"))
        remote_observer = typing.cast(RemoteFSObserver, self._fs_observer)
        log_file = Setup.options.log_file
        if not isinstance(log_file, str):
            log_file = None
        remote_observer.start_server(log_file=log_file)

        self._server_manager = ServerManager(self._pm, language_server=self)
        self._lint_manager = _LintManager(self._server_manager,
                                          self._lsp_messages)

    def get_remote_fs_observer_port(self) -> Optional[int]:
        from robocorp_ls_core.remote_fs_observer_impl import RemoteFSObserver

        remote_observer = typing.cast(RemoteFSObserver, self._fs_observer)
        return remote_observer.port

    @overrides(PythonLanguageServer._create_config)
    def _create_config(self) -> IConfig:
        from robotframework_ls.robot_config import RobotConfig

        return RobotConfig()

    @overrides(PythonLanguageServer._on_workspace_set)
    def _on_workspace_set(self, workspace: IWorkspace):
        PythonLanguageServer._on_workspace_set(self, workspace)
        self._server_manager.set_workspace(workspace)

    @overrides(PythonLanguageServer._obtain_fs_observer)
    def _obtain_fs_observer(self) -> IFSObserver:
        return self._fs_observer

    @overrides(PythonLanguageServer._create_workspace)
    def _create_workspace(self, root_uri: str, fs_observer: IFSObserver,
                          workspace_folders):
        from robotframework_ls.impl.robot_workspace import RobotWorkspace

        return RobotWorkspace(root_uri,
                              fs_observer,
                              workspace_folders,
                              generate_ast=False)

    def m_initialize(
        self,
        processId=None,
        rootUri=None,
        rootPath=None,
        initializationOptions=None,
        workspaceFolders=None,
        **_kwargs,
    ) -> dict:
        # capabilities = _kwargs.get("capabilities", {})
        # text_document_capabilities = capabilities.get("textDocument", {})
        # document_symbol_capabilities = text_document_capabilities.get(
        #     "documentSymbol", {}
        # )
        # hierarchical_document_symbol_support = document_symbol_capabilities.get(
        #     "hierarchicalDocumentSymbolSupport", False
        # )
        # self._hierarchical_document_symbol_support = (
        #     hierarchical_document_symbol_support
        # )

        ret = PythonLanguageServer.m_initialize(
            self,
            processId=processId,
            rootUri=rootUri,
            rootPath=rootPath,
            initializationOptions=initializationOptions,
            workspaceFolders=workspaceFolders,
            **_kwargs,
        )

        initialization_options = initializationOptions
        if initialization_options:
            plugins_dir = initialization_options.get("pluginsDir")
            if isinstance(plugins_dir, str):
                if not os.path.isdir(plugins_dir):
                    log.critical(f"Expected: {plugins_dir} to be a directory.")
                else:
                    self._pm.load_plugins_from(Path(plugins_dir))

        return ret

    @overrides(PythonLanguageServer.capabilities)
    def capabilities(self):
        from robocorp_ls_core.lsp import TextDocumentSyncKind
        from robotframework_ls.impl.semantic_tokens import TOKEN_TYPES, TOKEN_MODIFIERS
        from robotframework_ls import commands

        server_capabilities = {
            "codeActionProvider": False,
            "codeLensProvider": {
                "resolveProvider": True
            },
            "completionProvider": {
                "resolveProvider": False  # We know everything ahead of time
            },
            "documentFormattingProvider": True,
            "documentHighlightProvider": False,
            "documentRangeFormattingProvider": False,
            "documentSymbolProvider": True,
            "definitionProvider": True,
            "executeCommandProvider": {
                "commands": [
                    "robot.addPluginsDir",
                    "robot.resolveInterpreter",
                    "robot.getLanguageServerVersion",
                    "robot.getInternalInfo",
                    "robot.listTests",
                ] + commands.ALL_SERVER_COMMANDS
            },
            "hoverProvider": True,
            "referencesProvider": False,
            "renameProvider": False,
            "foldingRangeProvider": True,
            # Note that there are no auto-trigger characters (there's no good
            # character as there's no `(` for parameters and putting it as a
            # space becomes a bit too much).
            "signatureHelpProvider": {
                "triggerCharacters": []
            },
            "textDocumentSync": {
                "change": TextDocumentSyncKind.INCREMENTAL,
                "save": {
                    "includeText": False
                },
                "openClose": True,
            },
            "workspace": {
                "workspaceFolders": {
                    "supported": True,
                    "changeNotifications": True
                }
            },
            "workspaceSymbolProvider": True,
            # The one below isn't accepted by lsp4j (it's still in LSP 3.15.0).
            # "workspaceSymbolProvider": {"workDoneProgress": False},
            "semanticTokensProvider": {
                "legend": {
                    "tokenTypes": TOKEN_TYPES,
                    "tokenModifiers": TOKEN_MODIFIERS,
                },
                "range": False,
                "full": True,
            },
        }
        log.info("Server capabilities: %s", server_capabilities)
        return server_capabilities

    def m_workspace__execute_command(self, command=None, arguments=()) -> Any:
        if command == "robot.addPluginsDir":
            directory: str = arguments[0]
            assert os.path.isdir(
                directory), f"Expected: {directory} to be a directory."
            self._pm.load_plugins_from(Path(directory))
            return True

        elif command == "robot.getInternalInfo":
            in_memory_docs = []
            workspace = self.workspace
            if workspace:
                for doc in workspace.iter_documents():
                    in_memory_docs.append({"uri": doc.uri})
            return {
                "settings": self.config.get_full_settings(),
                "inMemoryDocs": in_memory_docs,
                "processId": os.getpid(),
            }

        elif command == "robot.resolveInterpreter":
            try:
                from robocorp_ls_core import uris
                from robotframework_ls.ep_resolve_interpreter import (
                    EPResolveInterpreter, )
                from robotframework_ls.ep_resolve_interpreter import IInterpreterInfo

                target_robot: str = arguments[0]

                for ep in self._pm.get_implementations(EPResolveInterpreter):
                    interpreter_info: IInterpreterInfo = ep.get_interpreter_info_for_doc_uri(
                        uris.from_fs_path(target_robot))
                    if interpreter_info is not None:
                        return {
                            "pythonExe":
                            interpreter_info.get_python_exe(),
                            "environ":
                            interpreter_info.get_environ(),
                            "additionalPythonpathEntries":
                            interpreter_info.get_additional_pythonpath_entries(
                            ),
                        }
            except:
                log.exception(
                    f"Error resolving interpreter. Args: {arguments}")

        elif command == "robot.getLanguageServerVersion":
            return __version__

        elif command.startswith("robot.internal.rfinteractive."):
            return rf_interactive_integration.execute_command(
                command, self, self._rf_interpreters_manager, arguments)

        elif command == "robot.listTests":
            doc_uri = arguments[0]["uri"]

            rf_api_client = self._server_manager.get_others_api_client(doc_uri)
            if rf_api_client is not None:
                func = partial(
                    self._async_api_request,
                    rf_api_client,
                    "request_list_tests",
                    doc_uri=doc_uri,
                )
                func = require_monitor(func)
                return func

            log.info("Unable to list tests (no api available).")
            return []

    @overrides(PythonLanguageServer.m_workspace__did_change_configuration)
    @log_and_silence_errors(log)
    def m_workspace__did_change_configuration(self, **kwargs):
        PythonLanguageServer.m_workspace__did_change_configuration(
            self, **kwargs)
        self._server_manager.set_config(self.config)

    # --- Methods to forward to the api

    @overrides(PythonLanguageServer.m_shutdown)
    @log_and_silence_errors(log)
    def m_shutdown(self, **kwargs):
        try:
            from robocorp_ls_core.remote_fs_observer_impl import RemoteFSObserver

            remote_observer = typing.cast(RemoteFSObserver, self._fs_observer)
            remote_observer.dispose()
        except Exception:
            log.exception("Error disposing RemoteFSObserver.")
        self._server_manager.shutdown()

        PythonLanguageServer.m_shutdown(self, **kwargs)

    @overrides(PythonLanguageServer.m_exit)
    @log_and_silence_errors(log)
    def m_exit(self, **kwargs):
        self._server_manager.exit()

        PythonLanguageServer.m_exit(self, **kwargs)

    def m_text_document__formatting(self,
                                    textDocument=None,
                                    options=None) -> Optional[list]:
        doc_uri = textDocument["uri"]

        source_format_rf_api_client = self._server_manager.get_others_api_client(
            doc_uri)
        if source_format_rf_api_client is None:
            log.info("Unable to get API for source format.")
            return []

        message_matcher = source_format_rf_api_client.request_source_format(
            text_document=textDocument, options=options)
        if message_matcher is None:
            raise RuntimeError(
                "Error requesting code formatting (message_matcher==None).")
        curtime = time.time()
        maxtime = curtime + DEFAULT_COMPLETIONS_TIMEOUT

        # i.e.: wait X seconds for the code format and bail out if we
        # can't get it.
        available_time = maxtime - time.time()
        if available_time <= 0:
            raise RuntimeError(
                "Code formatting timed-out (available_time <= 0).")

        if message_matcher.event.wait(available_time):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result
                else:
                    return []
        raise RuntimeError("Code formatting timed-out.")

    @overrides(PythonLanguageServer.m_text_document__did_close)
    def m_text_document__did_close(self, textDocument=None, **_kwargs):
        self._server_manager.forward(
            ("api", "lint", "others"),
            "textDocument/didClose",
            {"textDocument": textDocument},
        )
        PythonLanguageServer.m_text_document__did_close(
            self, textDocument=textDocument, **_kwargs)

    @overrides(PythonLanguageServer.m_text_document__did_open)
    def m_text_document__did_open(self, textDocument=None, **_kwargs):
        self._server_manager.forward(
            ("api", "lint", "others"),
            "textDocument/didOpen",
            {"textDocument": textDocument},
        )
        PythonLanguageServer.m_text_document__did_open(
            self, textDocument=textDocument, **_kwargs)

    @overrides(PythonLanguageServer.m_text_document__did_change)
    def m_text_document__did_change(self,
                                    contentChanges=None,
                                    textDocument=None,
                                    **_kwargs):
        self._server_manager.forward(
            ("api", "lint", "others"),
            "textDocument/didChange",
            {
                "contentChanges": contentChanges,
                "textDocument": textDocument
            },
        )
        PythonLanguageServer.m_text_document__did_change(
            self,
            contentChanges=contentChanges,
            textDocument=textDocument,
            **_kwargs)

    @overrides(PythonLanguageServer.m_workspace__did_change_workspace_folders)
    def m_workspace__did_change_workspace_folders(self, event=None, **_kwargs):
        self._server_manager.forward(
            ("api", "lint", "others"),
            "workspace/didChangeWorkspaceFolders",
            {"event": event},
        )
        PythonLanguageServer.m_workspace__did_change_workspace_folders(
            self, event=event, **_kwargs)

    # --- Customized implementation

    @overrides(PythonLanguageServer.lint)
    def lint(self, doc_uri, is_saved) -> None:
        self._lint_manager.schedule_lint(doc_uri, is_saved)

    @overrides(PythonLanguageServer.cancel_lint)
    def cancel_lint(self, doc_uri) -> None:
        self._lint_manager.cancel_lint(doc_uri)

    def m_text_document__completion(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(self._threaded_document_completion, rf_api_client,
                           doc_uri, line, col)
            func = require_monitor(func)
            return func

        log.info("Unable to get completions (no api available).")
        return []

    @log_and_silence_errors(log, return_on_error=[])
    def _threaded_document_completion(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        doc_uri: str,
        line: int,
        col: int,
        monitor: IMonitor,
    ) -> list:
        from robotframework_ls.impl.completion_context import CompletionContext
        from robotframework_ls.impl import section_completions
        from robotframework_ls.impl import snippets_completions
        from robocorp_ls_core.client_base import wait_for_message_matchers

        ws = self.workspace
        if not ws:
            log.critical("Workspace must be set before returning completions.")
            return []

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for completions." %
                         (doc_uri, ))
            return []

        ctx = CompletionContext(document, line, col, config=self.config)
        completions = []

        # Asynchronous completion.
        message_matchers: List[Optional[IIdMessageMatcher]] = []
        message_matchers.append(
            rf_api_client.request_complete_all(doc_uri, line, col))

        # These run locally (no need to get from the server).
        completions.extend(section_completions.complete(ctx))
        completions.extend(snippets_completions.complete(ctx))

        accepted_message_matchers = wait_for_message_matchers(
            message_matchers,
            monitor,
            rf_api_client.request_cancel,
            DEFAULT_COMPLETIONS_TIMEOUT,
        )
        for message_matcher in accepted_message_matchers:
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    completions.extend(result)

        return completions

    @log_and_silence_errors(log)
    def _async_api_request(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        request_method_name: str,
        doc_uri: str,
        monitor: IMonitor,
        **kwargs,
    ):
        from robocorp_ls_core.client_base import wait_for_message_matcher

        func = getattr(rf_api_client, request_method_name)

        ws = self.workspace
        if not ws:
            log.critical("Workspace must be set before calling %s.",
                         request_method_name)
            return None

        document = ws.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for %s." %
                         (doc_uri, request_method_name))
            return None

        # Asynchronous completion.
        message_matcher: Optional[IIdMessageMatcher] = func(doc_uri, **kwargs)
        if message_matcher is None:
            log.debug("Message matcher for %s returned None.",
                      request_method_name)
            return None

        if wait_for_message_matcher(
                message_matcher,
                rf_api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    @log_and_silence_errors(log)
    def _async_api_request_no_doc(
        self,
        rf_api_client: IRobotFrameworkApiClient,
        request_method_name: str,
        monitor: Optional[IMonitor],
        **kwargs,
    ):
        from robocorp_ls_core.client_base import wait_for_message_matcher

        func = getattr(rf_api_client, request_method_name)

        # Asynchronous completion.
        message_matcher: Optional[IIdMessageMatcher] = func(**kwargs)
        if message_matcher is None:
            log.debug("Message matcher for %s returned None.",
                      request_method_name)
            return None

        if wait_for_message_matcher(
                message_matcher,
                rf_api_client.request_cancel,
                DEFAULT_COMPLETIONS_TIMEOUT,
                monitor,
        ):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    def m_text_document__definition(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(
                self._async_api_request,
                rf_api_client,
                "request_find_definition",
                doc_uri=doc_uri,
                line=line,
                col=col,
            )
            func = require_monitor(func)
            return func

        log.info("Unable to find definition (no api available).")
        return None

    def m_text_document__signature_help(self, **kwargs):
        """
        "params": {
            "textDocument": {
                "uri": "file:///x%3A/vscode-robot/local_test/Basic/resources/keywords.robot"
            },
            "position": {"line": 7, "character": 22},
            "context": {
                "isRetrigger": False,
                "triggerCharacter": " ",
                "triggerKind": 2,
            },
        },
        """
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(
                self._async_api_request,
                rf_api_client,
                "request_signature_help",
                doc_uri=doc_uri,
                line=line,
                col=col,
            )
            func = require_monitor(func)
            return func

        log.info("Unable to get signature (no api available).")
        return []

    def m_text_document__folding_range(self, **kwargs):
        """
        "params": {
            "textDocument": {
                "uri": "file:///x%3A/vscode-robot/local_test/Basic/resources/keywords.robot"
            },
        },
        """
        doc_uri = kwargs["textDocument"]["uri"]

        rf_api_client = self._server_manager.get_others_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(
                self._async_api_request,
                rf_api_client,
                "request_folding_range",
                doc_uri=doc_uri,
            )
            func = require_monitor(func)
            return func

        log.info("Unable to get folding range (no api available).")
        return []

    def m_text_document__code_lens(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]

        rf_api_client = self._server_manager.get_others_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(
                self._async_api_request,
                rf_api_client,
                "request_code_lens",
                doc_uri=doc_uri,
            )
            func = require_monitor(func)
            return func

        log.info("Unable to get code lens (no api available).")
        return []

    def m_code_lens__resolve(self, **kwargs):
        code_lens: CodeLensTypedDict = kwargs

        code_lens_command = code_lens.get("command")
        data = code_lens.get("data")
        if code_lens_command is None and isinstance(data, dict):
            # For the interactive shell we need to resolve the arguments.
            uri = data.get("uri")

            rf_api_client = self._server_manager.get_others_api_client(uri)
            if rf_api_client is not None:
                func = partial(
                    self._async_api_request_no_doc,
                    rf_api_client,
                    "request_resolve_code_lens",
                    code_lens=code_lens,
                )
                func = require_monitor(func)
                return func

            log.info("Unable to resolve code lens (no api available).")

        return code_lens

    def m_text_document__document_symbol(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]

        rf_api_client = self._server_manager.get_others_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(
                self._async_api_request,
                rf_api_client,
                "request_document_symbol",
                doc_uri=doc_uri,
            )
            func = require_monitor(func)
            return func

        log.info("Unable to get document symbol (no api available).")
        return []

    def m_text_document__hover(self, **kwargs):
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]
        rf_api_client = self._server_manager.get_regular_rf_api_client(doc_uri)
        if rf_api_client is not None:
            func = partial(
                self._async_api_request,
                rf_api_client,
                "request_hover",
                doc_uri=doc_uri,
                line=line,
                col=col,
            )
            func = require_monitor(func)
            return func

        log.info("Unable to compute hover (no api available).")
        return []

    def m_text_document__semantic_tokens__range(self,
                                                textDocument=None,
                                                range=None):
        raise RuntimeError("Not currently implemented!")

    def m_text_document__semantic_tokens__full(self, textDocument=None):
        doc_uri = textDocument["uri"]
        api = self._server_manager.get_others_api_client(doc_uri)
        if api is None:
            log.info(
                "Unable to get api client when computing semantic tokens (full)."
            )
            return {"resultId": None, "data": []}

        func = partial(
            self._async_api_request_no_doc,
            api,
            "request_semantic_tokens_full",
            text_document=textDocument,
        )
        func = require_monitor(func)
        return func

    def m_workspace__symbol(self, query: Optional[str] = None) -> Any:
        api = self._server_manager.get_others_api_client("")
        if api is None:
            log.info("Unable to search workspace symbols (no api available).")
            return None

        func = partial(
            self._async_api_request_no_doc,
            api,
            "request_workspace_symbols",
            query=query,
        )
        func = require_monitor(func)
        return func
Ejemplo n.º 6
0
class RobotFrameworkLanguageServer(PythonLanguageServer):
    def __init__(self, rx, tx):
        from robocode_ls_core.pluginmanager import PluginManager
        from robotframework_ls.server_manager import ServerManager
        from robotframework_ls.ep_providers import DefaultConfigurationProvider
        from robotframework_ls.ep_providers import DefaultEndPointProvider
        from robotframework_ls.ep_providers import DefaultDirCacheProvider

        PythonLanguageServer.__init__(self, rx, tx)

        from robocode_ls_core.cache import DirCache

        from robotframework_ls import robot_config

        home = robot_config.get_robotframework_ls_home()
        cache_dir = os.path.join(home, ".cache")

        log.debug(f"Cache dir: {cache_dir}")

        self._dir_cache = DirCache(cache_dir)

        self._pm = PluginManager()
        self._config_provider = DefaultConfigurationProvider(self.config)
        self._pm.set_instance(EPConfigurationProvider, self._config_provider)
        self._pm.set_instance(EPDirCacheProvider,
                              DefaultDirCacheProvider(self._dir_cache))
        self._pm.set_instance(EPEndPointProvider,
                              DefaultEndPointProvider(self._endpoint))
        self._server_manager = ServerManager(self._pm, language_server=self)

    @overrides(PythonLanguageServer._create_config)
    def _create_config(self) -> IConfig:
        from robotframework_ls.robot_config import RobotConfig

        return RobotConfig()

    @overrides(PythonLanguageServer._on_workspace_set)
    def _on_workspace_set(self, workspace: IWorkspace):
        PythonLanguageServer._on_workspace_set(self, workspace)
        self._server_manager.set_workspace(workspace)

    @overrides(PythonLanguageServer._create_workspace)
    def _create_workspace(self, root_uri, workspace_folders):
        from robotframework_ls.impl.robot_workspace import RobotWorkspace

        return RobotWorkspace(root_uri, workspace_folders, generate_ast=False)

    @overrides(PythonLanguageServer.capabilities)
    def capabilities(self):
        from robocode_ls_core.lsp import TextDocumentSyncKind

        server_capabilities = {
            "codeActionProvider": False,
            # "codeLensProvider": {
            #     "resolveProvider": False,  # We may need to make this configurable
            # },
            "completionProvider": {
                "resolveProvider": False  # We know everything ahead of time
            },
            "documentFormattingProvider": True,
            "documentHighlightProvider": False,
            "documentRangeFormattingProvider": False,
            "documentSymbolProvider": False,
            "definitionProvider": True,
            "executeCommandProvider": {
                "commands":
                ["robot.addPluginsDir", "robot.resolveInterpreter"]
            },
            "hoverProvider": False,
            "referencesProvider": False,
            "renameProvider": False,
            "foldingRangeProvider": False,
            # "signatureHelpProvider": {
            #     'triggerCharacters': ['(', ',', '=']
            # },
            "textDocumentSync": {
                "change": TextDocumentSyncKind.INCREMENTAL,
                "save": {
                    "includeText": False
                },
                "openClose": True,
            },
            "workspace": {
                "workspaceFolders": {
                    "supported": True,
                    "changeNotifications": True
                }
            },
        }
        log.info("Server capabilities: %s", server_capabilities)
        return server_capabilities

    def m_workspace__execute_command(self, command=None, arguments=()) -> Any:
        if command == "robot.addPluginsDir":
            directory: str = arguments[0]
            assert os.path.isdir(
                directory), f"Expected: {directory} to be a directory."
            self._pm.load_plugins_from(Path(directory))
            return True

        elif command == "robot.resolveInterpreter":
            try:
                from robocode_ls_core import uris
                from robotframework_ls.ep_resolve_interpreter import (
                    EPResolveInterpreter, )
                from robotframework_ls.ep_resolve_interpreter import IInterpreterInfo

                target_robot: str = arguments[0]

                for ep in self._pm.get_implementations(EPResolveInterpreter):
                    interpreter_info: IInterpreterInfo = ep.get_interpreter_info_for_doc_uri(
                        uris.from_fs_path(target_robot))
                    if interpreter_info is not None:
                        return {
                            "pythonExe":
                            interpreter_info.get_python_exe(),
                            "environ":
                            interpreter_info.get_environ(),
                            "additionalPythonpathEntries":
                            interpreter_info.get_additional_pythonpath_entries(
                            ),
                        }
            except:
                log.exception(
                    f"Error resolving interpreter. Args: {arguments}")

    @overrides(PythonLanguageServer.m_workspace__did_change_configuration)
    @log_and_silence_errors(log)
    def m_workspace__did_change_configuration(self, **kwargs):
        PythonLanguageServer.m_workspace__did_change_configuration(
            self, **kwargs)
        self._server_manager.set_config(self.config)

    # --- Methods to forward to the api

    @overrides(PythonLanguageServer.m_shutdown)
    @log_and_silence_errors(log)
    def m_shutdown(self, **kwargs):
        self._server_manager.shutdown()

        PythonLanguageServer.m_shutdown(self, **kwargs)

    @overrides(PythonLanguageServer.m_exit)
    @log_and_silence_errors(log)
    def m_exit(self, **kwargs):
        self._server_manager.exit()

        PythonLanguageServer.m_exit(self, **kwargs)

    def m_text_document__formatting(self,
                                    textDocument=None,
                                    options=None) -> Optional[list]:
        source_format_api = self._server_manager.get_source_format_api()
        message_matcher = source_format_api.request_source_format(
            text_document=textDocument, options=options)
        if message_matcher is None:
            raise RuntimeError(
                "Error requesting code formatting (message_matcher==None).")
        curtime = time.time()
        maxtime = curtime + DEFAULT_COMPLETIONS_TIMEOUT

        # i.e.: wait X seconds for the code format and bail out if we
        # can't get it.
        available_time = maxtime - time.time()
        if available_time <= 0:
            raise RuntimeError(
                "Code formatting timed-out (available_time <= 0).")

        if message_matcher.event.wait(available_time):
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result
                else:
                    return []
        raise RuntimeError("Code formatting timed-out.")

    @overrides(PythonLanguageServer.m_text_document__did_close)
    def m_text_document__did_close(self, textDocument=None, **_kwargs):
        self._server_manager.forward(("api", "lint"), "textDocument/didClose",
                                     {"textDocument": textDocument})
        PythonLanguageServer.m_text_document__did_close(
            self, textDocument=textDocument, **_kwargs)

    @overrides(PythonLanguageServer.m_text_document__did_open)
    def m_text_document__did_open(self, textDocument=None, **_kwargs):
        self._server_manager.forward(("api", "lint"), "textDocument/didOpen",
                                     {"textDocument": textDocument})
        PythonLanguageServer.m_text_document__did_open(
            self, textDocument=textDocument, **_kwargs)

    @overrides(PythonLanguageServer.m_text_document__did_change)
    def m_text_document__did_change(self,
                                    contentChanges=None,
                                    textDocument=None,
                                    **_kwargs):
        self._server_manager.forward(
            ("api", "lint"),
            "textDocument/didChange",
            {
                "contentChanges": contentChanges,
                "textDocument": textDocument
            },
        )
        PythonLanguageServer.m_text_document__did_change(
            self,
            contentChanges=contentChanges,
            textDocument=textDocument,
            **_kwargs)

    @overrides(PythonLanguageServer.m_workspace__did_change_workspace_folders)
    def m_workspace__did_change_workspace_folders(self, event=None, **_kwargs):
        self._server_manager.forward(
            ("api", "lint"), "workspace/didChangeWorkspaceFolders", event)
        PythonLanguageServer.m_workspace__did_change_workspace_folders(
            self, event=event, **_kwargs)

    # --- Customized implementation

    @overrides(PythonLanguageServer.lint)
    @basic.debounce(LINT_DEBOUNCE_S, keyed_by="doc_uri")
    def lint(self, doc_uri, is_saved) -> None:
        # Since we're debounced, the document may no longer be open
        try:
            lint_api = self._server_manager.get_lint_api(doc_uri)
            found = []
            diagnostics_msg = lint_api.lint(doc_uri)
            if diagnostics_msg:
                found = diagnostics_msg.get("result", [])
            self._lsp_messages.publish_diagnostics(doc_uri, found)
        except Exception:
            # Because it's debounced, we can't use the log_and_silence_errors decorator.
            log.exception("Error linting.")

    def m_text_document__definition(self, **kwargs) -> Optional[list]:
        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        workspace = self.workspace
        if not workspace:
            error_msg = "Workspace is closed."
            log.critical(error_msg)
            raise RuntimeError(error_msg)

        document = workspace.get_document(doc_uri, accept_from_file=True)
        if document is None:
            error_msg = "Unable to find document (%s) for definition." % (
                doc_uri, )
            log.critical(error_msg)
            raise RuntimeError(error_msg)

        api = self._server_manager.get_regular_api(doc_uri)

        message_matchers = [api.request_find_definition(doc_uri, line, col)]
        accepted_message_matchers = self._wait_for_message_matchers(
            message_matchers)
        message_matcher: IMessageMatcher
        for message_matcher in accepted_message_matchers:
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    return result

        return None

    @log_and_silence_errors(log, return_on_error=[])
    def m_text_document__completion(self, **kwargs):
        from robotframework_ls.impl.completion_context import CompletionContext
        from robotframework_ls.impl import section_completions
        from robotframework_ls.impl import snippets_completions

        doc_uri = kwargs["textDocument"]["uri"]
        # Note: 0-based
        line, col = kwargs["position"]["line"], kwargs["position"]["character"]

        document = self.workspace.get_document(doc_uri, accept_from_file=True)
        if document is None:
            log.critical("Unable to find document (%s) for completions." %
                         (doc_uri, ))
            return []

        api = self._server_manager.get_regular_api(doc_uri)

        ctx = CompletionContext(document, line, col, config=self.config)
        completions = []

        # Asynchronous completion.
        message_matchers = []
        message_matchers.append(api.request_complete_all(doc_uri, line, col))

        # These run locally (no need to get from the server).
        completions.extend(section_completions.complete(ctx))
        completions.extend(snippets_completions.complete(ctx))

        accepted_message_matchers = self._wait_for_message_matchers(
            message_matchers)
        for message_matcher in accepted_message_matchers:
            msg = message_matcher.msg
            if msg is not None:
                result = msg.get("result")
                if result:
                    completions.extend(result)

        return completions

    def _wait_for_message_matchers(self, message_matchers):
        accepted_message_matchers = []
        curtime = time.time()
        maxtime = curtime + DEFAULT_COMPLETIONS_TIMEOUT
        for message_matcher in message_matchers:
            if message_matcher is not None:
                # i.e.: wait X seconds and bail out if we can't get it.
                available_time = maxtime - time.time()
                if available_time <= 0:
                    available_time = 0.0001  # Wait at least a bit for each.

                if message_matcher.event.wait(available_time):
                    accepted_message_matchers.append(message_matcher)

        return accepted_message_matchers