summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOpeOginni <[email protected]>2025-12-18 20:37:48 +0100
committerGitHub <[email protected]>2025-12-18 13:37:48 -0600
commitab9ac7c87a22dc97a53aeada2e9337401f9acaad (patch)
treef10bbfa53d24d9f2deddf8a755171f4ff63c5b21
parentee9f9796134ea0227f3ce228bcb4903eee92ef80 (diff)
downloadopencode-ab9ac7c87a22dc97a53aeada2e9337401f9acaad.tar.gz
opencode-ab9ac7c87a22dc97a53aeada2e9337401f9acaad.zip
feat: add experimental support for Ty language server (#5575)
-rw-r--r--packages/opencode/src/flag/flag.ts1
-rw-r--r--packages/opencode/src/lsp/index.ts20
-rw-r--r--packages/opencode/src/lsp/server.ts56
3 files changed, 77 insertions, 0 deletions
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<typeof DocumentSymbol>
+ const filterExperimentalServers = (servers: Record<string, LSPServer.Info>) => {
+ 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<string, string> = {}
+
+ 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"],