diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 6a3f60073..412377693 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -29,6 +29,7 @@ export namespace Flag { export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS") export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX") export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT") + export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("OPENCODE_EXPERIMENTAL_LSP_TY") function truthy(key: string) { const value = process.env[key]?.toLowerCase() diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index 764c91fcc..9fd0ec643 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -9,6 +9,7 @@ import z from "zod" import { Config } from "../config/config" import { spawn } from "child_process" import { Instance } from "../project/instance" +import { Flag } from "@/flag/flag" export namespace LSP { const log = Log.create({ service: "lsp" }) @@ -60,6 +61,21 @@ export namespace LSP { }) export type DocumentSymbol = z.infer + const filterExperimentalServers = (servers: Record) => { + if (Flag.OPENCODE_EXPERIMENTAL_LSP_TY) { + // If experimental flag is enabled, disable pyright + if(servers["pyright"]) { + log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled") + delete servers["pyright"] + } + } else { + // If experimental flag is disabled, disable ty + if(servers["ty"]) { + delete servers["ty"] + } + } + } + const state = Instance.state( async () => { const clients: LSPClient.Info[] = [] @@ -79,6 +95,9 @@ export namespace LSP { for (const server of Object.values(LSPServer)) { servers[server.id] = server } + + filterExperimentalServers(servers) + for (const [name, item] of Object.entries(cfg.lsp ?? {})) { const existing = servers[name] if (item.disabled) { @@ -204,6 +223,7 @@ export namespace LSP { for (const server of Object.values(s.servers)) { if (server.extensions.length && !server.extensions.includes(extension)) continue + const root = await server.root(file) if (!root) continue if (s.broken.has(root + server.id)) continue diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index 939a31a2d..82d767188 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -361,6 +361,62 @@ export namespace LSPServer { }, } + export const Ty: Info = { + id: "ty", + extensions: [".py", ".pyi"], + root: NearestRoot(["pyproject.toml", "ty.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]), + async spawn(root) { + if(!Flag.OPENCODE_EXPERIMENTAL_LSP_TY) { + return undefined + } + + let binary = Bun.which("ty") + + const initialization: Record = {} + + const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")].filter( + (p): p is string => p !== undefined, + ) + for (const venvPath of potentialVenvPaths) { + const isWindows = process.platform === "win32" + const potentialPythonPath = isWindows + ? path.join(venvPath, "Scripts", "python.exe") + : path.join(venvPath, "bin", "python") + if (await Bun.file(potentialPythonPath).exists()) { + initialization["pythonPath"] = potentialPythonPath + break + } + } + + if(!binary) { + for (const venvPath of potentialVenvPaths) { + const isWindows = process.platform === "win32" + const potentialTyPath = isWindows + ? path.join(venvPath, "Scripts", "ty.exe") + : path.join(venvPath, "bin", "ty") + if (await Bun.file(potentialTyPath).exists()) { + binary = potentialTyPath + break + } + } + } + + if(!binary) { + log.error("ty not found, please install ty first") + return + } + + const proc = spawn(binary, ["server"], { + cwd: root, + }) + + return { + process: proc, + initialization, + } + }, + } + export const Pyright: Info = { id: "pyright", extensions: [".py", ".pyi"],