Skip to content

Commit ab9ac7c

Browse files
authored
feat: add experimental support for Ty language server (anomalyco#5575)
1 parent ee9f979 commit ab9ac7c

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

packages/opencode/src/flag/flag.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export namespace Flag {
2929
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")
3030
export const OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX = number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX")
3131
export const OPENCODE_EXPERIMENTAL_OXFMT = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_OXFMT")
32+
export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("OPENCODE_EXPERIMENTAL_LSP_TY")
3233

3334
function truthy(key: string) {
3435
const value = process.env[key]?.toLowerCase()

packages/opencode/src/lsp/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import z from "zod"
99
import { Config } from "../config/config"
1010
import { spawn } from "child_process"
1111
import { Instance } from "../project/instance"
12+
import { Flag } from "@/flag/flag"
1213

1314
export namespace LSP {
1415
const log = Log.create({ service: "lsp" })
@@ -60,6 +61,21 @@ export namespace LSP {
6061
})
6162
export type DocumentSymbol = z.infer<typeof DocumentSymbol>
6263

64+
const filterExperimentalServers = (servers: Record<string, LSPServer.Info>) => {
65+
if (Flag.OPENCODE_EXPERIMENTAL_LSP_TY) {
66+
// If experimental flag is enabled, disable pyright
67+
if(servers["pyright"]) {
68+
log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled")
69+
delete servers["pyright"]
70+
}
71+
} else {
72+
// If experimental flag is disabled, disable ty
73+
if(servers["ty"]) {
74+
delete servers["ty"]
75+
}
76+
}
77+
}
78+
6379
const state = Instance.state(
6480
async () => {
6581
const clients: LSPClient.Info[] = []
@@ -79,6 +95,9 @@ export namespace LSP {
7995
for (const server of Object.values(LSPServer)) {
8096
servers[server.id] = server
8197
}
98+
99+
filterExperimentalServers(servers)
100+
82101
for (const [name, item] of Object.entries(cfg.lsp ?? {})) {
83102
const existing = servers[name]
84103
if (item.disabled) {
@@ -204,6 +223,7 @@ export namespace LSP {
204223

205224
for (const server of Object.values(s.servers)) {
206225
if (server.extensions.length && !server.extensions.includes(extension)) continue
226+
207227
const root = await server.root(file)
208228
if (!root) continue
209229
if (s.broken.has(root + server.id)) continue

packages/opencode/src/lsp/server.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,62 @@ export namespace LSPServer {
361361
},
362362
}
363363

364+
export const Ty: Info = {
365+
id: "ty",
366+
extensions: [".py", ".pyi"],
367+
root: NearestRoot(["pyproject.toml", "ty.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
368+
async spawn(root) {
369+
if(!Flag.OPENCODE_EXPERIMENTAL_LSP_TY) {
370+
return undefined
371+
}
372+
373+
let binary = Bun.which("ty")
374+
375+
const initialization: Record<string, string> = {}
376+
377+
const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")].filter(
378+
(p): p is string => p !== undefined,
379+
)
380+
for (const venvPath of potentialVenvPaths) {
381+
const isWindows = process.platform === "win32"
382+
const potentialPythonPath = isWindows
383+
? path.join(venvPath, "Scripts", "python.exe")
384+
: path.join(venvPath, "bin", "python")
385+
if (await Bun.file(potentialPythonPath).exists()) {
386+
initialization["pythonPath"] = potentialPythonPath
387+
break
388+
}
389+
}
390+
391+
if(!binary) {
392+
for (const venvPath of potentialVenvPaths) {
393+
const isWindows = process.platform === "win32"
394+
const potentialTyPath = isWindows
395+
? path.join(venvPath, "Scripts", "ty.exe")
396+
: path.join(venvPath, "bin", "ty")
397+
if (await Bun.file(potentialTyPath).exists()) {
398+
binary = potentialTyPath
399+
break
400+
}
401+
}
402+
}
403+
404+
if(!binary) {
405+
log.error("ty not found, please install ty first")
406+
return
407+
}
408+
409+
const proc = spawn(binary, ["server"], {
410+
cwd: root,
411+
})
412+
413+
return {
414+
process: proc,
415+
initialization,
416+
}
417+
},
418+
}
419+
364420
export const Pyright: Info = {
365421
id: "pyright",
366422
extensions: [".py", ".pyi"],

0 commit comments

Comments
 (0)