refactor(lsp): move ty flag to runtime flags (#27610)
This commit is contained in:
parent
93b1ccc029
commit
e22cfa435a
6 changed files with 87 additions and 10 deletions
|
|
@ -56,7 +56,6 @@ export const Flag = {
|
||||||
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
|
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_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_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_LSP_TOOL: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
||||||
OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
||||||
OPENCODE_EXPERIMENTAL_SCOUT: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SCOUT"),
|
OPENCODE_EXPERIMENTAL_SCOUT: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SCOUT"),
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
|
||||||
enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"),
|
enableQuestionTool: bool("OPENCODE_ENABLE_QUESTION_TOOL"),
|
||||||
experimentalScout: enabledByExperimental("OPENCODE_EXPERIMENTAL_SCOUT"),
|
experimentalScout: enabledByExperimental("OPENCODE_EXPERIMENTAL_SCOUT"),
|
||||||
experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"),
|
experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS"),
|
||||||
|
experimentalLspTy: bool("OPENCODE_EXPERIMENTAL_LSP_TY"),
|
||||||
experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
experimentalLspTool: enabledByExperimental("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
|
||||||
experimentalOxfmt: enabledByExperimental("OPENCODE_EXPERIMENTAL_OXFMT"),
|
experimentalOxfmt: enabledByExperimental("OPENCODE_EXPERIMENTAL_OXFMT"),
|
||||||
experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
experimentalPlanMode: enabledByExperimental("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ import path from "path"
|
||||||
import { pathToFileURL, fileURLToPath } from "url"
|
import { pathToFileURL, fileURLToPath } from "url"
|
||||||
import * as LSPServer from "./server"
|
import * as LSPServer from "./server"
|
||||||
import { Config } from "@/config/config"
|
import { Config } from "@/config/config"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { Process } from "@/util/process"
|
import { Process } from "@/util/process"
|
||||||
import { spawn as lspspawn } from "./launch"
|
import { spawn as lspspawn } from "./launch"
|
||||||
import { Effect, Layer, Context, Schema } from "effect"
|
import { Effect, Layer, Context, Schema } from "effect"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { containsPath } from "@/project/instance-context"
|
import { containsPath } from "@/project/instance-context"
|
||||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
const log = Log.create({ service: "lsp" })
|
const log = Log.create({ service: "lsp" })
|
||||||
|
|
||||||
|
|
@ -98,8 +98,8 @@ const kinds = [
|
||||||
SymbolKind.Enum,
|
SymbolKind.Enum,
|
||||||
]
|
]
|
||||||
|
|
||||||
const filterExperimentalServers = (servers: Record<string, LSPServer.Info>) => {
|
const filterExperimentalServers = (servers: Record<string, LSPServer.Info>, flags: RuntimeFlags.Info) => {
|
||||||
if (Flag.OPENCODE_EXPERIMENTAL_LSP_TY) {
|
if (flags.experimentalLspTy) {
|
||||||
if (servers["pyright"]) {
|
if (servers["pyright"]) {
|
||||||
log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled")
|
log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled")
|
||||||
delete servers["pyright"]
|
delete servers["pyright"]
|
||||||
|
|
@ -143,6 +143,7 @@ export const layer = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const config = yield* Config.Service
|
const config = yield* Config.Service
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
const state = yield* InstanceState.make<State>(
|
const state = yield* InstanceState.make<State>(
|
||||||
Effect.fn("LSP.state")(function* (ctx) {
|
Effect.fn("LSP.state")(function* (ctx) {
|
||||||
|
|
@ -157,7 +158,7 @@ export const layer = Layer.effect(
|
||||||
servers[server.id] = server
|
servers[server.id] = server
|
||||||
}
|
}
|
||||||
|
|
||||||
filterExperimentalServers(servers)
|
filterExperimentalServers(servers, flags)
|
||||||
|
|
||||||
if (cfg.lsp !== true) {
|
if (cfg.lsp !== true) {
|
||||||
for (const [name, item] of Object.entries(cfg.lsp)) {
|
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) {
|
async function schedule(server: LSPServer.Info, root: string, key: string) {
|
||||||
const handle = await server
|
const handle = await server
|
||||||
.spawn(root, ctx)
|
.spawn(root, ctx, flags)
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
if (!value) s.broken.add(key)
|
if (!value) s.broken.add(key)
|
||||||
return value
|
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"
|
export * as Diagnostic from "./diagnostic"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { which } from "../util/which"
|
||||||
import { Module } from "@opencode-ai/core/util/module"
|
import { Module } from "@opencode-ai/core/util/module"
|
||||||
import { spawn } from "./launch"
|
import { spawn } from "./launch"
|
||||||
import { Npm } from "@opencode-ai/core/npm"
|
import { Npm } from "@opencode-ai/core/npm"
|
||||||
|
import type { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
const log = Log.create({ service: "lsp.server" })
|
const log = Log.create({ service: "lsp.server" })
|
||||||
const pathExists = async (p: string) =>
|
const pathExists = async (p: string) =>
|
||||||
|
|
@ -60,7 +61,7 @@ export interface Info {
|
||||||
extensions: string[]
|
extensions: string[]
|
||||||
global?: boolean
|
global?: boolean
|
||||||
root: RootFunction
|
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 = {
|
export const Deno: Info = {
|
||||||
|
|
@ -431,8 +432,8 @@ export const Ty: Info = {
|
||||||
"Pipfile",
|
"Pipfile",
|
||||||
"pyrightconfig.json",
|
"pyrightconfig.json",
|
||||||
]),
|
]),
|
||||||
async spawn(root) {
|
async spawn(root, _ctx, flags) {
|
||||||
if (!Flag.OPENCODE_EXPERIMENTAL_LSP_TY) {
|
if (!flags.experimentalLspTy) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ describe("RuntimeFlags", () => {
|
||||||
expect(flags.enableQuestionTool).toBe(true)
|
expect(flags.enableQuestionTool).toBe(true)
|
||||||
expect(flags.experimentalScout).toBe(true)
|
expect(flags.experimentalScout).toBe(true)
|
||||||
expect(flags.experimentalBackgroundSubagents).toBe(true)
|
expect(flags.experimentalBackgroundSubagents).toBe(true)
|
||||||
|
expect(flags.experimentalLspTy).toBe(false)
|
||||||
expect(flags.experimentalLspTool).toBe(true)
|
expect(flags.experimentalLspTool).toBe(true)
|
||||||
expect(flags.experimentalOxfmt).toBe(true)
|
expect(flags.experimentalOxfmt).toBe(true)
|
||||||
expect(flags.experimentalPlanMode).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", () =>
|
it.effect("layer accepts partial test overrides and fills defaults from Config definitions", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const flags = yield* readFlags.pipe(
|
const flags = yield* readFlags.pipe(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { describe, expect, spyOn } from "bun:test"
|
import { describe, expect, spyOn } from "bun:test"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Effect, Layer } from "effect"
|
import { Effect, Layer } from "effect"
|
||||||
|
import { Config } from "@/config/config"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { LSP } from "@/lsp/lsp"
|
import { LSP } from "@/lsp/lsp"
|
||||||
import * as LSPServer from "@/lsp/server"
|
import * as LSPServer from "@/lsp/server"
|
||||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||||
|
|
@ -8,6 +10,12 @@ import { provideTmpdirInstance } from "../fixture/fixture"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
|
|
||||||
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
|
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", () => {
|
describe("lsp.spawn", () => {
|
||||||
it.live("does not spawn builtin LSP for files outside instance", () =>
|
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 } },
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue