Compare commits
1 commit
dev
...
kit/ns-fil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e61fe8e5f8 |
35 changed files with 834 additions and 839 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { EOL } from "os"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { File } from "../../../file"
|
||||
import { Ripgrep } from "@/file/ripgrep"
|
||||
import { Ripgrep } from "@/file"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { EOL } from "os"
|
||||
import { Effect, Stream } from "effect"
|
||||
import { AppRuntime } from "../../../effect/app-runtime"
|
||||
import { Ripgrep } from "../../../file/ripgrep"
|
||||
import { Ripgrep } from "../../../file"
|
||||
import { Instance } from "../../../project/instance"
|
||||
import { bootstrap } from "../../bootstrap"
|
||||
import { cmd } from "../cmd"
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import { Auth } from "@/auth"
|
|||
import { Account } from "@/account"
|
||||
import { Config } from "@/config"
|
||||
import { Git } from "@/git"
|
||||
import { Ripgrep } from "@/file/ripgrep"
|
||||
import { FileTime } from "@/file/time"
|
||||
import { Ripgrep } from "@/file"
|
||||
import { FileTime } from "@/file"
|
||||
import { File } from "@/file"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { FileWatcher } from "@/file"
|
||||
import { Storage } from "@/storage"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { Plugin } from "@/plugin"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { memoMap } from "./run-service"
|
|||
|
||||
import { Plugin } from "@/plugin"
|
||||
import { LSP } from "@/lsp"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { FileWatcher } from "@/file"
|
||||
import { Format } from "@/format"
|
||||
import { ShareNext } from "@/share"
|
||||
import { File } from "@/file"
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import z from "zod"
|
|||
import { Global } from "../global"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Log } from "../util"
|
||||
import { Protected } from "./protected"
|
||||
import { Ripgrep } from "./ripgrep"
|
||||
import * as Protected from "./protected"
|
||||
import * as Ripgrep from "./ripgrep"
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Glob } from "@opencode-ai/shared/util/glob"
|
||||
|
||||
export namespace FileIgnore {
|
||||
const FOLDERS = new Set([
|
||||
const FOLDERS = new Set([
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
".pnpm-store",
|
||||
|
|
@ -30,9 +29,9 @@ export namespace FileIgnore {
|
|||
"mypy_cache",
|
||||
".history",
|
||||
".gradle",
|
||||
])
|
||||
])
|
||||
|
||||
const FILES = [
|
||||
const FILES = [
|
||||
"**/*.swp",
|
||||
"**/*.swo",
|
||||
|
||||
|
|
@ -51,17 +50,17 @@ export namespace FileIgnore {
|
|||
// Coverage/test outputs
|
||||
"**/coverage/**",
|
||||
"**/.nyc_output/**",
|
||||
]
|
||||
]
|
||||
|
||||
export const PATTERNS = [...FILES, ...FOLDERS]
|
||||
export const PATTERNS = [...FILES, ...FOLDERS]
|
||||
|
||||
export function match(
|
||||
export function match(
|
||||
filepath: string,
|
||||
opts?: {
|
||||
extra?: string[]
|
||||
whitelist?: string[]
|
||||
},
|
||||
) {
|
||||
) {
|
||||
for (const pattern of opts?.whitelist || []) {
|
||||
if (Glob.match(pattern, filepath)) return false
|
||||
}
|
||||
|
|
@ -77,5 +76,4 @@ export namespace FileIgnore {
|
|||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,6 @@
|
|||
export * as File from "./file"
|
||||
export * as Protected from "./protected"
|
||||
export * as FileIgnore from "./ignore"
|
||||
export * as FileWatcher from "./watcher"
|
||||
export * as FileTime from "./time"
|
||||
export * as Ripgrep from "./ripgrep"
|
||||
|
|
|
|||
|
|
@ -37,16 +37,15 @@ const DARWIN_ROOT = ["/.DocumentRevisions-V100", "/.Spotlight-V100", "/.Trashes"
|
|||
|
||||
const WIN32_HOME = ["AppData", "Downloads", "Desktop", "Documents", "Pictures", "Music", "Videos", "OneDrive"]
|
||||
|
||||
export namespace Protected {
|
||||
/** Directory basenames to skip when scanning the home directory. */
|
||||
export function names(): ReadonlySet<string> {
|
||||
/** Directory basenames to skip when scanning the home directory. */
|
||||
export function names(): ReadonlySet<string> {
|
||||
if (process.platform === "darwin") return new Set(DARWIN_HOME)
|
||||
if (process.platform === "win32") return new Set(WIN32_HOME)
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
/** Absolute paths that should never be watched, stated, or scanned. */
|
||||
export function paths(): string[] {
|
||||
/** Absolute paths that should never be watched, stated, or scanned. */
|
||||
export function paths(): string[] {
|
||||
if (process.platform === "darwin")
|
||||
return [
|
||||
...DARWIN_HOME.map((n) => path.join(home, n)),
|
||||
|
|
@ -55,5 +54,4 @@ export namespace Protected {
|
|||
]
|
||||
if (process.platform === "win32") return WIN32_HOME.map((n) => path.join(home, n))
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ import { ripgrep } from "ripgrep"
|
|||
import { Filesystem } from "@/util"
|
||||
import { Log } from "@/util"
|
||||
|
||||
export namespace Ripgrep {
|
||||
const log = Log.create({ service: "ripgrep" })
|
||||
const log = Log.create({ service: "ripgrep" })
|
||||
|
||||
const Stats = z.object({
|
||||
const Stats = z.object({
|
||||
elapsed: z.object({
|
||||
secs: z.number(),
|
||||
nanos: z.number(),
|
||||
|
|
@ -23,18 +22,18 @@ export namespace Ripgrep {
|
|||
bytes_printed: z.number(),
|
||||
matched_lines: z.number(),
|
||||
matches: z.number(),
|
||||
})
|
||||
})
|
||||
|
||||
const Begin = z.object({
|
||||
const Begin = z.object({
|
||||
type: z.literal("begin"),
|
||||
data: z.object({
|
||||
path: z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export const Match = z.object({
|
||||
export const Match = z.object({
|
||||
type: z.literal("match"),
|
||||
data: z.object({
|
||||
path: z.object({
|
||||
|
|
@ -55,9 +54,9 @@ export namespace Ripgrep {
|
|||
}),
|
||||
),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
const End = z.object({
|
||||
const End = z.object({
|
||||
type: z.literal("end"),
|
||||
data: z.object({
|
||||
path: z.object({
|
||||
|
|
@ -66,9 +65,9 @@ export namespace Ripgrep {
|
|||
binary_offset: z.number().nullable(),
|
||||
stats: Stats,
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
const Summary = z.object({
|
||||
const Summary = z.object({
|
||||
type: z.literal("summary"),
|
||||
data: z.object({
|
||||
elapsed_total: z.object({
|
||||
|
|
@ -78,33 +77,33 @@ export namespace Ripgrep {
|
|||
}),
|
||||
stats: Stats,
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
const Result = z.union([Begin, Match, End, Summary])
|
||||
const Result = z.union([Begin, Match, End, Summary])
|
||||
|
||||
export type Result = z.infer<typeof Result>
|
||||
export type Match = z.infer<typeof Match>
|
||||
export type Item = Match["data"]
|
||||
export type Begin = z.infer<typeof Begin>
|
||||
export type End = z.infer<typeof End>
|
||||
export type Summary = z.infer<typeof Summary>
|
||||
export type Row = Match["data"]
|
||||
export type Result = z.infer<typeof Result>
|
||||
export type Match = z.infer<typeof Match>
|
||||
export type Item = Match["data"]
|
||||
export type Begin = z.infer<typeof Begin>
|
||||
export type End = z.infer<typeof End>
|
||||
export type Summary = z.infer<typeof Summary>
|
||||
export type Row = Match["data"]
|
||||
|
||||
export interface SearchResult {
|
||||
export interface SearchResult {
|
||||
items: Item[]
|
||||
partial: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface FilesInput {
|
||||
export interface FilesInput {
|
||||
cwd: string
|
||||
glob?: string[]
|
||||
hidden?: boolean
|
||||
follow?: boolean
|
||||
maxDepth?: number
|
||||
signal?: AbortSignal
|
||||
}
|
||||
}
|
||||
|
||||
export interface SearchInput {
|
||||
export interface SearchInput {
|
||||
cwd: string
|
||||
pattern: string
|
||||
glob?: string[]
|
||||
|
|
@ -112,91 +111,91 @@ export namespace Ripgrep {
|
|||
follow?: boolean
|
||||
file?: string[]
|
||||
signal?: AbortSignal
|
||||
}
|
||||
}
|
||||
|
||||
export interface TreeInput {
|
||||
export interface TreeInput {
|
||||
cwd: string
|
||||
limit?: number
|
||||
signal?: AbortSignal
|
||||
}
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
export interface Interface {
|
||||
readonly files: (input: FilesInput) => Stream.Stream<string, Error>
|
||||
readonly tree: (input: TreeInput) => Effect.Effect<string, Error>
|
||||
readonly search: (input: SearchInput) => Effect.Effect<SearchResult, Error>
|
||||
}
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Ripgrep") {}
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Ripgrep") {}
|
||||
|
||||
type Run = { kind: "files" | "search"; cwd: string; args: string[] }
|
||||
type Run = { kind: "files" | "search"; cwd: string; args: string[] }
|
||||
|
||||
type WorkerResult = {
|
||||
type WorkerResult = {
|
||||
type: "result"
|
||||
code: number
|
||||
stdout: string
|
||||
stderr: string
|
||||
}
|
||||
}
|
||||
|
||||
type WorkerLine = {
|
||||
type WorkerLine = {
|
||||
type: "line"
|
||||
line: string
|
||||
}
|
||||
}
|
||||
|
||||
type WorkerDone = {
|
||||
type WorkerDone = {
|
||||
type: "done"
|
||||
code: number
|
||||
stderr: string
|
||||
}
|
||||
}
|
||||
|
||||
type WorkerError = {
|
||||
type WorkerError = {
|
||||
type: "error"
|
||||
error: {
|
||||
message: string
|
||||
name?: string
|
||||
stack?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function env() {
|
||||
function env() {
|
||||
const env = Object.fromEntries(
|
||||
Object.entries(process.env).filter((item): item is [string, string] => item[1] !== undefined),
|
||||
)
|
||||
delete env.RIPGREP_CONFIG_PATH
|
||||
return env
|
||||
}
|
||||
}
|
||||
|
||||
function text(input: unknown) {
|
||||
function text(input: unknown) {
|
||||
if (typeof input === "string") return input
|
||||
if (input instanceof ArrayBuffer) return Buffer.from(input).toString()
|
||||
if (ArrayBuffer.isView(input)) return Buffer.from(input.buffer, input.byteOffset, input.byteLength).toString()
|
||||
return String(input)
|
||||
}
|
||||
}
|
||||
|
||||
function toError(input: unknown) {
|
||||
function toError(input: unknown) {
|
||||
if (input instanceof Error) return input
|
||||
if (typeof input === "string") return new Error(input)
|
||||
return new Error(String(input))
|
||||
}
|
||||
}
|
||||
|
||||
function abort(signal?: AbortSignal) {
|
||||
function abort(signal?: AbortSignal) {
|
||||
const err = signal?.reason
|
||||
if (err instanceof Error) return err
|
||||
const out = new Error("Aborted")
|
||||
out.name = "AbortError"
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
function error(stderr: string, code: number) {
|
||||
function error(stderr: string, code: number) {
|
||||
const err = new Error(stderr.trim() || `ripgrep failed with code ${code}`)
|
||||
err.name = "RipgrepError"
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
function clean(file: string) {
|
||||
function clean(file: string) {
|
||||
return path.normalize(file.replace(/^\.[\\/]/, ""))
|
||||
}
|
||||
}
|
||||
|
||||
function row(data: Row): Row {
|
||||
function row(data: Row): Row {
|
||||
return {
|
||||
...data,
|
||||
path: {
|
||||
|
|
@ -204,16 +203,16 @@ export namespace Ripgrep {
|
|||
text: clean(data.path.text),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function opts(cwd: string) {
|
||||
function opts(cwd: string) {
|
||||
return {
|
||||
env: env(),
|
||||
preopens: { ".": cwd },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function check(cwd: string) {
|
||||
function check(cwd: string) {
|
||||
return Effect.tryPromise({
|
||||
try: () => fs.stat(cwd).catch(() => undefined),
|
||||
catch: toError,
|
||||
|
|
@ -230,9 +229,9 @@ export namespace Ripgrep {
|
|||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function filesArgs(input: FilesInput) {
|
||||
function filesArgs(input: FilesInput) {
|
||||
const args = ["--files", "--glob=!.git/*"]
|
||||
if (input.follow) args.push("--follow")
|
||||
if (input.hidden !== false) args.push("--hidden")
|
||||
|
|
@ -244,9 +243,9 @@ export namespace Ripgrep {
|
|||
}
|
||||
args.push(".")
|
||||
return args
|
||||
}
|
||||
}
|
||||
|
||||
function searchArgs(input: SearchInput) {
|
||||
function searchArgs(input: SearchInput) {
|
||||
const args = ["--json", "--hidden", "--glob=!.git/*", "--no-messages"]
|
||||
if (input.follow) args.push("--follow")
|
||||
if (input.glob) {
|
||||
|
|
@ -257,20 +256,20 @@ export namespace Ripgrep {
|
|||
if (input.limit) args.push(`--max-count=${input.limit}`)
|
||||
args.push("--", input.pattern, ...(input.file ?? ["."]))
|
||||
return args
|
||||
}
|
||||
}
|
||||
|
||||
function parse(stdout: string) {
|
||||
function parse(stdout: string) {
|
||||
return stdout
|
||||
.trim()
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.map((line) => Result.parse(JSON.parse(line)))
|
||||
.flatMap((item) => (item.type === "match" ? [row(item.data)] : []))
|
||||
}
|
||||
}
|
||||
|
||||
declare const OPENCODE_RIPGREP_WORKER_PATH: string
|
||||
declare const OPENCODE_RIPGREP_WORKER_PATH: string
|
||||
|
||||
function target(): Effect.Effect<string | URL, Error> {
|
||||
function target(): Effect.Effect<string | URL, Error> {
|
||||
if (typeof OPENCODE_RIPGREP_WORKER_PATH !== "undefined") {
|
||||
return Effect.succeed(OPENCODE_RIPGREP_WORKER_PATH)
|
||||
}
|
||||
|
|
@ -279,26 +278,26 @@ export namespace Ripgrep {
|
|||
try: () => Filesystem.exists(fileURLToPath(js)),
|
||||
catch: toError,
|
||||
}).pipe(Effect.map((exists) => (exists ? js : new URL("./ripgrep.worker.ts", import.meta.url))))
|
||||
}
|
||||
}
|
||||
|
||||
function worker() {
|
||||
function worker() {
|
||||
return target().pipe(Effect.flatMap((file) => Effect.sync(() => new Worker(file, { env: env() }))))
|
||||
}
|
||||
}
|
||||
|
||||
function drain(buf: string, chunk: unknown, push: (line: string) => void) {
|
||||
function drain(buf: string, chunk: unknown, push: (line: string) => void) {
|
||||
const lines = (buf + text(chunk)).split(/\r?\n/)
|
||||
buf = lines.pop() || ""
|
||||
for (const line of lines) {
|
||||
if (line) push(line)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
}
|
||||
|
||||
function fail(queue: Queue.Queue<string, Error | Cause.Done>, err: Error) {
|
||||
function fail(queue: Queue.Queue<string, Error | Cause.Done>, err: Error) {
|
||||
Queue.failCauseUnsafe(queue, Cause.fail(err))
|
||||
}
|
||||
}
|
||||
|
||||
function searchDirect(input: SearchInput) {
|
||||
function searchDirect(input: SearchInput) {
|
||||
return Effect.tryPromise({
|
||||
try: () =>
|
||||
ripgrep(searchArgs(input), {
|
||||
|
|
@ -318,9 +317,9 @@ export namespace Ripgrep {
|
|||
}))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function searchWorker(input: SearchInput) {
|
||||
function searchWorker(input: SearchInput) {
|
||||
if (input.signal?.aborted) return Effect.fail(abort(input.signal))
|
||||
|
||||
return Effect.acquireUseRelease(
|
||||
|
|
@ -377,9 +376,9 @@ export namespace Ripgrep {
|
|||
}),
|
||||
(w) => Effect.sync(() => w.terminate()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function filesDirect(input: FilesInput) {
|
||||
function filesDirect(input: FilesInput) {
|
||||
return Stream.callback<string, Error>(
|
||||
Effect.fnUntraced(function* (queue: Queue.Queue<string, Error | Cause.Done>) {
|
||||
let buf = ""
|
||||
|
|
@ -427,9 +426,9 @@ export namespace Ripgrep {
|
|||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function filesWorker(input: FilesInput) {
|
||||
function filesWorker(input: FilesInput) {
|
||||
return Stream.callback<string, Error>(
|
||||
Effect.fnUntraced(function* (queue: Queue.Queue<string, Error | Cause.Done>) {
|
||||
if (input.signal?.aborted) {
|
||||
|
|
@ -489,9 +488,9 @@ export namespace Ripgrep {
|
|||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const source = (input: FilesInput) => {
|
||||
|
|
@ -569,7 +568,6 @@ export namespace Ripgrep {
|
|||
|
||||
return Service.of({ files, tree, search })
|
||||
}),
|
||||
)
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
}
|
||||
export const defaultLayer = layer
|
||||
|
|
|
|||
|
|
@ -5,39 +5,38 @@ import { Flag } from "@/flag/flag"
|
|||
import type { SessionID } from "@/session/schema"
|
||||
import { Log } from "../util"
|
||||
|
||||
export namespace FileTime {
|
||||
const log = Log.create({ service: "file.time" })
|
||||
const log = Log.create({ service: "file.time" })
|
||||
|
||||
export type Stamp = {
|
||||
export type Stamp = {
|
||||
readonly read: Date
|
||||
readonly mtime: number | undefined
|
||||
readonly size: number | undefined
|
||||
}
|
||||
}
|
||||
|
||||
const session = (reads: Map<SessionID, Map<string, Stamp>>, sessionID: SessionID) => {
|
||||
const session = (reads: Map<SessionID, Map<string, Stamp>>, sessionID: SessionID) => {
|
||||
const value = reads.get(sessionID)
|
||||
if (value) return value
|
||||
|
||||
const next = new Map<string, Stamp>()
|
||||
reads.set(sessionID, next)
|
||||
return next
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
interface State {
|
||||
reads: Map<SessionID, Map<string, Stamp>>
|
||||
locks: Map<string, Semaphore.Semaphore>
|
||||
}
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
export interface Interface {
|
||||
readonly read: (sessionID: SessionID, file: string) => Effect.Effect<void>
|
||||
readonly get: (sessionID: SessionID, file: string) => Effect.Effect<Date | undefined>
|
||||
readonly assert: (sessionID: SessionID, filepath: string) => Effect.Effect<void>
|
||||
readonly withLock: <T>(filepath: string, fn: () => Effect.Effect<T>) => Effect.Effect<T>
|
||||
}
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileTime") {}
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileTime") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fsys = yield* AppFileSystem.Service
|
||||
|
|
@ -107,7 +106,6 @@ export namespace FileTime {
|
|||
|
||||
return Service.of({ read, get, assert, withLock })
|
||||
}),
|
||||
).pipe(Layer.orDie)
|
||||
).pipe(Layer.orDie)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer))
|
||||
}
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer))
|
||||
|
|
|
|||
|
|
@ -13,17 +13,16 @@ import { Git } from "@/git"
|
|||
import { Instance } from "@/project/instance"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Config } from "../config"
|
||||
import { FileIgnore } from "./ignore"
|
||||
import { Protected } from "./protected"
|
||||
import * as FileIgnore from "./ignore"
|
||||
import * as Protected from "./protected"
|
||||
import { Log } from "../util"
|
||||
|
||||
declare const OPENCODE_LIBC: string | undefined
|
||||
|
||||
export namespace FileWatcher {
|
||||
const log = Log.create({ service: "file.watcher" })
|
||||
const SUBSCRIBE_TIMEOUT_MS = 10_000
|
||||
const log = Log.create({ service: "file.watcher" })
|
||||
const SUBSCRIBE_TIMEOUT_MS = 10_000
|
||||
|
||||
export const Event = {
|
||||
export const Event = {
|
||||
Updated: BusEvent.define(
|
||||
"file.watcher.updated",
|
||||
z.object({
|
||||
|
|
@ -31,9 +30,9 @@ export namespace FileWatcher {
|
|||
event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]),
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
|
||||
const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
|
||||
try {
|
||||
const binding = require(
|
||||
`@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${OPENCODE_LIBC || "glibc"}` : ""}`,
|
||||
|
|
@ -43,30 +42,30 @@ export namespace FileWatcher {
|
|||
log.error("failed to load watcher binding", { error })
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function getBackend() {
|
||||
function getBackend() {
|
||||
if (process.platform === "win32") return "windows"
|
||||
if (process.platform === "darwin") return "fs-events"
|
||||
if (process.platform === "linux") return "inotify"
|
||||
}
|
||||
}
|
||||
|
||||
function protecteds(dir: string) {
|
||||
function protecteds(dir: string) {
|
||||
return Protected.paths().filter((item) => {
|
||||
const rel = path.relative(dir, item)
|
||||
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const hasNativeBinding = () => !!watcher()
|
||||
export const hasNativeBinding = () => !!watcher()
|
||||
|
||||
export interface Interface {
|
||||
export interface Interface {
|
||||
readonly init: () => Effect.Effect<void>
|
||||
}
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileWatcher") {}
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/FileWatcher") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -157,7 +156,6 @@ export namespace FileWatcher {
|
|||
}),
|
||||
})
|
||||
}),
|
||||
)
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Git.defaultLayer))
|
||||
}
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Git.defaultLayer))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Bus } from "../bus"
|
|||
import { Command } from "../command"
|
||||
import { Instance } from "./instance"
|
||||
import { Log } from "@/util"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { FileWatcher } from "@/file"
|
||||
import { ShareNext } from "@/share"
|
||||
import * as Effect from "effect/Effect"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Bus } from "@/bus"
|
|||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { FileWatcher } from "@/file"
|
||||
import { Git } from "@/git"
|
||||
import { Log } from "@/util"
|
||||
import { Instance } from "./instance"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Effect } from "effect"
|
|||
import z from "zod"
|
||||
import { AppRuntime } from "../../effect/app-runtime"
|
||||
import { File } from "../../file"
|
||||
import { Ripgrep } from "../../file/ripgrep"
|
||||
import { Ripgrep } from "../../file"
|
||||
import { LSP } from "../../lsp"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { lazy } from "../../util/lazy"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import MAX_STEPS from "../session/prompt/max-steps.txt"
|
|||
import { ToolRegistry } from "../tool/registry"
|
||||
import { MCP } from "../mcp"
|
||||
import { LSP } from "../lsp"
|
||||
import { FileTime } from "../file/time"
|
||||
import { FileTime } from "../file"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { ulid } from "ulid"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as path from "path"
|
|||
import { Effect } from "effect"
|
||||
import { Tool } from "./tool"
|
||||
import { Bus } from "../bus"
|
||||
import { FileWatcher } from "../file/watcher"
|
||||
import { FileWatcher } from "../file"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Patch } from "../patch"
|
||||
import { createTwoFilesPatch, diffLines } from "diff"
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import { LSP } from "../lsp"
|
|||
import { createTwoFilesPatch, diffLines } from "diff"
|
||||
import DESCRIPTION from "./edit.txt"
|
||||
import { File } from "../file"
|
||||
import { FileWatcher } from "../file/watcher"
|
||||
import { FileWatcher } from "../file"
|
||||
import { Bus } from "../bus"
|
||||
import { Format } from "../format"
|
||||
import { FileTime } from "../file/time"
|
||||
import { FileTime } from "../file"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Effect, Option } from "effect"
|
|||
import * as Stream from "effect/Stream"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Ripgrep } from "../file"
|
||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||
import DESCRIPTION from "./glob.txt"
|
||||
import { Tool } from "./tool"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import z from "zod"
|
|||
import { Effect, Option } from "effect"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Ripgrep } from "../file"
|
||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||
import DESCRIPTION from "./grep.txt"
|
||||
import { Tool } from "./tool"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { createInterface } from "readline"
|
|||
import { Tool } from "./tool"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { LSP } from "../lsp"
|
||||
import { FileTime } from "../file/time"
|
||||
import { FileTime } from "../file"
|
||||
import DESCRIPTION from "./read.txt"
|
||||
import { Instance } from "../project/instance"
|
||||
import { assertExternalDirectoryEffect } from "./external-directory"
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ import { Effect, Layer, Context } from "effect"
|
|||
import { FetchHttpClient, HttpClient } from "effect/unstable/http"
|
||||
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
||||
import * as CrossSpawnSpawner from "@/effect/cross-spawn-spawner"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Ripgrep } from "../file"
|
||||
import { Format } from "../format"
|
||||
import { InstanceState } from "@/effect"
|
||||
import { Question } from "../question"
|
||||
import { Todo } from "../session/todo"
|
||||
import { LSP } from "../lsp"
|
||||
import { FileTime } from "../file/time"
|
||||
import { FileTime } from "../file"
|
||||
import { Instruction } from "../session/instruction"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Bus } from "../bus"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import z from "zod"
|
|||
import { Effect } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { EffectLogger } from "@/effect"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Ripgrep } from "../file"
|
||||
import { Skill } from "../skill"
|
||||
import { Tool } from "./tool"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import { createTwoFilesPatch } from "diff"
|
|||
import DESCRIPTION from "./write.txt"
|
||||
import { Bus } from "../bus"
|
||||
import { File } from "../file"
|
||||
import { FileWatcher } from "../file/watcher"
|
||||
import { FileWatcher } from "../file"
|
||||
import { Format } from "../format"
|
||||
import { FileTime } from "../file/time"
|
||||
import { FileTime } from "../file"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Instance } from "../project/instance"
|
||||
import { trimDiff } from "./edit"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { test, expect } from "bun:test"
|
||||
import { FileIgnore } from "../../src/file/ignore"
|
||||
import { FileIgnore } from "../../src/file"
|
||||
|
||||
test("match nested and non-nested", () => {
|
||||
expect(FileIgnore.match("node_modules/index.js")).toBe(true)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import * as Stream from "effect/Stream"
|
|||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { Ripgrep } from "../../src/file/ripgrep"
|
||||
import { Ripgrep } from "../../src/file"
|
||||
|
||||
const run = <A>(effect: Effect.Effect<A, unknown, Ripgrep.Service>) =>
|
||||
effect.pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import fs from "fs/promises"
|
|||
import path from "path"
|
||||
import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
|
||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { FileTime } from "../../src/file/time"
|
||||
import { FileTime } from "../../src/file"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { SessionID } from "../../src/session/schema"
|
||||
import { Filesystem } from "../../src/util"
|
||||
|
|
@ -43,7 +43,7 @@ const fail = Effect.fn("FileTimeTest.fail")(function* <A, E, R>(self: Effect.Eff
|
|||
throw new Error("expected file time effect to fail")
|
||||
})
|
||||
|
||||
describe("file/time", () => {
|
||||
describe("file", () => {
|
||||
describe("read() and get()", () => {
|
||||
it.live("stores read timestamp", () =>
|
||||
provideTmpdirInstance((dir) =>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { ConfigProvider, Deferred, Effect, Layer, ManagedRuntime, Option } from
|
|||
import { tmpdir } from "../fixture/fixture"
|
||||
import { Bus } from "../../src/bus"
|
||||
import { Config } from "../../src/config"
|
||||
import { FileWatcher } from "../../src/file/watcher"
|
||||
import { FileWatcher } from "../../src/file"
|
||||
import { Git } from "../../src/git"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import fs from "fs/promises"
|
|||
import path from "path"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { AppRuntime } from "../../src/effect/app-runtime"
|
||||
import { FileWatcher } from "../../src/file/watcher"
|
||||
import { FileWatcher } from "../../src/file"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { GlobalBus } from "../../src/bus/global"
|
||||
import { Vcs } from "../../src/project"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Agent as AgentSvc } from "../../src/agent/agent"
|
|||
import { Bus } from "../../src/bus"
|
||||
import { Command } from "../../src/command"
|
||||
import { Config } from "../../src/config"
|
||||
import { FileTime } from "../../src/file/time"
|
||||
import { FileTime } from "../../src/file"
|
||||
import { LSP } from "../../src/lsp"
|
||||
import { MCP } from "../../src/mcp"
|
||||
import { Permission } from "../../src/permission"
|
||||
|
|
@ -38,7 +38,7 @@ import { ToolRegistry } from "../../src/tool/registry"
|
|||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { Log } from "../../src/util"
|
||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { Ripgrep } from "../../src/file/ripgrep"
|
||||
import { Ripgrep } from "../../src/file"
|
||||
import { Format } from "../../src/format"
|
||||
import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { Agent as AgentSvc } from "../../src/agent/agent"
|
|||
import { Bus } from "../../src/bus"
|
||||
import { Command } from "../../src/command"
|
||||
import { Config } from "../../src/config"
|
||||
import { FileTime } from "../../src/file/time"
|
||||
import { FileTime } from "../../src/file"
|
||||
import { LSP } from "../../src/lsp"
|
||||
import { MCP } from "../../src/mcp"
|
||||
import { Permission } from "../../src/permission"
|
||||
|
|
@ -54,7 +54,7 @@ import { ToolRegistry } from "../../src/tool/registry"
|
|||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { Ripgrep } from "../../src/file/ripgrep"
|
||||
import { Ripgrep } from "../../src/file"
|
||||
import { Format } from "../../src/format"
|
||||
|
||||
void Log.init({ print: false })
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Effect, Layer, ManagedRuntime } from "effect"
|
|||
import { EditTool } from "../../src/tool/edit"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { FileTime } from "../../src/file/time"
|
||||
import { FileTime } from "../../src/file"
|
||||
import { LSP } from "../../src/lsp"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Format } from "../../src/format"
|
||||
|
|
@ -138,7 +138,7 @@ describe("tool.edit", () => {
|
|||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const { FileWatcher } = await import("../../src/file/watcher")
|
||||
const { FileWatcher } = await import("../../src/file")
|
||||
|
||||
const updated = await onceBus(FileWatcher.Event.Updated)
|
||||
|
||||
|
|
@ -371,7 +371,7 @@ describe("tool.edit", () => {
|
|||
fn: async () => {
|
||||
await readFileTime(ctx.sessionID, filepath)
|
||||
|
||||
const { FileWatcher } = await import("../../src/file/watcher")
|
||||
const { FileWatcher } = await import("../../src/file")
|
||||
|
||||
const updated = await onceBus(FileWatcher.Event.Updated)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Cause, Effect, Exit, Layer } from "effect"
|
|||
import { GlobTool } from "../../src/tool/glob"
|
||||
import { SessionID, MessageID } from "../../src/session/schema"
|
||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { Ripgrep } from "../../src/file/ripgrep"
|
||||
import { Ripgrep } from "../../src/file"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { SessionID, MessageID } from "../../src/session/schema"
|
|||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
import { Ripgrep } from "../../src/file/ripgrep"
|
||||
import { Ripgrep } from "../../src/file"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import path from "path"
|
|||
import { Agent } from "../../src/agent/agent"
|
||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { FileTime } from "../../src/file/time"
|
||||
import { FileTime } from "../../src/file"
|
||||
import { LSP } from "../../src/lsp"
|
||||
import { Permission } from "../../src/permission"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { WriteTool } from "../../src/tool/write"
|
|||
import { Instance } from "../../src/project/instance"
|
||||
import { LSP } from "../../src/lsp"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { FileTime } from "../../src/file/time"
|
||||
import { FileTime } from "../../src/file"
|
||||
import { Bus } from "../../src/bus"
|
||||
import { Format } from "../../src/format"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue