113 lines
3.1 KiB
JavaScript
113 lines
3.1 KiB
JavaScript
/** Stdin chunk queue + async key reader for edit/chat TUIs (requires edit-key-parse.js first). */
|
|
|
|
/**
|
|
* @param {unknown} chunk
|
|
* @returns {number[]}
|
|
*/
|
|
function bareEditChunkBytes(chunk) {
|
|
if (chunk == null) return []
|
|
if (typeof chunk === 'string') {
|
|
const out = []
|
|
for (let i = 0; i < chunk.length; i++) out.push(chunk.charCodeAt(i) & 0xff)
|
|
return out
|
|
}
|
|
const len = /** @type {{ length: number, [k: number]: number }} */ (chunk).length
|
|
const out = []
|
|
for (let i = 0; i < len; i++) out.push(Number(chunk[i]) & 0xff)
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* @param {{ nextByte: () => Promise<number|undefined>, dispose?: () => void, _keyq?: number[] }} reader
|
|
*/
|
|
async function bareEditReadKey(reader) {
|
|
reader._keyq = reader._keyq || []
|
|
const q = reader._keyq
|
|
for (;;) {
|
|
const pasteText = bareEditTryConsumeBracketedPaste(q)
|
|
if (pasteText !== undefined && pasteText !== null) {
|
|
return { type: 'paste', text: pasteText }
|
|
}
|
|
if (pasteText === null) {
|
|
const b = await reader.nextByte()
|
|
if (b === undefined) {
|
|
const eofPaste = bareEditFinalizeBracketedPasteOnEof(q)
|
|
if (eofPaste !== undefined) {
|
|
return { type: 'paste', text: eofPaste }
|
|
}
|
|
if (!q.length) return { type: 'eof' }
|
|
if (q.length === 1 && q[0] === 27) {
|
|
q.length = 0
|
|
return { type: 'key', ch: '\x1b' }
|
|
}
|
|
const lone = q.shift()
|
|
if (lone !== undefined && lone < 0x20) {
|
|
return { type: 'ctrl', code: lone }
|
|
}
|
|
if (lone !== undefined) {
|
|
return { type: 'key', ch: String.fromCharCode(lone) }
|
|
}
|
|
return { type: 'eof' }
|
|
}
|
|
q.push(b)
|
|
continue
|
|
}
|
|
const ev = bareEditTryConsumeKey(q)
|
|
if (ev) return ev
|
|
const b = await reader.nextByte()
|
|
if (b === undefined) {
|
|
const eofPaste = bareEditFinalizeBracketedPasteOnEof(q)
|
|
if (eofPaste !== undefined) {
|
|
return { type: 'paste', text: eofPaste }
|
|
}
|
|
if (!q.length) return { type: 'eof' }
|
|
if (q.length === 1 && q[0] === 27) {
|
|
q.length = 0
|
|
return { type: 'key', ch: '\x1b' }
|
|
}
|
|
const lone = q.shift()
|
|
if (lone !== undefined && lone < 0x20) {
|
|
return { type: 'ctrl', code: lone }
|
|
}
|
|
if (lone !== undefined) {
|
|
return { type: 'key', ch: String.fromCharCode(lone) }
|
|
}
|
|
return { type: 'eof' }
|
|
}
|
|
q.push(b)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {import('stream').Readable} stdin
|
|
*/
|
|
function bareEditCreateStdinReader(stdin) {
|
|
/** @type {number[]} */
|
|
const bytes = []
|
|
/** @type {(() => void)[]} */
|
|
const waiters = []
|
|
function drain() {
|
|
while (waiters.length && bytes.length) {
|
|
const w = waiters.shift()
|
|
if (w) w()
|
|
}
|
|
}
|
|
/** @param {unknown} chunk */
|
|
function onData(chunk) {
|
|
bytes.push(...bareEditChunkBytes(chunk))
|
|
drain()
|
|
}
|
|
stdin.on('data', onData)
|
|
return {
|
|
nextByte() {
|
|
if (bytes.length) return Promise.resolve(bytes.shift())
|
|
return new Promise((resolve) => {
|
|
waiters.push(() => resolve(bytes.shift()))
|
|
})
|
|
},
|
|
dispose() {
|
|
stdin.removeListener('data', onData)
|
|
}
|
|
}
|
|
}
|