diff --git a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py index ed4bfae..4b910d2 100644 --- a/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py +++ b/src/multilspy/language_servers/eclipse_jdtls/eclipse_jdtls.py @@ -14,6 +14,8 @@ from contextlib import asynccontextmanager from typing import AsyncIterator +from typing_extensions import Optional + from multilspy.multilspy_logger import MultilspyLogger from multilspy.language_server import LanguageServer from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo @@ -22,7 +24,7 @@ from multilspy.multilspy_settings import MultilspySettings from multilspy.multilspy_utils import FileUtils from multilspy.multilspy_utils import PlatformUtils -from pathlib import PurePath +from pathlib import PurePath, Path @dataclasses.dataclass @@ -46,24 +48,39 @@ class EclipseJDTLS(LanguageServer): The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class """ - def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): + NAME: str = "EclipseJDTLS" + + def __init__(self, + config: MultilspyConfig, + logger: MultilspyLogger, + repository_root_path: str): """ Creates a new EclipseJDTLS instance initializing the language server settings appropriately. This class is not meant to be instantiated directly. Use LanguageServer.create() instead. """ + self.config = config + self.jdtls_home_dir = Path(MultilspySettings.get_language_server_directory()) / EclipseJDTLS.NAME + os.makedirs(str(self.jdtls_home_dir), exist_ok=True) + + repo_name = os.path.basename(repository_root_path) runtime_dependency_paths = self.setupRuntimeDependencies(logger, config) self.runtime_dependency_paths = runtime_dependency_paths - # ws_dir is the workspace directory for the EclipseJDTLS server - ws_dir = str( - PurePath( - MultilspySettings.get_language_server_directory(), - "EclipseJDTLS", - "workspaces", - uuid.uuid4().hex, - ) - ) + if config.ws_path is None: + ws_dir = self.get_workspace_path(repository_root_path) + if ws_dir is None: + config.ws_path = self.jdtls_home_dir / "workspaces" / f"{repo_name}_{uuid.uuid4().hex}" + ws_dir = str(config.ws_path) + if not config.temp_workspace: + self.register_workspace(repository_root_path, ws_dir) + else: + # If the client provided the workspace we ignore the temp_workspace config. + config.temp_workspace = False + ws_dir = str(config.ws_path) + self.register_workspace(repository_root_path, ws_dir) + + os.makedirs(ws_dir, exist_ok=True) # shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces shared_cache_location = str( @@ -75,8 +92,6 @@ def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_ jdtls_launcher_jar = self.runtime_dependency_paths.jdtls_launcher_jar_path - os.makedirs(ws_dir, exist_ok=True) - data_dir = str(PurePath(ws_dir, "data_dir")) jdtls_config_path = str(PurePath(ws_dir, "config_path")) @@ -139,6 +154,40 @@ def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_ super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java") + + def get_workspace_path(self, repo_path: str) -> Optional[str]: + ws_table = str(self.jdtls_home_dir / "ws_table") + if not os.path.exists(ws_table): + return None + with open(ws_table, 'r') as f: + for line in f: + line = line.strip() + repo, ws = line.split("=") + if repo == repo_path and len(ws) > 0: + return ws + return None + + def register_workspace(self, repo_path: str, ws_path: str): + ws_tab_path = self.jdtls_home_dir / "ws_table" + ws_tab_path.touch(exist_ok=True) + ws_table = str(ws_tab_path) + j = -1 + with open(ws_table, 'r+') as f: + lines = [l.strip() for l in f.readlines()] + for i in range(len(lines)): + repo, ws = lines[i].split("=") + if repo == repo_path: + j = i + break + nline = f"{repo_path}={ws_path}" + if j < 0: + lines.append(nline) + else: + lines[j] = nline + f.seek(0) + f.truncate() + f.write("\n".join(lines)) + def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> RuntimeDependencyPaths: """ Setup runtime dependencies for EclipseJDTLS. @@ -403,3 +452,6 @@ async def do_nothing(params): await self.server.shutdown() await self.server.stop() + + if self.config.temp_workspace and self.config.ws_path.exists(): + shutil.rmtree(str(self.config.ws_path)) diff --git a/src/multilspy/multilspy_config.py b/src/multilspy/multilspy_config.py index 573cc5e..e22731e 100644 --- a/src/multilspy/multilspy_config.py +++ b/src/multilspy/multilspy_config.py @@ -4,6 +4,8 @@ from enum import Enum from dataclasses import dataclass +from pathlib import Path + class Language(str, Enum): """ @@ -26,8 +28,13 @@ class MultilspyConfig: Configuration parameters """ code_language: Language + trace_lsp_communication: bool = False + ws_path: Path = None + + temp_workspace: bool = False + @classmethod def from_dict(cls, env: dict): """ @@ -37,4 +44,4 @@ def from_dict(cls, env: dict): return cls(**{ k: v for k, v in env.items() if k in inspect.signature(cls).parameters - }) \ No newline at end of file + }) diff --git a/src/multilspy/multilspy_utils.py b/src/multilspy/multilspy_utils.py index 1e6ac61..eb4cc43 100644 --- a/src/multilspy/multilspy_utils.py +++ b/src/multilspy/multilspy_utils.py @@ -107,6 +107,9 @@ def read_file(logger: MultilspyLogger, file_path: str) -> str: return inp_file.read() except UnicodeError: continue + except FileNotFoundError as exc: + logger.log(f"File'{file_path}' not found: {exc}", logging.ERROR) + raise MultilspyException(f"File not found {file_path}") from None except Exception as exc: logger.log(f"File read '{file_path}' failed: {exc}", logging.ERROR) raise MultilspyException("File read failed.") from None @@ -245,4 +248,3 @@ def get_dotnet_version() -> DotnetVersion: return DotnetVersion.VMONO except (FileNotFoundError, subprocess.CalledProcessError): raise MultilspyException("dotnet or mono not found on the system") - diff --git a/tests/test_utils.py b/tests/test_utils.py index 49b33b1..4059104 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,7 @@ import pathlib import contextlib import shutil +import uuid from multilspy.multilspy_config import MultilspyConfig from multilspy.multilspy_logger import MultilspyLogger @@ -11,11 +12,12 @@ from multilspy.multilspy_utils import FileUtils @contextlib.contextmanager -def create_test_context(params: dict) -> Iterator[MultilspyContext]: +def create_test_context(params: dict, temp_workspace=True) -> Iterator[MultilspyContext]: """ Creates a test context for the given parameters. """ config = MultilspyConfig.from_dict(params) + config.temp_workspace = temp_workspace logger = MultilspyLogger() user_home_dir = os.path.expanduser("~")