Przeglądaj źródła

refactor(file): stream ripgrep search parsing (#22303)

Kit Langton 5 miesięcy temu
rodzic
commit
3644581b55
1 zmienionych plików z 47 dodań i 12 usunięć
  1. 47 12
      packages/opencode/src/file/ripgrep.ts

+ 47 - 12
packages/opencode/src/file/ripgrep.ts

@@ -3,7 +3,7 @@ import path from "path"
 import { Global } from "../global"
 import fs from "fs/promises"
 import z from "zod"
-import { Effect, Layer, Context } from "effect"
+import { Effect, Layer, Context, Schema } from "effect"
 import * as Stream from "effect/Stream"
 import { ChildProcess } from "effect/unstable/process"
 import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
@@ -94,6 +94,40 @@ export namespace Ripgrep {
 
   const Result = z.union([Begin, Match, End, Summary])
 
+  const Hit = Schema.Struct({
+    type: Schema.Literal("match"),
+    data: Schema.Struct({
+      path: Schema.Struct({
+        text: Schema.String,
+      }),
+      lines: Schema.Struct({
+        text: Schema.String,
+      }),
+      line_number: Schema.Number,
+      absolute_offset: Schema.Number,
+      submatches: Schema.mutable(
+        Schema.Array(
+          Schema.Struct({
+            match: Schema.Struct({
+              text: Schema.String,
+            }),
+            start: Schema.Number,
+            end: Schema.Number,
+          }),
+        ),
+      ),
+    }),
+  })
+
+  const Row = Schema.Union([
+    Schema.Struct({ type: Schema.Literal("begin"), data: Schema.Unknown }),
+    Hit,
+    Schema.Struct({ type: Schema.Literal("end"), data: Schema.Unknown }),
+    Schema.Struct({ type: Schema.Literal("summary"), data: Schema.Unknown }),
+  ])
+
+  const decode = Schema.decodeUnknownEffect(Schema.fromJsonString(Row))
+
   export type Result = z.infer<typeof Result>
   export type Match = z.infer<typeof Match>
   export type Item = Match["data"]
@@ -389,9 +423,19 @@ export namespace Ripgrep {
               }),
             )
 
-            const [stdout, stderr, code] = yield* Effect.all(
+            const [items, stderr, code] = yield* Effect.all(
               [
-                Stream.mkString(Stream.decodeText(handle.stdout)),
+                Stream.decodeText(handle.stdout).pipe(
+                  Stream.splitLines,
+                  Stream.filter((line) => line.length > 0),
+                  Stream.mapEffect((line) =>
+                    decode(line).pipe(Effect.mapError((cause) => new Error("invalid ripgrep output", { cause }))),
+                  ),
+                  Stream.filter((row): row is Schema.Schema.Type<typeof Hit> => row.type === "match"),
+                  Stream.map((row): Item => row.data),
+                  Stream.runCollect,
+                  Effect.map((chunk) => [...chunk]),
+                ),
                 Stream.mkString(Stream.decodeText(handle.stderr)),
                 handle.exitCode,
               ],
@@ -402,15 +446,6 @@ export namespace Ripgrep {
               return yield* Effect.fail(new Error(`ripgrep failed: ${stderr}`))
             }
 
-            const items = stdout
-              .trim()
-              .split(/\r?\n/)
-              .filter(Boolean)
-              .map((line) => JSON.parse(line))
-              .map((parsed) => Result.parse(parsed))
-              .filter((row): row is Match => row.type === "match")
-              .map((row) => row.data)
-
             return {
               items,
               partial: code === 2,