From 8018396682f9426c67d2df46950eb8956246cf70 Mon Sep 17 00:00:00 2001 From: Troy Gaines Date: Wed, 31 Dec 2025 18:47:21 -0600 Subject: [PATCH 1/4] feat: Add support for Kotlin language server --- packages/opencode/src/lsp/language.ts | 2 + packages/opencode/src/lsp/server.ts | 83 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/packages/opencode/src/lsp/language.ts b/packages/opencode/src/lsp/language.ts index d279f7d64e7c..430b10caa236 100644 --- a/packages/opencode/src/lsp/language.ts +++ b/packages/opencode/src/lsp/language.ts @@ -45,6 +45,8 @@ export const LANGUAGE_EXTENSIONS: Record = { ".ini": "ini", ".java": "java", ".js": "javascript", + ".kt": "kotlin", + ".kts": "kotlin", ".jsx": "javascriptreact", ".json": "json", ".tex": "latex", diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 379c2db89d4c..992551e31c48 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1209,6 +1209,89 @@ 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"]), + async spawn(root) { + const distPath = path.join(Global.Path.bin, "kotlin-ls") + const launcherScript = + process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh") + const installed = await fs.exists(launcherScript) + if (!installed) { + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return + log.info("Downloading Kotlin Language Server from GitHub.") + + const releaseResponse = await fetch("https://api.github.com/repos/Kotlin/kotlin-lsp/releases/latest") + if (!releaseResponse.ok) { + log.error("Failed to fetch kotlin-lsp release info") + return + } + + const release = await releaseResponse.json() + const version = release.name.replace(/^v/, '') + + if (!version) { + log.error("Could not determine Kotlin LSP version from release") + return + } + + const platform = process.platform + const arch = process.arch + + let kotlinArch: string = arch + if (arch === "arm64") kotlinArch = "aarch64" + else if (arch === "x64") kotlinArch = "x64" + + let kotlinPlatform: string = platform + if (platform === "darwin") kotlinPlatform = "mac" + else if (platform === "linux") kotlinPlatform = "linux" + else if (platform === "win32") kotlinPlatform = "win" + + const supportedCombos = [ + "mac-x64", "mac-aarch64", + "linux-x64", "linux-aarch64", + "win-x64", "win-aarch64" + ] + + const combo = `${kotlinPlatform}-${kotlinArch}` + + if (!supportedCombos.includes(combo)) { + log.error(`Platform ${platform}/${arch} is not supported by Kotlin LSP`) + return + } + + const assetName = `kotlin-lsp-${version}-${kotlinPlatform}-${kotlinArch}.zip` + const releaseURL = `https://download-cdn.jetbrains.com/kotlin-lsp/${version}/${assetName}` + + await fs.mkdir(distPath, { recursive: true }) + const archivePath = path.join(distPath, "kotlin-ls.zip") + await $`curl -L -o '${archivePath}' '${releaseURL}'`.quiet().nothrow() + const ok = await Archive.extractZip(archivePath, distPath) + .then(() => true) + .catch((error) => { + log.error("Failed to extract Kotlin LS archive", { error }) + return false + }) + if (!ok) return + await fs.rm(archivePath, { force: true }) + if (process.platform !== "win32") { + await $`chmod +x ${launcherScript}`.quiet().nothrow() + } + log.info("Installed Kotlin Language Server", { path: launcherScript }) + } + if (!(await fs.exists(launcherScript))) { + log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`) + return + } + return { + process: spawn(launcherScript, ["--stdio"], { + cwd: root, + }), + } + }, + } + export const YamlLS: Info = { id: "yaml-ls", extensions: [".yaml", ".yml"], From 5e275262608e12fba5ad9a42e71e0ff3baa7f936 Mon Sep 17 00:00:00 2001 From: Troy Gaines Date: Thu, 1 Jan 2026 19:54:27 -0600 Subject: [PATCH 2/4] Fix possible null pointer --- packages/opencode/src/lsp/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 992551e31c48..6d7b9105b1f3 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1229,7 +1229,7 @@ export namespace LSPServer { } const release = await releaseResponse.json() - const version = release.name.replace(/^v/, '') + const version = release.name?.replace(/^v/, '') if (!version) { log.error("Could not determine Kotlin LSP version from release") From bc31710abf7f241c461153bde9650c42408c43bc Mon Sep 17 00:00:00 2001 From: Troy Gaines Date: Fri, 2 Jan 2026 12:49:40 -0600 Subject: [PATCH 3/4] Using Bun api to match style guide --- packages/opencode/src/lsp/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 6d7b9105b1f3..07c7839c6431 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1217,7 +1217,7 @@ export namespace LSPServer { const distPath = path.join(Global.Path.bin, "kotlin-ls") const launcherScript = process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh") - const installed = await fs.exists(launcherScript) + const installed = await Bun.file(launcherScript).exists() if (!installed) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return log.info("Downloading Kotlin Language Server from GitHub.") @@ -1280,7 +1280,7 @@ export namespace LSPServer { } log.info("Installed Kotlin Language Server", { path: launcherScript }) } - if (!(await fs.exists(launcherScript))) { + if (!(await Bun.file(launcherScript).exists())) { log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`) return } From 0c7d4b3ecd0187ee95b11992833b11b88937c5bc Mon Sep 17 00:00:00 2001 From: Troy Gaines Date: Fri, 2 Jan 2026 12:52:25 -0600 Subject: [PATCH 4/4] Updating documentation to match Kotlin lsp --- packages/web/src/content/docs/lsp.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/src/content/docs/lsp.mdx b/packages/web/src/content/docs/lsp.mdx index a4232a70e744..c1d23d4f50a0 100644 --- a/packages/web/src/content/docs/lsp.mdx +++ b/packages/web/src/content/docs/lsp.mdx @@ -26,6 +26,7 @@ OpenCode comes with several built-in LSP servers for popular languages: | gleam | .gleam | `gleam` command available | | gopls | .go | `go` command available | | jdtls | .java | `Java SDK (version 21+)` installed | +| kotlin-ls | .kt, .kts | Auto-installs for Kotlin projects | | lua-ls | .lua | Auto-installs for Lua projects | | nixd | .nix | `nixd` command available | | ocaml-lsp | .ml, .mli | `ocamllsp` command available |