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)
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
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