Skip to content
3 changes: 2 additions & 1 deletion packages/opencode/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export namespace Flag {
truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = number("OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH")
export const OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS = number("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS")

export const OPENCODE_EXPERIMENTAL_LSP_TY = truthy("OPENCODE_EXPERIMENTAL_LSP_TY")

function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
return value === "true" || value === "1"
Expand Down
6 changes: 6 additions & 0 deletions packages/opencode/src/lsp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand Down Expand Up @@ -204,6 +205,11 @@ export namespace LSP {

for (const server of Object.values(s.servers)) {
if (server.extensions.length && !server.extensions.includes(extension)) continue
if (extension === ".py" || extension === ".pyi") {
if (Flag.OPENCODE_EXPERIMENTAL_LSP_TY && !server.experimental) continue // If experimental flag is enabled and server is not experimental, skip
if (!Flag.OPENCODE_EXPERIMENTAL_LSP_TY && server.experimental) continue // If experimental flag is disabled and server is experimental, skip
}
Comment thread
rekram1-node marked this conversation as resolved.
Outdated

const root = await server.root(file)
if (!root) continue
if (s.broken.has(root + server.id)) continue
Expand Down
54 changes: 54 additions & 0 deletions packages/opencode/src/lsp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export namespace LSPServer {
global?: boolean
root: RootFunction
spawn(root: string): Promise<Handle | undefined>
experimental?: boolean,
}

export const Deno: Info = {
Expand Down Expand Up @@ -354,6 +355,59 @@ 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) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this spawning even when the var isn't set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I thought the way the logic goes, the servers only get spawned at after being loaded to the Instance.state? Before then the server is removed when the var isn't set.

But then again i could just add a check there just to be 1000% guaranteed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like how in index.ts

      if (cfg.lsp === false) {
        log.info("all LSPs are disabled")
        return {
          broken: new Set<string>(),
          servers,
          clients,
          spawning: new Map<string, Promise<LSPClient.Info | undefined>>(),
        }
      }

it can return just empty servers and spawning methods

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
}
}

Comment thread
rekram1-node marked this conversation as resolved.
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
}
}
}

Comment thread
rekram1-node marked this conversation as resolved.
if(!binary) {
log.error("ty not found, please install ty first")
return
}

const proc = spawn(binary, ["server"], {
cwd: root,
})

return {
process: proc,
initialization,
}
},
experimental: true,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue: The experimental: true property is added here but it is not actually used anywhere. The filterExperimentalServers function checks for specific server IDs ("ty", "pyright") rather than using this property. This could be intentional for now, but it is worth noting that this property is currently unused.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense bro-chacho

export const Pyright: Info = {
id: "pyright",
extensions: [".py", ".pyi"],
Expand Down
Loading