Files
Codeman/src/terminal-input-classifier.ts
T

63 lines
1.9 KiB
TypeScript

/** @fileoverview Distinguish complete terminal focus/mouse reports from keyboard-bearing input. */
const FOCUS_REPORT_LENGTH = 3;
const X10_REPORT_LENGTH = 6;
function sgrMouseReportLength(rest: string): number {
if (!rest.startsWith('\x1b[<')) return 0;
let offset = 3;
for (let field = 0; field < 3; field += 1) {
const digitStart = offset;
while (offset < rest.length && rest.charCodeAt(offset) >= 48 && rest.charCodeAt(offset) <= 57) {
offset += 1;
}
if (offset === digitStart) return 0;
if (field < 2) {
if (rest[offset] !== ';') return 0;
offset += 1;
}
}
return rest[offset] === 'M' || rest[offset] === 'm' ? offset + 1 : 0;
}
function x10MouseReportLength(rest: string): number {
if (!rest.startsWith('\x1b[M') || rest.length < X10_REPORT_LENGTH) return 0;
const button = rest.charCodeAt(3);
const x = rest.charCodeAt(4);
const y = rest.charCodeAt(5);
if (button < 32 || button > 255 || x < 33 || x > 255 || y < 33 || y > 255) return 0;
return X10_REPORT_LENGTH;
}
function terminalReportLengthAt(data: string, offset: number): number {
const rest = data.slice(offset);
if (rest.startsWith('\x1b[I') || rest.startsWith('\x1b[O')) return FOCUS_REPORT_LENGTH;
const sgrLength = sgrMouseReportLength(rest);
if (sgrLength > 0) return sgrLength;
return x10MouseReportLength(rest);
}
/**
* Whether terminal input contains any keyboard-bearing bytes.
*
* Returns false only when every byte is consumed as an allowlisted mouse or
* focus report. Unknown, malformed, mixed, and empty input fails closed.
*/
export function isKeyboardTerminalInput(data: string): boolean {
if (data.length === 0) return true;
let offset = 0;
while (offset < data.length) {
const reportLength = terminalReportLengthAt(data, offset);
if (reportLength === 0) return true;
offset += reportLength;
}
return false;
}