/** * Loads lib/edit-key-parse.js in an isolated function scope (same source as /bin/edit preamble). */ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = dirname(fileURLToPath(import.meta.url)) const parseSrc = readFileSync(join(__dirname, '../lib/edit/edit-key-parse.js'), 'utf8') const hook = {} new Function( 'exports', `${parseSrc}\nexports.bareEditTryConsumeKey = bareEditTryConsumeKey;\nexports.bareEditTryConsumeBracketedPaste = bareEditTryConsumeBracketedPaste;\nexports.bareEditFinalizeBracketedPasteOnEof = bareEditFinalizeBracketedPasteOnEof;` )(hook) const { bareEditTryConsumeKey, bareEditTryConsumeBracketedPaste, bareEditFinalizeBracketedPasteOnEof } = hook function consumeAll(bytes) { const q = [...bytes] const out = [] while (q.length) { const ev = bareEditTryConsumeKey(q) assert.ok(ev, 'expected complete key, queue=' + JSON.stringify(q)) out.push(ev) } return out } assert.equal(bareEditTryConsumeKey([97])?.type, 'key') assert.equal(bareEditTryConsumeKey([97])?.ch, 'a') const upCsi = consumeAll([27, 91, 65]) assert.deepEqual(upCsi, [{ type: 'nav', key: 'up' }]) const upCsiNum = consumeAll([27, 91, 49, 59, 53, 65]) assert.deepEqual(upCsiNum, [{ type: 'nav', key: 'up' }]) const leftCsi = consumeAll([27, 91, 68]) assert.deepEqual(leftCsi, [{ type: 'nav', key: 'left' }]) const stabCsi = consumeAll([27, 91, 90]) assert.deepEqual(stabCsi, [{ type: 'nav', key: 'stab' }]) const stabCsiMod = consumeAll([27, 91, 49, 59, 50, 90]) assert.deepEqual(stabCsiMod, [{ type: 'nav', key: 'stab' }]) const tabKey = consumeAll([9]) assert.deepEqual(tabKey, [{ type: 'key', ch: '\t' }]) const ss3Up = consumeAll([27, 79, 65]) assert.deepEqual(ss3Up, [{ type: 'nav', key: 'up' }]) const ss3Left = consumeAll([27, 79, 68]) assert.deepEqual(ss3Left, [{ type: 'nav', key: 'left' }]) const del = consumeAll([27, 91, 51, 126]) assert.deepEqual(del, [{ type: 'ctrl', code: 'delete' }]) /* ESC must not be consumed as generic ctrl before CSI: order test */ const q = [27, 91, 66] const ev1 = bareEditTryConsumeKey(q) assert.deepEqual(ev1, { type: 'nav', key: 'down' }) assert.equal(q.length, 0) /* Incomplete CSI waits for more bytes */ const q2 = [27, 91] assert.equal(bareEditTryConsumeKey(q2), null) assert.deepEqual(q2, [27, 91]) q2.push(67) const ev2 = bareEditTryConsumeKey(q2) assert.deepEqual(ev2, { type: 'nav', key: 'right' }) /** ESC [ 200 ~ */ const BP_START = [27, 91, 50, 48, 48, 126] /** ESC [ 201 ~ */ const BP_END = [27, 91, 50, 48, 49, 126] /* Incomplete bracketed paste waits without consuming */ const partial = [...BP_START.slice(0, 4)] assert.equal(bareEditTryConsumeBracketedPaste(partial), null) assert.deepEqual(partial, BP_START.slice(0, 4)) /* Complete bracketed paste returns inner UTF-8 text */ const hello = [104, 101, 108, 108, 111] const qPaste = [...BP_START, ...hello, ...BP_END, 97] assert.equal(bareEditTryConsumeBracketedPaste(qPaste), 'hello') assert.deepEqual(qPaste, [97]) const evLetter = bareEditTryConsumeKey(qPaste) assert.deepEqual(evLetter, { type: 'key', ch: 'a' }) /* Inner € (UTF-8) decodes correctly */ const euro = [0xe2, 0x82, 0xac] const qEuro = [...BP_START, ...euro, ...BP_END] assert.equal(bareEditTryConsumeBracketedPaste(qEuro), '\u20ac') assert.equal(qEuro.length, 0) /* EOF finalize: START without END yields inner text */ const qEof = [...BP_START, ...hello] assert.equal(bareEditFinalizeBracketedPasteOnEof(qEof), 'hello') assert.equal(qEof.length, 0) console.log('edit-key-parse tests ok')