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)
def test_load_plugins(): from pathlib import Path pm = PluginManager() p = Path(__file__).parent / "_resources" / "plugins" assert p.exists() assert pm.load_plugins_from(p) == 1 for impl in pm.get_implementations(EPFoo): assert impl.Foo() == "from_plugin"
def test_inject(): from robocode_ls_core.pluginmanager import inject pm = PluginManager() pm.register(EPFoo, FooImpl, keep_instance=True) @inject(foo=EPFoo) def m1(foo, pm): return foo assert m1(pm=pm) == pm.get_instance(EPFoo)
def test_inject_class(): from robocode_ls_core.pluginmanager import inject pm = PluginManager() pm.register(EPFoo, FooImpl, keep_instance=True) pm.register(EPBar, FooImpl, keep_instance=False) pm.register(EPBar, AnotherFooImpl, keep_instance=False) @inject(foo=EPFoo, foo2=[EPBar]) def m1(foo, foo2, pm): return foo, foo2 assert m1(pm=pm)[0] == pm.get_instance(EPFoo) assert len(m1(pm=pm)[1]) == 2
def test_resolve_interpreter( cases: CasesFixture, config_provider: IConfigProvider, rcc_conda_installed ) -> None: from robocode_ls_core.constants import NULL from robocode_vscode.plugins.resolve_interpreter import RobocodeResolveInterpreter from robocode_ls_core import uris from robocode_ls_core.pluginmanager import PluginManager from robotframework_ls.ep_providers import EPConfigurationProvider from robotframework_ls.ep_providers import EPEndPointProvider from pathlib import Path from robocode_vscode.plugins.resolve_interpreter import _CacheInfo _CacheInfo._cache_hit_files = 0 pm = PluginManager() pm.set_instance(EPConfigurationProvider, config_provider) pm.set_instance(EPEndPointProvider, NULL) resolve_interpreter = RobocodeResolveInterpreter(weak_pm=weakref.ref(pm)) path = cases.get_path( "custom_envs/simple-web-scraper/tasks/simple-web-scraper.robot" ) interpreter_info = resolve_interpreter.get_interpreter_info_for_doc_uri( uris.from_fs_path(path) ) assert interpreter_info assert os.path.exists(interpreter_info.get_python_exe()) assert interpreter_info.get_environ() additional_pythonpath_entries = interpreter_info.get_additional_pythonpath_entries() assert len(additional_pythonpath_entries) == 3 found = set() for v in additional_pythonpath_entries: p = Path(v) assert p.is_dir() found.add(p.name) assert found == {"variables", "libraries", "resources"} assert _CacheInfo._cache_hit_files == 0 assert _CacheInfo._cache_hit_interpreter == 0 interpreter_info = resolve_interpreter.get_interpreter_info_for_doc_uri( uris.from_fs_path(path) ) assert _CacheInfo._cache_hit_files == 2 assert _CacheInfo._cache_hit_interpreter == 1
def pm(): from robocode_ls_core.pluginmanager import PluginManager p = PluginManager() p.register(EPResolveInterpreter, ResolveInterpreterInTest) return p
def test_plugins(): from robocode_ls_core.pluginmanager import NotRegisteredError pm = PluginManager() pm.register(EPFoo, FooImpl, keep_instance=True) with pytest.raises(InstanceAlreadyRegisteredError): pm.register(EPFoo, AnotherFooImpl, keep_instance=True) foo = pm.get_instance(EPFoo) assert pm.get_instance(EPFoo) is foo assert pm[EPFoo] is foo assert pm["EPFoo"] is foo # It's only registered in a way where the instance is kept assert not pm.get_implementations(EPFoo) assert not pm.get_implementations(EPBar) with pytest.raises(NotRegisteredError): pm.get_instance(EPBar) pm.register(EPFoo, AnotherFooImpl, context="context2", keep_instance=True) assert len(list(pm.iter_existing_instances(EPFoo))) == 1 assert isinstance(pm.get_instance(EPFoo, context="context2"), AnotherFooImpl) assert len(list(pm.iter_existing_instances(EPFoo))) == 2 assert set(pm.iter_existing_instances(EPFoo)) == set( [pm.get_instance(EPFoo, context="context2"), pm.get_instance(EPFoo)]) # Request using a string. assert len(list(pm.iter_existing_instances("EPFoo"))) == 2 assert set(pm.iter_existing_instances("EPFoo")) == set( [pm.get_instance(EPFoo, context="context2"), pm.get_instance("EPFoo")])
def register_plugins(pm: PluginManager): pm.register( EPResolveInterpreter, RobocodeResolveInterpreter, kwargs={"weak_pm": weakref.ref(pm)}, )
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
def register_plugins(pm: PluginManager): pm.register(EPResolveInterpreter, ResolveInterpreterInTests)