refactor(core): replace legacy logger with Effect logging (#31310)

This commit is contained in:
Dax
2026-06-08 15:41:56 -04:00
committed by GitHub
parent 0a7cb20e66
commit c06ad7c881
152 changed files with 698 additions and 2243 deletions
+1 -37
View File
@@ -2,7 +2,6 @@ import path from "path"
import { pathToFileURL, fileURLToPath } from "url"
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
import * as Log from "@opencode-ai/core/util/log"
import { Process } from "@/util/process"
import { LANGUAGE_EXTENSIONS } from "./language"
import { Effect, Schema } from "effect"
@@ -23,7 +22,6 @@ const FILE_CHANGE_CREATED = 1
const FILE_CHANGE_CHANGED = 2
const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2
const log = Log.create({ service: "lsp.client" })
export type Info = NonNullable<Awaited<ReturnType<typeof create>>>
export type Diagnostic = VSCodeDiagnostic
@@ -129,23 +127,13 @@ export async function create(input: {
directory: string
instance: InstanceContext
}) {
const logger = log.clone().tag("serverID", input.serverID)
logger.info("starting client")
const instance = input.instance
const connection = createMessageConnection(
new StreamMessageReader(input.server.process.stdout as any),
new StreamMessageWriter(input.server.process.stdin as any),
)
// Server stderr can contain both real errors and routine informational logs,
// which is normal stderr practice for some tools. Keep the raw stream at
// debug so users can opt in with --print-logs --log-level DEBUG without
// polluting normal logs.
input.server.process.stderr?.on("data", (data: Buffer) => {
const text = data.toString().trim()
if (text) logger.debug("server stderr", { text: text.slice(0, 1000) })
})
input.server.process.stderr?.resume()
// --- Connection state ---
const pushDiagnostics = new Map<string, Diagnostic[]>()
@@ -172,11 +160,6 @@ export async function create(input: {
connection.onNotification("textDocument/publishDiagnostics", (params) => {
const filePath = getFilePath(params.uri)
if (!filePath) return
logger.info("textDocument/publishDiagnostics", {
path: filePath,
count: params.diagnostics.length,
version: params.version,
})
published.set(filePath, {
at: Date.now(),
version: typeof params.version === "number" ? params.version : undefined,
@@ -188,7 +171,6 @@ export async function create(input: {
updatePushDiagnostics(filePath, params.diagnostics)
})
connection.onRequest("window/workDoneProgress/create", (params) => {
logger.info("window/workDoneProgress/create", params)
return null
})
connection.onRequest("workspace/configuration", async (params) => {
@@ -226,7 +208,6 @@ export async function create(input: {
// --- Initialize handshake ---
logger.info("sending initialize")
const initialized = await withTimeout(
connection.sendRequest<{ capabilities?: ServerCapabilities }>("initialize", {
rootUri: pathToFileURL(input.root).href,
@@ -270,7 +251,6 @@ export async function create(input: {
}),
INITIALIZE_TIMEOUT_MS,
).catch((err) => {
logger.error("initialize error", { error: err })
throw new InitializeError({ serverID: input.serverID, cause: err })
})
@@ -585,7 +565,6 @@ export async function create(input: {
// re-emit diagnostics when the content actually changes, so clearing
// here would lose errors for no-op touchFile calls. Let the server's
// next push/pull overwrite naturally.
logger.info("workspace/didChangeWatchedFiles", request)
await connection.sendNotification("workspace/didChangeWatchedFiles", {
changes: [
{
@@ -597,10 +576,6 @@ export async function create(input: {
const next = document.version + 1
files[request.path] = { version: next, text }
logger.info("textDocument/didChange", {
path: request.path,
version: next,
})
await connection.sendNotification("textDocument/didChange", {
textDocument: {
uri: pathToFileURL(request.path).href,
@@ -622,7 +597,6 @@ export async function create(input: {
return next
}
logger.info("workspace/didChangeWatchedFiles", request)
await connection.sendNotification("workspace/didChangeWatchedFiles", {
changes: [
{
@@ -632,7 +606,6 @@ export async function create(input: {
],
})
logger.info("textDocument/didOpen", request)
pushDiagnostics.delete(request.path)
pullDiagnostics.delete(request.path)
await connection.sendNotification("textDocument/didOpen", {
@@ -658,11 +631,6 @@ export async function create(input: {
const normalizedPath = Filesystem.normalizePath(
path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path),
)
logger.info("waiting for diagnostics", {
path: normalizedPath,
mode: request.mode ?? "full",
version: request.version,
})
if (request.mode === "document") {
await waitForDocumentDiagnostics({ path: normalizedPath, version: request.version, after: request.after })
return
@@ -670,16 +638,12 @@ export async function create(input: {
await waitForFullDiagnostics({ path: normalizedPath, version: request.version, after: request.after })
},
async shutdown() {
logger.info("shutting down")
connection.end()
connection.dispose()
await Process.stop(input.server.process)
logger.info("shutdown")
},
}
logger.info("initialized")
return result
}
+7 -17
View File
@@ -1,6 +1,5 @@
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import * as Log from "@opencode-ai/core/util/log"
import * as LSPClient from "./client"
import path from "path"
import { pathToFileURL, fileURLToPath } from "url"
@@ -14,8 +13,6 @@ import { containsPath } from "@/project/instance-context"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { RuntimeFlags } from "@/effect/runtime-flags"
const log = Log.create({ service: "lsp" })
export const Event = {
Updated: EventV2.define({ type: "lsp.updated", schema: {} }),
}
@@ -101,7 +98,6 @@ const kinds = [
const filterExperimentalServers = (servers: Record<string, LSPServer.Info>, flags: RuntimeFlags.Info) => {
if (flags.experimentalLspTy) {
if (servers["pyright"]) {
log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled")
delete servers["pyright"]
}
} else {
@@ -153,7 +149,7 @@ export const layer = Layer.effect(
const servers: Record<string, LSPServer.Info> = {}
if (!cfg.lsp) {
log.info("all LSPs are disabled")
yield* Effect.logInfo("all LSPs are disabled")
} else {
for (const server of Object.values(LSPServer)) {
servers[server.id] = server
@@ -165,7 +161,7 @@ export const layer = Layer.effect(
for (const [name, item] of Object.entries(cfg.lsp)) {
const existing = servers[name]
if (item.disabled) {
log.info(`LSP server ${name} is disabled`)
yield* Effect.logInfo(`LSP server ${name} is disabled`)
delete servers[name]
continue
}
@@ -185,7 +181,7 @@ export const layer = Layer.effect(
}
}
log.info("enabled LSP servers", {
yield* Effect.logInfo("enabled LSP servers", {
serverIds: Object.values(servers)
.map((server) => server.id)
.join(", "),
@@ -225,25 +221,21 @@ export const layer = Layer.effect(
if (!value) s.broken.add(key)
return value
})
.catch((err) => {
.catch(() => {
s.broken.add(key)
log.error(`Failed to spawn LSP server ${server.id}`, { error: err })
return undefined
})
if (!handle) return undefined
log.info("spawned lsp server", { serverID: server.id, root })
const client = await LSPClient.create({
serverID: server.id,
server: handle,
root,
directory: ctx.directory,
instance: ctx,
}).catch(async (err) => {
}).catch(async () => {
s.broken.add(key)
await Process.stop(handle.process)
log.error(`Failed to initialize LSP client ${server.id}`, { error: err })
return undefined
})
@@ -350,7 +342,7 @@ export const layer = Layer.effect(
})
const touchFile = Effect.fn("LSP.touchFile")(function* (input: string, diagnostics?: "document" | "full") {
log.info("touching file", { file: input })
yield* Effect.logInfo("touching file", { file: input })
const clients = yield* getClients(input)
yield* Effect.promise(() =>
Promise.all(
@@ -365,9 +357,7 @@ export const layer = Layer.effect(
after,
})
}),
).catch((err) => {
log.error("failed to touch file", { err, file: input })
}),
).catch(() => {}),
)
})
-124
View File
@@ -2,7 +2,6 @@ import type { ChildProcessWithoutNullStreams } from "child_process"
import path from "path"
import os from "os"
import { Global } from "@opencode-ai/core/global"
import * as Log from "@opencode-ai/core/util/log"
import { text } from "node:stream/consumers"
import fs from "fs/promises"
import { Filesystem } from "@/util/filesystem"
@@ -15,7 +14,6 @@ import { spawn } from "./launch"
import { Npm } from "@opencode-ai/core/npm"
import type { RuntimeFlags } from "@/effect/runtime-flags"
const log = Log.create({ service: "lsp.server" })
const pathExists = async (p: string) =>
fs
.stat(p)
@@ -104,7 +102,6 @@ export const Deno: Info = {
async spawn(root) {
const deno = which("deno")
if (!deno) {
log.info("deno not found, please install deno first")
return
}
return {
@@ -124,7 +121,6 @@ export const Typescript: Info = {
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
async spawn(root, ctx) {
const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory)
log.info("typescript server", { tsserver })
if (!tsserver) return
const bin = await Npm.which("typescript-language-server")
if (!bin) return
@@ -181,11 +177,9 @@ export const ESLint: Info = {
async spawn(root, ctx, flags) {
const eslint = Module.resolve("eslint", ctx.directory)
if (!eslint) return
log.info("spawning eslint server")
const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
if (!(await Filesystem.exists(serverPath))) {
if (flags.disableLspDownload) return
log.info("downloading and building VS Code ESLint server")
const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
if (!response.ok) return
@@ -195,7 +189,6 @@ export const ESLint: Info = {
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract vscode-eslint archive", { error })
return false
})
if (!ok) return
@@ -206,7 +199,6 @@ export const ESLint: Info = {
const stats = await fs.stat(finalPath).catch(() => undefined)
if (stats) {
log.info("removing old eslint installation", { path: finalPath })
await fs.rm(finalPath, { force: true, recursive: true })
}
await fs.rename(extractedPath, finalPath)
@@ -215,7 +207,6 @@ export const ESLint: Info = {
await Process.run([npmCmd, "install"], { cwd: finalPath })
await Process.run([npmCmd, "run", "compile"], { cwd: finalPath })
log.info("installed VS Code ESLint server", { serverPath })
}
const proc = spawn("node", [serverPath, "--stdio"], {
@@ -299,7 +290,6 @@ export const Oxlint: Info = {
}
}
log.info("oxlint not found, please install oxlint")
return
},
}
@@ -380,7 +370,6 @@ export const Gopls: Info = {
if (!which("go")) return
if (flags.disableLspDownload) return
log.info("installing gopls")
const proc = Process.spawn(["go", "install", "golang.org/x/tools/gopls@latest"], {
env: { ...process.env, GOBIN: Global.Path.bin },
stdout: "pipe",
@@ -389,13 +378,9 @@ export const Gopls: Info = {
})
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!, {
@@ -415,11 +400,9 @@ export const Rubocop: Info = {
const ruby = which("ruby")
const gem = which("gem")
if (!ruby || !gem) {
log.info("Ruby not found, please install Ruby first")
return
}
if (flags.disableLspDownload) return
log.info("installing rubocop")
const proc = Process.spawn(["gem", "install", "rubocop", "--bindir", Global.Path.bin], {
stdout: "pipe",
stderr: "pipe",
@@ -427,13 +410,9 @@ export const Rubocop: Info = {
})
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install rubocop")
return
}
bin = path.join(Global.Path.bin, "rubocop" + (process.platform === "win32" ? ".exe" : ""))
log.info(`installed rubocop`, {
bin,
})
}
return {
process: spawn(bin!, ["--lsp"], {
@@ -490,7 +469,6 @@ export const Ty: Info = {
}
if (!binary) {
log.error("ty not found, please install ty first")
return
}
@@ -567,12 +545,10 @@ export const ElixirLS: Info = {
if (!(await Filesystem.exists(binary))) {
const elixir = which("elixir")
if (!elixir) {
log.error("elixir is required to run elixir-ls")
return
}
if (flags.disableLspDownload) 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
@@ -582,7 +558,6 @@ export const ElixirLS: Info = {
const ok = await Archive.extractZip(zipPath, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract elixir-ls archive", { error })
return false
})
if (!ok) return
@@ -598,9 +573,6 @@ export const ElixirLS: Info = {
await Process.run(["mix", "compile"], { cwd, env })
await Process.run(["mix", "elixir_ls.release2", "-o", "release"], { cwd, env })
log.info(`installed elixir-ls`, {
path: elixirLsPath,
})
}
}
@@ -622,16 +594,13 @@ export const Zls: Info = {
if (!bin) {
const zig = which("zig")
if (!zig) {
log.error("Zig is required to use zls. Please install Zig first.")
return
}
if (flags.disableLspDownload) return
log.info("downloading zls from GitHub releases")
const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
if (!releaseResponse.ok) {
log.error("Failed to fetch zls release info")
return
}
@@ -668,20 +637,17 @@ export const Zls: Info = {
]
if (!supportedCombos.includes(assetName)) {
log.error(`Platform ${platform} and architecture ${arch} is not supported by zls`)
return
}
const asset = release.assets?.find((a) => a.name === assetName)
if (!asset?.browser_download_url) {
log.error(`Could not find asset ${assetName} in latest zls release`)
return
}
const downloadUrl = asset.browser_download_url
const downloadResponse = await fetch(downloadUrl)
if (!downloadResponse.ok) {
log.error("Failed to download zls")
return
}
@@ -692,7 +658,6 @@ export const Zls: Info = {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract zls archive", { error })
return false
})
if (!ok) return
@@ -705,7 +670,6 @@ export const Zls: Info = {
bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract zls binary")
return
}
@@ -713,7 +677,6 @@ export const Zls: Info = {
await fs.chmod(bin, 0o755).catch(() => {})
}
log.info(`installed zls`, { bin })
}
return {
@@ -750,11 +713,9 @@ export const Razor: Info = {
const razor = await findVscodeRazorExtension()
if (!razor) {
log.info("VS Code C# extension with Razor support not found, skipping Razor LSP")
return
}
log.info("using VS Code Razor extension for roslyn-language-server", { extension: razor.extension })
return {
process: spawn(
bin,
@@ -791,12 +752,10 @@ async function getRoslynLanguageServer(disableLspDownload: boolean) {
async function installRoslynLanguageServer(disableLspDownload: boolean) {
if (!which("dotnet")) {
log.error(".NET SDK is required to install roslyn-language-server")
return
}
if (disableLspDownload) return
log.info("installing roslyn-language-server via dotnet tool")
const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], {
stdout: "pipe",
stderr: "pipe",
@@ -804,23 +763,19 @@ async function installRoslynLanguageServer(disableLspDownload: boolean) {
})
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install roslyn-language-server")
return
}
const resolved = which("roslyn-language-server")
if (resolved) {
log.info(`installed roslyn-language-server`, { bin: resolved })
return resolved
}
const global = await roslynLanguageServerGlobalPath()
if (global) {
log.info(`installed roslyn-language-server`, { bin: global })
return global
}
log.error("Installed roslyn-language-server but could not resolve executable")
}
async function roslynLanguageServerGlobalPath() {
@@ -877,12 +832,10 @@ export const FSharp: Info = {
let bin = which("fsautocomplete")
if (!bin) {
if (!which("dotnet")) {
log.error(".NET SDK is required to install fsautocomplete")
return
}
if (flags.disableLspDownload) return
log.info("installing fsautocomplete via dotnet tool")
const proc = Process.spawn(["dotnet", "tool", "install", "fsautocomplete", "--tool-path", Global.Path.bin], {
stdout: "pipe",
stderr: "pipe",
@@ -890,12 +843,10 @@ export const FSharp: Info = {
})
const exit = await proc.exited
if (exit !== 0) {
log.error("Failed to install fsautocomplete")
return
}
bin = path.join(Global.Path.bin, "fsautocomplete" + (process.platform === "win32" ? ".exe" : ""))
log.info(`installed fsautocomplete`, { bin })
}
return {
@@ -975,7 +926,6 @@ export const RustAnalyzer: Info = {
async spawn(root) {
const bin = which("rust-analyzer")
if (!bin) {
log.info("rust-analyzer not found in path, please install it")
return
}
return {
@@ -1026,11 +976,9 @@ export const Clangd: Info = {
}
if (flags.disableLspDownload) return
log.info("downloading clangd from GitHub releases")
const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
if (!releaseResponse.ok) {
log.error("Failed to fetch clangd release info")
return
}
@@ -1041,7 +989,6 @@ export const Clangd: Info = {
const tag = release.tag_name
if (!tag) {
log.error("clangd release did not include a tag name")
return
}
const platform = process.platform
@@ -1052,7 +999,6 @@ export const Clangd: Info = {
}
const token = tokens[platform]
if (!token) {
log.error(`Platform ${platform} is not supported by clangd auto-download`)
return
}
@@ -1069,21 +1015,18 @@ export const Clangd: Info = {
assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ??
assets.find((item) => valid(item))
if (!asset?.name || !asset.browser_download_url) {
log.error("clangd could not match release asset", { tag, platform })
return
}
const name = asset.name
const downloadResponse = await fetch(asset.browser_download_url)
if (!downloadResponse.ok) {
log.error("Failed to download clangd")
return
}
const archive = path.join(Global.Path.bin, name)
const buf = await downloadResponse.arrayBuffer()
if (buf.byteLength === 0) {
log.error("Failed to write clangd archive")
return
}
await Filesystem.write(archive, Buffer.from(buf))
@@ -1091,7 +1034,6 @@ export const Clangd: Info = {
const zip = name.endsWith(".zip")
const tar = name.endsWith(".tar.xz")
if (!zip && !tar) {
log.error("clangd encountered unsupported asset", { asset: name })
return
}
@@ -1099,7 +1041,6 @@ export const Clangd: Info = {
const ok = await Archive.extractZip(archive, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract clangd archive", { error })
return false
})
if (!ok) return
@@ -1111,7 +1052,6 @@ export const Clangd: Info = {
const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract clangd binary")
return
}
@@ -1122,7 +1062,6 @@ export const Clangd: Info = {
await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {})
await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {})
log.info(`installed clangd`, { bin })
return {
process: spawn(bin, args, {
@@ -1166,7 +1105,6 @@ export const Astro: Info = {
async spawn(root, ctx, flags) {
const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory)
if (!tsserver) {
log.info("typescript not found, required for Astro language server")
return
}
const tsdk = path.dirname(tsserver)
@@ -1255,7 +1193,6 @@ export const JDTLS: Info = {
async spawn(root, _ctx, flags) {
const java = which("java")
if (!java) {
log.error("Java 21 or newer is required to run the JDTLS. Please install it first.")
return
}
const javaMajorVersion = await run(["java", "-version"]).then((result) => {
@@ -1263,7 +1200,6 @@ export const JDTLS: Info = {
return !m ? undefined : parseInt(m[1])
})
if (javaMajorVersion == null || javaMajorVersion < 21) {
log.error("JDTLS requires at least Java 21.")
return
}
const distPath = path.join(Global.Path.bin, "jdtls")
@@ -1271,29 +1207,23 @@ export const JDTLS: Info = {
const installed = await pathExists(launcherDir)
if (!installed) {
if (flags.disableLspDownload) return
log.info("Downloading JDTLS LSP server.")
await fs.mkdir(distPath, { recursive: true })
const releaseURL =
"https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz"
const archiveName = "release.tar.gz"
log.info("Downloading JDTLS archive", { url: releaseURL, dest: distPath })
const download = await fetch(releaseURL)
if (!download.ok || !download.body) {
log.error("Failed to download JDTLS", { status: download.status, statusText: download.statusText })
return
}
await Filesystem.writeStream(path.join(distPath, archiveName), download.body)
log.info("Extracting JDTLS archive")
const tarResult = await run(["tar", "-xzf", archiveName], { cwd: distPath })
if (tarResult.code !== 0) {
log.error("Failed to extract JDTLS", { exitCode: tarResult.code, stderr: tarResult.stderr.toString() })
return
}
await fs.rm(path.join(distPath, archiveName), { force: true })
log.info("JDTLS download and extraction completed")
}
const jarFileName =
(await fs.readdir(launcherDir).catch(() => []))
@@ -1301,7 +1231,6 @@ export const JDTLS: Info = {
?.trim() ?? ""
const launcherJar = path.join(launcherDir, jarFileName)
if (!(await pathExists(launcherJar))) {
log.error(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`)
return
}
const configFile = path.join(
@@ -1369,11 +1298,9 @@ export const KotlinLS: Info = {
const installed = await Filesystem.exists(launcherScript)
if (!installed) {
if (flags.disableLspDownload) 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
}
@@ -1381,7 +1308,6 @@ export const KotlinLS: Info = {
const version = release.name?.replace(/^v/, "")
if (!version) {
log.error("Could not determine Kotlin LSP version from release")
return
}
@@ -1402,7 +1328,6 @@ export const KotlinLS: Info = {
const combo = `${kotlinPlatform}-${kotlinArch}`
if (!supportedCombos.includes(combo)) {
log.error(`Platform ${platform}/${arch} is not supported by Kotlin LSP`)
return
}
@@ -1413,17 +1338,12 @@ export const KotlinLS: Info = {
const archivePath = path.join(distPath, "kotlin-ls.zip")
const download = await fetch(releaseURL)
if (!download.ok || !download.body) {
log.error("Failed to download Kotlin Language Server", {
status: download.status,
statusText: download.statusText,
})
return
}
await Filesystem.writeStream(archivePath, download.body)
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
@@ -1431,10 +1351,8 @@ export const KotlinLS: Info = {
if (process.platform !== "win32") {
await fs.chmod(launcherScript, 0o755).catch(() => {})
}
log.info("Installed Kotlin Language Server", { path: launcherScript })
}
if (!(await Filesystem.exists(launcherScript))) {
log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`)
return
}
return {
@@ -1488,11 +1406,9 @@ export const LuaLS: Info = {
if (!bin) {
if (flags.disableLspDownload) return
log.info("downloading lua-language-server from GitHub releases")
const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest")
if (!releaseResponse.ok) {
log.error("Failed to fetch lua-language-server release info")
return
}
@@ -1527,20 +1443,17 @@ export const LuaLS: Info = {
const assetSuffix = `${lualsPlatform}-${lualsArch}.${ext}`
if (!supportedCombos.includes(assetSuffix)) {
log.error(`Platform ${platform} and architecture ${arch} is not supported by lua-language-server`)
return
}
const asset = release.assets.find((a: any) => a.name === assetName)
if (!asset) {
log.error(`Could not find asset ${assetName} in latest lua-language-server release`)
return
}
const downloadUrl = asset.browser_download_url
const downloadResponse = await fetch(downloadUrl)
if (!downloadResponse.ok) {
log.error("Failed to download lua-language-server")
return
}
@@ -1564,7 +1477,6 @@ export const LuaLS: Info = {
const ok = await Archive.extractZip(tempPath, installDir)
.then(() => true)
.catch((error) => {
log.error("Failed to extract lua-language-server archive", { error })
return false
})
if (!ok) return
@@ -1572,7 +1484,6 @@ export const LuaLS: Info = {
const ok = await run(["tar", "-xzf", tempPath, "-C", installDir])
.then((result) => result.code === 0)
.catch((error: unknown) => {
log.error("Failed to extract lua-language-server archive", { error })
return false
})
if (!ok) return
@@ -1584,7 +1495,6 @@ export const LuaLS: Info = {
bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract lua-language-server binary")
return
}
@@ -1593,15 +1503,11 @@ export const LuaLS: Info = {
.chmod(bin, 0o755)
.then(() => true)
.catch((error: unknown) => {
log.error("Failed to set executable permission for lua-language-server binary", {
error,
})
return false
})
if (!ok) return
}
log.info(`installed lua-language-server`, { bin })
}
return {
@@ -1650,7 +1556,6 @@ export const Prisma: Info = {
async spawn(root) {
const prisma = which("prisma")
if (!prisma) {
log.info("prisma not found, please install prisma")
return
}
return {
@@ -1668,7 +1573,6 @@ export const Dart: Info = {
async spawn(root) {
const dart = which("dart")
if (!dart) {
log.info("dart not found, please install dart first")
return
}
return {
@@ -1686,7 +1590,6 @@ export const Ocaml: Info = {
async spawn(root) {
const bin = which("ocamllsp")
if (!bin) {
log.info("ocamllsp not found, please install ocaml-lsp-server")
return
}
return {
@@ -1731,11 +1634,9 @@ export const TerraformLS: Info = {
if (!bin) {
if (flags.disableLspDownload) return
log.info("downloading terraform-ls from HashiCorp releases")
const releaseResponse = await fetch("https://api.releases.hashicorp.com/v1/releases/terraform-ls/latest")
if (!releaseResponse.ok) {
log.error("Failed to fetch terraform-ls release info")
return
}
@@ -1753,13 +1654,11 @@ export const TerraformLS: Info = {
const builds = release.builds ?? []
const build = builds.find((b) => b.arch === tfArch && b.os === tfPlatform)
if (!build?.url) {
log.error(`Could not find build for ${tfPlatform}/${tfArch} terraform-ls release version ${release.version}`)
return
}
const downloadResponse = await fetch(build.url)
if (!downloadResponse.ok) {
log.error("Failed to download terraform-ls")
return
}
@@ -1769,7 +1668,6 @@ export const TerraformLS: Info = {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract terraform-ls archive", { error })
return false
})
if (!ok) return
@@ -1778,7 +1676,6 @@ export const TerraformLS: Info = {
bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract terraform-ls binary")
return
}
@@ -1786,7 +1683,6 @@ export const TerraformLS: Info = {
await fs.chmod(bin, 0o755).catch(() => {})
}
log.info(`installed terraform-ls`, { bin })
}
return {
@@ -1812,11 +1708,9 @@ export const TexLab: Info = {
if (!bin) {
if (flags.disableLspDownload) return
log.info("downloading texlab from GitHub releases")
const response = await fetch("https://api.github.com/repos/latex-lsp/texlab/releases/latest")
if (!response.ok) {
log.error("Failed to fetch texlab release info")
return
}
@@ -1826,7 +1720,6 @@ export const TexLab: Info = {
}
const version = release.tag_name?.replace("v", "")
if (!version) {
log.error("texlab release did not include a version tag")
return
}
@@ -1841,13 +1734,11 @@ export const TexLab: Info = {
const assets = release.assets ?? []
const asset = assets.find((a) => a.name === assetName)
if (!asset?.browser_download_url) {
log.error(`Could not find asset ${assetName} in texlab release`)
return
}
const downloadResponse = await fetch(asset.browser_download_url)
if (!downloadResponse.ok) {
log.error("Failed to download texlab")
return
}
@@ -1858,7 +1749,6 @@ export const TexLab: Info = {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract texlab archive", { error })
return false
})
if (!ok) return
@@ -1872,7 +1762,6 @@ export const TexLab: Info = {
bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : ""))
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract texlab binary")
return
}
@@ -1880,7 +1769,6 @@ export const TexLab: Info = {
await fs.chmod(bin, 0o755).catch(() => {})
}
log.info("installed texlab", { bin })
}
return {
@@ -1924,7 +1812,6 @@ export const Gleam: Info = {
async spawn(root) {
const gleam = which("gleam")
if (!gleam) {
log.info("gleam not found, please install gleam first")
return
}
return {
@@ -1945,7 +1832,6 @@ export const Clojure: Info = {
bin = which("clojure-lsp.exe")
}
if (!bin) {
log.info("clojure-lsp not found, please install clojure-lsp first")
return
}
return {
@@ -1973,7 +1859,6 @@ export const Nixd: Info = {
async spawn(root) {
const nixd = which("nixd")
if (!nixd) {
log.info("nixd not found, please install nixd first")
return
}
return {
@@ -1996,11 +1881,9 @@ export const Tinymist: Info = {
if (!bin) {
if (flags.disableLspDownload) return
log.info("downloading tinymist from GitHub releases")
const response = await fetch("https://api.github.com/repos/Myriad-Dreamin/tinymist/releases/latest")
if (!response.ok) {
log.error("Failed to fetch tinymist release info")
return
}
@@ -2032,13 +1915,11 @@ export const Tinymist: Info = {
const assets = release.assets ?? []
const asset = assets.find((a) => a.name === assetName)
if (!asset?.browser_download_url) {
log.error(`Could not find asset ${assetName} in tinymist release`)
return
}
const downloadResponse = await fetch(asset.browser_download_url)
if (!downloadResponse.ok) {
log.error("Failed to download tinymist")
return
}
@@ -2049,7 +1930,6 @@ export const Tinymist: Info = {
const ok = await Archive.extractZip(tempPath, Global.Path.bin)
.then(() => true)
.catch((error) => {
log.error("Failed to extract tinymist archive", { error })
return false
})
if (!ok) return
@@ -2062,7 +1942,6 @@ export const Tinymist: Info = {
bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : ""))
if (!(await Filesystem.exists(bin))) {
log.error("Failed to extract tinymist binary")
return
}
@@ -2070,7 +1949,6 @@ export const Tinymist: Info = {
await fs.chmod(bin, 0o755).catch(() => {})
}
log.info("installed tinymist", { bin })
}
return {
@@ -2086,7 +1964,6 @@ export const HLS: Info = {
async spawn(root) {
const bin = which("haskell-language-server-wrapper")
if (!bin) {
log.info("haskell-language-server-wrapper not found, please install haskell-language-server")
return
}
return {
@@ -2104,7 +1981,6 @@ export const JuliaLS: Info = {
async spawn(root) {
const julia = which("julia")
if (!julia) {
log.info("julia not found, please install julia first (https://julialang.org/downloads/)")
return
}
return {