refactor(lsp): move ty flag to runtime flags (#27610)

This commit is contained in:
Shoubhit Dash
2026-05-15 03:40:30 +05:30
committed by GitHub
parent 93b1ccc029
commit e22cfa435a
6 changed files with 87 additions and 10 deletions

View File

@@ -56,7 +56,6 @@ export const Flag = {
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
OPENCODE_ENABLE_EXA: truthy("OPENCODE_ENABLE_EXA") || OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA"),
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: number("OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX"),
OPENCODE_EXPERIMENTAL_LSP_TY: truthy("OPENCODE_EXPERIMENTAL_LSP_TY"),
OPENCODE_EXPERIMENTAL_LSP_TOOL: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
OPENCODE_EXPERIMENTAL_SCOUT: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SCOUT"),

View File

@@ -31,6 +31,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"),
experimentalScout: enabledByExperimental("OPENCODE_EXPERIMENTAL_SCOUT"),
experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"),
experimentalLspTy: bool("OPENCODE_EXPERIMENTAL_LSP_TY"),
experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
experimentalOxfmt: enabledByExperimental("OPENCODE_EXPERIMENTAL_OXFMT"),
experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"),

View File

@@ -6,13 +6,13 @@ import path from "path"
import { pathToFileURL, fileURLToPath } from "url"
import * as LSPServer from "./server"
import { Config } from "@/config/config"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Process } from "@/util/process"
import { spawn as lspspawn } from "./launch"
import { Effect, Layer, Context, Schema } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { containsPath } from "@/project/instance-context"
import { NonNegativeInt } from "@opencode-ai/core/schema"
import { RuntimeFlags } from "@/effect/runtime-flags"
const log = Log.create({ service: "lsp" })
@@ -98,8 +98,8 @@ const kinds = [
SymbolKind.Enum,
]
const filterExperimentalServers = (servers: Record<string, LSPServer.Info>) => {
if (Flag.OPENCODE_EXPERIMENTAL_LSP_TY) {
const filterExperimentalServers = (servers: Record<string, LSPServer.Info>, flags: RuntimeFlags.Info) => {
if (flags.experimentalLspTy) {
if (servers["pyright"]) {
log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled")
delete servers["pyright"]
@@ -143,6 +143,7 @@ export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const config = yield* Config.Service
const flags = yield* RuntimeFlags.Service
const state = yield* InstanceState.make<State>(
Effect.fn("LSP.state")(function* (ctx) {
@@ -157,7 +158,7 @@ export const layer = Layer.effect(
servers[server.id] = server
}
filterExperimentalServers(servers)
filterExperimentalServers(servers, flags)
if (cfg.lsp !== true) {
for (const [name, item] of Object.entries(cfg.lsp)) {
@@ -217,7 +218,7 @@ export const layer = Layer.effect(
async function schedule(server: LSPServer.Info, root: string, key: string) {
const handle = await server
.spawn(root, ctx)
.spawn(root, ctx, flags)
.then((value) => {
if (!value) s.broken.add(key)
return value
@@ -498,7 +499,7 @@ export const layer = Layer.effect(
}),
)
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer))
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer))
export * as Diagnostic from "./diagnostic"

View File

@@ -14,6 +14,7 @@ import { which } from "../util/which"
import { Module } from "@opencode-ai/core/util/module"
import { spawn } from "./launch"
import { Npm } from "@opencode-ai/core/npm"
import type { RuntimeFlags } from "@/effect/runtime-flags"
const log = Log.create({ service: "lsp.server" })
const pathExists = async (p: string) =>
@@ -60,7 +61,7 @@ export interface Info {
extensions: string[]
global?: boolean
root: RootFunction
spawn(root: string, ctx: InstanceContext): Promise<Handle | undefined>
spawn(root: string, ctx: InstanceContext, flags: RuntimeFlags.Info): Promise<Handle | undefined>
}
export const Deno: Info = {
@@ -431,8 +432,8 @@ export const Ty: Info = {
"Pipfile",
"pyrightconfig.json",
]),
async spawn(root) {
if (!Flag.OPENCODE_EXPERIMENTAL_LSP_TY) {
async spawn(root, _ctx, flags) {
if (!flags.experimentalLspTy) {
return undefined
}

View File

@@ -34,6 +34,7 @@ describe("RuntimeFlags", () => {
expect(flags.enableQuestionTool).toBe(true)
expect(flags.experimentalScout).toBe(true)
expect(flags.experimentalBackgroundSubagents).toBe(true)
expect(flags.experimentalLspTy).toBe(false)
expect(flags.experimentalLspTool).toBe(true)
expect(flags.experimentalOxfmt).toBe(true)
expect(flags.experimentalPlanMode).toBe(true)
@@ -44,6 +45,20 @@ describe("RuntimeFlags", () => {
}),
)
it.effect("defaultLayer parses OPENCODE_EXPERIMENTAL_LSP_TY", () =>
Effect.gen(function* () {
const flags = yield* readFlags.pipe(
Effect.provide(
fromConfig({
OPENCODE_EXPERIMENTAL_LSP_TY: "true",
}),
),
)
expect(flags.experimentalLspTy).toBe(true)
}),
)
it.effect("layer accepts partial test overrides and fills defaults from Config definitions", () =>
Effect.gen(function* () {
const flags = yield* readFlags.pipe(

View File

@@ -1,6 +1,8 @@
import { describe, expect, spyOn } from "bun:test"
import path from "path"
import { Effect, Layer } from "effect"
import { Config } from "@/config/config"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { LSP } from "@/lsp/lsp"
import * as LSPServer from "@/lsp/server"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
@@ -8,6 +10,12 @@ import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
const experimentalTyIt = testEffect(
Layer.mergeAll(
LSP.layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(RuntimeFlags.layer({ experimentalLspTy: true }))),
CrossSpawnSpawner.defaultLayer,
),
)
describe("lsp.spawn", () => {
it.live("does not spawn builtin LSP for files outside instance", () =>
@@ -106,4 +114,56 @@ describe("lsp.spawn", () => {
},
),
)
it.live("uses pyright instead of ty by default", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const ty = spyOn(LSPServer.Ty, "spawn").mockResolvedValue(undefined)
const pyright = spyOn(LSPServer.Pyright, "spawn").mockResolvedValue(undefined)
try {
yield* lsp.hover({
file: path.join(dir, "src", "inside.py"),
line: 0,
character: 0,
})
expect(ty).toHaveBeenCalledTimes(0)
expect(pyright).toHaveBeenCalledTimes(1)
} finally {
ty.mockRestore()
pyright.mockRestore()
}
}),
),
{ config: { lsp: true } },
),
)
experimentalTyIt.live("uses ty instead of pyright when experimentalLspTy is enabled", () =>
provideTmpdirInstance(
(dir) =>
LSP.Service.use((lsp) =>
Effect.gen(function* () {
const ty = spyOn(LSPServer.Ty, "spawn").mockResolvedValue(undefined)
const pyright = spyOn(LSPServer.Pyright, "spawn").mockResolvedValue(undefined)
try {
yield* lsp.hover({
file: path.join(dir, "src", "inside.py"),
line: 0,
character: 0,
})
expect(ty).toHaveBeenCalledTimes(1)
expect(pyright).toHaveBeenCalledTimes(0)
} finally {
ty.mockRestore()
pyright.mockRestore()
}
}),
),
{ config: { lsp: true } },
),
)
})