From fc0c78a7a21f9a4e61d0e18dd3ba8ba99ad4acc5 Mon Sep 17 00:00:00 2001 From: Gabriel Patzleiner Date: Sat, 3 Jan 2026 02:23:14 +0100 Subject: [PATCH 1/2] fix(kotlin-ls): improve root detection for Gradle multi-project builds Ensure the Kotlin language server is started at the correct Gradle root for multi-project and composite builds. The root resolution now prioritizes settings.gradle(.kts), followed by the Gradle wrapper, before falling back to module-level build files or Maven projects. This prevents submodules from being incorrectly detected as the workspace root. --- packages/opencode/src/lsp/server.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 4629f924d6a..b8e2af64284 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1212,7 +1212,15 @@ export namespace LSPServer { export const KotlinLS: Info = { id: "kotlin-ls", extensions: [".kt", ".kts"], - root: NearestRoot(["build.gradle", "build.gradle.kts", "settings.gradle.kts", "pom.xml"]), + root: (start) => + // 1) Nearest Gradle root (multi-project or included build) + NearestRoot(["settings.gradle.kts", "settings.gradle"])(start) ?? + // 2) Gradle wrapper (strong root signal) + NearestRoot(["gradlew", "gradlew.bat"])(start) ?? + // 3) Single-project or module-level build + NearestRoot(["build.gradle.kts", "build.gradle"])(start) ?? + // 4) Maven fallback + NearestRoot(["pom.xml"])(start), async spawn(root) { const distPath = path.join(Global.Path.bin, "kotlin-ls") const launcherScript = From 3399b5e62439fd363ea6bc57b924ce1c3c1d422c Mon Sep 17 00:00:00 2001 From: Gabriel Patzleiner Date: Sat, 3 Jan 2026 08:05:39 +0100 Subject: [PATCH 2/2] Fix async root detection for Gradle multi-project builds Root detection now correctly handles async NearestRoot resolution. The previous implementation relied on nullish coalescing, which does not work with async functions and could incorrectly detect a subproject as the root. This change resolves roots sequentially using await and early returns. Priority order: 1. settings.gradle(.kts) for multi-project and included builds 2. Gradle wrapper as a strong root signal 3. build.gradle(.kts) for single-project or module builds 4. pom.xml as a Maven fallback Also renames `start` to `file` for consistency. --- packages/opencode/src/lsp/server.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index b8e2af64284..29d027ace60 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1212,15 +1212,19 @@ export namespace LSPServer { export const KotlinLS: Info = { id: "kotlin-ls", extensions: [".kt", ".kts"], - root: (start) => - // 1) Nearest Gradle root (multi-project or included build) - NearestRoot(["settings.gradle.kts", "settings.gradle"])(start) ?? - // 2) Gradle wrapper (strong root signal) - NearestRoot(["gradlew", "gradlew.bat"])(start) ?? - // 3) Single-project or module-level build - NearestRoot(["build.gradle.kts", "build.gradle"])(start) ?? - // 4) Maven fallback - NearestRoot(["pom.xml"])(start), + root: async (file) => { + // 1) Nearest Gradle root (multi-project or included build) + const settingsRoot = await NearestRoot(["settings.gradle.kts", "settings.gradle"])(file) + if (settingsRoot) return settingsRoot + // 2) Gradle wrapper (strong root signal) + const wrapperRoot = await NearestRoot(["gradlew", "gradlew.bat"])(file) + if (wrapperRoot) return wrapperRoot + // 3) Single-project or module-level build + const buildRoot = await NearestRoot(["build.gradle.kts", "build.gradle"])(file) + if (buildRoot) return buildRoot + // 4) Maven fallback + return NearestRoot(["pom.xml"])(file) + }, async spawn(root) { const distPath = path.join(Global.Path.bin, "kotlin-ls") const launcherScript =