Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/opencode/src/lsp/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
".ini": "ini",
".java": "java",
".js": "javascript",
".kt": "kotlin",
".kts": "kotlin",
".jsx": "javascriptreact",
".json": "json",
".tex": "latex",
Expand Down
83 changes: 83 additions & 0 deletions packages/opencode/src/lsp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, thanks for your work on this. I am really looking forward to it!

But in my opinion, this currently doesn't correctly support multi-project structures, as it would detect a subproject/module as root.

I would suggest:

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),

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch that should be fixed

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 Bun.file(launcherScript).exists()
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 Bun.file(launcherScript).exists())) {
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"],
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/content/docs/lsp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down