Files
neuron/packages/opencode/src/lsp/server.ts
T
2025-07-03 19:29:51 -04:00

206 lines
5.2 KiB
TypeScript

import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
import type { App } from "../app/app"
import path from "path"
import { Global } from "../global"
import { Log } from "../util/log"
import { BunProc } from "../bun"
import { $ } from "bun"
import fs from "fs/promises"
export namespace LSPServer {
const log = Log.create({ service: "lsp.server" })
export interface Handle {
process: ChildProcessWithoutNullStreams
initialization?: Record<string, any>
}
export interface Info {
id: string
extensions: string[]
spawn(app: App.Info): Promise<Handle | undefined>
}
export const Typescript: Info = {
id: "typescript",
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
async spawn(app) {
const tsserver = await Bun.resolve(
"typescript/lib/tsserver.js",
app.path.cwd,
).catch(() => {})
if (!tsserver) return
const proc = spawn(
BunProc.which(),
["x", "typescript-language-server", "--stdio"],
{
env: {
...process.env,
BUN_BE_BUN: "1",
},
},
)
return {
process: proc,
initialization: {
tsserver: {
path: tsserver,
},
},
}
},
}
export const Gopls: Info = {
id: "golang",
extensions: [".go"],
async spawn() {
let bin = Bun.which("gopls", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
if (!bin) {
if (!Bun.which("go")) return
log.info("installing gopls")
const proc = Bun.spawn({
cmd: ["go", "install", "golang.org/x/tools/gopls@latest"],
env: { ...process.env, GOBIN: Global.Path.bin },
stdout: "pipe",
stderr: "pipe",
stdin: "pipe",
})
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install gopls")
return
}
bin = path.join(
Global.Path.bin,
"gopls" + (process.platform === "win32" ? ".exe" : ""),
)
log.info(`installed gopls`, {
bin,
})
}
return {
process: spawn(bin!),
}
},
}
export const RubyLsp: Info = {
id: "ruby-lsp",
extensions: [".rb", ".rake", ".gemspec", ".ru"],
async spawn() {
let bin = Bun.which("ruby-lsp", {
PATH: process.env["PATH"] + ":" + Global.Path.bin,
})
if (!bin) {
const ruby = Bun.which("ruby")
const gem = Bun.which("gem")
if (!ruby || !gem) {
log.info("Ruby not found, please install Ruby first")
return
}
log.info("installing ruby-lsp")
const proc = Bun.spawn({
cmd: ["gem", "install", "ruby-lsp", "--bindir", Global.Path.bin],
stdout: "pipe",
stderr: "pipe",
stdin: "pipe",
})
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install ruby-lsp")
return
}
bin = path.join(
Global.Path.bin,
"ruby-lsp" + (process.platform === "win32" ? ".exe" : ""),
)
log.info(`installed ruby-lsp`, {
bin,
})
}
return {
process: spawn(bin!, ["--stdio"]),
}
},
}
export const Pyright: Info = {
id: "pyright",
extensions: [".py", ".pyi"],
async spawn() {
const proc = spawn(
BunProc.which(),
["x", "pyright-langserver", "--stdio"],
{
env: {
...process.env,
BUN_BE_BUN: "1",
},
},
)
return {
process: proc,
}
},
}
export const ElixirLS: Info = {
id: "elixir-ls",
extensions: [".ex", ".exs"],
async spawn() {
let binary = Bun.which("elixir-ls")
if (!binary) {
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
binary = path.join(
Global.Path.bin,
"elixir-ls-master",
"release",
process.platform === "win32"
? "language_server.bar"
: "language_server.sh",
)
if (!(await Bun.file(binary).exists())) {
const elixir = Bun.which("elixir")
if (!elixir) {
log.error("elixir is required to run elixir-ls")
return
}
log.info("downloading elixir-ls from GitHub releases")
const response = await fetch(
"https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip",
)
if (!response.ok) return
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
await Bun.file(zipPath).write(response)
await $`unzip -o -q ${zipPath}`.cwd(Global.Path.bin).nothrow()
await fs.rm(zipPath, {
force: true,
recursive: true,
})
await $`mix deps.get && mix compile && mix elixir_ls.release2 -o release`
.quiet()
.cwd(path.join(Global.Path.bin, "elixir-ls-master"))
.env({ MIX_ENV: "prod", ...process.env })
log.info(`installed elixir-ls`, {
path: elixirLsPath,
})
}
}
return {
process: spawn(binary),
}
},
}
}