diff --git a/python_packages/jupyter_lsp/jupyter_lsp/handlers.py b/python_packages/jupyter_lsp/jupyter_lsp/handlers.py index 5e9327603..a7312f4a4 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/handlers.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/handlers.py @@ -25,7 +25,8 @@ class LanguageServerWebSocketHandler( # type: ignore language_server = None # type: Optional[Text] - def open(self, language_server): + async def open(self, language_server): + await self.manager.ready() self.language_server = language_server self.manager.subscribe(self) self.log.debug("[{}] Opened a handler".format(self.language_server)) @@ -51,8 +52,10 @@ class LanguageServersHandler(BaseHandler): def initialize(self, *args, **kwargs): super().initialize(*args, **kwargs) - def get(self): + async def get(self): """finish with the JSON representations of the sessions""" + await self.manager.ready() + response = { "version": 2, "sessions": { diff --git a/python_packages/jupyter_lsp/jupyter_lsp/manager.py b/python_packages/jupyter_lsp/jupyter_lsp/manager.py index 7b0a70f7c..ea1a7bb1f 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/manager.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/manager.py @@ -1,5 +1,6 @@ """ A configurable frontend for stdio-based Language Servers """ +import asyncio import os import traceback from typing import Dict, Text, Tuple, cast @@ -74,6 +75,10 @@ class LanguageServerManager(LanguageServerManagerAPI): """ ).tag(config=True) + _ready = Bool( + help="""Whether the manager has been initialized""", default_value=False + ) + all_listeners = List_(trait=LoadableCallable).tag(config=True) server_listeners = List_(trait=LoadableCallable).tag(config=True) client_listeners = List_(trait=LoadableCallable).tag(config=True) @@ -111,6 +116,12 @@ def initialize(self, *args, **kwargs): self.init_language_servers() self.init_listeners() self.init_sessions() + self._ready = True + + async def ready(self): + while not self._ready: # pragma: no cover + asyncio.sleep(0.1) + return True def init_language_servers(self) -> None: """determine the final language server configuration.""" diff --git a/python_packages/jupyter_lsp/jupyter_lsp/serverextension.py b/python_packages/jupyter_lsp/jupyter_lsp/serverextension.py index 06828febf..008b7b5cf 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/serverextension.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/serverextension.py @@ -8,43 +8,53 @@ from .handlers import add_handlers from .manager import LanguageServerManager from .paths import normalized_uri -from .virtual_documents_shadow import setup_shadow_filesystem + + +async def initialize(nbapp, virtual_documents_uri): # pragma: no cover + """Perform lazy initialization.""" + import concurrent.futures + + from .virtual_documents_shadow import setup_shadow_filesystem + + manager = nbapp.language_server_manager + + with concurrent.futures.ThreadPoolExecutor() as pool: + await nbapp.io_loop.run_in_executor(pool, manager.initialize) + + setup_shadow_filesystem(virtual_documents_uri=virtual_documents_uri) + + nbapp.log.debug( + "[lsp] The following Language Servers will be available: {}".format( + json.dumps(manager.language_servers, indent=2, sort_keys=True) + ) + ) def load_jupyter_server_extension(nbapp): """create a LanguageServerManager and add handlers""" nbapp.add_traits(language_server_manager=traitlets.Instance(LanguageServerManager)) manager = nbapp.language_server_manager = LanguageServerManager(parent=nbapp) - manager.initialize() contents = nbapp.contents_manager page_config = nbapp.web_app.settings.setdefault("page_config_data", {}) + root_uri = "" + virtual_documents_uri = "" + # try to set the rootUri from the contents manager path if hasattr(contents, "root_dir"): root_uri = normalized_uri(contents.root_dir) - page_config["rootUri"] = root_uri nbapp.log.debug("[lsp] rootUri will be %s", root_uri) - virtual_documents_uri = normalized_uri( Path(contents.root_dir) / manager.virtual_documents_dir ) - page_config["virtualDocumentsUri"] = virtual_documents_uri nbapp.log.debug("[lsp] virtualDocumentsUri will be %s", virtual_documents_uri) else: # pragma: no cover - page_config["rootUri"] = "" - page_config["virtualDocumentsUri"] = "" nbapp.log.warn( "[lsp] %s did not appear to have a root_dir, could not set rootUri", contents, ) + page_config.update(rootUri=root_uri, virtualDocumentsUri=virtual_documents_uri) add_handlers(nbapp) - - nbapp.log.debug( - "[lsp] The following Language Servers will be available: {}".format( - json.dumps(manager.language_servers, indent=2, sort_keys=True) - ) - ) - - setup_shadow_filesystem(virtual_documents_uri=virtual_documents_uri) + nbapp.io_loop.call_later(0, initialize, nbapp, virtual_documents_uri) diff --git a/python_packages/jupyter_lsp/jupyter_lsp/specs/julia_language_server.py b/python_packages/jupyter_lsp/jupyter_lsp/specs/julia_language_server.py index 5d1f23518..11e17ca9a 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/specs/julia_language_server.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/specs/julia_language_server.py @@ -24,5 +24,5 @@ class JuliaLanguageServer(ShellSpec): issues="https://github.com/julia-vscode/LanguageServer.jl/issues", ), install=dict(julia='using Pkg; Pkg.add("LanguageServer")'), - config_schema=load_config_schema(key) + config_schema=load_config_schema(key), ) diff --git a/python_packages/jupyter_lsp/jupyter_lsp/specs/pyright.py b/python_packages/jupyter_lsp/jupyter_lsp/specs/pyright.py index 8acf01d47..2df194207 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/specs/pyright.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/specs/pyright.py @@ -19,5 +19,5 @@ class PyrightLanguageServer(NodeModuleSpec): yarn="yarn add --dev {}".format(key), jlpm="jlpm add --dev {}".format(key), ), - config_schema=load_config_schema(key) + config_schema=load_config_schema(key), ) diff --git a/python_packages/jupyter_lsp/jupyter_lsp/specs/typescript_language_server.py b/python_packages/jupyter_lsp/jupyter_lsp/specs/typescript_language_server.py index 703da608c..680d70dfe 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/specs/typescript_language_server.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/specs/typescript_language_server.py @@ -37,5 +37,5 @@ class TypescriptLanguageServer(NodeModuleSpec): yarn="yarn add --dev {}".format(key), jlpm="jlpm add --dev {}".format(key), ), - config_schema=load_config_schema(key) + config_schema=load_config_schema(key), ) diff --git a/python_packages/jupyter_lsp/jupyter_lsp/tests/test_listener.py b/python_packages/jupyter_lsp/jupyter_lsp/tests/test_listener.py index 98d40a59b..8e2863f60 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/tests/test_listener.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/tests/test_listener.py @@ -79,7 +79,7 @@ async def all_listener( assert len(manager._listeners["client"]) == 2 assert len(manager._listeners["all"]) == 2 - ws_handler.open(known_server) + await ws_handler.open(known_server) await ws_handler.on_message(jsonrpc_init_msg) diff --git a/python_packages/jupyter_lsp/jupyter_lsp/tests/test_session.py b/python_packages/jupyter_lsp/jupyter_lsp/tests/test_session.py index 30fdc7198..5e704550b 100644 --- a/python_packages/jupyter_lsp/jupyter_lsp/tests/test_session.py +++ b/python_packages/jupyter_lsp/jupyter_lsp/tests/test_session.py @@ -5,8 +5,8 @@ from ..schema import SERVERS_RESPONSE -def assert_status_set(handler, expected_statuses, language_server=None): - handler.get() +async def assert_status_set(handler, expected_statuses, language_server=None): + await handler.get() payload = handler._payload errors = list(SERVERS_RESPONSE.iter_errors(payload)) @@ -28,13 +28,13 @@ async def test_start_known(known_server, handlers, jsonrpc_init_msg): manager.initialize() - assert_status_set(handler, {"not_started"}) + await assert_status_set(handler, {"not_started"}) - ws_handler.open(known_server) + await ws_handler.open(known_server) session = manager.sessions[ws_handler.language_server] assert session.process is not None - assert_status_set(handler, {"started"}, known_server) + await assert_status_set(handler, {"started"}, known_server) await ws_handler.on_message(jsonrpc_init_msg) @@ -50,8 +50,8 @@ async def test_start_known(known_server, handlers, jsonrpc_init_msg): assert not session.handlers assert not session.process - assert_status_set(handler, {"stopped"}, known_server) - assert_status_set(handler, {"stopped", "not_started"}) + await assert_status_set(handler, {"stopped"}, known_server) + await assert_status_set(handler, {"stopped", "not_started"}) @pytest.mark.asyncio @@ -61,18 +61,18 @@ async def test_start_unknown(known_unknown_server, handlers, jsonrpc_init_msg): manager = handler.manager manager.initialize() - assert_status_set(handler, {"not_started"}) + await assert_status_set(handler, {"not_started"}) - ws_handler.open(known_unknown_server) + await ws_handler.open(known_unknown_server) - assert_status_set(handler, {"not_started"}) + await assert_status_set(handler, {"not_started"}) await ws_handler.on_message(jsonrpc_init_msg) - assert_status_set(handler, {"not_started"}) + await assert_status_set(handler, {"not_started"}) ws_handler.on_close() assert not manager.sessions.get(ws_handler.language_server) - assert_status_set(handler, {"not_started"}) + await assert_status_set(handler, {"not_started"}) @pytest.mark.asyncio @@ -92,7 +92,7 @@ async def test_ping(handlers): assert ws_handler._ping_sent is False - ws_handler.open(a_server) + await ws_handler.open(a_server) assert ws_handler.ping_callback is not None and ws_handler.ping_callback.is_running await asyncio.sleep(ws_handler.ping_interval * 3)