diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index 66cc8eaa856..45a7bffe926 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -96,7 +96,7 @@ export namespace LSPClient { }, }, }), - 5_000, + 15_000, ).catch((err) => { l.error("initialize error", { error: err }) throw new InitializeError( diff --git a/packages/opencode/src/lsp/language.ts b/packages/opencode/src/lsp/language.ts index 2b6e0476c7e..77808d67824 100644 --- a/packages/opencode/src/lsp/language.ts +++ b/packages/opencode/src/lsp/language.ts @@ -98,4 +98,6 @@ export const LANGUAGE_EXTENSIONS: Record = { ".vue": "vue", ".zig": "zig", ".zon": "zig", + ".nf": "nextflow", + ".config": "nextflow", } as const diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index e4b77be045f..38d34b7c906 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -706,4 +706,130 @@ export namespace LSPServer { } }, } + + export const NextflowLS: Info = { + id: "nextflow-ls", + extensions: [".nf", ".config"], + root: async (file) => { + // First try to find nextflow.config + const configRoot = await NearestRoot(["nextflow.config"])(file) + if (configRoot) return configRoot + // Fall back to git ancestor + return NearestRoot([".git"])(file) + }, + async spawn(root) { + try { + log.info("starting Nextflow Language Server", { root }) + + const java = Bun.which("java") + if (!java) { + log.error("Java is required to run Nextflow Language Server. Please install Java 17 or later.") + return + } + log.info("found Java executable", { java }) + + const nextflowLsDir = path.join(Global.Path.bin, "nextflow-ls") + const jarPath = path.join(nextflowLsDir, "language-server-all.jar") + log.info("checking for Nextflow Language Server JAR", { jarPath }) + + if (!(await Bun.file(jarPath).exists())) { + if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) { + log.warn("Nextflow Language Server JAR not found and downloads disabled") + return + } + + log.info("downloading Nextflow Language Server from GitHub releases") + + try { + // Create nextflow-ls directory + await fs.mkdir(nextflowLsDir, { recursive: true }) + log.info("created directory", { nextflowLsDir }) + + // Fetch latest release + log.info("fetching latest release info from GitHub") + const releaseResponse = await fetch("https://api.github.com/repos/nextflow-io/language-server/releases/latest") + if (!releaseResponse.ok) { + log.error("Failed to fetch Nextflow Language Server release info", { + status: releaseResponse.status, + statusText: releaseResponse.statusText + }) + return + } + + const release = await releaseResponse.json() + log.info("found release", { version: release.tag_name, assetsCount: release.assets?.length }) + + const asset = release.assets.find((a: any) => a.name === "language-server-all.jar") + if (!asset) { + log.error("Could not find language-server-all.jar in latest Nextflow Language Server release", { + availableAssets: release.assets?.map((a: any) => a.name) + }) + return + } + + const downloadUrl = asset.browser_download_url + log.info("downloading JAR file", { downloadUrl, size: asset.size }) + + const downloadResponse = await fetch(downloadUrl) + if (!downloadResponse.ok) { + log.error("Failed to download Nextflow Language Server JAR", { + status: downloadResponse.status, + statusText: downloadResponse.statusText + }) + return + } + + const jarData = await downloadResponse.arrayBuffer() + await Bun.file(jarPath).write(jarData) + + // Validate the downloaded file + const downloadedFile = await Bun.file(jarPath) + const fileSize = downloadedFile.size + if (fileSize === 0) { + log.error("Downloaded JAR file is empty") + await fs.rm(jarPath, { force: true }) + return + } + + log.info(`successfully installed Nextflow Language Server`, { + jarPath, + version: release.tag_name, + fileSize + }) + } catch (downloadError) { + log.error("Error during Nextflow Language Server download", { error: downloadError }) + // Clean up any partial download + try { + await fs.rm(jarPath, { force: true }) + } catch (cleanupError) { + log.warn("Failed to clean up partial download", { error: cleanupError }) + } + return + } + } else { + log.info("Nextflow Language Server JAR already exists", { jarPath }) + } + + // Spawn the Java process + log.info("spawning Nextflow Language Server process", { java, jarPath, cwd: root }) + const process = spawn(java, ["-jar", jarPath], { + cwd: root, + stdio: ["pipe", "pipe", "pipe"] + }) + + // Add error handling for the process + process.on('error', (error) => { + log.error("Nextflow Language Server process error", { error }) + }) + + return { + process, + initialization: {}, + } + } catch (error) { + log.error("Failed to start Nextflow Language Server", { error }) + return + } + }, + } }