import { KeyCommand } from './ansi.ts'; const decoder = new TextDecoder(); // ---------------------------------------------------------------------------- // Misc // ---------------------------------------------------------------------------- /** * An empty function */ export const noop = () => {}; /** * Convert input from ANSI escape sequences into a form * that can be more easily mapped to editor commands * * @param raw - the raw chunk of input */ export function readKey(raw: Uint8Array): string { if (raw.length === 0) { return ''; } const parsed = decoder.decode(raw); // Return the input if it's unambiguous if (parsed in KeyCommand) { return parsed; } // Some key codes have multiple potential inputs switch (parsed) { case '\x1b[1~': case '\x1b[7~': case '\x1bOH': case '\x1b[H': return KeyCommand.Home; case '\x1b[4~': case '\x1b[8~': case '\x1bOF': case '\x1b[F': return KeyCommand.End; case '\n': case '\v': return KeyCommand.Enter; case ctrlKey('l'): return KeyCommand.Escape; case ctrlKey('h'): return KeyCommand.Backspace; default: return parsed; } } // ---------------------------------------------------------------------------- // Array manipulation // ---------------------------------------------------------------------------- /** * Insert a value into an array at the specified index * @param arr - the array * @param at - the index to insert at * @param value - what to add into the array */ export function arrayInsert( arr: Array, at: number, value: T | Array, ): Array { const insert = Array.isArray(value) ? value : [value]; if (at >= arr.length) { arr.push(...insert); return arr; } return [...arr.slice(0, at), ...insert, ...arr.slice(at)]; } // ---------------------------------------------------------------------------- // Math // ---------------------------------------------------------------------------- /** * Subtract two numbers, returning a zero if the result is negative * @param l * @param s */ export function posSub(l: number, s: number): number { return minSub(l, s, 0); } /** * Subtract two numbers, returning at least the minimum specified * @param l * @param s * @param min */ export function minSub(l: number, s: number, min: number): number { return Math.max(l - s, min); } /** * Add two numbers, up to a max value * @param n1 * @param n2 * @param max */ export function maxAdd(n1: number, n2: number, max: number): number { return Math.min(n1 + n2, max); } // ---------------------------------------------------------------------------- // Strings // ---------------------------------------------------------------------------- /** * Get the codepoint of the first byte of a string. If the string * is empty, this will return 256 * * @param s - the string */ export function ord(s: string): number { if (s.length > 0) { return s.codePointAt(0)!; } return 256; } /** * Split a string by graphemes, not just bytes * * @param s - the string to split into unicode code points */ export function strChars(s: string): string[] { return [...s]; } /** * Get the 'character length' of a string, not its UTF16 byte count * * @param s - the string to check */ export function strlen(s: string): number { return strChars(s).length; } /** * Are all the characters in the string in ASCII range? * * @param char - string to check */ export function isAscii(char: string): boolean { return strChars(char).every((char) => ord(char) < 0x80); } /** * Are all the characters numerals? * * @param char - string to check */ export function isAsciiDigit(char: string): boolean { return isAscii(char) && strChars(char).every((char) => ord(char) >= 0x30 && ord(char) < 0x3a); } /** * Is the one char in the string an ascii control character? * * @param char - a one character string to check */ export function isControl(char: string): boolean { const code = ord(char); return isAscii(char) && (code === 0x7f || code < 0x20); } /** * Get the key code for a ctrl chord * * @param char - a one character string */ export function ctrlKey(char: string): string { // This is the normal use case, of course if (isAscii(char)) { const point = ord(char); return String.fromCodePoint(point & 0x1f); } // If it's not ascii, just return the input key code return char; } /** * Trim a string to a max number of characters * @param s * @param maxLen */ export function truncate(s: string, maxLen: number): string { const chin = strChars(s); if (maxLen >= chin.length) { return s; } return chin.slice(0, maxLen).join(''); }