diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index ac8ea52d9..7e32ddb60 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -264,7 +264,10 @@ const layer = Layer.effect( }), line: match.line_number, offset: match.absolute_offset, - text: match.lines.text.length > 2_000 ? match.lines.text.slice(0, 2_000) + "..." : match.lines.text, + text: + match.lines.text.length > 2_000 + ? match.lines.text.slice(0, 2_000).replace(/[\uD800-\uDBFF]$/, "") + "..." + : match.lines.text, submatches: match.submatches.map((submatch) => ({ text: submatch.match.text, start: submatch.start, diff --git a/packages/core/test/ripgrep.test.ts b/packages/core/test/ripgrep.test.ts index 3abce1c02..5695af000 100644 --- a/packages/core/test/ripgrep.test.ts +++ b/packages/core/test/ripgrep.test.ts @@ -62,4 +62,24 @@ describe("Ripgrep", () => { (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), ), ) + it.live("does not split surrogate pairs in oversized line previews", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + yield* Effect.promise(() => + fs.writeFile(path.join(tmp.path, "unicode.txt"), `needle${"x".repeat(1_993)}😀\n`), + ) + + const matches = yield* (yield* Ripgrep.Service).grep({ + cwd: tmp.path, + pattern: "needle", + limit: 10, + }) + + expect(matches[0]?.text).toBe(`needle${"x".repeat(1_993)}...`) + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + ) })