Просмотр исходного кода

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

Shoubhit Dash 3 месяцев назад
Родитель
Сommit
e22cfa435a

+ 0 - 1
packages/core/src/flag/flag.ts

@@ -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"),

+ 1 - 0
packages/opencode/src/effect/runtime-flags.ts

@@ -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"),

+ 7 - 6
packages/opencode/src/lsp/lsp.ts

@@ -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"
 

+ 4 - 3
packages/opencode/src/lsp/server.ts

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

+ 15 - 0
packages/opencode/test/effect/runtime-flags.test.ts

@@ -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(

+ 60 - 0
packages/opencode/test/lsp/index.test.ts

@@ -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 } },
+    ),
+  )
 })