refactor(natives): migrated native bindings to NAPI-RS with auto-generated types

- Migrated native bindings from TypeScript wrappers to NAPI-RS generated modules with auto-generated type definitions and runtime enums.
- Replaced chunk tree API with stateful ChunkState class supporting render, edit, and resolve operations with improved error handling.
- Converted callback signatures to error-first pattern (error, result) for shell, PTY, glob, and grep operations.
- Introduced type-safe enums for MacOSAppearance, GrepOutputMode, KeyEventType, ImageFormat, and AstMatchStrictness replacing string literals.
- Refactored chunk tree implementation with dedicated modules for edit, indent, resolve, and state management with comprehensive validation.
- Moved clipboard utilities from native bindings to coding-agent package with improved OSC 52 and Termux compatibility.
This commit is contained in:
can1357
2026-04-07 02:00:59 +02:00
parent 24a0d0ebde
commit 9b78a6fa85
119 changed files with 6370 additions and 4236 deletions
@@ -0,0 +1,80 @@
import { execSync } from "node:child_process";
import type { ClipboardImage } from "@oh-my-pi/pi-natives";
import * as native from "@oh-my-pi/pi-natives";
const hasDisplay = process.platform !== "linux" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
/**
* Copy text to the system clipboard.
*
* Emits OSC 52 first when running in a real terminal (works over SSH/mosh),
* then attempts native clipboard copy as best-effort for local sessions.
* On Termux, tries `termux-clipboard-set` before native.
*
* @param text - UTF-8 text to place on the clipboard.
*/
export async function copyToClipboard(text: string): Promise<void> {
if (process.stdout.isTTY) {
const onError = (err: unknown) => {
process.stdout.off("error", onError);
// Prevent unhandled 'error' from crashing the process when stdout is a closed pipe.
if ((err as NodeJS.ErrnoException | null | undefined)?.code === "EPIPE") {
return;
}
};
try {
const encoded = Buffer.from(text).toString("base64");
const osc52 = `\x1b]52;c;${encoded}\x07`;
process.stdout.on("error", onError);
process.stdout.write(osc52, err => {
process.stdout.off("error", onError);
// If stdout is closed (e.g. piped to a process that exits early),
// ignore EPIPE and proceed with native clipboard best-effort.
if ((err as NodeJS.ErrnoException | null | undefined)?.code === "EPIPE") {
return;
}
});
} catch (err) {
process.stdout.off("error", onError);
if ((err as NodeJS.ErrnoException | null | undefined)?.code !== "EPIPE") {
// Ignore all write failures (OSC 52 is best-effort).
}
}
}
// Also try native tools (best effort for local sessions)
try {
if (process.env.TERMUX_VERSION) {
try {
execSync("termux-clipboard-set", { input: text, timeout: 5000 });
return;
} catch {
// Fall through to native
}
}
await native.copyToClipboard(text);
} catch {
// Ignore — clipboard copy is best-effort
}
}
/**
* Read an image from the system clipboard.
*
* Returns null on Termux (no image clipboard support) or when no display
* server is available (headless/SSH without forwarding).
*
* @returns PNG payload or null when no image is available.
*/
export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
if (process.env.TERMUX_VERSION) {
return null;
}
if (!hasDisplay) {
return null;
}
return (await native.readImageFromClipboard()) ?? null;
}
@@ -7,6 +7,7 @@ import { resolveEditMode } from "./edit-mode";
export interface FileDisplayMode {
lineNumbers: boolean;
hashLines: boolean;
chunked: boolean;
}
/** Session-like object providing settings and tool availability for display mode resolution. */
@@ -27,8 +28,10 @@ export function resolveFileDisplayMode(session: FileDisplayModeSession): FileDis
const { settings } = session;
const hasEditTool = session.hasEditTool ?? true;
const hashLines = hasEditTool && resolveEditMode(session) === "hashline" && settings.get("readHashLines") !== false;
const chunked = hasEditTool && resolveEditMode(session) === "chunk";
return {
hashLines,
lineNumbers: hashLines || settings.get("readLineNumbers") === true,
chunked,
};
}