diff --git a/bun.lock b/bun.lock index ffa5b51b7..7fbc4580f 100644 --- a/bun.lock +++ b/bun.lock @@ -180,13 +180,13 @@ "name": "@oh-my-pi/pi-utils", "version": "15.1.2", "dependencies": { + "@oh-my-pi/pi-natives": "catalog:", "beautiful-mermaid": "catalog:", "handlebars": "catalog:", "winston": "catalog:", "winston-daily-rotate-file": "catalog:", }, "devDependencies": { - "@oh-my-pi/pi-natives": "catalog:", "@types/bun": "catalog:", }, }, diff --git a/crates/pi-natives/src/text.rs b/crates/pi-natives/src/text.rs index a00a6badc..42c4fece0 100644 --- a/crates/pi-natives/src/text.rs +++ b/crates/pi-natives/src/text.rs @@ -1263,89 +1263,6 @@ pub fn extract_segments( }) } -// ============================================================================ -// sanitizeText -// ============================================================================ - -/// Strip ANSI escape sequences, remove control characters / lone surrogates, -/// and normalize line endings. -#[napi] -pub fn sanitize_text(text: JsString<'_>) -> Result, Utf16String>> { - let original = text; - let text_u16 = text.into_utf16()?; - let data = text_u16.as_slice(); - - let mut did_change = false; - let mut out: Vec = Vec::new(); - let mut last = 0usize; - let mut i = 0usize; - let len = data.len(); - - while i < len { - let u = data[i]; - - // Allow tab + newline; normalize CR by removing it. - if u == 0x09 || u == 0x0a { - i += 1; - continue; - } - - let mut remove_len = if u == ESC - && let Some(seq_len) = ansi_seq_len_u16(data, i) - { - seq_len - } else { - 0usize - }; - - if remove_len == 0 { - // Drop CR to normalize line endings. - if u == 0x0d { - remove_len = 1; - } else if u <= 0x1f || u == 0x7f || (0x80..=0x9f).contains(&u) { - // C0 + DEL + C1 controls. - remove_len = 1; - } else if (0xd800..=0xdbff).contains(&u) { - // High surrogate: keep only if followed by a valid low surrogate. - if i + 1 < len { - let lo = data[i + 1]; - if (0xdc00..=0xdfff).contains(&lo) { - i += 2; - continue; - } - } - remove_len = 1; - } else if (0xdc00..=0xdfff).contains(&u) { - // Lone low surrogate. - remove_len = 1; - } - } - - if remove_len == 0 { - i += 1; - continue; - } - - if !did_change { - did_change = true; - out = Vec::with_capacity(len); - } - if last != i { - out.extend_from_slice(&data[last..i]); - } - i += remove_len; - last = i; - } - - if !did_change { - return Ok(Either::A(original)); - } - if last < len { - out.extend_from_slice(&data[last..]); - } - Ok(Either::B(build_utf16_string(out))) -} - // ============================================================================ // visibleWidth // ============================================================================ diff --git a/packages/natives/native/index.d.ts b/packages/natives/native/index.d.ts index 7153c09d6..0b59e0bd8 100644 --- a/packages/natives/native/index.d.ts +++ b/packages/natives/native/index.d.ts @@ -1154,12 +1154,6 @@ export interface PtyStartOptions { */ export declare function readImageFromClipboard(): Promise -/** - * Strip ANSI escape sequences, remove control characters / lone surrogates, - * and normalize line endings. - */ -export declare function sanitizeText(text: string): string - /** * Search content for a pattern (one-shot, compiles pattern each time). * For repeated searches with the same pattern, use [`grep`] with file filters. diff --git a/packages/natives/native/index.js b/packages/natives/native/index.js index 965e0d7f3..568dd0887 100644 --- a/packages/natives/native/index.js +++ b/packages/natives/native/index.js @@ -56,7 +56,6 @@ export const matchesLegacySequence = nativeBindings.matchesLegacySequence; export const parseKey = nativeBindings.parseKey; export const parseKittySequence = nativeBindings.parseKittySequence; export const readImageFromClipboard = nativeBindings.readImageFromClipboard; -export const sanitizeText = nativeBindings.sanitizeText; export const search = nativeBindings.search; export const sliceWithWidth = nativeBindings.sliceWithWidth; export const summarizeCode = nativeBindings.summarizeCode; diff --git a/packages/natives/test/native.test.ts b/packages/natives/test/native.test.ts index b67bf3325..29380df91 100644 --- a/packages/natives/test/native.test.ts +++ b/packages/natives/test/native.test.ts @@ -15,7 +15,6 @@ import { listWorkspace, MacOSPowerAssertion, PtySession, - sanitizeText, summarizeCode, truncateToWidth, visibleWidth, @@ -584,28 +583,11 @@ describe("pi-natives", () => { }); }); - describe("sanitizeText", () => { - it("should strip ANSI, remove control chars and normalize CR", () => { - const input = "\x1b[31mred\x1b[0m\ra\u0000b\tline\ncarriage\r\u0001\u0085"; - expect(sanitizeText(input)).toBe("redab\tline\ncarriage"); - }); - - it("should remove lone surrogates but keep valid pairs", () => { - expect(sanitizeText(`a\ud800b\udc00c`)).toBe("abc"); - const validPair = "a\u{1f600}b"; - expect(sanitizeText(validPair)).toBe(validPair); - }); - - it("should strip OSC sequences", () => { - const input = "\x1b]0;title\x07hello"; - expect(sanitizeText(input)).toBe("hello"); - }); - describe("MacOSPowerAssertion", () => { - it("should create a stoppable power assertion handle", () => { - const assertion = MacOSPowerAssertion.start({ reason: "pi-natives test" }); - assertion.stop(); - assertion.stop(); - }); + describe("MacOSPowerAssertion", () => { + it("should create a stoppable power assertion handle", () => { + const assertion = MacOSPowerAssertion.start({ reason: "pi-natives test" }); + assertion.stop(); + assertion.stop(); }); }); }); diff --git a/packages/tui/bench/sanitize.ts b/packages/tui/bench/sanitize.ts index fa6dec8f2..cad98a321 100644 --- a/packages/tui/bench/sanitize.ts +++ b/packages/tui/bench/sanitize.ts @@ -1,4 +1,3 @@ -import { sanitizeText as nativeSanitizeText } from "@oh-my-pi/pi-natives"; import { sanitizeText as currentSanitizeText } from "@oh-my-pi/pi-utils/sanitize-text"; const STRIP_RE = new RegExp( @@ -313,39 +312,35 @@ console.log(`Text layout benchmark (${ITERATIONS} iterations)\n`); for (const name in samples) { const text = samples[name as keyof typeof samples]; + const baseline = currentSanitizeText(text); const jsResult = jsSanitizeText(text); - const nativeResult = nativeSanitizeText(text); - const currentResult = currentSanitizeText(text); const regexResult = regexSanitizeText(text); - if (jsResult !== nativeResult) { - console.log(`MISMATCH js/native ${name}`); + if (jsResult !== baseline) { + console.log(`MISMATCH js/current ${name}`); } - if (currentResult !== nativeResult) { - console.log(`MISMATCH current/native ${name}: current=${JSON.stringify(currentResult)} native=${JSON.stringify(nativeResult)}`); - } - if (regexResult !== nativeResult) { - console.log(`MISMATCH regex/native ${name}: regex=${JSON.stringify(regexResult)} native=${JSON.stringify(nativeResult)}`); + if (regexResult !== baseline) { + console.log(`MISMATCH regex/current ${name}: regex=${JSON.stringify(regexResult)} baseline=${JSON.stringify(baseline)}`); } const gatedResult = gatedSanitizeText(text); const skipResult = skipRunSanitizeText(text); - const removalStartResult = removalStartSanitizeText(text); - const wellFormedControlResult = wellFormedControlSanitizeText(text); - const lazyWellFormedResult = lazyWellFormedSanitizeText(text); - if (gatedResult !== nativeResult) { - console.log(`MISMATCH gated/native ${name}`); + const removalStartResult = removalStartSanitizeText(text); + const wellFormedControlResult = wellFormedControlSanitizeText(text); + const lazyWellFormedResult = lazyWellFormedSanitizeText(text); + if (gatedResult !== baseline) { + console.log(`MISMATCH gated/current ${name}`); } - if (skipResult !== nativeResult) { - console.log(`MISMATCH skip/native ${name}: skip=${JSON.stringify(skipResult)} native=${JSON.stringify(nativeResult)}`); + if (skipResult !== baseline) { + console.log(`MISMATCH skip/current ${name}: skip=${JSON.stringify(skipResult)} baseline=${JSON.stringify(baseline)}`); + } + if (removalStartResult !== baseline) { + console.log(`MISMATCH removalStart/current ${name}: removalStart=${JSON.stringify(removalStartResult)} baseline=${JSON.stringify(baseline)}`); + } + if (wellFormedControlResult !== baseline) { + console.log(`MISMATCH wellFormedControl/current ${name}: wellFormedControl=${JSON.stringify(wellFormedControlResult)} baseline=${JSON.stringify(baseline)}`); + } + if (lazyWellFormedResult !== baseline) { + console.log(`MISMATCH lazyWellFormed/current ${name}: lazyWellFormed=${JSON.stringify(lazyWellFormedResult)} baseline=${JSON.stringify(baseline)}`); } - if (removalStartResult !== nativeResult) { - console.log(`MISMATCH removalStart/native ${name}: removalStart=${JSON.stringify(removalStartResult)} native=${JSON.stringify(nativeResult)}`); - } - if (wellFormedControlResult !== nativeResult) { - console.log(`MISMATCH wellFormedControl/native ${name}: wellFormedControl=${JSON.stringify(wellFormedControlResult)} native=${JSON.stringify(nativeResult)}`); - } - if (lazyWellFormedResult !== nativeResult) { - console.log(`MISMATCH lazyWellFormed/native ${name}: lazyWellFormed=${JSON.stringify(lazyWellFormedResult)} native=${JSON.stringify(nativeResult)}`); - } bench(`jsSanitizeText/${name}`, () => { jsSanitizeText(text); @@ -362,17 +357,14 @@ for (const name in samples) { bench(`skipRunSanitizeText/${name}`, () => { skipRunSanitizeText(text); }); - bench(`removalStartSanitizeText/${name}`, () => { - removalStartSanitizeText(text); - }); - bench(`wellFormedControlSanitizeText/${name}`, () => { - wellFormedControlSanitizeText(text); - }); - bench(`lazyWellFormedSanitizeText/${name}`, () => { - lazyWellFormedSanitizeText(text); - }); - bench(`nativeSanitizeText/${name}`, () => { - nativeSanitizeText(text); + bench(`removalStartSanitizeText/${name}`, () => { + removalStartSanitizeText(text); + }); + bench(`wellFormedControlSanitizeText/${name}`, () => { + wellFormedControlSanitizeText(text); + }); + bench(`lazyWellFormedSanitizeText/${name}`, () => { + lazyWellFormedSanitizeText(text); }); console.log(); }