From c3d945548cec9ebc83edc90ff2f8c4501a5dc7a9 Mon Sep 17 00:00:00 2001 From: Andrej Kurocenko Date: Wed, 18 Feb 2026 01:22:59 +0100 Subject: [PATCH 1/2] fix(opencode): prefer monorepo roots for jdtls --- packages/opencode/src/lsp/server.ts | 24 ++++++- .../opencode/test/lsp/server-root.test.ts | 64 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 packages/opencode/test/lsp/server-root.test.ts diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index b0755b8b563..d32d8f17d93 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -50,6 +50,20 @@ export namespace LSPServer { } } + const MarkerRoot = (patterns: string[]): RootFunction => { + return async (file) => { + const files = Filesystem.up({ + targets: patterns, + start: path.dirname(file), + stop: Instance.directory, + }) + const first = await files.next() + await files.return() + if (!first.value) return undefined + return path.dirname(first.value) + } + } + export interface Info { id: string extensions: string[] @@ -1129,7 +1143,15 @@ export namespace LSPServer { export const JDTLS: Info = { id: "jdtls", - root: NearestRoot(["pom.xml", "build.gradle", "build.gradle.kts", ".project", ".classpath"]), + root: async (file) => { + const settingsRoot = await MarkerRoot(["settings.gradle.kts", "settings.gradle"])(file) + if (settingsRoot) return settingsRoot + + const wrapperRoot = await MarkerRoot(["gradlew", "gradlew.bat"])(file) + if (wrapperRoot) return wrapperRoot + + return NearestRoot(["build.gradle.kts", "build.gradle", "pom.xml", ".project", ".classpath"])(file) + }, extensions: [".java"], async spawn(root) { const java = Bun.which("java") diff --git a/packages/opencode/test/lsp/server-root.test.ts b/packages/opencode/test/lsp/server-root.test.ts new file mode 100644 index 00000000000..bc02346ff09 --- /dev/null +++ b/packages/opencode/test/lsp/server-root.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, test } from "bun:test" +import * as fs from "fs/promises" +import path from "path" +import { LSPServer } from "../../src/lsp/server" +import { Instance } from "../../src/project/instance" +import { tmpdir } from "../fixture/fixture" + +describe("lsp.server roots", () => { + test("jdtls prefers settings.gradle root", async () => { + await using tmp = await tmpdir() + const root = tmp.path + const module = path.join(root, "services/api") + const file = path.join(module, "src/main/java/App.java") + + await fs.mkdir(path.dirname(file), { recursive: true }) + await Bun.write(path.join(root, "settings.gradle.kts"), "") + await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(file, "class App {}") + + const result = await Instance.provide({ + directory: root, + fn: () => LSPServer.JDTLS.root(file), + }) + + expect(result).toBe(root) + }) + + test("jdtls uses gradle wrapper root when settings is missing", async () => { + await using tmp = await tmpdir() + const root = tmp.path + const module = path.join(root, "services/api") + const file = path.join(module, "src/main/java/App.java") + + await fs.mkdir(path.dirname(file), { recursive: true }) + await Bun.write(path.join(root, "gradlew"), "") + await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(file, "class App {}") + + const result = await Instance.provide({ + directory: root, + fn: () => LSPServer.JDTLS.root(file), + }) + + expect(result).toBe(root) + }) + + test("jdtls falls back to nearest build file", async () => { + await using tmp = await tmpdir() + const root = tmp.path + const module = path.join(root, "services/api") + const file = path.join(module, "src/main/java/App.java") + + await fs.mkdir(path.dirname(file), { recursive: true }) + await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(file, "class App {}") + + const result = await Instance.provide({ + directory: root, + fn: () => LSPServer.JDTLS.root(file), + }) + + expect(result).toBe(module) + }) +}) From 322a886728b691eecb01c6bf054ee2ead8d0b043 Mon Sep 17 00:00:00 2001 From: Andrej Kurocenko Date: Wed, 18 Feb 2026 01:29:14 +0100 Subject: [PATCH 2/2] test(opencode): cover groovy gradle roots for jdtls --- .../opencode/test/lsp/server-root.test.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/opencode/test/lsp/server-root.test.ts b/packages/opencode/test/lsp/server-root.test.ts index bc02346ff09..5079a63d038 100644 --- a/packages/opencode/test/lsp/server-root.test.ts +++ b/packages/opencode/test/lsp/server-root.test.ts @@ -13,8 +13,8 @@ describe("lsp.server roots", () => { const file = path.join(module, "src/main/java/App.java") await fs.mkdir(path.dirname(file), { recursive: true }) - await Bun.write(path.join(root, "settings.gradle.kts"), "") - await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(path.join(root, "settings.gradle"), "") + await Bun.write(path.join(module, "build.gradle"), "") await Bun.write(file, "class App {}") const result = await Instance.provide({ @@ -33,7 +33,7 @@ describe("lsp.server roots", () => { await fs.mkdir(path.dirname(file), { recursive: true }) await Bun.write(path.join(root, "gradlew"), "") - await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(path.join(module, "build.gradle"), "") await Bun.write(file, "class App {}") const result = await Instance.provide({ @@ -51,7 +51,7 @@ describe("lsp.server roots", () => { const file = path.join(module, "src/main/java/App.java") await fs.mkdir(path.dirname(file), { recursive: true }) - await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(path.join(module, "build.gradle"), "") await Bun.write(file, "class App {}") const result = await Instance.provide({ @@ -61,4 +61,23 @@ describe("lsp.server roots", () => { expect(result).toBe(module) }) + + test("jdtls supports kotlin gradle markers", async () => { + await using tmp = await tmpdir() + const root = tmp.path + const module = path.join(root, "services/api") + const file = path.join(module, "src/main/java/App.java") + + await fs.mkdir(path.dirname(file), { recursive: true }) + await Bun.write(path.join(root, "settings.gradle.kts"), "") + await Bun.write(path.join(module, "build.gradle.kts"), "") + await Bun.write(file, "class App {}") + + const result = await Instance.provide({ + directory: root, + fn: () => LSPServer.JDTLS.root(file), + }) + + expect(result).toBe(root) + }) })