From 8917a4c609362d407a8a4e2f25423d474215edda Mon Sep 17 00:00:00 2001 From: rari404 <138394996+edlsh@users.noreply.github.com> Date: Sat, 13 Dec 2025 00:50:09 -0500 Subject: [PATCH] feat: add texlab language server and latexindent formatter (#5251) --- packages/opencode/src/format/formatter.ts | 9 +++ packages/opencode/src/lsp/server.ts | 84 +++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index bcde6e7aa..4e49bb324 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -275,3 +275,12 @@ export const terraform: Info = { return Bun.which("terraform") !== null }, } + +export const latexindent: Info = { + name: "latexindent", + command: ["latexindent", "-w", "-s", "$FILE"], + extensions: [".tex"], + async enabled() { + return Bun.which("latexindent") !== null + }, +} diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index daee7db73..5230117ee 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -1386,4 +1386,88 @@ export namespace LSPServer { } }, } + + export const TexLab: Info = { + id: "texlab", + extensions: [".tex", ".bib"], + root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]), + async spawn(root) { + let bin = Bun.which("texlab", { + PATH: process.env["PATH"] + ":" + Global.Path.bin, + }) + + if (!bin) { + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) 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 + } + + const release = (await response.json()) as { + tag_name?: string + assets?: { name?: string; browser_download_url?: string }[] + } + const version = release.tag_name?.replace("v", "") + if (!version) { + log.error("texlab release did not include a version tag") + return + } + + const platform = process.platform + const arch = process.arch + + const texArch = arch === "arm64" ? "aarch64" : "x86_64" + const texPlatform = platform === "darwin" ? "macos" : platform === "win32" ? "windows" : "linux" + const ext = platform === "win32" ? "zip" : "tar.gz" + const assetName = `texlab-${texArch}-${texPlatform}.${ext}` + + 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 + } + + const tempPath = path.join(Global.Path.bin, assetName) + await Bun.file(tempPath).write(downloadResponse) + + if (ext === "zip") { + await $`unzip -o -q ${tempPath}`.cwd(Global.Path.bin).nothrow() + } + if (ext === "tar.gz") { + await $`tar -xzf ${tempPath}`.cwd(Global.Path.bin).nothrow() + } + + await fs.rm(tempPath, { force: true }) + + bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : "")) + + if (!(await Bun.file(bin).exists())) { + log.error("Failed to extract texlab binary") + return + } + + if (platform !== "win32") { + await $`chmod +x ${bin}`.nothrow() + } + + log.info("installed texlab", { bin }) + } + + return { + process: spawn(bin, { + cwd: root, + }), + } + }, + } }