717 lines
20 KiB
JavaScript
717 lines
20 KiB
JavaScript
/** Full-screen swarm chat TUI — preamble: edit-ansi → edit-key-parse → edit-stream-read. */
|
|
|
|
const BARE_CHAT_MAX_TRANSCRIPT = 4000
|
|
const BARE_CHAT_INPUT_MAX = 8192
|
|
|
|
/**
|
|
* @param {string} s
|
|
* @param {number} maxCols
|
|
*/
|
|
function bareChatTruncateVis(s, maxCols) {
|
|
const t = String(s || '')
|
|
if (maxCols < 8) return ''
|
|
if (t.length <= maxCols) return t
|
|
return t.slice(0, Math.max(0, maxCols - 1)) + '\u2026'
|
|
}
|
|
|
|
/**
|
|
* @param {unknown} ms
|
|
*/
|
|
function bareChatFmtClock(ms) {
|
|
if (typeof ms !== 'number' || !Number.isFinite(ms)) return '--:--:--'
|
|
const d = new Date(ms)
|
|
const z = (n) => (n < 10 ? '0' : '') + n
|
|
return z(d.getHours()) + ':' + z(d.getMinutes()) + ':' + z(d.getSeconds())
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ev
|
|
*/
|
|
function bareChatFmtEventLine(ev) {
|
|
const nickRaw = String(ev.displayName ?? '').trim()
|
|
const pkHint =
|
|
typeof ev.fromPeerKey === 'string'
|
|
? ev.fromPeerKey.slice(0, 10)
|
|
: ''
|
|
const nick = nickRaw || pkHint || 'peer'
|
|
const body = String(ev.body ?? '')
|
|
.replace(/\r\n/g, '\n')
|
|
.split('\n')
|
|
.join('\u2423 ')
|
|
const tag = ev.local ? '*' : ' '
|
|
const ms =
|
|
typeof ev.receivedAtMs === 'number'
|
|
? ev.receivedAtMs
|
|
: typeof ev.tsMs === 'number'
|
|
? ev.tsMs
|
|
: Date.now()
|
|
return '[' + bareChatFmtClock(ms) + ']' + tag + nick + ': ' + body
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ctx
|
|
*/
|
|
async function bareChatReadProcSnapshot(ctx) {
|
|
try {
|
|
const vfs = ctx.vfs
|
|
if (!vfs || typeof vfs.readFile !== 'function') return null
|
|
const b = await vfs.readFile('/proc/bare_os/chat.json')
|
|
if (!b) return null
|
|
const t = ctx.b4a.toString(b).trim()
|
|
if (!t) return null
|
|
return /** @type {Record<string, unknown>} */ (JSON.parse(t))
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown> | null} snap
|
|
* @param {{
|
|
* swarmPeers: { current: number | null },
|
|
* muxRx: { current: number | null },
|
|
* muxTx: { current: number | null },
|
|
* wireRxTotal: { current: number | null },
|
|
* dropRate: { current: number | null },
|
|
* dropVerify: { current: number | null }
|
|
* }} out
|
|
*/
|
|
function bareChatApplyProcSnap(snap, out) {
|
|
if (!snap || typeof snap !== 'object') return
|
|
const sp = snap.swarmPeers
|
|
out.swarmPeers.current =
|
|
typeof sp === 'number' && Number.isFinite(sp) ? sp : null
|
|
const met = snap.metrics
|
|
if (met && typeof met === 'object') {
|
|
const m = /** @type {{ rxEvent?: number, txEvent?: number, droppedRate?: number, droppedVerify?: number }} */ (
|
|
met
|
|
)
|
|
out.muxRx.current =
|
|
typeof m.rxEvent === 'number' && Number.isFinite(m.rxEvent)
|
|
? m.rxEvent
|
|
: null
|
|
out.muxTx.current =
|
|
typeof m.txEvent === 'number' && Number.isFinite(m.txEvent)
|
|
? m.txEvent
|
|
: null
|
|
out.dropRate.current =
|
|
typeof m.droppedRate === 'number' && Number.isFinite(m.droppedRate)
|
|
? m.droppedRate
|
|
: null
|
|
out.dropVerify.current =
|
|
typeof m.droppedVerify === 'number' &&
|
|
Number.isFinite(m.droppedVerify)
|
|
? m.droppedVerify
|
|
: null
|
|
} else {
|
|
out.muxRx.current = null
|
|
out.muxTx.current = null
|
|
out.dropRate.current = null
|
|
out.dropVerify.current = null
|
|
}
|
|
const wire = snap.protomuxChatRxTotal
|
|
out.wireRxTotal.current =
|
|
typeof wire === 'number' && Number.isFinite(wire) ? wire : null
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {string} argv0
|
|
*/
|
|
async function bareOsRunChatTui(ctx, argv0) {
|
|
const stdin = /** @type {import('stream').Readable | undefined} */ (
|
|
ctx.replStdin
|
|
)
|
|
const stdout = bareEditResolveStdout(ctx)
|
|
if (!stdin || !stdout) {
|
|
ctx.console.error('chat: missing stdin/stdout')
|
|
ctx.exitCode = 1
|
|
return
|
|
}
|
|
const useColor = bareEditUseColor(ctx)
|
|
|
|
const envEarly =
|
|
ctx.env && typeof ctx.env === 'object'
|
|
? /** @type {Record<string, string>} */ (ctx.env)
|
|
: {}
|
|
|
|
let tickMs = 1000
|
|
const tickRaw = envEarly.BARE_CHAT_STATUS_MS
|
|
if (tickRaw != null && String(tickRaw) !== '') {
|
|
const n = Number.parseInt(String(tickRaw), 10)
|
|
if (Number.isFinite(n) && n >= 0) tickMs = Math.min(Math.max(0, n), 3_600_000)
|
|
}
|
|
|
|
/** @type {'main'|'help'} */
|
|
let mode = 'main'
|
|
|
|
/** @type {string[]} */
|
|
const transcript = []
|
|
/** First visible transcript index */
|
|
let scrollTop = 0
|
|
/** When true, new messages snap scroll to bottom */
|
|
let stickToBottom = true
|
|
|
|
let inputBuf = ''
|
|
let inputCursor = 0
|
|
|
|
const metricRef = {
|
|
swarmPeers: /** @type {{ current: number | null }} */ ({ current: null }),
|
|
muxRx: /** @type {{ current: number | null }} */ ({ current: null }),
|
|
muxTx: /** @type {{ current: number | null }} */ ({ current: null }),
|
|
wireRxTotal: /** @type {{ current: number | null }} */ ({ current: null }),
|
|
dropRate: /** @type {{ current: number | null }} */ ({ current: null }),
|
|
dropVerify: /** @type {{ current: number | null }} */ ({ current: null })
|
|
}
|
|
|
|
async function refreshProcStrip() {
|
|
let snap = null
|
|
if (typeof ctx.bareOsChatProcSnapshot === 'function') {
|
|
try {
|
|
snap = ctx.bareOsChatProcSnapshot()
|
|
} catch {
|
|
snap = null
|
|
}
|
|
}
|
|
if (!snap || typeof snap !== 'object') {
|
|
snap = await bareChatReadProcSnapshot(ctx)
|
|
}
|
|
bareChatApplyProcSnap(snap, metricRef)
|
|
}
|
|
|
|
function clampScroll(viewH) {
|
|
const maxTop = Math.max(0, transcript.length - viewH)
|
|
if (stickToBottom || scrollTop > maxTop) scrollTop = maxTop
|
|
if (scrollTop < 0) scrollTop = 0
|
|
}
|
|
|
|
function appendEvent(rec) {
|
|
bareChatPushTranscript(transcript, bareChatFmtEventLine(rec))
|
|
}
|
|
|
|
function bareChatPushTranscript(lines, text) {
|
|
lines.push(text)
|
|
while (lines.length > BARE_CHAT_MAX_TRANSCRIPT) {
|
|
lines.shift()
|
|
if (scrollTop > 0) scrollTop--
|
|
}
|
|
}
|
|
|
|
function termDims() {
|
|
const cols = /** @type {{ columns?: number }} */ (stdout).columns ||
|
|
parseInt(envEarly.COLUMNS || '80', 10) ||
|
|
80
|
|
const rows = /** @type {{ rows?: number }} */ (stdout).rows ||
|
|
parseInt(envEarly.LINES || '24', 10) ||
|
|
24
|
|
return { cols: Math.max(40, cols), rows: Math.max(10, rows) }
|
|
}
|
|
|
|
let paintBusy = false
|
|
let paintAgain = false
|
|
|
|
function draw() {
|
|
const { cols, rows } = termDims()
|
|
const headerRows = 3
|
|
const footerRows = 3
|
|
const msgH = Math.max(1, rows - headerRows - footerRows)
|
|
clampScroll(msgH)
|
|
|
|
let out = '\x1b[?25l\x1b[2J\x1b[H'
|
|
|
|
if (mode === 'help') {
|
|
out +=
|
|
bareEditSgr('keyword', useColor) +
|
|
'Bare OS — swarm chat' +
|
|
EDIT_ANSI_RESET +
|
|
'\r\n\r\n' +
|
|
'Send with Enter. Scroll transcript with \u2191/\u2193 or PgUp/PgDn.\r\n' +
|
|
'Input: Backspace/Delete, \u2190/\u2192, Home/End, ^A/^E line start/end, ^U kill to start, ^K kill to end.\r\n' +
|
|
'^R refresh status ^L redraw ^Q / ^X / Ctrl+C exit\r\n' +
|
|
'`chat send TEXT` / `chat who` / `chat history` remain for scripts.\r\n' +
|
|
'Env: BARE_CHAT_STATUS_MS — status refresh interval in ms (default 1000; 0 disables timer).\r\n\r\n' +
|
|
'Press any key.\r\n'
|
|
bareEditWrite(ctx, stdout, out + '\x1b[?25h')
|
|
return
|
|
}
|
|
|
|
const rooms =
|
|
typeof ctx.bareOsChatRooms === 'function'
|
|
? ctx.bareOsChatRooms()
|
|
: []
|
|
const roomStr = Array.isArray(rooms) && rooms.length ? rooms.join(', ') : 'general'
|
|
|
|
const peerStr =
|
|
metricRef.swarmPeers.current != null
|
|
? String(metricRef.swarmPeers.current)
|
|
: '?'
|
|
const rxStr =
|
|
metricRef.muxRx.current != null ? String(metricRef.muxRx.current) : '?'
|
|
const txStr =
|
|
metricRef.muxTx.current != null ? String(metricRef.muxTx.current) : '?'
|
|
const wireStr =
|
|
metricRef.wireRxTotal.current != null
|
|
? String(metricRef.wireRxTotal.current)
|
|
: '?'
|
|
|
|
/** @type {string[]} */
|
|
const titleParts = [
|
|
argv0 || 'chat',
|
|
roomStr,
|
|
'peers ' + peerStr,
|
|
'rx ' + rxStr,
|
|
'tx ' + txStr,
|
|
'wire ' + wireStr
|
|
]
|
|
const dr = metricRef.dropRate.current ?? 0
|
|
const dv = metricRef.dropVerify.current ?? 0
|
|
if (dr > 0 || dv > 0) {
|
|
titleParts.push('drops r' + dr + '/v' + dv)
|
|
}
|
|
|
|
const title =
|
|
bareEditSgr('status', useColor) +
|
|
bareChatTruncateVis(
|
|
' \u250c ' + titleParts.join(' \u00b7 ') + ' ',
|
|
cols
|
|
) +
|
|
EDIT_ANSI_RESET
|
|
|
|
const nowClock = bareChatFmtClock(Date.now())
|
|
const hint =
|
|
bareEditSgr('dim', useColor) +
|
|
bareChatTruncateVis(
|
|
nowClock +
|
|
' bare-os-chat-v1 ? help ^Q quit ^R refresh' +
|
|
(tickMs > 0 ? ' tick ' + tickMs + 'ms' : ' tick off'),
|
|
cols
|
|
) +
|
|
EDIT_ANSI_RESET
|
|
out += bareEditCup(1, 1) + '\x1b[K' + title
|
|
out += bareEditCup(2, 1) + '\x1b[K' + hint
|
|
out +=
|
|
bareEditCup(3, 1) +
|
|
'\x1b[K' +
|
|
bareEditSgr('dim', useColor) +
|
|
'\u2500'.repeat(Math.min(cols, 120)) +
|
|
EDIT_ANSI_RESET
|
|
|
|
for (let i = 0; i < msgH; i++) {
|
|
const idx = scrollTop + i
|
|
const raw =
|
|
idx >= 0 && idx < transcript.length ? transcript[idx] : ''
|
|
const line = bareChatTruncateVis(raw, cols)
|
|
const row = headerRows + 1 + i
|
|
const dim =
|
|
idx === transcript.length - 1 && stickToBottom
|
|
? bareEditSgr('string', useColor)
|
|
: ''
|
|
out +=
|
|
bareEditCup(row, 1) +
|
|
'\x1b[K' +
|
|
dim +
|
|
line +
|
|
EDIT_ANSI_RESET
|
|
}
|
|
|
|
const sepRow = rows - footerRows + 1
|
|
out +=
|
|
bareEditCup(sepRow, 1) +
|
|
'\x1b[K' +
|
|
bareEditSgr('dim', useColor) +
|
|
'\u2500'.repeat(Math.min(cols, 120)) +
|
|
EDIT_ANSI_RESET
|
|
|
|
const hintRow = sepRow + 1
|
|
const linesBelow = Math.max(0, transcript.length - scrollTop - msgH)
|
|
let scrollHint = ''
|
|
if (!stickToBottom && (scrollTop > 0 || linesBelow > 0)) {
|
|
const parts = []
|
|
if (scrollTop > 0) parts.push('\u2191 ' + scrollTop + ' older')
|
|
if (linesBelow > 0) parts.push('\u2193 ' + linesBelow + ' newer')
|
|
scrollHint = parts.join(' ')
|
|
}
|
|
const hint2 =
|
|
bareEditSgr('dim', useColor) +
|
|
bareChatTruncateVis(
|
|
(scrollHint ? scrollHint + ' ' : '') +
|
|
'Enter send \u2191\u2193 transcript Backspace / ^A ^E ^U ^K',
|
|
cols
|
|
) +
|
|
EDIT_ANSI_RESET
|
|
out += bareEditCup(hintRow, 1) + '\x1b[K' + hint2
|
|
|
|
const prompt = '> '
|
|
const promptLen = prompt.length
|
|
const budget = Math.max(8, cols - promptLen - 2)
|
|
const ib = inputBuf
|
|
const ic = Math.min(Math.max(0, inputCursor), ib.length)
|
|
let winStart = 0
|
|
if (ib.length > budget) {
|
|
winStart = ic - Math.floor(budget / 2)
|
|
if (winStart < 0) winStart = 0
|
|
if (winStart > ib.length - budget) {
|
|
winStart = Math.max(0, ib.length - budget)
|
|
}
|
|
}
|
|
const slice = ib.slice(winStart, winStart + budget)
|
|
const rel = ic - winStart
|
|
const before = slice.slice(0, rel)
|
|
const curCh = rel < slice.length ? slice.charAt(rel) : ' '
|
|
const after = slice.slice(rel + 1)
|
|
const inputRow = hintRow + 1
|
|
const inputLine =
|
|
bareEditSgr('dim', useColor) +
|
|
prompt +
|
|
EDIT_ANSI_RESET +
|
|
before +
|
|
bareEditSgr('inverse', useColor) +
|
|
(curCh || ' ') +
|
|
EDIT_ANSI_RESET +
|
|
after
|
|
|
|
out += bareEditCup(inputRow, 1) + '\x1b[K' + inputLine
|
|
|
|
bareEditWrite(ctx, stdout, out + '\x1b[?25h')
|
|
}
|
|
|
|
function paint() {
|
|
if (paintBusy) {
|
|
paintAgain = true
|
|
return
|
|
}
|
|
paintBusy = true
|
|
try {
|
|
do {
|
|
paintAgain = false
|
|
draw()
|
|
} while (paintAgain)
|
|
} finally {
|
|
paintBusy = false
|
|
}
|
|
}
|
|
|
|
/** @type {(() => void) | null} */
|
|
let unsub = null
|
|
if (typeof ctx.bareOsChatSubscribe === 'function') {
|
|
unsub = ctx.bareOsChatSubscribe((ev) => {
|
|
appendEvent(
|
|
/** @type {Record<string, unknown>} */ (
|
|
ev && typeof ev === 'object' ? ev : {}
|
|
)
|
|
)
|
|
void refreshProcStrip().then(() => paint())
|
|
})
|
|
}
|
|
|
|
if (typeof ctx.bareOsChatHistory === 'function') {
|
|
try {
|
|
const hist = ctx.bareOsChatHistory(BARE_CHAT_MAX_TRANSCRIPT)
|
|
if (Array.isArray(hist)) {
|
|
for (const h of hist) {
|
|
if (h && typeof h === 'object') {
|
|
bareChatPushTranscript(
|
|
transcript,
|
|
bareChatFmtEventLine(
|
|
/** @type {Record<string, unknown>} */ (h)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
stickToBottom = true
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
const reader = bareEditCreateStdinReader(stdin)
|
|
let suspended = false
|
|
let useAltScreen = false
|
|
let useBracketPaste = false
|
|
|
|
/** @type {ReturnType<typeof setInterval> | null} */
|
|
let tickTimer = null
|
|
|
|
function onResize() {
|
|
void refreshProcStrip().then(() => paint())
|
|
}
|
|
|
|
await refreshProcStrip()
|
|
|
|
try {
|
|
if (typeof ctx.suspendReplForSubprocess === 'function') {
|
|
ctx.suspendReplForSubprocess()
|
|
suspended = true
|
|
}
|
|
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true)
|
|
if (typeof stdin.resume === 'function') stdin.resume()
|
|
|
|
const bareChatNoAltScreen =
|
|
envEarly.BARE_EDIT_NO_ALTSCREEN != null &&
|
|
String(envEarly.BARE_EDIT_NO_ALTSCREEN) !== ''
|
|
if (!bareChatNoAltScreen) {
|
|
bareEditWrite(ctx, stdout, '\x1b[?1049h')
|
|
useAltScreen = true
|
|
}
|
|
|
|
const bareChatNoBracketPaste =
|
|
envEarly.BARE_EDIT_NO_BRACKETED_PASTE != null &&
|
|
String(envEarly.BARE_EDIT_NO_BRACKETED_PASTE) !== ''
|
|
if (!bareChatNoBracketPaste) {
|
|
bareEditWrite(ctx, stdout, '\x1b[?2004h')
|
|
useBracketPaste = true
|
|
}
|
|
|
|
if (typeof stdout.on === 'function') {
|
|
try {
|
|
stdout.on('resize', onResize)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
try {
|
|
if (typeof process !== 'undefined' && typeof process.on === 'function') {
|
|
process.on('SIGWINCH', onResize)
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
|
|
if (tickMs > 0 && typeof setInterval === 'function') {
|
|
tickTimer = setInterval(() => {
|
|
void refreshProcStrip().then(() => paint())
|
|
}, tickMs)
|
|
}
|
|
|
|
paint()
|
|
|
|
for (;;) {
|
|
const ev = await bareEditReadKey(reader)
|
|
if (ev.type === 'eof') break
|
|
|
|
if (mode === 'help') {
|
|
mode = 'main'
|
|
paint()
|
|
continue
|
|
}
|
|
|
|
if (ev.type === 'ctrl') {
|
|
if (ev.code === 'interrupt') break
|
|
/** Backspace — key-parse uses `code: 'backspace'` for bytes 8 and 127 (not numeric). */
|
|
if (ev.code === 'backspace') {
|
|
if (inputCursor > 0) {
|
|
inputBuf =
|
|
inputBuf.slice(0, inputCursor - 1) + inputBuf.slice(inputCursor)
|
|
inputCursor--
|
|
}
|
|
paint()
|
|
continue
|
|
}
|
|
if (ev.code === 'delete') {
|
|
if (inputCursor < inputBuf.length) {
|
|
inputBuf =
|
|
inputBuf.slice(0, inputCursor) + inputBuf.slice(inputCursor + 1)
|
|
}
|
|
paint()
|
|
continue
|
|
}
|
|
const code = typeof ev.code === 'number' ? ev.code : 0
|
|
if (code === 3) break
|
|
if (code === 12) {
|
|
paint()
|
|
continue
|
|
}
|
|
if (code === 17 || code === 24) break
|
|
if (code === 18) {
|
|
await refreshProcStrip()
|
|
paint()
|
|
continue
|
|
}
|
|
/** Readline-style shortcuts (ASCII control chars). */
|
|
if (code === 1) {
|
|
inputCursor = 0
|
|
paint()
|
|
continue
|
|
}
|
|
if (code === 5) {
|
|
inputCursor = inputBuf.length
|
|
paint()
|
|
continue
|
|
}
|
|
if (code === 21) {
|
|
inputBuf = inputBuf.slice(inputCursor)
|
|
inputCursor = 0
|
|
paint()
|
|
continue
|
|
}
|
|
if (code === 11) {
|
|
inputBuf = inputBuf.slice(0, inputCursor)
|
|
paint()
|
|
continue
|
|
}
|
|
if (code === 8 || code === 127) {
|
|
if (inputCursor > 0) {
|
|
inputBuf =
|
|
inputBuf.slice(0, inputCursor - 1) + inputBuf.slice(inputCursor)
|
|
inputCursor--
|
|
}
|
|
paint()
|
|
continue
|
|
}
|
|
continue
|
|
}
|
|
|
|
if (ev.type === 'nav') {
|
|
const k = /** @type {{ key?: string }} */ (ev).key
|
|
if (k === 'up') {
|
|
stickToBottom = false
|
|
if (scrollTop > 0) scrollTop--
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'down') {
|
|
const { rows: rr } = termDims()
|
|
const msgH = Math.max(1, rr - 6)
|
|
const maxTop = Math.max(0, transcript.length - msgH)
|
|
if (scrollTop < maxTop) scrollTop++
|
|
if (scrollTop >= maxTop) stickToBottom = true
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'left') {
|
|
if (inputCursor > 0) inputCursor--
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'right') {
|
|
if (inputCursor < inputBuf.length) inputCursor++
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'home') {
|
|
inputCursor = 0
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'end') {
|
|
inputCursor = inputBuf.length
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'pageup') {
|
|
stickToBottom = false
|
|
const { rows: rr } = termDims()
|
|
const msgH = Math.max(1, rr - 6)
|
|
scrollTop = Math.max(0, scrollTop - msgH)
|
|
paint()
|
|
continue
|
|
}
|
|
if (k === 'pagedown') {
|
|
const { rows: rr } = termDims()
|
|
const msgH = Math.max(1, rr - 6)
|
|
const maxTop = Math.max(0, transcript.length - msgH)
|
|
scrollTop = Math.min(maxTop, scrollTop + msgH)
|
|
if (scrollTop >= maxTop) stickToBottom = true
|
|
paint()
|
|
continue
|
|
}
|
|
continue
|
|
}
|
|
|
|
if (ev.type === 'key' && ev.ch) {
|
|
if (ev.ch === '?') {
|
|
mode = 'help'
|
|
paint()
|
|
continue
|
|
}
|
|
const ch = ev.ch
|
|
if (ch === '\n') {
|
|
const line = inputBuf.trim()
|
|
inputBuf = ''
|
|
inputCursor = 0
|
|
if (line && typeof ctx.bareOsChatSend === 'function') {
|
|
ctx.bareOsChatSend(line)
|
|
}
|
|
stickToBottom = true
|
|
await refreshProcStrip()
|
|
paint()
|
|
continue
|
|
}
|
|
if (inputBuf.length < BARE_CHAT_INPUT_MAX) {
|
|
inputBuf =
|
|
inputBuf.slice(0, inputCursor) + ch + inputBuf.slice(inputCursor)
|
|
inputCursor += ch.length
|
|
}
|
|
paint()
|
|
continue
|
|
}
|
|
|
|
if (ev.type === 'paste') {
|
|
const first = String(ev.text || '')
|
|
.split(/\r?\n/)[0]
|
|
?.slice(0, BARE_CHAT_INPUT_MAX) ?? ''
|
|
if (first) {
|
|
const room = Math.max(
|
|
0,
|
|
BARE_CHAT_INPUT_MAX -
|
|
inputBuf.length +
|
|
(inputBuf.length - inputCursor)
|
|
)
|
|
const chunk = first.slice(0, room)
|
|
inputBuf =
|
|
inputBuf.slice(0, inputCursor) + chunk + inputBuf.slice(inputCursor)
|
|
inputCursor += chunk.length
|
|
}
|
|
paint()
|
|
continue
|
|
}
|
|
}
|
|
} finally {
|
|
if (tickTimer != null && typeof clearInterval === 'function') {
|
|
try {
|
|
clearInterval(tickTimer)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
tickTimer = null
|
|
}
|
|
if (typeof stdout.removeListener === 'function') {
|
|
try {
|
|
stdout.removeListener('resize', onResize)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
try {
|
|
if (typeof process !== 'undefined' && typeof process.off === 'function') {
|
|
process.off('SIGWINCH', onResize)
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
try {
|
|
if (useBracketPaste) bareEditWrite(ctx, stdout, '\x1b[?2004l')
|
|
if (useAltScreen) bareEditWrite(ctx, stdout, '\x1b[?1049l')
|
|
else bareEditWrite(ctx, stdout, '\x1b[2J\x1b[H')
|
|
bareEditWrite(ctx, stdout, '\x1b[?25h\x1b[0m')
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
try {
|
|
if (typeof unsub === 'function') unsub()
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
reader.dispose()
|
|
try {
|
|
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
|
|
ctx.resumeReplAfterSubprocess()
|
|
}
|
|
}
|
|
}
|