refactor(natives): removed sanitizeText from pi-natives, use JS impl

- Moved @oh-my-pi/pi-natives from devDependencies to dependencies in pi-utils.
- Deleted Rust sanitizeText implementation and its JS bindings/types.
- Updated sanitize benchmark to use pi-utils implementation as baseline.
This commit is contained in:
can1357
2026-05-16 20:21:36 +02:00
parent 7ea9e16408
commit 22848e89ff
6 changed files with 35 additions and 151 deletions
+1 -1
View File
@@ -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:",
},
},
-83
View File
@@ -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<Either<JsString<'_>, 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<u16> = 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
// ============================================================================
-6
View File
@@ -1154,12 +1154,6 @@ export interface PtyStartOptions {
*/
export declare function readImageFromClipboard(): Promise<ClipboardImage | undefined | null>
/**
* 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.
-1
View File
@@ -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;
+5 -23
View File
@@ -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();
});
});
});
+29 -37
View File
@@ -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();
}