import { color, crlf } from './terminal.js'; export function configureTerminal(stream, title) { stream.write('\x1b[?2004h\x1b[?7h\x1b[?25h\x1b[?1h\x1b='); stream.write(`\x1b]2;${String(title).replace(/[\x00-\x1f\x7f]/g, '')}\x07`); } export function createTuiSession({ channel, ctx, router, renderHome, renderPage, prompt, onClose, onCommand, maxInputLength = 1024 }) { const state = { input: '', cursor: 0, history: [], historyIndex: 0, closed: false, inputHandler: null }; const redrawInput = () => { if (state.closed) return; channel.write(`\r\x1b[2K${prompt(ctx)}${state.input}`); if (state.cursor < state.input.length) channel.write(`\x1b[${state.input.length - state.cursor}D`); }; const insert = text => { let clean = text.replace(/\x1b\[20[01]~/g, '').replace(/[\r\n]+/g, ' ').replace(/[\x00-\x1f\x7f-\x9f]/g, ''); if (!clean) return; const room = Math.max(0, maxInputLength - state.input.length); clean = clean.slice(0, room); if (!clean) return; state.input = state.input.slice(0, state.cursor) + clean + state.input.slice(state.cursor); state.cursor += clean.length; redrawInput(); }; const showHome = () => { if (!state.closed) channel.write(renderHome(ctx) + prompt(ctx) + state.input); }; const notify = text => { if (!state.closed && !state.inputHandler) { channel.write(`\r\x1b[2K${crlf(text)}\r\n`); redrawInput(); } }; const updateHeader = text => { if (!state.closed && !state.inputHandler) channel.write(`\x1b7\x1b[1;1H\x1b[2K${text}\x1b8`); }; const setInputHandler = handler => { state.inputHandler = handler; }; const finishLive = text => { state.inputHandler = null; if (!state.closed) { channel.write(`\x1b[2J\x1b[H${crlf(text)}\r\n`); redrawInput(); } }; const submit = () => { const input = state.input.trim(); channel.write('\r\n'); if (input) { if (state.history.at(-1) !== input) state.history.push(input); if (state.history.length > 100) state.history.shift(); onCommand?.(input); } state.historyIndex = state.history.length; state.input = ''; state.cursor = 0; const name = input.split(/\s+/)[0]?.toLowerCase() || 'home'; if (name === 'home' || name === 'clear') { router.execute(ctx, input); showHome(); return; } const output = router.execute(ctx, input); if (state.closed || channel.destroyed) return; if (output === '__LIVE__') return; channel.write(renderPage(ctx, input, output) + prompt(ctx)); }; const history = direction => { if (!state.history.length) return; state.historyIndex = Math.max(0, Math.min(state.history.length, state.historyIndex + direction)); state.input = state.historyIndex === state.history.length ? '' : state.history[state.historyIndex]; state.cursor = state.input.length; redrawInput(); }; const move = delta => { const next = Math.max(0, Math.min(state.input.length, state.cursor + delta)); if (next !== state.cursor) { channel.write(delta < 0 ? `\x1b[${state.cursor - next}D` : `\x1b[${next - state.cursor}C`); state.cursor = next; } }; channel.on('data', data => { if (state.inputHandler?.(data.toString()) === true) return; let key = data.toString(); if (key.includes('\x1b[200~')) { insert(key); return; } while (key.length) { const sequence = ['\x1b[A', '\x1bOA', '\x1b[B', '\x1bOB', '\x1b[C', '\x1bOC', '\x1b[D', '\x1bOD'].find(item => key.startsWith(item)); if (sequence) { if (sequence.endsWith('A')) history(-1); else if (sequence.endsWith('B')) history(1); else if (sequence.endsWith('C')) move(1); else move(-1); key = key.slice(sequence.length); continue; } const char = key[0]; key = key.slice(1); if (char === '\r' || char === '\n') { if (char === '\r' && key[0] === '\n') key = key.slice(1); submit(); } else if (char === '\x03' || (char === '\x04' && !state.input)) channel.end(); else if (char === '\x7f' || char === '\b') { if (state.cursor > 0) { state.input = state.input.slice(0, state.cursor - 1) + state.input.slice(state.cursor); state.cursor--; redrawInput(); } } else if (char === '\x01') move(-state.cursor); else if (char === '\x05') move(state.input.length - state.cursor); else if (char >= ' ') insert(char); } }); channel.on('close', () => { state.closed = true; onClose?.(); }); return { showHome, notify, redrawInput, updateHeader, setInputHandler, finishLive, state }; }