Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/app/src/pages/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,9 @@ export default function Page() {
),
)

// Bump diff_version when the watcher reports external changes to files in the
// current session's worktree (#743). Debounced: 1s trailing-edge per session.
let watcherDebounce: ReturnType<typeof setTimeout> | undefined
const stopVcs = sdk().event.listen((evt) => {
const details = evt.details as { type: string; properties?: unknown }
if (details.type !== "file.watcher.updated" && details.type !== "filesystem.changed") return
Expand All @@ -1020,8 +1023,18 @@ export default function Page() {
: undefined
const file = typeof props?.file === "string" ? props.file : undefined
if (!file || file.startsWith(".git/")) return
const id = params.id
if (!id) return
if (watcherDebounce !== undefined) clearTimeout(watcherDebounce)
watcherDebounce = setTimeout(() => {
watcherDebounce = undefined
sync().set("diff_version", id, (v: number | undefined) => (v ?? 0) + 1)
}, 1000)
})
onCleanup(() => {
stopVcs()
if (watcherDebounce !== undefined) clearTimeout(watcherDebounce)
})
onCleanup(stopVcs)

createEffect(
on(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/filesystem/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const layer = Layer.effect(
const config = (yield* (yield* Config.Service).entries())
.filter((entry): entry is Config.Document => entry.type === "document")
.flatMap((item) => item.info.watcher?.ignore ?? [])
if (location.vcs && (yield* Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER)) {
if (location.vcs) {
yield* Effect.forkScoped(
subscribe(location.directory, [...Ignore.PATTERNS, ...config, ...protecteds(location.directory)]),
)
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ export const Flag = {
OPENCODE_DISABLE_FFF: fff === undefined ? process.platform === "win32" : truthy("OPENCODE_DISABLE_FFF"),

// Experimental
OPENCODE_EXPERIMENTAL_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_FILEWATCHER").pipe(
Config.withDefault(false),
),
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER").pipe(
Config.withDefault(false),
),
Expand Down
37 changes: 34 additions & 3 deletions packages/core/test/filesystem/watcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ const configLayer = Layer.succeed(

const flagsLayer = ConfigProvider.layer(
ConfigProvider.fromUnknown({
OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: "false",
}),
)

// Same flags — verifies the watcher runs without any explicit enable flag (#743).
const defaultFlagsLayer = flagsLayer

function provide(directory: string, vcs?: Location.Interface["vcs"]) {
const locationLayer = Layer.succeed(
Location.Service,
Expand All @@ -48,10 +50,24 @@ function provide(directory: string, vcs?: Location.Interface["vcs"]) {
)
}

function provideDefault(directory: string, vcs?: Location.Interface["vcs"]) {
const locationLayer = Layer.succeed(
Location.Service,
Location.Service.of(location({ directory: AbsolutePath.make(directory) }, { vcs })),
)
return Effect.provide(
AppNodeBuilder.build(Watcher.node, [
[Config.node, configLayer],
[Location.node, locationLayer],
]).pipe(Layer.provide(defaultFlagsLayer)),
)
}

function withTmp<A, E, R>(
f: (directory: string, vcs?: Location.Interface["vcs"]) => Effect.Effect<A, E, R>,
options?: { git?: boolean; init?: (directory: string) => Promise<void> },
options?: { git?: boolean; init?: (directory: string) => Promise<void>; useDefaultFlags?: boolean },
) {
const providerFn = options?.useDefaultFlags ? provideDefault : provide
return Effect.acquireRelease(
Effect.promise(async () => {
const tmp = await tmpdir()
Expand All @@ -66,7 +82,7 @@ function withTmp<A, E, R>(
return { tmp, vcs: { type: "git" as const, store: AbsolutePath.make(path.join(tmp.path, ".git")) } }
}),
({ tmp }) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
).pipe(Effect.flatMap(({ tmp, vcs }) => f(tmp.path, vcs).pipe(provide(tmp.path, vcs))))
).pipe(Effect.flatMap(({ tmp, vcs }) => f(tmp.path, vcs).pipe(providerFn(tmp.path, vcs))))
}

function wait(check: (event: WatcherEvent) => boolean) {
Expand Down Expand Up @@ -263,4 +279,19 @@ describeWatcher("Watcher", () => {
),
)
})

it.live("runs by default for git repos without the enable flag (#743)", () =>
withTmp(
(directory) =>
Effect.gen(function* () {
const fs = yield* FSUtil.Service
const file = path.join(directory, "default-watch.txt")
yield* ready(directory)
expect(
yield* nextUpdate((event) => event.file === file && event.event === "add", fs.writeFileString(file, "a")),
).toEqual({ file, event: "add" })
}),
{ git: true, useDefaultFlags: true },
),
)
})
1 change: 0 additions & 1 deletion packages/desktop/src/main/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export function preferAppEnv(userDataPath: string) {
Object.assign(process.env, {
...shellEnv,
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true",
OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
OPENCODE_CLIENT: "desktop",
XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,
})
Expand Down
Loading