diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d20301..312d9de5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ Status of the `main` branch. Changes prior to the next official version change w - Allow language server priorities to be configured in `serena_config.yml` (for auto-detection during project creation) - Add `python_basedpyright` as an alternative Python language server + - Java/JDTLS: stop downloading and loading the unused IntelliCode completion-ranking bundle; the retired + `intellicode_version`, `intellicode_xmx` and `intellicode_xms` settings remain accepted but are ignored #1821 - Nix/nixd: support custom `ls_path` launchers and external JSON settings through `config_path` #1737 - Fix: Nix/nixd diagnostics now use published diagnostics instead of the unsupported `textDocument/diagnostic` request, which terminated nixd #1802 diff --git a/docs/02-usage/050_configuration.md b/docs/02-usage/050_configuration.md index 65b5e6cc..84facd85 100644 --- a/docs/02-usage/050_configuration.md +++ b/docs/02-usage/050_configuration.md @@ -666,8 +666,7 @@ Supported settings: Java support has two installation modes: 1. **Default vscode-java VSIX mode** (no extra config required): Serena downloads the platform-specific - vscode-java VSIX (~500 MB: JDTLS + bundled JRE 21 + Lombok + IntelliCode), Gradle distribution and - IntelliCode VSIX from public hosts on first use. + vscode-java VSIX (JDTLS + bundled JRE 21 + Lombok) and a Gradle distribution from public hosts on first use. 2. **Upstream JDTLS mode** (offline-friendly): Activated by setting both `jdtls_path` and `lombok_path`. Uses an existing JDTLS installation (~100 MB) and the system JDK 21+. Nothing is downloaded. Recommended for restricted-network/corporate environments. @@ -677,9 +676,8 @@ Java support has two installation modes: - **Default vscode-java VSIX mode** — recommended for most users. No setup required; Serena downloads everything on first use. - **Upstream JDTLS mode** — recommended when: - - you cannot reach `github.com`, `services.gradle.org` or `marketplace.visualstudio.com` - from the host (corporate proxy, air-gapped network); - - you want a smaller on-disk footprint (~100 MB vs ~500 MB); + - you cannot reach `github.com` or `services.gradle.org` from the host (corporate proxy, air-gapped network); + - you want a smaller on-disk footprint; - you already maintain a JDTLS installation (e.g. for `nvim-jdtls` or another editor); - your security policy prohibits per-project runtime downloads. @@ -702,23 +700,19 @@ The following settings are supported for the Java language server: | `runtimes` | `[]` | Extra JRE/JDK entries registered with JDT-LS via `java.configuration.runtimes`. Use this when a project's source/target level exceeds the JDK JDT-LS itself runs on (currently JDK 21 in default vscode-java VSIX mode). Each entry is a mapping with required `name` (e.g. `JavaSE-25`, matching the `JavaSE-NN` container the build tool requests) and `path` (JDK/JRE home directory; must exist), plus optional `default`, `sources`, and `javadoc` (passed through to JDT-LS). Entries extend rather than replace the bundled `JavaSE-21` runtime; an entry that reuses the `JavaSE-21` name overrides the bundled one. Changing this setting invalidates the JDTLS workspace hash so a fresh import is performed. | | `gradle_version` | `8.14.2` | (vscode-java mode only) Override the Gradle distribution version Serena downloads by default. | | `vscode_java_version` | `1.54.0-923` | (vscode-java mode only) Override the bundled `vscode-java` runtime bundle version Serena downloads by default. | -| `intellicode_version` | `1.2.30` | (vscode-java mode only) Override the IntelliCode VSIX version Serena downloads by default. | | `lombok_show_generated` | `true` | Show Lombok-generated methods (`getX/setX`, `builder()`, `equals/hashCode/toString`, `withX`, fluent accessors) in `find_symbol`, `get_symbols_overview` and the symbol-edit tools. Set to `false` to restore the previous JDTLS default and hide the synthetic methods (e.g. when `@Data` classes pollute the outline with too many getters/setters). Requires JDTLS commit `b2d8952` / `vscode-java >= 1.53.0`; the bundled default already meets this. | | `jdtls_xmx` | `3G` | Maximum heap size for the JDTLS server JVM. | | `jdtls_xms` | `100m` | Initial heap size for the JDTLS server JVM. | -| `intellicode_xmx` | `1G` | (vscode-java mode only) Maximum heap size for the IntelliCode embedded JVM. | -| `intellicode_xms` | `100m` | (vscode-java mode only) Initial heap size for the IntelliCode embedded JVM. | Notes: - When overriding `vscode_java_version`, Serena still assumes that the downloaded runtime bundle keeps the same internal directory layout and file names as the bundled default version. -- In upstream-jdtls mode, IntelliCode is not loaded (it's an ML completions ranker that is irrelevant to Serena's - symbol-tools workflow), and Serena does not ship a Gradle distribution. Maven projects work via JDTLS's bundled m2e. +- Serena does not download or load IntelliCode because its completion ranking is not used by Serena's tools. The retired + `intellicode_version`, `intellicode_xmx` and `intellicode_xms` keys remain accepted and ignored so existing + configurations continue to load; they can be removed. +- In upstream-jdtls mode Serena does not ship a Gradle distribution. Maven projects work via JDTLS's bundled m2e. Gradle projects must have `./gradlew` in the project, or rely on a system-installed Gradle through Buildship's - default discovery rules. -- In upstream-jdtls mode the `gradle_version`, `vscode_java_version`, `intellicode_version`, - `intellicode_xmx`, `intellicode_xms` settings are silently ignored — they only apply to the - vscode-java VSIX mode. + default discovery rules. The `gradle_version` and `vscode_java_version` settings are silently ignored in this mode. - Without `runtimes`, JDT-LS only knows about the bundled `JavaSE-21` JRE. Projects that request a newer container (e.g. `sourceCompatibility = JavaVersion.VERSION_25`) then fail to resolve JDK types such as `java.lang.Object`. Register the matching installed JDK via `runtimes` instead of symlinking over Serena's diff --git a/src/solidlsp/language_servers/eclipse_jdtls.py b/src/solidlsp/language_servers/eclipse_jdtls.py index 94531a9a..baa45de9 100644 --- a/src/solidlsp/language_servers/eclipse_jdtls.py +++ b/src/solidlsp/language_servers/eclipse_jdtls.py @@ -35,11 +35,6 @@ log = logging.getLogger(__name__) GRADLE_ALLOWED_HOSTS = ("services.gradle.org", "github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com") VSCODE_JAVA_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com") -INTELLICODE_ALLOWED_HOSTS = ( - "visualstudioexptteam.gallery.vsassets.io", - "marketplace.visualstudio.com", - "download.visualstudio.microsoft.com", -) @dataclasses.dataclass(frozen=True) @@ -77,10 +72,9 @@ class RuntimeDependencyPaths: """ Stores the paths to the runtime dependencies of EclipseJDTLS. - In the default mode (vscode-java VSIX), all paths are populated. - In the upstream-jdtls mode (when ``jdtls_path`` and ``lombok_path`` are set), - fields that have no upstream equivalent (gradle distribution, IntelliCode bundle) - are set to None. + In the default mode (vscode-java VSIX), all paths are populated. In the + upstream-jdtls mode (when ``jdtls_path`` and ``lombok_path`` are set), the + Gradle distribution path is ``None`` because Serena does not download it. """ jre_path: str @@ -89,8 +83,6 @@ class RuntimeDependencyPaths: jdtls_readonly_config_path: str lombok_jar_path: str gradle_path: str | None = None - intellicode_jar_path: str | None = None - intellisense_members_path: str | None = None @dataclass @@ -113,8 +105,8 @@ class EclipseJDTLS(SolidLanguageServer): Two installation modes are supported: 1. **Default vscode-java VSIX mode** (no extra config required) — Serena downloads the platform-specific - vscode-java VSIX bundle (~500 MB: JDTLS + bundled JRE 21 + Lombok + IntelliCode), Gradle distribution - and IntelliCode VSIX from public hosts. Suitable when public network access is available. + vscode-java VSIX bundle (JDTLS + bundled JRE 21 + Lombok) and Gradle distribution from public hosts. + Suitable when public network access is available. 2. **Upstream JDTLS mode** (activated by setting both ``jdtls_path`` and ``lombok_path``) — uses an existing JDTLS installation and the system JDK. Nothing is downloaded. Suitable for restricted-network @@ -143,8 +135,6 @@ class EclipseJDTLS(SolidLanguageServer): gradle_java_home is unset, Gradle import (default: false) - jdtls_xmx: Maximum heap size for the JDTLS server JVM (default: "3G") - jdtls_xms: Initial heap size for the JDTLS server JVM (default: "100m") - - intellicode_xmx: Maximum heap size for the IntelliCode embedded JVM (default: "1G") - - intellicode_xms: Initial heap size for the IntelliCode embedded JVM (default: "100m") - lombok_show_generated: Show Lombok-generated methods (getX/setX/builder()/...) in document symbols by sending java.symbols.includeGeneratedCode=true to JDTLS (default: true). Set to false for @Data-heavy projects where the extra getters/setters are noise. @@ -155,7 +145,6 @@ class EclipseJDTLS(SolidLanguageServer): Pinned versions: "1.54.0-923" (default) and "1.42.0-561" (legacy / initial). Other versions are not supported in default VSIX mode (the resource paths inside the archive change between releases); use upstream-jdtls mode for arbitrary versions. - - intellicode_version: Override the pinned IntelliCode VSIX version downloaded by Serena - runtimes: Additional JRE/JDK entries to register with JDT-LS's ``java.configuration.runtimes``, for projects whose source/target level exceeds the JDK JDT-LS itself runs on (currently JDK 21 in default vscode-java VSIX mode). Each entry is a mapping with: @@ -190,12 +179,9 @@ class EclipseJDTLS(SolidLanguageServer): use_system_java_home: true # set to true to use system JAVA_HOME for JDTLS and Gradle fallback jdtls_xmx: "3G" # maximum heap size for the JDTLS server JVM jdtls_xms: "100m" # initial heap size for the JDTLS server JVM - intellicode_xmx: "1G" # maximum heap size for the IntelliCode embedded JVM - intellicode_xms: "100m" # initial heap size for the IntelliCode embedded JVM lombok_show_generated: true # show Lombok-generated methods in document symbols (default true) gradle_version: "8.14.2" vscode_java_version: "1.54.0-923" # also accepts pinned legacy "1.42.0-561" - intellicode_version: "1.2.30" runtimes: # register additional JDKs for projects targeting a newer Java version - name: "JavaSE-25" path: "/home/user/Java/jdk25" @@ -220,7 +206,6 @@ class EclipseJDTLS(SolidLanguageServer): self._service_ready_event = threading.Event() self._project_ready_event = threading.Event() - self._intellicode_enable_command_available = threading.Event() def _create_dependency_provider(self) -> LanguageServerDependencyProvider: ls_resources_dir = self.ls_resources_dir(self._solidlsp_settings) @@ -258,7 +243,6 @@ class EclipseJDTLS(SolidLanguageServer): # versions used initially (which use default paths without version suffix) INITIAL_VSCODE_JAVA_VERSION = "1.42.0-561" - INITIAL_INTELLICODE_VERSION = "1.2.30" INITIAL_VSCODE_JAVA_PATHS = VsixResourcePaths( jre_version="21.0.7", lombok_jar_basename="lombok-1.18.36.jar", @@ -270,7 +254,6 @@ class EclipseJDTLS(SolidLanguageServer): # scripts/update_downloaded_dependency_hashes.py and commit the resulting changes. DEFAULT_GRADLE_VERSION = "8.14.2" DEFAULT_VSCODE_JAVA_VERSION = "1.54.0-923" - DEFAULT_INTELLICODE_VERSION = "1.2.30" DEFAULT_VSCODE_JAVA_PATHS = VsixResourcePaths( jre_version="21.0.10", lombok_jar_basename="lombok-1.18.39-4050.jar", @@ -320,19 +303,9 @@ class EclipseJDTLS(SolidLanguageServer): ) return deps - @classmethod - def _create_dep_intellicode(cls, intellicode_version: str | None = None) -> DownloadedDependency: - intellicode_version = intellicode_version or cls.DEFAULT_INTELLICODE_VERSION - return DownloadedDependency( - url=f"https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/{intellicode_version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage", - archive_type="zip", - allowed_hosts=INTELLICODE_ALLOWED_HOSTS, - verified=intellicode_version == cls.DEFAULT_INTELLICODE_VERSION, - ) - @classmethod def update_dep_hashes(cls) -> None: - deps = [cls._create_dep_gradle(), cls._create_dep_intellicode()] + list(cls._create_deps_vscode_java().values()) + deps = [cls._create_dep_gradle()] + list(cls._create_deps_vscode_java().values()) with DownloadedDependencyHashDatabase.get_instance().update_context() as db: for dep in deps: db.update(dep) @@ -351,8 +324,8 @@ class EclipseJDTLS(SolidLanguageServer): ``brew install jdtls`` or extracted ``jdt-language-server-*.tar.gz``) and the system JDK. Nothing is downloaded by Serena. Suitable for restricted-network/corporate environments. * **Default vscode-java VSIX mode** (activated otherwise): downloads the platform-specific - vscode-java VSIX bundle (containing JDTLS, bundled JRE 21, Lombok, IntelliCode), Gradle - distribution and IntelliCode VSIX from public hosts. Original behaviour, unchanged. + vscode-java VSIX bundle (containing JDTLS, bundled JRE 21 and Lombok) and a Gradle + distribution from public hosts. """ jdtls_path = custom_settings.get("jdtls_path") lombok_path = custom_settings.get("lombok_path") @@ -369,16 +342,12 @@ class EclipseJDTLS(SolidLanguageServer): platformId = PlatformUtils.get_platform_id() gradle_version = custom_settings.get("gradle_version", cls.DEFAULT_GRADLE_VERSION) vscode_java_version = custom_settings.get("vscode_java_version", cls.DEFAULT_VSCODE_JAVA_VERSION) - intellicode_version = custom_settings.get("intellicode_version", cls.DEFAULT_INTELLICODE_VERSION) # install-dir name per the version-pinning convention (see module-level block): # INITIAL -> legacy unversioned dir; everything else -> "{name}-{resolved}" subdir vscode_java_dirname = ( "vscode-java" if vscode_java_version == cls.INITIAL_VSCODE_JAVA_VERSION else f"vscode-java-{vscode_java_version}" ) - intellicode_dirname = ( - "intellicode" if intellicode_version == cls.INITIAL_INTELLICODE_VERSION else f"intellicode-{intellicode_version}" - ) # Resolve internal VSIX paths (JRE / Lombok / launcher filenames). For pinned versions # these are known; for any other user-supplied version we bail out — guessing would @@ -478,26 +447,6 @@ class EclipseJDTLS(SolidLanguageServer): assert os.path.exists(jdtls_launcher_jar_path) assert os.path.exists(jdtls_readonly_config_path) - intellicode_dep = cls._create_dep_intellicode(intellicode_version) - intellicode_directory_path = str(PurePath(ls_resources_dir, intellicode_dirname)) - os.makedirs(intellicode_directory_path, exist_ok=True) - intellicode_jar_path = str( - PurePath(intellicode_directory_path, "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar") - ) - intellisense_members_path = str(PurePath(intellicode_directory_path, "extension/dist/bundledModels/java_intellisense-members")) - if not all( - [ - os.path.exists(intellicode_directory_path), - os.path.exists(intellicode_jar_path), - os.path.exists(intellisense_members_path), - ] - ): - intellicode_dep.download_to(intellicode_directory_path) - - assert os.path.exists(intellicode_directory_path) - assert os.path.exists(intellicode_jar_path) - assert os.path.exists(intellisense_members_path) - return RuntimeDependencyPaths( gradle_path=gradle_path, lombok_jar_path=lombok_jar_path, @@ -505,8 +454,6 @@ class EclipseJDTLS(SolidLanguageServer): jre_home_path=jre_home_path, jdtls_launcher_jar_path=jdtls_launcher_jar_path, jdtls_readonly_config_path=jdtls_readonly_config_path, - intellicode_jar_path=intellicode_jar_path, - intellisense_members_path=intellisense_members_path, ) @staticmethod @@ -520,7 +467,7 @@ class EclipseJDTLS(SolidLanguageServer): :param jdtls_path: absolute path to the JDTLS root (containing ``plugins/`` and ``config_/``) :param lombok_path: absolute path to the Lombok jar (mandatory; agent is always attached) :param custom_settings: language-server-specific settings from ls_specific_settings.java - :return: populated RuntimeDependencyPaths with gradle/intellicode fields set to None + :return: populated RuntimeDependencyPaths with ``gradle_path`` set to ``None`` """ # validate jdtls_path structure (root + plugins dir) jdtls_root = Path(jdtls_path) @@ -569,8 +516,6 @@ class EclipseJDTLS(SolidLanguageServer): jdtls_readonly_config_path=str(config_dir), lombok_jar_path=lombok_path, gradle_path=None, - intellicode_jar_path=None, - intellisense_members_path=None, ) @staticmethod @@ -1006,10 +951,6 @@ class EclipseJDTLS(SolidLanguageServer): gradle_user_home = None log.info(f"Gradle user home not found at default location ({default_gradle_home}), will use JDTLS defaults") - # IntelliCode JVM settings (used in vmargs for the embedded JVM) - intellicode_xmx = self._custom_settings.get("intellicode_xmx", "1G") - intellicode_xms = self._custom_settings.get("intellicode_xms", "100m") - # Lombok-generated symbols (getX/setX/builder()/equals/hashCode/toString/...): JDTLS filters # these out of documentSymbol results by default. Without them, find_symbol/get_symbols_overview # return only user-written sources, which breaks navigation around @Data/@Builder/@Getter/@Setter @@ -1194,19 +1135,10 @@ class EclipseJDTLS(SolidLanguageServer): "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}}, }, "initializationOptions": { - "bundles": ["intellicode-core.jar"], + "bundles": [], "settings": { "java": { "home": None, - "jdt": { - "ls": { - "java": {"home": None}, - "vmargs": f"-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx{intellicode_xmx} -Xms{intellicode_xms} -Xlog:disable", - "lombokSupport": {"enabled": True}, - "protobufSupport": {"enabled": True}, - "androidSupport": {"enabled": True}, - } - }, "errors": {"incompleteClasspath": {"severity": "error"}}, "configuration": { "checkProjectSettingsExclusions": False, @@ -1326,14 +1258,6 @@ class EclipseJDTLS(SolidLanguageServer): initialize_params["initializationOptions"]["workspaceFolders"] = [repo_uri] - # IntelliCode bundle: only attached in default vscode-java VSIX mode. - # In upstream-jdtls mode (jdtls_path set) we don't ship IntelliCode — agentic Serena workflows - # don't use completion ranking, so the bundle would be inert dead weight. - if self.runtime_dependency_paths.intellicode_jar_path is not None: - initialize_params["initializationOptions"]["bundles"] = [self.runtime_dependency_paths.intellicode_jar_path] - else: - initialize_params["initializationOptions"]["bundles"] = [] - # merge the bundled JRE with any additional runtimes configured via ls_specific_settings.java.runtimes # (e.g. so projects targeting a newer Java version than the bundled JRE resolve their JRE container)... default_runtime = {"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True} @@ -1378,9 +1302,6 @@ class EclipseJDTLS(SolidLanguageServer): "*", " ", ] - if registration["method"] == "workspace/executeCommand": - if "java.intellicode.enable" in registration["registerOptions"]["commands"]: - self._intellicode_enable_command_available.set() return def lang_status_handler(params: dict) -> None: @@ -1424,23 +1345,6 @@ class EclipseJDTLS(SolidLanguageServer): self.server.notify.workspace_did_change_configuration({"settings": initialize_params["initializationOptions"]["settings"]}) # type: ignore - # IntelliCode enablement is only relevant in the default vscode-java VSIX mode where the - # IntelliCode bundle is shipped. In upstream-jdtls mode it's absent and the - # 'java.intellicode.enable' command will never be registered, so we skip the wait/call. - if self.runtime_dependency_paths.intellicode_jar_path is not None: - self._intellicode_enable_command_available.wait() - - java_intellisense_members_path = self.runtime_dependency_paths.intellisense_members_path - assert java_intellisense_members_path is not None - assert os.path.exists(java_intellisense_members_path) - intellicode_enable_result = self.server.send.execute_command( - { - "command": "java.intellicode.enable", - "arguments": [True, java_intellisense_members_path], - } - ) - assert intellicode_enable_result - if not self._service_ready_event.is_set(): log.info("Waiting for service to be ready ...") self._service_ready_event.wait() diff --git a/src/solidlsp/resources/downloaded_dependency_hashes.json b/src/solidlsp/resources/downloaded_dependency_hashes.json index 7fc0bdd9..a02ce776 100644 --- a/src/solidlsp/resources/downloaded_dependency_hashes.json +++ b/src/solidlsp/resources/downloaded_dependency_hashes.json @@ -1,9 +1,8 @@ { "https://services.gradle.org/distributions/gradle-8.14.2-bin.zip": "7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999", - "https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage": "7f61a7f96d101cdf230f96821be3fddd8f890ebfefb3695d18beee43004ae251", "https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-darwin-arm64-1.54.0-923.vsix": "c54c45cb0d2579d8e0a4ddeb24d4a9dd0b460d07d9366adea2b38a1da22a463c", "https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-darwin-x64-1.54.0-923.vsix": "dfc98abc4e54165a78372e280242a039671729b1b03420608df3b10c6b629fb6", "https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-linux-arm64-1.54.0-923.vsix": "e2bb22c427d90da8dbb1afff72ff1e2dce38d50b76deb02d7bc313a330a1330c", "https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-linux-x64-1.54.0-923.vsix": "9d4b15da54e25a0192f9bac073f086c015397d3676623b68dbf83a5dbaf5132b", "https://github.com/redhat-developer/vscode-java/releases/download/v1.54.0/java-win32-x64-1.54.0-923.vsix": "66f3914987edeccfee8a2558470e0fde4f8c4154232ff4baa5d73373ebc819d4" -} \ No newline at end of file +} diff --git a/test/solidlsp/java/test_jdtls_path_resolution.py b/test/solidlsp/java/test_jdtls_path_resolution.py index 3bf738b7..6ffd2ea4 100644 --- a/test/solidlsp/java/test_jdtls_path_resolution.py +++ b/test/solidlsp/java/test_jdtls_path_resolution.py @@ -455,7 +455,7 @@ class TestSetupFromExistingInstall: (jdk / "bin" / "java").touch() return jdk - def test_happy_path_returns_runtime_paths_with_no_gradle_and_no_intellicode( + def test_happy_path_returns_runtime_paths_with_no_gradle( self, tmp_path: Path, jdtls_root: Path, lombok_jar: Path, custom_settings: SolidLSPSettings.CustomLSSettings ) -> None: jdk = self._fake_jdk(tmp_path) @@ -465,8 +465,6 @@ class TestSetupFromExistingInstall: result = EclipseJDTLS.DependencyProvider._setup_from_existing_install(str(jdtls_root), str(lombok_jar), custom_settings) assert result.gradle_path is None - assert result.intellicode_jar_path is None - assert result.intellisense_members_path is None assert result.lombok_jar_path == str(lombok_jar) assert result.jre_home_path == str(jdk) assert Path(result.jdtls_launcher_jar_path).name.startswith("org.eclipse.equinox.launcher_")