Răsfoiți Sursa

refactor(core): remove unused filesystem search methods (#40703)

Kit Langton 1 săptămână în urmă
părinte
comite
934935963d

+ 1 - 5
packages/core/src/filesystem.ts

@@ -7,7 +7,7 @@ import { FSUtil } from "@opencode-ai/util/fs-util"
 import { Location } from "./location"
 import { PositiveInt, RelativePath } from "./schema"
 import { FileSystemSearch } from "./filesystem/search"
-import { Entry, FileSystem, FindInput, Match } from "@opencode-ai/schema/filesystem"
+import { Entry, FileSystem, FindInput } from "@opencode-ai/schema/filesystem"
 export { Entry, Match, Submatch } from "@opencode-ai/schema/filesystem"
 
 export const ReadInput = Schema.Struct({
@@ -53,8 +53,6 @@ export interface Interface {
   readonly read: (input: ReadInput) => Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
   readonly list: (input?: ListInput) => Effect.Effect<Entry[]>
   readonly find: (input: FindInput) => Effect.Effect<Entry[]>
-  readonly glob: (input: GlobInput) => Effect.Effect<readonly Entry[]>
-  readonly grep: (input: GrepInput) => Effect.Effect<readonly Match[]>
 }
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/FileSystem") {}
@@ -76,8 +74,6 @@ const baseLayer = Layer.effect(
     })
     return Service.of({
       find: search.find,
-      glob: search.glob,
-      grep: search.grep,
       read: Effect.fn("FileSystem.read")(function* (input) {
         const target = yield* resolve(input.path)
         const info = yield* fs.stat(target.real).pipe(Effect.orDie)

+ 0 - 38
packages/core/src/filesystem/fff.bun.ts

@@ -3,9 +3,6 @@ import {
   type DirItem,
   type DirSearchResult,
   type FileItem,
-  type GrepCursor,
-  type GrepMatch,
-  type GrepResult,
   type InitOptions,
   type MixedItem,
   type MixedSearchResult,
@@ -45,19 +42,6 @@ export interface MixedSearch {
 export type File = FileItem
 export type Directory = DirItem
 export type Mixed = MixedItem
-export type Cursor = GrepCursor | null
-export type Hit = GrepMatch
-
-export interface Grep {
-  items: GrepResult["items"]
-  totalMatched: number
-  totalFilesSearched: number
-  totalFiles: number
-  filteredFileCount: number
-  nextCursor: Cursor
-  regexFallbackError?: string
-}
-
 export interface Picker {
   destroy(): void
   isScanning(): boolean
@@ -71,14 +55,6 @@ export interface Picker {
       pageSize?: number
     },
   ): Result<Search>
-  glob(
-    pattern: string,
-    opts?: {
-      currentFile?: string
-      pageIndex?: number
-      pageSize?: number
-    },
-  ): Result<Search>
   directorySearch(
     query: string,
     opts?: {
@@ -95,18 +71,6 @@ export interface Picker {
       pageSize?: number
     },
   ): Result<MixedSearch>
-  grep(
-    query: string,
-    opts?: {
-      mode?: "plain" | "regex" | "fuzzy"
-      maxMatchesPerFile?: number
-      timeBudgetMs?: number
-      beforeContext?: number
-      afterContext?: number
-      cursor?: Cursor
-      pageSize?: number
-    },
-  ): Result<Grep>
   trackQuery(query: string, file: string): Result<boolean>
   getHistoricalQuery(offset: number): Result<string | null>
 }
@@ -127,10 +91,8 @@ export function create(opts: Init): Result<Picker> {
       waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
       refreshGitStatus: () => pick.refreshGitStatus(),
       fileSearch: (query, next) => pick.fileSearch(query, next),
-      glob: (pattern, next) => pick.glob(pattern, next),
       directorySearch: (query, next) => pick.directorySearch(query, next),
       mixedSearch: (query, next) => pick.mixedSearch(query, next),
-      grep: (query, next) => pick.grep(query, next),
       trackQuery: (query, file) => pick.trackQuery(query, file),
       getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
     },

+ 0 - 38
packages/core/src/filesystem/fff.node.ts

@@ -2,9 +2,6 @@ import type {
   DirItem,
   DirSearchResult,
   FileItem,
-  GrepCursor,
-  GrepMatch,
-  GrepResult,
   InitOptions,
   MixedItem,
   MixedSearchResult,
@@ -42,19 +39,6 @@ export interface MixedSearch {
 export type File = FileItem
 export type Directory = DirItem
 export type Mixed = MixedItem
-export type Cursor = GrepCursor | null
-export type Hit = GrepMatch
-
-export interface Grep {
-  items: GrepResult["items"]
-  totalMatched: number
-  totalFilesSearched: number
-  totalFiles: number
-  filteredFileCount: number
-  nextCursor: Cursor
-  regexFallbackError?: string
-}
-
 export interface Picker {
   destroy(): void
   isScanning(): boolean
@@ -68,14 +52,6 @@ export interface Picker {
       pageSize?: number
     },
   ): Result<Search>
-  glob(
-    pattern: string,
-    opts?: {
-      currentFile?: string
-      pageIndex?: number
-      pageSize?: number
-    },
-  ): Result<Search>
   directorySearch(
     query: string,
     opts?: {
@@ -92,18 +68,6 @@ export interface Picker {
       pageSize?: number
     },
   ): Result<MixedSearch>
-  grep(
-    query: string,
-    opts?: {
-      mode?: "plain" | "regex" | "fuzzy"
-      maxMatchesPerFile?: number
-      timeBudgetMs?: number
-      beforeContext?: number
-      afterContext?: number
-      cursor?: Cursor
-      pageSize?: number
-    },
-  ): Result<Grep>
   trackQuery(query: string, file: string): Result<boolean>
   getHistoricalQuery(offset: number): Result<string | null>
 }
@@ -125,10 +89,8 @@ export function create(opts: Init): Result<Picker> {
       waitForScan: (timeoutMs) => pick.waitForScan(timeoutMs),
       refreshGitStatus: () => pick.refreshGitStatus(),
       fileSearch: (query, next) => pick.fileSearch(query, next),
-      glob: (pattern, next) => pick.glob(pattern, next),
       directorySearch: (query, next) => pick.directorySearch(query, next),
       mixedSearch: (query, next) => pick.mixedSearch(query, next),
-      grep: (query, next) => pick.grep(query, next),
       trackQuery: (query, file) => pick.trackQuery(query, file),
       getHistoricalQuery: (offset) => pick.getHistoricalQuery(offset),
     },

+ 1 - 101
packages/core/src/filesystem/search.ts

@@ -6,7 +6,6 @@ import { Context, Effect, Layer, Schema, Scope } from "effect"
 import { Fff } from "#fff"
 import fuzzysort from "fuzzysort"
 import { FileSystem } from "../filesystem"
-import { FSUtil } from "@opencode-ai/util/fs-util"
 import { Location } from "../location"
 import { Ripgrep } from "../ripgrep"
 import { RelativePath } from "../schema"
@@ -14,8 +13,6 @@ import { Protected } from "./protected"
 
 export interface Interface {
   readonly find: (input: FileSystem.FindInput) => Effect.Effect<FileSystem.Entry[]>
-  readonly glob: (input: FileSystem.GlobInput) => Effect.Effect<readonly FileSystem.Entry[]>
-  readonly grep: (input: FileSystem.GrepInput) => Effect.Effect<readonly FileSystem.Match[]>
 }
 
 export const Options = Schema.Struct({
@@ -28,7 +25,6 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Fi
 export const ripgrepLayer = Layer.effect(
   Service,
   Effect.gen(function* () {
-    const fs = yield* FSUtil.Service
     const location = yield* Location.Service
     const ripgrep = yield* Ripgrep.Service
     const scope = yield* Scope.Scope
@@ -50,57 +46,6 @@ export const ripgrepLayer = Layer.effect(
       })
       .pipe(Effect.orDie, Effect.asVoid, Effect.forkIn(scope))
     return Service.of({
-      glob: (input) =>
-        Effect.gen(function* () {
-          const target = path.resolve(location.directory, input.path ?? ".")
-          const info = yield* fs.stat(target).pipe(Effect.orDie)
-          const cwd = info.type === "File" ? path.dirname(target) : target
-          return yield* ripgrep
-            .glob({
-              cwd,
-              pattern: input.pattern,
-              limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
-            })
-            .pipe(
-              Effect.map((result) =>
-                result.map((entry) =>
-                  FileSystem.Entry.make({
-                    ...entry,
-                    path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))),
-                  }),
-                ),
-              ),
-              Effect.orDie,
-            )
-        }),
-      grep: (input) =>
-        Effect.gen(function* () {
-          const target = path.resolve(location.directory, input.path ?? ".")
-          const info = yield* fs.stat(target).pipe(Effect.orDie)
-          const cwd = info.type === "File" ? path.dirname(target) : target
-          return yield* ripgrep
-            .grep({
-              cwd,
-              pattern: input.pattern,
-              file: info.type === "File" ? path.basename(target) : undefined,
-              include: input.include,
-              limit: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
-            })
-            .pipe(
-              Effect.map((result) =>
-                result.map((match) =>
-                  FileSystem.Match.make({
-                    ...match,
-                    entry: FileSystem.Entry.make({
-                      ...match.entry,
-                      path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))),
-                    }),
-                  }),
-                ),
-              ),
-              Effect.orDie,
-            )
-        }),
       find: (input) =>
         Effect.gen(function* () {
           const items =
@@ -142,55 +87,10 @@ export const fffLayer = Layer.effect(
       if (result) yield* Effect.logWarning("failed to initialize fff", { error: result.error })
       return Service.of({
         find: () => Effect.succeed([]),
-        glob: () => Effect.succeed([]),
-        grep: () => Effect.succeed([]),
       })
     }
     yield* Effect.addFinalizer(() => Effect.sync(() => result.value.destroy()).pipe(Effect.ignore))
     return Service.of({
-      glob: (input) =>
-        Effect.sync(() => {
-          const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")
-          const found = result.value.glob(prefix ? `${prefix}/${input.pattern}` : input.pattern, {
-            pageIndex: 0,
-            pageSize: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT,
-          })
-          if (!found.ok) throw found.error
-          return found.value.items.map((item) =>
-            FileSystem.Entry.make({
-              path: RelativePath.make(item.relativePath.replaceAll("\\", "/")),
-              type: "file",
-            }),
-          )
-        }),
-      grep: (input) =>
-        Effect.sync(() => {
-          const prefix = input.path?.replaceAll("\\", "/").replace(/\/$/, "")
-          const found = result.value.grep(
-            [prefix ? `${prefix}/**` : undefined, input.include, input.pattern]
-              .filter((value) => value !== undefined)
-              .join(" "),
-            { mode: "regex", pageSize: input.limit ?? FileSystem.DEFAULT_SEARCH_LIMIT, timeBudgetMs: 1_500 },
-          )
-          if (!found.ok) throw found.error
-          return found.value.items.map((match) => {
-            const bytes = Buffer.from(match.lineContent)
-            return FileSystem.Match.make({
-              entry: FileSystem.Entry.make({
-                path: RelativePath.make(match.relativePath.replaceAll("\\", "/")),
-                type: "file",
-              }),
-              line: match.lineNumber,
-              offset: match.byteOffset,
-              text: match.lineContent.length > 2_000 ? match.lineContent.slice(0, 2_000) + "..." : match.lineContent,
-              submatches: match.matchRanges.map(([start, end]) => ({
-                text: bytes.subarray(start, end).toString("utf8"),
-                start,
-                end,
-              })),
-            })
-          })
-        }),
       find: (input) =>
         Effect.sync(() => {
           const options = { pageIndex: 0, pageSize: input.limit ?? 50 }
@@ -247,7 +147,7 @@ export const layer = (options?: Options) =>
   )
 
 export function configured(options?: Options) {
-  return makeLocationNode({ service: Service, layer: layer(options), deps: [FSUtil.node, Location.node, Ripgrep.node] })
+  return makeLocationNode({ service: Service, layer: layer(options), deps: [Location.node, Ripgrep.node] })
 }
 
 export const node = configured()

+ 0 - 39
packages/core/test/filesystem/search.test.ts

@@ -1,5 +1,4 @@
 import { describe, expect, test } from "bun:test"
-import fs from "fs/promises"
 import os from "os"
 import path from "path"
 import { Effect, Layer } from "effect"
@@ -7,48 +6,10 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
 import { FileSystem } from "@opencode-ai/core/filesystem"
 import { Protected } from "@opencode-ai/core/filesystem/protected"
 import { FileSystemSearch } from "@opencode-ai/core/filesystem/search"
-import { LayerNode } from "@opencode-ai/util/effect/layer-node"
 import { Location } from "@opencode-ai/core/location"
 import { Ripgrep } from "@opencode-ai/core/ripgrep"
 import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
 import { location } from "../fixture/location"
-import { tmpdir } from "../fixture/tmpdir"
-import { testEffect } from "../lib/effect"
-
-const it = testEffect(LayerNode.compile(Ripgrep.node))
-
-const withTmp = <A, E, R>(f: (directory: AbsolutePath) => Effect.Effect<A, E, R>) =>
-  Effect.acquireRelease(
-    Effect.promise(() => tmpdir()),
-    (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
-  ).pipe(Effect.flatMap((tmp) => f(AbsolutePath.make(tmp.path))))
-
-describe("Ripgrep", () => {
-  it.live("globs files as an array", () =>
-    withTmp((cwd) =>
-      Effect.gen(function* () {
-        yield* Effect.promise(() => fs.mkdir(path.join(cwd, "src")))
-        yield* Effect.promise(() => fs.writeFile(path.join(cwd, "src", "match.ts"), "needle\n"))
-        const result = yield* (yield* Ripgrep.Service).glob({ cwd, pattern: "**/*.ts", limit: 10 })
-        expect(result.map((item) => item.path)).toEqual([RelativePath.make("src/match.ts")])
-      }),
-    ),
-  )
-
-  it.live("greps files with include filtering", () =>
-    withTmp((cwd) =>
-      Effect.gen(function* () {
-        yield* Effect.promise(() => fs.mkdir(path.join(cwd, "src")))
-        yield* Effect.promise(() => fs.writeFile(path.join(cwd, "src", "match.ts"), "needle\n"))
-        yield* Effect.promise(() => fs.writeFile(path.join(cwd, "src", "skip.txt"), "needle\n"))
-        const result = yield* (yield* Ripgrep.Service).grep({ cwd, pattern: "needle", include: "*.ts", limit: 10 })
-        expect(result).toHaveLength(1)
-        expect(result[0]?.entry.path).toBe(RelativePath.make("src/match.ts"))
-        expect(result[0]?.submatches[0]?.text).toBe("needle")
-      }),
-    ),
-  )
-})
 
 describe("FileSystemSearch", () => {
   test("bounds a home scan even when home is detected as a repository", async () => {

+ 38 - 0
packages/core/test/ripgrep.test.ts

@@ -11,6 +11,44 @@ import { testEffect } from "./lib/effect"
 const it = testEffect(LayerNode.compile(Ripgrep.node))
 
 describe("Ripgrep", () => {
+  it.live("globs files as an array", () =>
+    Effect.acquireUseRelease(
+      Effect.promise(() => tmpdir()),
+      (tmp) =>
+        Effect.gen(function* () {
+          yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src")))
+          yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "match.ts"), "needle\n"))
+
+          const result = yield* (yield* Ripgrep.Service).glob({ cwd: tmp.path, pattern: "**/*.ts", limit: 10 })
+          expect(result.map((item) => item.path)).toEqual([RelativePath.make("src/match.ts")])
+        }),
+      (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
+    ),
+  )
+
+  it.live("greps files with include filtering", () =>
+    Effect.acquireUseRelease(
+      Effect.promise(() => tmpdir()),
+      (tmp) =>
+        Effect.gen(function* () {
+          yield* Effect.promise(() => fs.mkdir(path.join(tmp.path, "src")))
+          yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "match.ts"), "needle\n"))
+          yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "src", "skip.txt"), "needle\n"))
+
+          const result = yield* (yield* Ripgrep.Service).grep({
+            cwd: tmp.path,
+            pattern: "needle",
+            include: "*.ts",
+            limit: 10,
+          })
+          expect(result).toHaveLength(1)
+          expect(result[0]?.entry.path).toBe(RelativePath.make("src/match.ts"))
+          expect(result[0]?.submatches[0]?.text).toBe("needle")
+        }),
+      (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
+    ),
+  )
+
   it.live("keeps ignored files out of catch-all find results", () =>
     Effect.acquireUseRelease(
       Effect.promise(() => tmpdir()),