(function (ctx) { "use strict"; /* src/00-prelude.js */ /** Bare OS TUI concat prelude. Guest scripts receive this via ctx only. */ var BARE_TUI_VERSION = '1.3.0' var bareTuiSessions = [] var bareTuiNextWidgetId = 1 /* src/ansi/codes.js */ /** CSI / SGR / DEC private-mode sequences for the guest TUI. */ var BARE_TUI_ESC = '\x1b' var BARE_TUI_CSI = '\x1b[' function bareTuiSgr(n) { return BARE_TUI_CSI + String(n) + 'm' } var bareTuiAnsi = { esc: BARE_TUI_ESC, csi: BARE_TUI_CSI, reset: bareTuiSgr(0), cursorHide: BARE_TUI_CSI + '?25l', cursorShow: BARE_TUI_CSI + '?25h', home: BARE_TUI_CSI + 'H', eraseDisplay: BARE_TUI_CSI + '2J', eraseDisplayEnd: BARE_TUI_CSI + 'J', eraseScrollback: BARE_TUI_CSI + '3J', eraseLine: BARE_TUI_CSI + '2K', eraseLineEnd: BARE_TUI_CSI + 'K', enterAltScreen: BARE_TUI_CSI + '?1049h', leaveAltScreen: BARE_TUI_CSI + '?1049l', enableMouseBasic: BARE_TUI_CSI + '?1000h' + BARE_TUI_CSI + '?1006h', enableMouseDrag: BARE_TUI_CSI + '?1000h' + BARE_TUI_CSI + '?1002h' + BARE_TUI_CSI + '?1006h', enableMouseAll: BARE_TUI_CSI + '?1000h' + BARE_TUI_CSI + '?1003h' + BARE_TUI_CSI + '?1006h', disableMouse: BARE_TUI_CSI + '?1006l' + BARE_TUI_CSI + '?1003l' + BARE_TUI_CSI + '?1002l' + BARE_TUI_CSI + '?1000l', enableBracketPaste: BARE_TUI_CSI + '?2004h', disableBracketPaste: BARE_TUI_CSI + '?2004l', enableFocus: BARE_TUI_CSI + '?1004h', disableFocus: BARE_TUI_CSI + '?1004l', syncOutputBegin: BARE_TUI_CSI + '?2026h', syncOutputEnd: BARE_TUI_CSI + '?2026l', modifierReverse: bareTuiSgr(7), modifierNotReverse: bareTuiSgr(27), modifierDim: bareTuiSgr(2), cursorTo: function (row, col) { var r = (row | 0) + 1 var c = (col | 0) + 1 if (r < 1) r = 1 if (c < 1) c = 1 return BARE_TUI_CSI + r + ';' + c + 'H' }, cursorUp: function (n) { return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'A' }, cursorDown: function (n) { return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'B' }, cursorForward: function (n) { return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'C' }, cursorBack: function (n) { return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'D' }, sgr: bareTuiSgr, fg256: function (n) { return BARE_TUI_CSI + '38;5;' + (n | 0) + 'm' }, bg256: function (n) { return BARE_TUI_CSI + '48;5;' + (n | 0) + 'm' }, fgRgb: function (r, g, b) { return ( BARE_TUI_CSI + '38;2;' + (r | 0) + ';' + (g | 0) + ';' + (b | 0) + 'm' ) }, bgRgb: function (r, g, b) { return ( BARE_TUI_CSI + '48;2;' + (r | 0) + ';' + (g | 0) + ';' + (b | 0) + 'm' ) } } /* src/input/decoder.js */ /** Byte-queue key / mouse / paste decoder. Produces TEA-style messages. */ function bareTuiUtf8Decode(bytes) { try { if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8', { fatal: false }).decode( new Uint8Array(bytes) ) } } catch (e) { /* fall through */ } var s = '' for (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]) return s } function bareTuiUtf8TrailCount(b1) { if (b1 >= 0xc0 && b1 < 0xe0) return 1 if (b1 >= 0xe0 && b1 < 0xf0) return 2 if (b1 >= 0xf0) return 3 return 0 } function bareTuiKeyToString() { var parts = [] if (this.ctrl) parts.push('ctrl') if (this.meta) parts.push('alt') if (this.shift && this.name && this.name.length > 1) parts.push('shift') parts.push(this.name === 'return' ? 'enter' : this.name) return parts.join('+') } function bareTuiKeyIs() { var str = this.toString() for (var i = 0; i < arguments.length; i++) { var chord = arguments[i] if (chord === 'esc') chord = 'escape' if (chord === str || chord === this.name) return true if (chord === 'enter' && (str === 'enter' || this.name === 'return')) return true } return false } function bareTuiMakeKey(name, seq, ctrl, meta, shift) { return { type: 'key', name: name, sequence: seq || '', ctrl: !!ctrl, meta: !!meta, shift: !!shift, toString: bareTuiKeyToString, is: bareTuiKeyIs } } function bareTuiKeyMatches(msg) { if (!msg || msg.type !== 'key' || typeof msg.is !== 'function') return false var chords = [] for (var i = 1; i < arguments.length; i++) { var item = arguments[i] if (item && typeof item === 'object' && item.keys && item.keys.length) { for (var k = 0; k < item.keys.length; k++) chords.push(item.keys[k]) } else { chords.push(item) } } return msg.is.apply(msg, chords) } function bareTuiKeyBinding(opts) { var o = opts || {} var keys = o.keys if (!keys) keys = [] if (typeof keys === 'string') keys = [keys] return { keys: keys.slice(), help: o.help || null } } var BARE_TUI_CSI_NAMES = { A: 'up', B: 'down', C: 'right', D: 'left', H: 'home', F: 'end', Z: 'tab', P: 'f1', Q: 'f2', R: 'f3', S: 'f4' } var BARE_TUI_TILDE_NAMES = { 1: 'home', 2: 'insert', 3: 'delete', 4: 'end', 5: 'pageup', 6: 'pagedown', 7: 'home', 8: 'end', 11: 'f1', 12: 'f2', 13: 'f3', 14: 'f4', 15: 'f5', 17: 'f6', 18: 'f7', 19: 'f8', 20: 'f9', 21: 'f10', 23: 'f11', 24: 'f12' } function bareTuiParseCsiPayload(payload) { if (!payload) return null if (payload === 'I') return { type: 'focus', gained: true } if (payload === 'O') return { type: 'focus', gained: false } if (payload.charAt(0) === '<') { var m = /^<(\d+);(\d+);(\d+)([Mm])$/.exec(payload) if (!m) return { type: 'unknown', sequence: payload } var btn = parseInt(m[1], 10) var x = parseInt(m[2], 10) var y = parseInt(m[3], 10) var release = m[4] === 'm' var motion = (btn & 32) !== 0 var wheel = (btn & 64) !== 0 var code = btn & 3 var button = code === 0 ? 'left' : code === 1 ? 'middle' : code === 2 ? 'right' : 'none' var action = 'press' if (wheel) action = (btn & 1) === 0 ? 'scrollup' : 'scrolldown' else if (release) action = 'release' else if (motion) action = 'drag' return { type: 'mouse', action: action, button: button, x: x, y: y, ctrl: (btn & 16) !== 0, meta: (btn & 8) !== 0, shift: (btn & 4) !== 0 } } var last = payload.charAt(payload.length - 1) var body = payload.slice(0, payload.length - 1) var mods = 1 var num = '' var semi = body.indexOf(';') if (last === '~') { if (semi >= 0) { num = body.slice(0, semi) mods = parseInt(body.slice(semi + 1), 10) || 1 } else { num = body } if (num === '200') return { type: 'paste-start' } if (num === '201') return { type: 'paste-end' } var tname = BARE_TUI_TILDE_NAMES[num] if (!tname) return { type: 'unknown', sequence: payload } return bareTuiMakeKey( tname, payload, !!((mods - 1) & 4), !!((mods - 1) & 2), !!((mods - 1) & 1) ) } var letter = last if (semi >= 0) { var parts = body.split(';') if (parts.length >= 2) mods = parseInt(parts[1], 10) || 1 } var lname = BARE_TUI_CSI_NAMES[letter] if (!lname) return { type: 'unknown', sequence: payload } var shift = !!((mods - 1) & 1) if (letter === 'Z') shift = true return bareTuiMakeKey( lname, payload, !!((mods - 1) & 4), !!((mods - 1) & 2), shift ) } function bareTuiTryConsume(q) { if (!q.length) return null var b1 = q[0] if (b1 === 27) { if (q.length < 2) return { type: 'incomplete' } var b2 = q[1] if (b2 === 91) { var i = 2 while (i < q.length) { var b = q[i] if (b >= 0x40 && b <= 0x7e) { var seq = String.fromCharCode.apply(null, q.slice(2, i + 1)) q.splice(0, i + 1) return bareTuiParseCsiPayload(seq) } i++ } return { type: 'incomplete' } } if (b2 === 79) { if (q.length < 3) return { type: 'incomplete' } var b3 = q[2] q.splice(0, 3) var ss3 = String.fromCharCode(b3) var ssName = { A: 'up', B: 'down', C: 'right', D: 'left', H: 'home', F: 'end', P: 'f1', Q: 'f2', R: 'f3', S: 'f4' }[ss3] if (!ssName) return { type: 'unknown', sequence: 'O' + ss3 } return bareTuiMakeKey(ssName, 'O' + ss3, false, false, false) } q.splice(0, 2) if (b2 === 27) return bareTuiMakeKey('escape', '\x1b', false, false, false) if (b2 >= 1 && b2 <= 26) { return bareTuiMakeKey( String.fromCharCode(96 + b2), String.fromCharCode(b2), true, true, false ) } return bareTuiMakeKey( String.fromCharCode(b2), String.fromCharCode(b2), false, true, false ) } if (b1 === 3) { q.shift() return bareTuiMakeKey('c', '\x03', true, false, false) } if (b1 === 4) { q.shift() return bareTuiMakeKey('d', '\x04', true, false, false) } if (b1 === 8 || b1 === 127) { q.shift() return bareTuiMakeKey( 'backspace', String.fromCharCode(b1), false, false, false ) } if (b1 === 13 || b1 === 10) { q.shift() return bareTuiMakeKey('enter', String.fromCharCode(b1), false, false, false) } if (b1 === 9) { q.shift() return bareTuiMakeKey('tab', '\t', false, false, false) } if (b1 === 32) { q.shift() return bareTuiMakeKey('space', ' ', false, false, false) } if (b1 === 27) { q.shift() return bareTuiMakeKey('escape', '\x1b', false, false, false) } if (b1 < 32) { q.shift() if (b1 === 0) return bareTuiMakeKey('space', '\x00', true, false, false) return bareTuiMakeKey( String.fromCharCode(96 + b1), String.fromCharCode(b1), true, false, false ) } if (b1 >= 0xc0) { var trail = bareTuiUtf8TrailCount(b1) if (q.length < trail + 1) return { type: 'incomplete' } var ub = q.splice(0, trail + 1) var ch = bareTuiUtf8Decode(ub) return bareTuiMakeKey(ch, ch, false, false, false) } q.shift() var one = String.fromCharCode(b1) return bareTuiMakeKey(one, one, false, false, false) } function bareTuiCreateDecoder() { var q = [] var paste = null return { push: function (chunk) { var i if (chunk == null) return if (typeof chunk === 'string') { for (i = 0; i < chunk.length; ) { var cp = chunk.codePointAt(i) i += cp > 0xffff ? 2 : 1 if (cp < 0x80) q.push(cp) else if (cp < 0x800) { q.push(0xc0 | (cp >> 6)) q.push(0x80 | (cp & 63)) } else if (cp < 0x10000) { q.push(0xe0 | (cp >> 12)) q.push(0x80 | ((cp >> 6) & 63)) q.push(0x80 | (cp & 63)) } else { q.push(0xf0 | (cp >> 18)) q.push(0x80 | ((cp >> 12) & 63)) q.push(0x80 | ((cp >> 6) & 63)) q.push(0x80 | (cp & 63)) } } return } if (chunk instanceof Uint8Array || Array.isArray(chunk)) { for (i = 0; i < chunk.length; i++) q.push(chunk[i] & 255) return } if (typeof chunk === 'number') q.push(chunk & 255) }, flushEscape: function () { if (q.length === 1 && q[0] === 27) { q.shift() return bareTuiMakeKey('escape', '\x1b', false, false, false) } return null }, take: function () { var out = [] for (;;) { if (!q.length) break if (paste !== null) { var end = -1 var j for (j = 0; j <= q.length - 6; j++) { if ( q[j] === 27 && q[j + 1] === 91 && q[j + 2] === 50 && q[j + 3] === 48 && q[j + 4] === 49 && q[j + 5] === 126 ) { end = j break } } if (end < 0) break var inner = q.splice(0, end) q.splice(0, 6) paste = null out.push({ type: 'paste', text: bareTuiUtf8Decode(inner) }) continue } var ev = bareTuiTryConsume(q) if (!ev || ev.type === 'incomplete') break if (ev.type === 'paste-start') { paste = [] continue } if (ev.type === 'paste-end') continue out.push(ev) } return out }, pending: function () { return q.length } } } function bareTuiDecodeBytes(chunk) { var d = bareTuiCreateDecoder() d.push(chunk) var evs = d.take() var esc = d.flushEscape() if (esc) evs.push(esc) return evs } /* src/adapter/session.js */ /** Session adapter: Fish suspend, raw mode, stream resolve. Widgets never call this. */ function bareTuiEnv(ctx) { return ctx && ctx.env && typeof ctx.env === 'object' ? ctx.env : {} } function bareTuiGetSession(ctx) { var i for (i = 0; i < bareTuiSessions.length; i++) { if (bareTuiSessions[i].ctx === ctx) return bareTuiSessions[i] } var s = { ctx: ctx, depth: 0, didSuspend: false, didRaw: false, stdin: null } bareTuiSessions.push(s) return s } function bareTuiResolveStdin(ctx, opts) { var o = opts || {} if (o.input) return o.input if (ctx && ctx.replStdin) return ctx.replStdin if (ctx && ctx.stdin) return ctx.stdin var p = typeof globalThis !== 'undefined' ? globalThis.process : null return p && p.stdin ? p.stdin : null } function bareTuiResolveStdout(ctx, opts) { var o = opts || {} if (o.output) return o.output if (ctx && ctx.replStdout) return ctx.replStdout if (ctx && ctx.stdout) return ctx.stdout var p = typeof globalThis !== 'undefined' ? globalThis.process : null return p && p.stdout ? p.stdout : null } function bareTuiWriteRaw(ctx, opts, chunk) { var out = bareTuiResolveStdout(ctx, opts) var s = typeof chunk === 'string' ? chunk : String(chunk) if (out && typeof out.write === 'function') { out.write(s) return } if (ctx && typeof ctx.writeScreen === 'function') ctx.writeScreen(s) } function bareTuiIsTTY(ctx, opts) { var o = opts || {} if (o.isTTY === true) return true if (o.isTTY === false) return false if (ctx && ctx.bareOsStdoutCaptured) return false var stdin = bareTuiResolveStdin(ctx, o) var stdout = bareTuiResolveStdout(ctx, o) var inTty = !!(stdin && stdin.isTTY) var outTty = !!(stdout && stdout.isTTY) return inTty || outTty } function bareTuiSize(ctx, opts) { var o = opts || {} if (o.width > 0 && o.height > 0) { return { width: o.width | 0, height: o.height | 0 } } var stdout = bareTuiResolveStdout(ctx, o) var env = bareTuiEnv(ctx) var w = (stdout && stdout.columns) || parseInt(env.COLUMNS || '', 10) || o.width || 80 var h = (stdout && stdout.rows) || parseInt(env.LINES || '', 10) || o.height || 24 if (!isFinite(w) || w < 1) w = 80 if (!isFinite(h) || h < 1) h = 24 return { width: w | 0, height: h | 0 } } function bareTuiNoAltScreen(ctx, opts) { var o = opts || {} if (o.altScreen === false) return true var env = bareTuiEnv(ctx) var v = env.BARE_OS_TUI_NO_ALTSCREEN return v != null && String(v) !== '' } function bareTuiAcquire(ctx, opts) { var sess = bareTuiGetSession(ctx) sess.depth++ if (sess.depth !== 1) return sess if (ctx && typeof ctx.suspendReplForSubprocess === 'function') { ctx.suspendReplForSubprocess() sess.didSuspend = true } var stdin = bareTuiResolveStdin(ctx, opts) sess.stdin = stdin if (stdin && typeof stdin.setRawMode === 'function') { try { stdin.setRawMode(true) sess.didRaw = true } catch (e) { sess.didRaw = false } } if (stdin && typeof stdin.resume === 'function') { try { stdin.resume() } catch (e2) { /* ignore */ } } return sess } function bareTuiRelease(ctx, opts) { var sess = bareTuiGetSession(ctx) if (sess.depth < 1) return sess sess.depth-- if (sess.depth !== 0) return sess var stdin = sess.stdin || bareTuiResolveStdin(ctx, opts) if (sess.didRaw && stdin && typeof stdin.setRawMode === 'function') { try { stdin.setRawMode(false) } catch (e) { /* ignore */ } } sess.didRaw = false if ( sess.didSuspend && ctx && typeof ctx.resumeReplAfterSubprocess === 'function' ) { try { ctx.resumeReplAfterSubprocess() } catch (e2) { /* ignore */ } } sess.didSuspend = false sess.stdin = null return sess } async function bareTuiWithSession(ctx, fn, opts) { bareTuiAcquire(ctx, opts) var entered = false try { bareTuiScreenEnter(ctx, opts) entered = true return await fn() } finally { if (entered) { try { bareTuiScreenLeave(ctx, opts) } catch (e) { /* ignore */ } } bareTuiRelease(ctx, opts) } } /* src/screen/screen.js */ /** Alternate-screen / cursor / mouse / paste enter-leave. Always pair with adapter. */ function bareTuiMouseMode(opts) { var m = opts && opts.mouse if (m === true || m === 'basic') return 'basic' if (m === 'drag' || m === 'motion') return 'drag' if (m === 'all') return 'all' return null } function bareTuiScreenEnter(ctx, opts) { var o = opts || {} var parts = [] if (!bareTuiNoAltScreen(ctx, o)) parts.push(bareTuiAnsi.enterAltScreen) else parts.push(bareTuiAnsi.home + bareTuiAnsi.eraseDisplay) parts.push(bareTuiAnsi.cursorHide) parts.push(bareTuiAnsi.home) if (o.bracketedPaste !== false) parts.push(bareTuiAnsi.enableBracketPaste) if (o.focus) parts.push(bareTuiAnsi.enableFocus) var mouse = bareTuiMouseMode(o) if (mouse === 'basic') parts.push(bareTuiAnsi.enableMouseBasic) else if (mouse === 'drag') parts.push(bareTuiAnsi.enableMouseDrag) else if (mouse === 'all') parts.push(bareTuiAnsi.enableMouseAll) bareTuiWriteRaw(ctx, o, parts.join('')) } function bareTuiScreenLeave(ctx, opts) { var o = opts || {} var parts = [] parts.push(bareTuiAnsi.disableMouse) parts.push(bareTuiAnsi.disableFocus) parts.push(bareTuiAnsi.disableBracketPaste) parts.push(bareTuiAnsi.cursorShow) parts.push(bareTuiAnsi.reset) if (!bareTuiNoAltScreen(ctx, o)) parts.push(bareTuiAnsi.leaveAltScreen) bareTuiWriteRaw(ctx, o, parts.join('')) } /* src/theme/theme.js */ /** Resolve BARE_OS_THEME / BARE_OS_COLOR_* / NO_COLOR into TUI tokens. */ var BARE_TUI_THEME_FALLBACK = { default: { accent: 'cyan', muted: 'gray', error: 'red', ok: 'green', border: 'blue' }, nord: { accent: 'cyan', muted: 'gray', error: 'red', ok: 'green', border: 'blue' }, dracula: { accent: 'magenta', muted: 'gray', error: 'red', ok: 'green', border: 'magenta' }, solarized_dark: { accent: 'blue', muted: 'gray', error: 'red', ok: 'green', border: 'cyan' }, gruvbox_dark: { accent: 'yellow', muted: 'gray', error: 'red', ok: 'green', border: 'yellow' }, catppuccin_mocha: { accent: 'blue', muted: 'gray', error: 'red', ok: 'green', border: 'magenta' }, tokyo_night: { accent: 'blue', muted: 'gray', error: 'red', ok: 'green', border: 'blue' }, github_dark: { accent: 'cyan', muted: 'gray', error: 'red', ok: 'green', border: 'blue' } } function bareTuiColorDepth(env) { var raw = env && env.BARE_OS_COLOR_DEPTH var d = raw == null ? '' : String(raw).toLowerCase() if (d === '16' || d === '8' || d === 'ansi') return '16' if (d === '256' || d === '8bit') return '256' return 'truecolor' } function bareTuiNoColor(env) { if (!env) return false if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return true if (String(env.TERM || '').toLowerCase() === 'dumb') return true return false } function bareTuiThemeName(env, opts) { var o = opts || {} if (o.theme && typeof o.theme === 'string') return String(o.theme) var e = env || {} var raw = e.BARE_OS_THEME var name = raw == null || raw === '' ? 'default' : String(raw) return name.toLowerCase().trim().replace(/\s+/g, '_') } function bareTuiEnvColor(env, short) { if (!env) return '' var key = 'BARE_OS_COLOR_' + String(short).toUpperCase() var v = env[key] return v == null ? '' : String(v) } function bareTuiResolveTheme(ctx, opts) { var env = bareTuiEnv(ctx) var name = bareTuiThemeName(env, opts) var fb = BARE_TUI_THEME_FALLBACK[name] || BARE_TUI_THEME_FALLBACK.default var noColor = bareTuiNoColor(env) return { name: name, noColor: noColor, depth: bareTuiColorDepth(env), tokens: { accent: bareTuiEnvColor(env, 'command') || fb.accent, muted: bareTuiEnvColor(env, 'ghost') || fb.muted, error: bareTuiEnvColor(env, 'envunset') || fb.error, ok: bareTuiEnvColor(env, 'prompt') || fb.ok, border: fb.border, path: bareTuiEnvColor(env, 'path') || 'yellow', search: bareTuiEnvColor(env, 'search') || fb.accent, status: bareTuiEnvColor(env, 'prompt') || fb.ok } } } /* src/layout/style.js */ /** Lip Gloss–style chainable style + ANSI-aware width. */ var BARE_TUI_ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g var BARE_TUI_ANSI_STICKY = /\x1b\[[0-9;?]*[A-Za-z]/y function bareTuiStripAnsi(str) { return String(str).replace(BARE_TUI_ANSI_RE, '') } function bareTuiIsZeroWidth(cp) { return ( (cp >= 0x0300 && cp <= 0x036f) || (cp >= 0x1ab0 && cp <= 0x1aff) || (cp >= 0x1dc0 && cp <= 0x1dff) || (cp >= 0x20d0 && cp <= 0x20ff) || (cp >= 0xfe20 && cp <= 0xfe2f) || cp === 0x200b || (cp >= 0x200c && cp <= 0x200f) || cp === 0xfeff ) } function bareTuiIsWide(cp) { return ( (cp >= 0x1100 && cp <= 0x115f) || (cp >= 0x2e80 && cp <= 0x303e) || (cp >= 0x3041 && cp <= 0x33ff) || (cp >= 0x3400 && cp <= 0x4dbf) || (cp >= 0x4e00 && cp <= 0x9fff) || (cp >= 0xa000 && cp <= 0xa4cf) || (cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0xf900 && cp <= 0xfaff) || (cp >= 0xfe30 && cp <= 0xfe4f) || (cp >= 0xff00 && cp <= 0xff60) || (cp >= 0xffe0 && cp <= 0xffe6) || (cp >= 0x1f300 && cp <= 0x1faff) || (cp >= 0x20000 && cp <= 0x3fffd) ) } function bareTuiCharWidth(cp) { if (cp === 0) return 0 if (cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0 if (bareTuiIsZeroWidth(cp)) return 0 if (bareTuiIsWide(cp)) return 2 return 1 } function bareTuiLineWidth(line) { var w = 0 var plain = bareTuiStripAnsi(line) for (var i = 0; i < plain.length; ) { var cp = plain.codePointAt(i) w += bareTuiCharWidth(cp) i += cp > 0xffff ? 2 : 1 } return w } function bareTuiBlockWidth(str) { var w = 0 var lines = String(str).split('\n') for (var i = 0; i < lines.length; i++) w = Math.max(w, bareTuiLineWidth(lines[i])) return w } function bareTuiBlockHeight(str) { return String(str).split('\n').length } function bareTuiTruncate(str, w) { if (w <= 0) return '' str = String(str) var out = '' var used = 0 var sawAnsi = false var i = 0 while (i < str.length) { if (str.charAt(i) === '\x1b') { BARE_TUI_ANSI_STICKY.lastIndex = i var m = BARE_TUI_ANSI_STICKY.exec(str) if (m) { out += m[0] sawAnsi = true i = BARE_TUI_ANSI_STICKY.lastIndex continue } } var cp = str.codePointAt(i) var ch = String.fromCodePoint(cp) var cw = bareTuiCharWidth(cp) if (used + cw > w) break out += ch used += cw i += ch.length } if (sawAnsi) out += bareTuiAnsi.reset return out } function bareTuiPadLine(line, w, pos) { if (pos == null) pos = 0 var lw = bareTuiLineWidth(line) if (lw > w) return bareTuiTruncate(line, w) var space = w - lw if (space === 0) return line if (pos <= 0) return line + bareTuiRepeat(' ', space) if (pos >= 1) return bareTuiRepeat(' ', space) + line var left = Math.floor(space * pos) return bareTuiRepeat(' ', left) + line + bareTuiRepeat(' ', space - left) } function bareTuiRepeat(ch, n) { if (n <= 0) return '' var s = '' while (s.length < n) s += ch return s } var BARE_TUI_NAMED = { black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37, default: 39, gray: 90, grey: 90, brightblack: 90, brightred: 91, brightgreen: 92, brightyellow: 93, brightblue: 94, brightmagenta: 95, brightcyan: 96, brightwhite: 97 } function bareTuiHexToRgb(hex) { var h = String(hex).replace(/^#/, '') if (h.length === 3) h = h.charAt(0) + h.charAt(0) + h.charAt(1) + h.charAt(1) + h.charAt(2) + h.charAt(2) var n = parseInt(h, 16) if (!isFinite(n)) return null return [(n >> 16) & 255, (n >> 8) & 255, n & 255] } function bareTuiRgbTo256(r, g, b) { if (r === g && g === b) { if (r < 8) return 16 if (r > 248) return 231 return Math.round(((r - 8) / 247) * 24) + 232 } return ( 16 + 36 * Math.round((r / 255) * 5) + 6 * Math.round((g / 255) * 5) + Math.round((b / 255) * 5) ) } function bareTuiRgbTo16(r, g, b) { var bright = r > 180 || g > 180 || b > 180 var max = Math.max(r, g, b) var min = Math.min(r, g, b) if (max - min < 30) return bright ? 97 : 37 if (r >= g && r >= b) return g > 80 && b < 80 ? (bright ? 93 : 33) : bright ? 91 : 31 if (g >= r && g >= b) return b > 80 ? (bright ? 96 : 36) : bright ? 92 : 32 return r > 80 ? (bright ? 95 : 35) : bright ? 94 : 34 } function bareTuiColorParams(spec, bg, depth) { if (spec === undefined || spec === null || spec === '') return [] var lead = bg ? 48 : 38 var d = depth || 'truecolor' if (typeof spec === 'number') { if (d === '16') return [ bg ? spec >= 8 ? spec - 8 + 100 : spec + 40 : spec >= 8 ? spec - 8 + 90 : spec + 30 ] return [lead, 5, spec & 255] } var s = String(spec) if (s.charAt(0) === '\x1b') return [] if (s.charAt(0) === '#') { var rgb = bareTuiHexToRgb(s) if (!rgb) return [] if (d === 'truecolor') return [lead, 2, rgb[0], rgb[1], rgb[2]] if (d === '256') return [lead, 5, bareTuiRgbTo256(rgb[0], rgb[1], rgb[2])] var n16 = bareTuiRgbTo16(rgb[0], rgb[1], rgb[2]) return [bg ? n16 + 10 : n16] } var name = s.toLowerCase() if (name in BARE_TUI_NAMED) return [bg ? BARE_TUI_NAMED[name] + 10 : BARE_TUI_NAMED[name]] if (/^\d+$/.test(s)) { var idx = parseInt(s, 10) & 255 if (d === '16') return [bg ? 40 : 30] return [lead, 5, idx] } return [] } function bareTuiSgrParams(params) { return params.length ? bareTuiAnsi.csi + params.join(';') + 'm' : '' } var BARE_TUI_BORDERS = { normal: { topLeft: '\u250c', top: '\u2500', topRight: '\u2510', left: '\u2502', right: '\u2502', bottomLeft: '\u2514', bottom: '\u2500', bottomRight: '\u2518' }, rounded: { topLeft: '\u256d', top: '\u2500', topRight: '\u256e', left: '\u2502', right: '\u2502', bottomLeft: '\u2570', bottom: '\u2500', bottomRight: '\u256f' }, thick: { topLeft: '\u250f', top: '\u2501', topRight: '\u2513', left: '\u2503', right: '\u2503', bottomLeft: '\u2517', bottom: '\u2501', bottomRight: '\u251b' }, double: { topLeft: '\u2554', top: '\u2550', topRight: '\u2557', left: '\u2551', right: '\u2551', bottomLeft: '\u255a', bottom: '\u2550', bottomRight: '\u255d' } } var BARE_TUI_POSITION = { top: 0, left: 0, center: 0.5, right: 1, bottom: 1 } function bareTuiSides(args) { var a = [] var i for (i = 0; i < args.length; i++) a.push(args[i] || 0) if (a.length <= 1) return [a[0] || 0, a[0] || 0, a[0] || 0, a[0] || 0] if (a.length === 2) return [a[0], a[1], a[0], a[1]] if (a.length === 3) return [a[0], a[1], a[2], a[1]] return [a[0], a[1], a[2], a[3]] } function BareTuiStyle(props) { this.props = props || {} } BareTuiStyle.prototype._with = function (patch) { var next = {} var k for (k in this.props) { if (Object.prototype.hasOwnProperty.call(this.props, k)) next[k] = this.props[k] } for (k in patch) { if (Object.prototype.hasOwnProperty.call(patch, k)) next[k] = patch[k] } return new BareTuiStyle(next) } BareTuiStyle.prototype.bold = function (v) { return this._with({ bold: v !== false }) } BareTuiStyle.prototype.faint = function (v) { return this._with({ faint: v !== false }) } BareTuiStyle.prototype.dim = function (v) { return this.faint(v) } BareTuiStyle.prototype.italic = function (v) { return this._with({ italic: v !== false }) } BareTuiStyle.prototype.underline = function (v) { return this._with({ underline: v !== false }) } BareTuiStyle.prototype.strikethrough = function (v) { return this._with({ strikethrough: v !== false }) } BareTuiStyle.prototype.reverse = function (v) { return this._with({ reverse: v !== false }) } BareTuiStyle.prototype.foreground = function (c) { return this._with({ fg: c }) } BareTuiStyle.prototype.background = function (c) { return this._with({ bg: c }) } BareTuiStyle.prototype.width = function (n) { return this._with({ width: n }) } BareTuiStyle.prototype.height = function (n) { return this._with({ height: n }) } BareTuiStyle.prototype.align = function (pos) { return this._with({ align: pos }) } BareTuiStyle.prototype.alignVertical = function (pos) { return this._with({ alignV: pos }) } BareTuiStyle.prototype.padding = function () { return this._with({ padding: bareTuiSides(arguments) }) } BareTuiStyle.prototype.margin = function () { return this._with({ margin: bareTuiSides(arguments) }) } BareTuiStyle.prototype.border = function (chars) { var rest = [] var i for (i = 1; i < arguments.length; i++) rest.push(arguments[i]) var on = rest.length ? bareTuiSides(rest).map(Boolean) : [true, true, true, true] return this._with({ border: chars, borderSides: on }) } BareTuiStyle.prototype.borderForeground = function (c) { return this._with({ borderFg: c }) } BareTuiStyle.prototype._open = function () { if (this.props.noColor) return '' var p = this.props var params = [] if (p.bold) params.push(1) if (p.faint) params.push(2) if (p.italic) params.push(3) if (p.underline) params.push(4) if (p.reverse) params.push(7) if (p.strikethrough) params.push(9) var depth = p.depth || 'truecolor' var fg = bareTuiColorParams(p.fg, false, depth) var bg = bareTuiColorParams(p.bg, true, depth) var i for (i = 0; i < fg.length; i++) params.push(fg[i]) for (i = 0; i < bg.length; i++) params.push(bg[i]) return bareTuiSgrParams(params) } BareTuiStyle.prototype.render = function (text) { var p = this.props var pad = p.padding || [0, 0, 0, 0] var mar = p.margin || [0, 0, 0, 0] var align = p.align || 0 var lines = String(text).split('\n') var block = !!p.border || (p.bg !== undefined && p.bg !== null) || !!p.width || align !== 0 || pad[0] || pad[1] || pad[2] || pad[3] var contentW = p.width || bareTuiBlockWidth(lines.join('\n')) var i if (block) { for (i = 0; i < lines.length; i++) lines[i] = bareTuiPadLine(lines[i], contentW, align) } else { for (i = 0; i < lines.length; i++) { if (bareTuiLineWidth(lines[i]) > contentW) lines[i] = bareTuiTruncate(lines[i], contentW) } } if (p.height) lines = bareTuiFitHeight(lines, p.height, contentW, p.alignV || 0) var innerW = contentW + pad[1] + pad[3] if (pad[1] || pad[3]) { var lp = bareTuiRepeat(' ', pad[3]) var rp = bareTuiRepeat(' ', pad[1]) for (i = 0; i < lines.length; i++) lines[i] = lp + lines[i] + rp } var blank = bareTuiRepeat(' ', innerW) for (i = 0; i < pad[0]; i++) lines.unshift(blank) for (i = 0; i < pad[2]; i++) lines.push(blank) var open = this._open() if (open) { var reset = bareTuiAnsi.reset for (i = 0; i < lines.length; i++) { lines[i] = open + String(lines[i]) .split(reset) .join(reset + open) + reset } } if (p.border) lines = bareTuiApplyBorder( lines, innerW, p.border, p.borderSides, p.borderFg, p ) if (mar[3] || mar[1]) { var lm = bareTuiRepeat(' ', mar[3]) var rm = bareTuiRepeat(' ', mar[1]) for (i = 0; i < lines.length; i++) lines[i] = lm + lines[i] + rm } var fullW = bareTuiBlockWidth(lines.join('\n')) var marginBlank = bareTuiRepeat(' ', fullW) for (i = 0; i < mar[0]; i++) lines.unshift(marginBlank) for (i = 0; i < mar[2]; i++) lines.push(marginBlank) return lines.join('\n') } function bareTuiFitHeight(lines, h, w, posV) { if (lines.length >= h) return lines.slice(0, h) var extra = h - lines.length var before = posV <= 0 ? 0 : posV >= 1 ? extra : Math.floor(extra * posV) var blank = bareTuiRepeat(' ', w) var out = [] var i for (i = 0; i < before; i++) out.push(blank) for (i = 0; i < lines.length; i++) out.push(lines[i]) for (i = 0; i < extra - before; i++) out.push(blank) return out } function bareTuiApplyBorder(lines, innerW, chars, on, fg, props) { var t = on[0] var r = on[1] var b = on[2] var l = on[3] var depth = (props && props.depth) || 'truecolor' var noColor = props && props.noColor var paint = function (s) { if (noColor) return s var params = bareTuiColorParams(fg, false, depth) return params.length ? bareTuiSgrParams(params) + s + bareTuiAnsi.reset : s } var out = [] if (t) { out.push( paint( (l ? chars.topLeft : '') + bareTuiRepeat(chars.top, innerW) + (r ? chars.topRight : '') ) ) } var left = l ? paint(chars.left) : '' var right = r ? paint(chars.right) : '' var i for (i = 0; i < lines.length; i++) out.push(left + lines[i] + right) if (b) { out.push( paint( (l ? chars.bottomLeft : '') + bareTuiRepeat(chars.bottom, innerW) + (r ? chars.bottomRight : '') ) ) } return out } function bareTuiJoinHorizontal(pos) { var blocks = [] var i for (i = 1; i < arguments.length; i++) blocks.push(arguments[i]) var cols = [] var widths = [] var h = 0 for (i = 0; i < blocks.length; i++) { var lines = String(blocks[i]).split('\n') cols.push(lines) widths.push(bareTuiBlockWidth(lines.join('\n'))) if (lines.length > h) h = lines.length } var padded = [] for (i = 0; i < cols.length; i++) { var filled = [] var j for (j = 0; j < cols[i].length; j++) filled.push(bareTuiPadLine(cols[i][j], widths[i], 0)) var extra = h - filled.length var before = pos <= 0 ? 0 : pos >= 1 ? extra : Math.floor(extra * pos) var blank = bareTuiRepeat(' ', widths[i]) var col = [] for (j = 0; j < before; j++) col.push(blank) for (j = 0; j < filled.length; j++) col.push(filled[j]) for (j = 0; j < extra - before; j++) col.push(blank) padded.push(col) } var out = [] var row for (row = 0; row < h; row++) { var s = '' for (i = 0; i < padded.length; i++) s += padded[i][row] out.push(s) } return out.join('\n') } function bareTuiJoinVertical(pos) { var blocks = [] var i for (i = 1; i < arguments.length; i++) blocks.push(arguments[i]) var w = 0 var cols = [] for (i = 0; i < blocks.length; i++) { var lines = String(blocks[i]).split('\n') cols.push(lines) var bw = bareTuiBlockWidth(lines.join('\n')) if (bw > w) w = bw } var out = [] for (i = 0; i < cols.length; i++) { var j for (j = 0; j < cols[i].length; j++) out.push(bareTuiPadLine(cols[i][j], w, pos)) } return out.join('\n') } function bareTuiMakeStyleFactory(ctx) { function style() { var theme = bareTuiResolveTheme(ctx, {}) return new BareTuiStyle({ noColor: theme.noColor, depth: theme.depth }) } style.Style = BareTuiStyle style.borders = BARE_TUI_BORDERS style.position = BARE_TUI_POSITION style.joinHorizontal = bareTuiJoinHorizontal style.joinVertical = bareTuiJoinVertical style.width = bareTuiBlockWidth style.height = bareTuiBlockHeight style.truncate = bareTuiTruncate style.stripAnsi = bareTuiStripAnsi return style } /* src/buffer/grid.js */ /** Cell grid: parse ANSI text, blit overlays, serialize rows. */ var BARE_TUI_CELL_BOLD = 1 var BARE_TUI_CELL_DIM = 2 var BARE_TUI_CELL_REV = 4 function bareTuiCellEmpty() { return { ch: ' ', fg: '', bg: '', attrs: 0 } } function bareTuiGridCreate(rows, cols) { rows = Math.max(1, rows | 0) cols = Math.max(1, cols | 0) var cells = [] var r for (r = 0; r < rows; r++) { var row = [] var c for (c = 0; c < cols; c++) row.push(bareTuiCellEmpty()) cells.push(row) } return { rows: rows, cols: cols, cells: cells } } function bareTuiGridClear(grid) { var r, c for (r = 0; r < grid.rows; r++) { for (c = 0; c < grid.cols; c++) grid.cells[r][c] = bareTuiCellEmpty() } } function bareTuiGridPut(grid, r, c, ch, fg, bg, attrs) { if (r < 0 || c < 0 || r >= grid.rows || c >= grid.cols) return grid.cells[r][c] = { ch: ch, fg: fg || '', bg: bg || '', attrs: attrs | 0 } } function bareTuiApplySgr(state, params) { var i = 0 if (!params.length) params = [0] while (i < params.length) { var n = params[i++] | 0 if (n === 0) { state.fg = '' state.bg = '' state.attrs = 0 } else if (n === 1) state.attrs |= BARE_TUI_CELL_BOLD else if (n === 2) state.attrs |= BARE_TUI_CELL_DIM else if (n === 22) state.attrs &= ~(BARE_TUI_CELL_BOLD | BARE_TUI_CELL_DIM) else if (n === 7) state.attrs |= BARE_TUI_CELL_REV else if (n === 27) state.attrs &= ~BARE_TUI_CELL_REV else if ((n >= 30 && n <= 37) || (n >= 90 && n <= 97)) state.fg = String(n) else if (n === 39) state.fg = '' else if ((n >= 40 && n <= 47) || (n >= 100 && n <= 107)) state.bg = String(n) else if (n === 49) state.bg = '' else if (n === 38 || n === 48) { var kind = params[i++] | 0 var key = n === 38 ? 'fg' : 'bg' if (kind === 5) { state[key] = n + ';5;' + (params[i++] | 0) } else if (kind === 2) { var r = params[i++] | 0 var g = params[i++] | 0 var b = params[i++] | 0 state[key] = n + ';2;' + r + ';' + g + ';' + b } } } } function bareTuiParseSgrParams(body) { if (!body) return [0] var parts = String(body).split(';') var out = [] var i for (i = 0; i < parts.length; i++) { if (parts[i] === '') out.push(0) else out.push(parseInt(parts[i], 10) || 0) } return out } function bareTuiGridWriteText(grid, startRow, startCol, text) { var state = { fg: '', bg: '', attrs: 0 } var r = startRow var c = startCol var i = 0 text = String(text == null ? '' : text) while (i < text.length) { var ch0 = text.charAt(i) if (ch0 === '\n') { r++ c = startCol i++ continue } if (ch0 === '\r') { i++ continue } if (ch0 === '\x1b') { BARE_TUI_ANSI_STICKY.lastIndex = i var m = BARE_TUI_ANSI_STICKY.exec(text) if (m) { var seq = m[0] i = BARE_TUI_ANSI_STICKY.lastIndex if (seq.charAt(seq.length - 1) === 'm') { var inner = seq.slice(2, seq.length - 1) bareTuiApplySgr(state, bareTuiParseSgrParams(inner)) } continue } } var cp = text.codePointAt(i) var glyph = String.fromCodePoint(cp) var cw = bareTuiCharWidth(cp) i += glyph.length if (cw <= 0) continue bareTuiGridPut(grid, r, c, glyph, state.fg, state.bg, state.attrs) if (cw === 2 && c + 1 < grid.cols) { bareTuiGridPut(grid, r, c + 1, '', state.fg, state.bg, state.attrs) } c += cw } } function bareTuiGridFill(grid, text) { bareTuiGridClear(grid) bareTuiGridWriteText(grid, 0, 0, text) return grid } function bareTuiGridBlit(grid, row, col, text) { var tmpRows = String(text == null ? '' : text).split('\n').length var tmpCols = Math.max(1, bareTuiBlockWidth(text || ' ')) var tmp = bareTuiGridCreate(tmpRows, tmpCols) bareTuiGridWriteText(tmp, 0, 0, text) var r, c for (r = 0; r < tmp.rows; r++) { for (c = 0; c < tmp.cols; c++) { var cell = tmp.cells[r][c] if (cell.ch === ' ' && !cell.fg && !cell.bg && !cell.attrs) continue bareTuiGridPut( grid, row + r, col + c, cell.ch, cell.fg, cell.bg, cell.attrs ) } } return grid } function bareTuiGridPlain(grid) { var lines = [] var r, c for (r = 0; r < grid.rows; r++) { var s = '' for (c = 0; c < grid.cols; c++) { var ch = grid.cells[r][c].ch if (ch !== '') s += ch } lines.push(s.replace(/\s+$/, '')) } return lines } function bareTuiGridRowKey(grid, r) { var s = '' var c for (c = 0; c < grid.cols; c++) { var cell = grid.cells[r][c] s += cell.ch + '\x01' + cell.fg + '\x01' + cell.bg + '\x01' + cell.attrs + '\x02' } return s } function bareTuiCellSgr(cell) { var params = [] if (cell.attrs & BARE_TUI_CELL_BOLD) params.push(1) if (cell.attrs & BARE_TUI_CELL_DIM) params.push(2) if (cell.attrs & BARE_TUI_CELL_REV) params.push(7) if (cell.fg) { var fg = String(cell.fg) if (fg.indexOf(';') >= 0) { var fp = fg.split(';') var fi for (fi = 0; fi < fp.length; fi++) params.push(parseInt(fp[fi], 10) || 0) } else params.push(parseInt(fg, 10) || 0) } if (cell.bg) { var bg = String(cell.bg) if (bg.indexOf(';') >= 0) { var bp = bg.split(';') var bi for (bi = 0; bi < bp.length; bi++) params.push(parseInt(bp[bi], 10) || 0) } else params.push(parseInt(bg, 10) || 0) } return params.length ? bareTuiAnsi.csi + params.join(';') + 'm' : '' } function bareTuiGridPaintRow(grid, r) { var out = '' var last = '' var c for (c = 0; c < grid.cols; c++) { var cell = grid.cells[r][c] if (cell.ch === '') continue var sg = bareTuiCellSgr(cell) if (sg !== last) { out += bareTuiAnsi.reset + sg last = sg } out += cell.ch } if (last) out += bareTuiAnsi.reset return out } /* src/render/line-diff.js */ /** Line-diff renderer. Repaints only changed rows. Does not own alt-screen. */ function BareTuiRenderer(output) { this.out = output this.mode = 'line' this.lastLines = null } BareTuiRenderer.prototype.clear = function () { this.lastLines = null } BareTuiRenderer.prototype.write = function (s) { if (!s) return if (this.out && typeof this.out.write === 'function') this.out.write(s) } BareTuiRenderer.prototype.render = function (view) { var lines = String(view).split('\n') var s = '' var i if (this.lastLines === null) { s += bareTuiAnsi.home for (i = 0; i < lines.length; i++) { s += bareTuiAnsi.eraseLineEnd + lines[i] if (i < lines.length - 1) s += '\r\n' } s += bareTuiAnsi.eraseDisplayEnd } else { for (i = 0; i < lines.length; i++) { if (lines[i] !== this.lastLines[i]) { s += bareTuiAnsi.cursorTo(i, 0) + bareTuiAnsi.eraseLineEnd + lines[i] } } if (this.lastLines.length > lines.length) { s += bareTuiAnsi.cursorTo(lines.length, 0) + bareTuiAnsi.eraseDisplayEnd } } this.lastLines = lines this.write(s) } /* src/render/cell.js */ /** Dirty-row cell renderer. Base view + overlays; no layout insert. */ function BareTuiCellRenderer(output, opts) { opts = opts || {} this.out = output this.mode = 'cell' this.rows = Math.max(1, opts.rows || opts.height || 24) this.cols = Math.max(1, opts.cols || opts.width || 80) this.grid = bareTuiGridCreate(this.rows, this.cols) this.lastKeys = null } BareTuiCellRenderer.prototype.write = function (s) { if (!s) return if (this.out && typeof this.out.write === 'function') this.out.write(s) } BareTuiCellRenderer.prototype.resize = function (rows, cols) { rows = Math.max(1, rows | 0) cols = Math.max(1, cols | 0) if (rows === this.rows && cols === this.cols) return this.rows = rows this.cols = cols this.grid = bareTuiGridCreate(rows, cols) this.lastKeys = null } BareTuiCellRenderer.prototype.clear = function () { this.lastKeys = null } BareTuiCellRenderer.prototype.render = function (view, overlays) { bareTuiGridFill(this.grid, view == null ? '' : view) if (overlays && overlays.length) { var i for (i = 0; i < overlays.length; i++) { var ov = overlays[i] if (!ov || ov.text == null) continue bareTuiGridBlit(this.grid, ov.row | 0, ov.col | 0, ov.text) } } var keys = [] var s = '' var r for (r = 0; r < this.grid.rows; r++) { keys[r] = bareTuiGridRowKey(this.grid, r) if (this.lastKeys && this.lastKeys[r] === keys[r]) continue s += bareTuiAnsi.cursorTo(r, 0) + bareTuiAnsi.eraseLineEnd + bareTuiGridPaintRow(this.grid, r) } if (!this.lastKeys) s = bareTuiAnsi.home + s this.lastKeys = keys this.write(s) } BareTuiCellRenderer.prototype.plain = function () { return bareTuiGridPlain(this.grid) } /* src/program/commands.js */ /** TEA commands. A Cmd is () => Msg | Promise | null, or a marker object. */ function bareTuiQuitCmd() { return { type: 'quit' } } function bareTuiTick(ms, fn) { var delay = ms | 0 if (delay < 0) delay = 0 return function () { return new Promise(function (resolve) { setTimeout(function () { resolve(fn ? fn(new Date()) : { type: 'tick' }) }, delay) }) } } function bareTuiEvery(ms, fn) { var period = ms | 0 if (period < 1) period = 1 return function () { return new Promise(function (resolve) { var wait = period - (Date.now() % period) setTimeout(function () { resolve(fn ? fn(new Date()) : { type: 'tick' }) }, wait) }) } } function bareTuiBatch() { var out = [] var i for (i = 0; i < arguments.length; i++) { var c = arguments[i] if (Array.isArray(c)) { var j for (j = 0; j < c.length; j++) if (c[j]) out.push(c[j]) } else if (c) { out.push(c) } } return out } function bareTuiSequence() { var out = [] var i for (i = 0; i < arguments.length; i++) { var c = arguments[i] if (Array.isArray(c)) { var j for (j = 0; j < c.length; j++) if (c[j]) out.push(c[j]) } else if (c) { out.push(c) } } return { __seq: out } } function bareTuiSuspend(fn) { return { __suspend: fn } } /* src/program/program.js */ /** TEA Program: init / update / view over the session adapter. */ var bareTuiActivePrograms = [] function bareTuiActiveProgram(ctx) { var i for (i = bareTuiActivePrograms.length - 1; i >= 0; i--) { if (bareTuiActivePrograms[i].ctx === ctx) return bareTuiActivePrograms[i] } return null } function BareTuiProgram(ctx, model, opts) { this.ctx = ctx this.model = model this.opts = opts || {} this.fps = this.opts.fps == null ? 60 : this.opts.fps this._frameMs = this.fps > 0 ? Math.max(1, Math.round(1000 / this.fps)) : 0 this._frameTimer = null this._needsRender = false this._queue = [] this._wake = null this._running = false this._tornDown = false this._suspended = false this._decoder = null this._onInput = null this._onResize = null this._escTimer = null this._abortOn = null this.input = bareTuiResolveStdin(ctx, this.opts) this.output = bareTuiResolveStdout(ctx, this.opts) if (!this.output || typeof this.output.write !== 'function') { throw new Error('tui: no output stream; pass opts.output') } this._overlayMap = {} var size0 = bareTuiSize(ctx, this.opts) if (this.opts.buffer === 'cell') { this.renderer = new BareTuiCellRenderer(this.output, { rows: this.opts.height || size0.height, cols: this.opts.width || size0.width }) } else { this.renderer = new BareTuiRenderer(this.output) } } BareTuiProgram.prototype.setOverlay = function (id, spec) { if (!id) return if (!spec) { delete this._overlayMap[id] return } this._overlayMap[id] = spec } BareTuiProgram.prototype.clearOverlay = function (id) { if (id) delete this._overlayMap[id] else this._overlayMap = {} } BareTuiProgram.prototype._overlaysForPaint = function () { var out = [] var id for (id in this._overlayMap) { if ( Object.prototype.hasOwnProperty.call(this._overlayMap, id) && this._overlayMap[id] ) { out.push(this._overlayMap[id]) } } var model = this.model if (!model) return out var size = bareTuiSize(this.ctx, this.opts) if (typeof model.overlays === 'function') { var list = model.overlays(size) || [] var i for (i = 0; i < list.length; i++) if (list[i]) out.push(list[i]) } else if (typeof model.overlay === 'function') { var one = model.overlay(size) if (one) out.push(one) } return out } BareTuiProgram.prototype._paint = function () { var view = this._view() if (this.renderer && this.renderer.mode === 'cell') { var sz = bareTuiSize(this.ctx, this.opts) this.renderer.resize( this.opts.height || sz.height, this.opts.width || sz.width ) this.renderer.render(view, this._overlaysForPaint()) } else { this.renderer.render(view) } } BareTuiProgram.prototype.send = function (msg) { if (!msg) return this._queue.push(msg) if (this._wake) { var wake = this._wake this._wake = null wake() } } BareTuiProgram.prototype.quit = function () { this.send({ type: 'quit' }) } BareTuiProgram.prototype.run = async function () { this._running = true bareTuiActivePrograms.push(this) var acquired = false var entered = false try { if ( !this.opts.input && !this.opts.allowDumb && !bareTuiIsTTY(this.ctx, this.opts) ) { if ( this.ctx && this.ctx.console && typeof this.ctx.console.error === 'function' ) { this.ctx.console.error('tui: needs a TTY') } if ( this.ctx && (this.ctx.exitCode === undefined || this.ctx.exitCode === null) ) { this.ctx.exitCode = 1 } return this.model } bareTuiAcquire(this.ctx, this.opts) acquired = true bareTuiScreenEnter(this.ctx, this.opts) entered = true this._setupInput() if (typeof this.model.init === 'function') this._exec(this.model.init()) var size = bareTuiSize(this.ctx, this.opts) this.send({ type: 'resize', width: size.width, height: size.height }) this._paint() while (this._running) { var msg = await this._next() if (!msg) continue if (msg.type === 'quit') break if (msg.type === 'resize') this.renderer.clear() var pair = this._update(msg) this.model = pair[0] this._invalidate() this._exec(pair[1]) } } finally { this._running = false this._cancelFrame() if (this._needsRender) { this._needsRender = false try { this._paint() } catch (e) { /* ignore */ } } this._teardownInput() if (entered) { try { bareTuiScreenLeave(this.ctx, this.opts) } catch (e2) { /* ignore */ } } if (acquired) { try { bareTuiRelease(this.ctx, this.opts) } catch (e3) { /* ignore */ } } var idx = bareTuiActivePrograms.indexOf(this) if (idx >= 0) bareTuiActivePrograms.splice(idx, 1) } return this.model } BareTuiProgram.prototype._invalidate = function () { var self = this if (this._suspended) return if (this._frameMs === 0) { this._paint() return } this._needsRender = true if (this._frameTimer) return this._frameTimer = setTimeout(function () { self._frameTimer = null if (self._needsRender) { self._needsRender = false self._paint() } }, this._frameMs) } BareTuiProgram.prototype._cancelFrame = function () { if (this._frameTimer) { clearTimeout(this._frameTimer) this._frameTimer = null } } BareTuiProgram.prototype._setupInput = function () { var self = this this._decoder = bareTuiCreateDecoder() this._onInput = function (data) { self._decoder.push(data) if (self._escTimer) { clearTimeout(self._escTimer) self._escTimer = null } var evs = self._decoder.take() var i for (i = 0; i < evs.length; i++) self.send(evs[i]) if (self._decoder.pending()) { self._escTimer = setTimeout(function () { self._escTimer = null var esc = self._decoder.flushEscape() if (esc) self.send(esc) var more = self._decoder.take() var j for (j = 0; j < more.length; j++) self.send(more[j]) }, 50) } } if (this.input && typeof this.input.on === 'function') { this.input.on('data', this._onInput) } if (this.output && typeof this.output.on === 'function') { this._onResize = function () { var sz = bareTuiSize(self.ctx, self.opts) self.send({ type: 'resize', width: sz.width, height: sz.height }) } this.output.on('resize', this._onResize) } var sig = this.opts.signal if (sig && typeof sig.addEventListener === 'function') { this._abortOn = function () { self.quit() } if (sig.aborted) this.quit() else sig.addEventListener('abort', this._abortOn) } } BareTuiProgram.prototype._teardownInput = function () { if (this._tornDown) return this._tornDown = true this._cancelFrame() if (this._escTimer) { clearTimeout(this._escTimer) this._escTimer = null } try { if ( this.input && this._onInput && typeof this.input.removeListener === 'function' ) { this.input.removeListener('data', this._onInput) } } catch (e) { /* ignore */ } try { if ( this.output && this._onResize && typeof this.output.removeListener === 'function' ) { this.output.removeListener('resize', this._onResize) } } catch (e2) { /* ignore */ } if ( this.opts.signal && this._abortOn && typeof this.opts.signal.removeEventListener === 'function' ) { try { this.opts.signal.removeEventListener('abort', this._abortOn) } catch (e3) { /* ignore */ } } } BareTuiProgram.prototype._suspendTerminal = function () { this._suspended = true this._cancelFrame() try { if ( this.input && this._onInput && typeof this.input.removeListener === 'function' ) { this.input.removeListener('data', this._onInput) } } catch (e) { /* ignore */ } try { if (this.input && typeof this.input.pause === 'function') this.input.pause() } catch (e2) { /* ignore */ } try { bareTuiScreenLeave(this.ctx, this.opts) } catch (e3) { /* ignore */ } } BareTuiProgram.prototype._resumeTerminal = function () { try { bareTuiScreenEnter(this.ctx, this.opts) } catch (e) { /* ignore */ } if (this.input && this._onInput && typeof this.input.on === 'function') { this.input.on('data', this._onInput) } try { if (this.input && typeof this.input.resume === 'function') this.input.resume() } catch (e2) { /* ignore */ } this.renderer.clear() this._suspended = false } BareTuiProgram.prototype._update = function (msg) { if (!this.model || typeof this.model.update !== 'function') return [this.model, null] var ret = this.model.update(msg) if (ret === undefined || ret === null) return [this.model, null] if (Array.isArray(ret)) return [ ret[0] != null ? ret[0] : this.model, ret[1] != null ? ret[1] : null ] return [ret, null] } BareTuiProgram.prototype._view = function () { try { if (!this.model || typeof this.model.view !== 'function') return '' return String(this.model.view()) } catch (err) { return 'view error: ' + (err && err.message) } } BareTuiProgram.prototype._exec = function (cmd) { this._runCmd(cmd) } BareTuiProgram.prototype._runCmd = async function (cmd) { if (!cmd || !this._running) return var self = this if (Array.isArray(cmd)) { await Promise.all( cmd.map(function (c) { return self._runCmd(c) }) ) return } if (cmd.__seq) { var i for (i = 0; i < cmd.__seq.length; i++) { if (!this._running) return await this._runCmd(cmd.__seq[i]) } return } if (cmd.__suspend) { this._suspendTerminal() var msg = null try { msg = await cmd.__suspend() } catch (error) { msg = { type: 'error', error: error } } if (this._running) { this._resumeTerminal() this._invalidate() } this.send(msg) return } if (typeof cmd !== 'function') return try { this.send(await cmd()) } catch (error) { this.send({ type: 'error', error: error }) } } BareTuiProgram.prototype._next = async function () { var self = this if (this._queue.length === 0) { await new Promise(function (resolve) { self._wake = resolve }) } return this._queue.shift() } function bareTuiCreateProgram(ctx, model, opts) { return new BareTuiProgram(ctx, model, opts) } function bareTuiRunProgram(ctx, model, opts) { return bareTuiCreateProgram(ctx, model, opts).run() } function bareTuiSendActive(ctx, msg) { var p = bareTuiActiveProgram(ctx) if (p) p.send(msg) } /* src/widgets/spinner.js */ /** Cmd-driven spinner. Route spinner.tick and thread the Cmd up. */ var BARE_TUI_SPINNER_DOTS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] var BARE_TUI_SPINNER_LINE = ['|', '/', '-', '\\'] var BARE_TUI_SPINNER_POINTS = ['∙∙∙', '●∙∙', '∙●∙', '∙∙●'] function BareTuiSpinner(opts) { opts = opts || {} this.frames = opts.frames || BARE_TUI_SPINNER_DOTS this.fps = opts.fps || 10 this.frame = 0 this.id = bareTuiNextWidgetId++ this.tag = 0 } BareTuiSpinner.prototype.init = function () { return this._tick() } BareTuiSpinner.prototype._tick = function () { var id = this.id var tag = this.tag var ms = Math.max(1, Math.round(1000 / this.fps)) return bareTuiTick(ms, function () { return { type: 'spinner.tick', id: id, tag: tag } }) } BareTuiSpinner.prototype.update = function (msg) { if (!msg || msg.type !== 'spinner.tick') return [this, null] if (msg.id !== this.id || msg.tag !== this.tag) return [this, null] this.frame = (this.frame + 1) % this.frames.length this.tag++ return [this, this._tick()] } BareTuiSpinner.prototype.view = function () { return this.frames[this.frame] } var bareTuiSpinner = { create: function (opts) { return new BareTuiSpinner(opts) }, dots: BARE_TUI_SPINNER_DOTS, line: BARE_TUI_SPINNER_LINE, points: BARE_TUI_SPINNER_POINTS } /* src/widgets/textinput.js */ /** Single-line field. Consumes keys only when focused. */ function BareTuiTextInput(opts) { opts = opts || {} this.value = opts.value || '' this.placeholder = opts.placeholder || '' this.prompt = opts.prompt || '' this.charLimit = opts.charLimit || 0 this.echoMode = opts.echoMode || 'normal' this.maskChar = opts.maskChar || '•' this.focused = !!opts.focused this.cursor = this.value.length } BareTuiTextInput.prototype.focus = function () { this.focused = true return this } BareTuiTextInput.prototype.blur = function () { this.focused = false return this } BareTuiTextInput.prototype.setValue = function (v) { v = String(v) this.value = this.charLimit ? v.slice(0, this.charLimit) : v if (this.cursor > this.value.length) this.cursor = this.value.length return this } BareTuiTextInput.prototype.reset = function () { this.value = '' this.cursor = 0 return this } BareTuiTextInput.prototype.update = function (msg) { if (!this.focused || !msg || msg.type !== 'key') return [this, null] if (msg.is('left')) this.cursor = Math.max(0, this.cursor - 1) else if (msg.is('right')) this.cursor = Math.min(this.value.length, this.cursor + 1) else if (msg.is('home')) this.cursor = 0 else if (msg.is('end')) this.cursor = this.value.length else if (msg.is('backspace')) { if (this.cursor > 0) { this.value = this.value.slice(0, this.cursor - 1) + this.value.slice(this.cursor) this.cursor-- } } else if (msg.is('delete')) { if (this.cursor < this.value.length) { this.value = this.value.slice(0, this.cursor) + this.value.slice(this.cursor + 1) } } else this._insert(msg) return [this, null] } BareTuiTextInput.prototype._insert = function (msg) { var ch = msg.sequence var printable = !msg.ctrl && !msg.meta && typeof ch === 'string' && ch.length === 1 && ch >= ' ' && ch !== '\x7f' if (!printable) return if (this.charLimit && this.value.length >= this.charLimit) return this.value = this.value.slice(0, this.cursor) + ch + this.value.slice(this.cursor) this.cursor++ } BareTuiTextInput.prototype._display = function () { if (this.echoMode === 'password') { var s = '' var i for (i = 0; i < this.value.length; i++) s += this.maskChar return s } return this.value } BareTuiTextInput.prototype.view = function () { var rev = function (s) { return bareTuiAnsi.modifierReverse + s + bareTuiAnsi.modifierNotReverse } var dim = function (s) { return bareTuiAnsi.modifierDim + s + bareTuiAnsi.reset } if (this.value.length === 0) { if (!this.focused) return this.prompt + dim(this.placeholder) var head = this.placeholder.slice(0, 1) || ' ' return this.prompt + rev(head) + dim(this.placeholder.slice(1)) } var text = this._display() if (!this.focused) return this.prompt + text var at = text.slice(this.cursor, this.cursor + 1) || ' ' return ( this.prompt + text.slice(0, this.cursor) + rev(at) + text.slice(this.cursor + 1) ) } var bareTuiTextinput = { create: function (opts) { return new BareTuiTextInput(opts) } } /* src/widgets/choices.js */ /** Checkbox, radio, and compact select. Focus-gated. */ function BareTuiCheckbox(opts) { opts = opts || {} this.label = opts.label || '' this.checked = !!opts.checked this.focused = !!opts.focused this.checkedGlyph = opts.checkedGlyph || '[x]' this.uncheckedGlyph = opts.uncheckedGlyph || '[ ]' } BareTuiCheckbox.prototype.focus = function () { this.focused = true return this } BareTuiCheckbox.prototype.blur = function () { this.focused = false return this } BareTuiCheckbox.prototype.setChecked = function (v) { this.checked = !!v return this } BareTuiCheckbox.prototype.toggle = function () { this.checked = !this.checked return this } BareTuiCheckbox.prototype.update = function (msg) { if (!this.focused || !msg || msg.type !== 'key') return [this, null] if (msg.is('space')) this.toggle() return [this, null] } BareTuiCheckbox.prototype.view = function () { var pointer = this.focused ? '› ' : ' ' var box = this.checked ? this.checkedGlyph : this.uncheckedGlyph return pointer + (this.label ? box + ' ' + this.label : box) } function bareTuiNormOptions(opts) { var raw = (opts && opts.options) || [] var out = [] var i for (i = 0; i < raw.length; i++) { var it = raw[i] if (it && typeof it === 'object') { out.push({ label: it.label != null ? String(it.label) : String(it.value), value: it.value }) } else { out.push({ label: String(it), value: it }) } } return out } function BareTuiRadio(opts) { opts = opts || {} this.options = bareTuiNormOptions(opts) this.selected = opts.selected | 0 this.focused = !!opts.focused if (this.selected < 0) this.selected = 0 if (this.selected >= this.options.length) this.selected = Math.max(0, this.options.length - 1) } BareTuiRadio.prototype.focus = function () { this.focused = true return this } BareTuiRadio.prototype.blur = function () { this.focused = false return this } BareTuiRadio.prototype.value = function () { var o = this.options[this.selected] return o ? o.value : null } BareTuiRadio.prototype.update = function (msg) { if (!this.focused || !msg || msg.type !== 'key') return [this, null] if (msg.is('up', 'k')) this.selected = Math.max(0, this.selected - 1) else if (msg.is('down', 'j')) this.selected = Math.min(this.options.length - 1, this.selected + 1) return [this, null] } BareTuiRadio.prototype.view = function () { var lines = [] var i for (i = 0; i < this.options.length; i++) { var mark = i === this.selected ? '(•)' : '( )' var ptr = this.focused && i === this.selected ? '› ' : ' ' lines.push(ptr + mark + ' ' + this.options[i].label) } return lines.join('\n') } function BareTuiSelect(opts) { opts = opts || {} this.options = bareTuiNormOptions(opts) this.selected = opts.selected | 0 this.placeholder = opts.placeholder || '' this.focused = !!opts.focused this.open = false if (this.selected < 0) this.selected = 0 } BareTuiSelect.prototype.focus = function () { this.focused = true return this } BareTuiSelect.prototype.blur = function () { this.focused = false this.open = false return this } BareTuiSelect.prototype.value = function () { var o = this.options[this.selected] return o ? o.value : null } BareTuiSelect.prototype.update = function (msg) { if (!this.focused || !msg || msg.type !== 'key') return [this, null] if (!this.open) { if (msg.is('enter', 'space', 'down')) this.open = true return [this, null] } if (msg.is('esc')) this.open = false else if (msg.is('enter')) this.open = false else if (msg.is('up', 'k')) this.selected = Math.max(0, this.selected - 1) else if (msg.is('down', 'j')) this.selected = Math.min(this.options.length - 1, this.selected + 1) return [this, null] } BareTuiSelect.prototype.view = function () { var cur = this.options[this.selected] var label = cur ? cur.label : this.placeholder var ptr = this.focused ? '› ' : ' ' return ptr + (label || this.placeholder) } BareTuiSelect.prototype.menuView = function () { if (!this.open) return '' var lines = [] var i for (i = 0; i < this.options.length; i++) { var mark = i === this.selected ? '› ' : ' ' lines.push(mark + this.options[i].label) } return lines.join('\n') } var bareTuiCheckbox = { create: function (opts) { return new BareTuiCheckbox(opts) } } var bareTuiRadio = { create: function (opts) { return new BareTuiRadio(opts) } } var bareTuiSelect = { create: function (opts) { return new BareTuiSelect(opts) } } /* src/widgets/scroll.js */ /** Viewport and paginator. */ function BareTuiViewport(opts) { opts = opts || {} this.width = opts.width || 0 this.height = opts.height || 0 this.yOffset = 0 this.lines = [] } BareTuiViewport.prototype.setContent = function (content) { this.lines = String(content).split('\n') this._clamp() return this } BareTuiViewport.prototype._max = function () { return Math.max(0, this.lines.length - this.height) } BareTuiViewport.prototype.atTop = function () { return this.yOffset <= 0 } BareTuiViewport.prototype.atBottom = function () { return this.yOffset >= this._max() } BareTuiViewport.prototype.setYOffset = function (n) { this.yOffset = n this._clamp() return this } BareTuiViewport.prototype.scrollUp = function (n) { return this.setYOffset(this.yOffset - (n == null ? 1 : n)) } BareTuiViewport.prototype.scrollDown = function (n) { return this.setYOffset(this.yOffset + (n == null ? 1 : n)) } BareTuiViewport.prototype.gotoTop = function () { return this.setYOffset(0) } BareTuiViewport.prototype.gotoBottom = function () { return this.setYOffset(this._max()) } BareTuiViewport.prototype._clamp = function () { var max = this._max() if (this.yOffset < 0) this.yOffset = 0 if (this.yOffset > max) this.yOffset = max } BareTuiViewport.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] var h = this.height || 1 if (msg.is('up', 'k')) this.scrollUp(1) else if (msg.is('down', 'j')) this.scrollDown(1) else if (msg.is('pageup', 'b')) this.scrollUp(h) else if (msg.is('pagedown', 'f')) this.scrollDown(h) else if (msg.is('home')) this.gotoTop() else if (msg.is('end')) this.gotoBottom() return [this, null] } BareTuiViewport.prototype.view = function () { var out = [] var i for (i = 0; i < this.height; i++) { var line = this.lines[this.yOffset + i] if (line == null) line = '' if (this.width > 0) line = bareTuiTruncate(line, this.width) out.push(line) } return out.join('\n') } function BareTuiPaginator(opts) { opts = opts || {} this.perPage = opts.perPage || 10 this.total = opts.total || 0 this.page = opts.page || 0 this.kind = opts.type || 'dots' } BareTuiPaginator.prototype.pages = function () { if (this.perPage <= 0) return 1 return Math.max(1, Math.ceil(this.total / this.perPage)) } BareTuiPaginator.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] var n = this.pages() if (msg.is('left', 'h', 'pageup')) this.page = Math.max(0, this.page - 1) else if (msg.is('right', 'l', 'pagedown')) this.page = Math.min(n - 1, this.page + 1) return [this, null] } BareTuiPaginator.prototype.view = function () { var n = this.pages() if (this.kind === 'arabic') return this.page + 1 + '/' + n var s = '' var i for (i = 0; i < n; i++) s += i === this.page ? '●' : '○' return s } var bareTuiViewport = { create: function (opts) { return new BareTuiViewport(opts) } } var bareTuiPaginator = { create: function (opts) { return new BareTuiPaginator(opts) } } /* src/widgets/list.js */ /** Selectable, optionally filterable list. */ function bareTuiItemTitle(item) { if (item == null) return '' if (typeof item === 'string') return item return item.title != null ? String(item.title) : String(item) } function bareTuiItemFilter(item) { if (item == null) return '' if (typeof item === 'string') return item return String(item.filterValue || item.title || item) } function BareTuiList(opts) { opts = opts || {} this.items = opts.items ? opts.items.slice() : [] this.height = opts.height || 10 this.width = opts.width || 0 this.title = opts.title || '' this.filterable = opts.filterable !== false this.input = bareTuiTextinput.create({ prompt: '' }) this.filter = '' this.filtering = false this.selected = 0 this.offset = 0 this.filtered = [] this._applyFilter() } BareTuiList.prototype.selectedItem = function () { if (!this.filtered.length) return null return this.items[this.filtered[this.selected]] } BareTuiList.prototype.setItems = function (items) { this.items = items.slice() this._applyFilter() return this } BareTuiList.prototype._applyFilter = function () { var q = this.filter.toLowerCase() this.filtered = [] var i for (i = 0; i < this.items.length; i++) { if (!q || bareTuiItemFilter(this.items[i]).toLowerCase().indexOf(q) >= 0) { this.filtered.push(i) } } if (this.selected >= this.filtered.length) this.selected = Math.max(0, this.filtered.length - 1) this._keepVisible() } BareTuiList.prototype._keepVisible = function () { if (this.selected < this.offset) this.offset = this.selected if (this.selected >= this.offset + this.height) this.offset = this.selected - this.height + 1 if (this.offset < 0) this.offset = 0 } BareTuiList.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (this.filtering) { if (msg.is('escape', 'esc')) { this.filtering = false this.filter = '' this.input.reset().blur() this._applyFilter() return [this, null] } if (msg.is('enter')) { this.filtering = false this.input.blur() return [this, null] } var pair = this.input.update(msg) this.input = pair[0] this.filter = this.input.value this._applyFilter() return [this, pair[1]] } if (this.filterable && msg.is('/')) { this.filtering = true this.input.focus() return [this, null] } if (msg.is('up', 'k')) this.selected = Math.max(0, this.selected - 1) else if (msg.is('down', 'j')) this.selected = Math.min(this.filtered.length - 1, this.selected + 1) else if (msg.is('pageup')) this.selected = Math.max(0, this.selected - this.height) else if (msg.is('pagedown')) { this.selected = Math.min( this.filtered.length - 1, this.selected + this.height ) } this._keepVisible() return [this, null] } BareTuiList.prototype.view = function () { var lines = [] if (this.title) lines.push(this.title) if (this.filtering) lines.push('/' + this.input.value) var i for (i = 0; i < this.height; i++) { var idx = this.offset + i if (idx >= this.filtered.length) { lines.push('') continue } var item = this.items[this.filtered[idx]] var title = bareTuiItemTitle(item) if (this.width > 0) title = bareTuiTruncate(title, this.width) if (idx === this.selected) { title = bareTuiAnsi.modifierReverse + title + bareTuiAnsi.modifierNotReverse } lines.push(title) } return lines.join('\n') } var bareTuiList = { create: function (opts) { return new BareTuiList(opts) } } /* src/widgets/rest.js */ /** Progress, table, textarea, help, focus, stopwatch, timer, autocomplete. */ function BareTuiProgress(opts) { opts = opts || {} this.width = opts.width || 40 this.full = opts.full || '█' this.empty = opts.empty || '░' this.showPercentage = opts.showPercentage !== false } BareTuiProgress.prototype.setWidth = function (n) { this.width = n return this } BareTuiProgress.prototype.view = function (percent) { percent = Math.max(0, Math.min(1, percent || 0)) var reserve = this.showPercentage ? 5 : 0 var w = Math.max(1, this.width - reserve) var filled = Math.round(w * percent) var gap = w - filled var bar = bareTuiRepeat(this.full, filled) + bareTuiRepeat(this.empty, gap) if (!this.showPercentage) return bar var pct = Math.round(percent * 100) var label = String(pct) + '%' while (label.length < 4) label = ' ' + label return bar + ' ' + label } function BareTuiTable(opts) { opts = opts || {} this.columns = opts.columns || [] this.rows = opts.rows || [] this.height = opts.height || 8 this.selected = 0 this.offset = 0 } BareTuiTable.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('up', 'k')) this.selected = Math.max(0, this.selected - 1) else if (msg.is('down', 'j')) this.selected = Math.min(this.rows.length - 1, this.selected + 1) if (this.selected < this.offset) this.offset = this.selected if (this.selected >= this.offset + this.height) this.offset = this.selected - this.height + 1 return [this, null] } BareTuiTable.prototype.selectedRow = function () { return this.rows[this.selected] || null } BareTuiTable.prototype.view = function () { var lines = [] var cols = this.columns var header = [] var i for (i = 0; i < cols.length; i++) { var c = cols[i] header.push(typeof c === 'string' ? c : c.title || c.key || '') } lines.push(header.join(' ')) var r for (r = 0; r < this.height; r++) { var idx = this.offset + r var row = this.rows[idx] if (!row) { lines.push('') continue } var cells = [] for (i = 0; i < cols.length; i++) { var col = cols[i] var key = typeof col === 'string' ? col : col.key var val = Array.isArray(row) ? row[i] : row[key] cells.push(val == null ? '' : String(val)) } var line = cells.join(' ') if (idx === this.selected) line = bareTuiAnsi.modifierReverse + line + bareTuiAnsi.modifierNotReverse lines.push(line) } return lines.join('\n') } function BareTuiTextarea(opts) { opts = opts || {} this.lines = String(opts.value || '').split('\n') if (!this.lines.length) this.lines = [''] this.width = opts.width || 40 this.height = opts.height || 6 this.placeholder = opts.placeholder || '' this.charLimit = opts.charLimit || 0 this.focused = !!opts.focused this.row = 0 this.col = 0 } BareTuiTextarea.prototype.focus = function () { this.focused = true return this } BareTuiTextarea.prototype.blur = function () { this.focused = false return this } BareTuiTextarea.prototype.value = function () { return this.lines.join('\n') } BareTuiTextarea.prototype.update = function (msg) { if (!this.focused || !msg || msg.type !== 'key') return [this, null] var line = this.lines[this.row] || '' if (msg.is('up')) this.row = Math.max(0, this.row - 1) else if (msg.is('down')) this.row = Math.min(this.lines.length - 1, this.row + 1) else if (msg.is('left')) { if (this.col > 0) this.col-- else if (this.row > 0) { this.row-- this.col = (this.lines[this.row] || '').length } } else if (msg.is('right')) { if (this.col < line.length) this.col++ else if (this.row < this.lines.length - 1) { this.row++ this.col = 0 } } else if (msg.is('enter')) { var left = line.slice(0, this.col) var right = line.slice(this.col) this.lines[this.row] = left this.lines.splice(this.row + 1, 0, right) this.row++ this.col = 0 } else if (msg.is('backspace')) { if (this.col > 0) { this.lines[this.row] = line.slice(0, this.col - 1) + line.slice(this.col) this.col-- } else if (this.row > 0) { var prev = this.lines[this.row - 1] this.col = prev.length this.lines[this.row - 1] = prev + line this.lines.splice(this.row, 1) this.row-- } } else if ( !msg.ctrl && !msg.meta && msg.sequence && msg.sequence.length === 1 && msg.sequence >= ' ' ) { var all = this.value() if (this.charLimit && all.length >= this.charLimit) return [this, null] this.lines[this.row] = line.slice(0, this.col) + msg.sequence + line.slice(this.col) this.col++ } if (this.col > (this.lines[this.row] || '').length) this.col = (this.lines[this.row] || '').length return [this, null] } BareTuiTextarea.prototype.view = function () { if (!this.value() && this.placeholder && !this.focused) { return bareTuiAnsi.modifierDim + this.placeholder + bareTuiAnsi.reset } var out = [] var i for (i = 0; i < this.height; i++) { var line = this.lines[i] || '' if (this.focused && i === this.row) { var at = line.slice(this.col, this.col + 1) || ' ' line = line.slice(0, this.col) + bareTuiAnsi.modifierReverse + at + bareTuiAnsi.modifierNotReverse + line.slice(this.col + 1) } if (this.width > 0) line = bareTuiTruncate(line, this.width) out.push(line) } return out.join('\n') } function BareTuiHelp() {} BareTuiHelp.prototype.view = function (keymap) { if (!keymap) return '' var parts = [] var k for (k in keymap) { if (!Object.prototype.hasOwnProperty.call(keymap, k)) continue var b = keymap[k] var help = b && b.help if (help) parts.push((help.key || k) + ' ' + (help.desc || '')) else if (b && b.keys) parts.push(b.keys.join('/') + ' ' + k) } return parts.join(' · ') } function BareTuiFocus(opts) { opts = opts || {} this.items = opts.items ? opts.items.slice() : [] this.index = opts.index || 0 this._clamp() this._sync() } BareTuiFocus.prototype.focused = function () { return this.items[this.index] || null } BareTuiFocus.prototype.setItems = function (items) { this.items = items ? items.slice() : [] this._clamp() this._sync() return this } BareTuiFocus.prototype.focus = function (i) { this.index = i this._clamp() this._sync() return this } BareTuiFocus.prototype.next = function () { this._move(1) return this } BareTuiFocus.prototype.prev = function () { this._move(-1) return this } BareTuiFocus.prototype.update = function (msg) { if (msg && msg.type === 'key' && msg.is('shift+tab')) { this._move(-1) return [this, null] } if (msg && msg.type === 'key' && msg.is('tab')) { this._move(1) return [this, null] } var cur = this.items[this.index] if (cur && typeof cur.update === 'function') { var pair = cur.update(msg) this.items[this.index] = pair[0] return [this, pair[1]] } return [this, null] } BareTuiFocus.prototype._move = function (dir) { if (this.items.length < 2) return this.index = (this.index + dir + this.items.length) % this.items.length this._sync() } BareTuiFocus.prototype._clamp = function () { if (!this.items.length) this.index = 0 else { if (this.index < 0) this.index = 0 if (this.index > this.items.length - 1) this.index = this.items.length - 1 } } BareTuiFocus.prototype._sync = function () { var i for (i = 0; i < this.items.length; i++) { var it = this.items[i] if (!it) continue if (i === this.index) { if (typeof it.focus === 'function') it.focus() } else if (typeof it.blur === 'function') it.blur() } } function BareTuiStopwatch(opts) { opts = opts || {} this.interval = opts.interval || 100 this.elapsed = 0 this.running = false this.id = bareTuiNextWidgetId++ this.tag = 0 this._last = 0 } BareTuiStopwatch.prototype.start = function () { if (this.running) return this this.running = true this._last = Date.now() return this } BareTuiStopwatch.prototype.stop = function () { this.running = false return this } BareTuiStopwatch.prototype.toggle = function () { return this.running ? this.stop() : this.start() } BareTuiStopwatch.prototype.reset = function () { this.elapsed = 0 this._last = Date.now() return this } BareTuiStopwatch.prototype.init = function () { return this._tick() } BareTuiStopwatch.prototype._tick = function () { var id = this.id var tag = this.tag var ms = this.interval return bareTuiTick(ms, function () { return { type: 'stopwatch.tick', id: id, tag: tag } }) } BareTuiStopwatch.prototype.update = function (msg) { if (msg && msg.type === 'key' && msg.is('space')) { this.toggle() return [this, null] } if ( !msg || msg.type !== 'stopwatch.tick' || msg.id !== this.id || msg.tag !== this.tag ) { return [this, null] } if (this.running) { var now = Date.now() this.elapsed += now - this._last this._last = now } this.tag++ return [this, this._tick()] } BareTuiStopwatch.prototype.view = function () { var ms = this.elapsed var s = Math.floor(ms / 1000) var m = Math.floor(s / 60) s = s % 60 var mm = m < 10 ? '0' + m : String(m) var ss = s < 10 ? '0' + s : String(s) return mm + ':' + ss } function BareTuiTimer(opts) { opts = opts || {} this.timeout = opts.timeout || 10000 this.interval = opts.interval || 100 this.remaining = this.timeout this.running = true this.id = bareTuiNextWidgetId++ this.tag = 0 this._last = Date.now() } BareTuiTimer.prototype.init = function () { return this._tick() } BareTuiTimer.prototype._tick = function () { var id = this.id var tag = this.tag var ms = this.interval return bareTuiTick(ms, function () { return { type: 'timer.tick', id: id, tag: tag } }) } BareTuiTimer.prototype.update = function (msg) { if ( !msg || msg.type !== 'timer.tick' || msg.id !== this.id || msg.tag !== this.tag ) { return [this, null] } if (this.running) { var now = Date.now() this.remaining -= now - this._last this._last = now if (this.remaining <= 0) { this.remaining = 0 this.running = false this.tag++ return [ this, function () { return { type: 'timer.timeout' } } ] } } this.tag++ return [this, this._tick()] } BareTuiTimer.prototype.view = function () { var s = Math.ceil(this.remaining / 1000) return String(s) + 's' } function BareTuiAutocomplete(opts) { opts = opts || {} this.input = bareTuiTextinput.create({ prompt: opts.prompt || '', placeholder: opts.placeholder || '', focused: opts.focused }) this.suggestions = opts.suggestions || [] this.trigger = opts.trigger || '' this.filtered = [] this.selected = 0 this.open = false } BareTuiAutocomplete.prototype.focus = function () { this.input.focus() return this } BareTuiAutocomplete.prototype.blur = function () { this.input.blur() this.open = false return this } BareTuiAutocomplete.prototype.value = function () { return this.input.value } BareTuiAutocomplete.prototype._refresh = function () { var q = this.input.value if (this.trigger && q.indexOf(this.trigger) < 0) { this.open = false this.filtered = [] return } var needle = q.toLowerCase() this.filtered = [] var i for (i = 0; i < this.suggestions.length; i++) { var s = String(this.suggestions[i]) if (!needle || s.toLowerCase().indexOf(needle) >= 0) this.filtered.push(s) } this.open = this.filtered.length > 0 if (this.selected >= this.filtered.length) this.selected = 0 } BareTuiAutocomplete.prototype.update = function (msg) { if (!this.input.focused || !msg || msg.type !== 'key') return [this, null] if (this.open && msg.is('down')) { this.selected = Math.min(this.filtered.length - 1, this.selected + 1) return [this, null] } if (this.open && msg.is('up')) { this.selected = Math.max(0, this.selected - 1) return [this, null] } if (this.open && msg.is('enter', 'tab')) { if (this.filtered[this.selected] != null) this.input.setValue(this.filtered[this.selected]) this.open = false return [this, null] } var pair = this.input.update(msg) this.input = pair[0] this._refresh() return [this, pair[1]] } BareTuiAutocomplete.prototype.view = function () { var out = this.input.view() if (!this.open) return out var i for (i = 0; i < this.filtered.length && i < 6; i++) { var line = this.filtered[i] if (i === this.selected) line = bareTuiAnsi.modifierReverse + line + bareTuiAnsi.modifierNotReverse out += '\n' + line } return out } var bareTuiProgress = { create: function (opts) { return new BareTuiProgress(opts) } } var bareTuiTable = { create: function (opts) { return new BareTuiTable(opts) } } var bareTuiTextarea = { create: function (opts) { return new BareTuiTextarea(opts) } } var bareTuiHelp = { create: function () { return new BareTuiHelp() } } var bareTuiFocus = { create: function (opts) { return new BareTuiFocus(opts) } } var bareTuiStopwatch = { create: function (opts) { return new BareTuiStopwatch(opts) } } var bareTuiTimer = { create: function (opts) { return new BareTuiTimer(opts) } } var bareTuiAutocomplete = { create: function (opts) { return new BareTuiAutocomplete(opts) } } /* src/widgets/filepicker.js */ /** VFS file picker. Inject vfs (or mock). Never opens host fs. */ function bareTuiJoinPath(a, b) { var left = String(a == null ? '/' : a) var right = String(b == null ? '' : b).replace(/^\/+/, '') if (!left || left === '/') return '/' + right return left.replace(/\/+$/, '') + '/' + right } function bareTuiDirname(p) { p = String(p || '/').replace(/\/+$/, '') if (!p) return '/' var i = p.lastIndexOf('/') if (i <= 0) return '/' return p.slice(0, i) } function bareTuiSortEntries(entries, showHidden) { var out = [] var i for (i = 0; i < entries.length; i++) { if (showHidden || String(entries[i].name).charAt(0) !== '.') out.push(entries[i]) } out.sort(function (a, b) { if (a.directory !== b.directory) return a.directory ? -1 : 1 if (a.name < b.name) return -1 if (a.name > b.name) return 1 return 0 }) return out } function bareTuiIsDirStat(st) { if (!st) return false if (st.type === 'directory') return true if (typeof st.isDirectory === 'function') return !!st.isDirectory() return false } function bareTuiListVfs(vfs, dir) { if (!vfs || typeof vfs.readdir !== 'function') { return Promise.reject(new Error('filepicker: vfs.readdir missing')) } return Promise.resolve(vfs.readdir(dir)).then(function (names) { var list = names || [] var i = 0 var entries = [] function next() { if (i >= list.length) return entries var name = list[i++] if (name && typeof name === 'object' && name.name) { var directory = !!( name.directory || (typeof name.isDirectory === 'function' && name.isDirectory()) ) entries.push({ name: name.name, directory: directory }) return next() } var full = bareTuiJoinPath(dir, name) if (typeof vfs.stat !== 'function') { entries.push({ name: String(name), directory: false }) return next() } return Promise.resolve(vfs.stat(full)).then( function (st) { entries.push({ name: String(name), directory: bareTuiIsDirStat(st) }) return next() }, function () { entries.push({ name: String(name), directory: false }) return next() } ) } return next() }) } function BareTuiFilepicker(opts) { opts = opts || {} this.vfs = opts.vfs this.cwd = opts.cwd || '/' this.height = opts.height || 12 this.showHidden = !!opts.showHidden this.pick = opts.pick === 'dir' ? 'dir' : opts.pick === 'any' ? 'any' : 'file' this.filter = typeof opts.filter === 'function' ? opts.filter : null this.entries = [] this.cursor = 0 this.offset = 0 this.selected = null this.error = null this.loading = true } BareTuiFilepicker.prototype.init = function () { return this._read(this.cwd) } BareTuiFilepicker.prototype.selectedPath = function () { return this.selected } BareTuiFilepicker.prototype._read = function (dir) { var vfs = this.vfs var showHidden = this.showHidden var filter = this.filter return function () { return bareTuiListVfs(vfs, dir).then( function (entries) { if (filter) { var kept = [] var i for (i = 0; i < entries.length; i++) { if (filter(entries[i].name, entries[i])) kept.push(entries[i]) } entries = kept } return { type: 'filepicker.entries', dir: dir, entries: bareTuiSortEntries(entries, showHidden) } }, function (err) { return { type: 'filepicker.error', dir: dir, error: (err && err.message) || String(err) } } ) } } BareTuiFilepicker.prototype.update = function (msg) { if (!msg) return [this, null] if (msg.type === 'filepicker.entries' && msg.dir === this.cwd) { this.entries = msg.entries || [] this.cursor = 0 this.offset = 0 this.loading = false this.error = null return [this, null] } if (msg.type === 'filepicker.error' && msg.dir === this.cwd) { this.error = msg.error this.entries = [] this.loading = false return [this, null] } if (msg.type === 'key') return this._key(msg) return [this, null] } BareTuiFilepicker.prototype._open = function (dir) { this.cwd = dir this.loading = true return [this, this._read(dir)] } BareTuiFilepicker.prototype._move = function (delta) { if (!this.entries.length) return this.cursor = Math.max( 0, Math.min(this.cursor + delta, this.entries.length - 1) ) if (this.cursor < this.offset) this.offset = this.cursor else if (this.cursor >= this.offset + this.height) this.offset = this.cursor - this.height + 1 } BareTuiFilepicker.prototype._choose = function (full) { this.selected = full return [ this, function () { return { type: 'filepicker.select', path: full } } ] } BareTuiFilepicker.prototype._key = function (msg) { if (msg.is('up', 'k')) { this._move(-1) return [this, null] } if (msg.is('down', 'j')) { this._move(1) return [this, null] } if (msg.is('backspace', 'left', 'h')) return this._open(bareTuiDirname(this.cwd)) var entry = this.entries[this.cursor] if (!entry) return [this, null] var full = bareTuiJoinPath(this.cwd, entry.name) if (this.pick === 'dir') { if (!entry.directory) return [this, null] if (msg.is('right', 'l')) return this._open(full) if (msg.is('enter')) return this._choose(full) return [this, null] } if (msg.is('enter', 'right', 'l')) { if (entry.directory) return this._open(full) return this._choose(full) } return [this, null] } BareTuiFilepicker.prototype.view = function () { var lines = [this.cwd || '/'] var rows = [] if (this.loading) rows.push(' loading…') else if (this.error) rows.push(' ! ' + this.error) else if (!this.entries.length) rows.push(' (empty)') else { var end = Math.min(this.offset + this.height, this.entries.length) var p for (p = this.offset; p < end; p++) { var entry = this.entries[p] var label = entry.directory ? entry.name + '/' : entry.name var text = (p === this.cursor ? '› ' : ' ') + label if (p === this.cursor) { text = bareTuiAnsi.modifierReverse + text + bareTuiAnsi.modifierNotReverse } rows.push(text) } } while (rows.length < this.height) rows.push('') return lines.concat(rows).join('\n') } function bareTuiMockResolve(tree, root, p) { if (p === root) return tree var rel = p if (root !== '/' && String(p).indexOf(root) === 0) rel = p.slice(root.length) var segs = String(rel).split('/').filter(Boolean) var node = tree var i for (i = 0; i < segs.length; i++) { if (node && typeof node === 'object' && segs[i] in node) node = node[segs[i]] else return undefined } return node } function bareTuiFilepickerMock(tree, opts) { opts = opts || {} var root = opts.root || '/' function readdir(dir) { var node = bareTuiMockResolve(tree, root, dir) if (!node || typeof node !== 'object') { return Promise.reject(new Error('ENOTDIR: ' + dir)) } return Promise.resolve(Object.keys(node)) } function stat(p) { var node = bareTuiMockResolve(tree, root, p) if (node === undefined) return Promise.reject(new Error('ENOENT: ' + p)) var directory = !!(node && typeof node === 'object') return Promise.resolve({ type: directory ? 'directory' : 'file' }) } return { root: root, vfs: { readdir: readdir, stat: stat }, path: { join: bareTuiJoinPath, dirname: bareTuiDirname } } } function bareTuiFilepickerCreate(ctx, opts) { opts = opts || {} var vfs = opts.vfs if (!vfs && ctx && ctx.vfs) vfs = ctx.vfs var cwd = opts.cwd if (!cwd && ctx && ctx.env) cwd = ctx.env.PWD || ctx.env.HOME if (!cwd) cwd = '/' return new BareTuiFilepicker({ vfs: vfs, cwd: cwd, height: opts.height, showHidden: opts.showHidden, pick: opts.pick, filter: opts.filter }) } var bareTuiFilepicker = { create: null, mock: bareTuiFilepickerMock } /* src/widgets/oneshot.js */ /** One-shot confirm / prompt / choose models and runners. */ function bareTuiBox(title, body, hint) { var inner = (title ? title + '\n\n' : '') + body + (hint ? '\n\n' + hint : '') return inner } function BareTuiConfirmApp(opts) { opts = opts || {} this.title = opts.title || 'Confirm' this.body = opts.body || opts.message || '' this.okLabel = opts.ok || 'Yes' this.cancelLabel = opts.cancel || 'No' this.result = false } BareTuiConfirmApp.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('y', 'Y', 'enter')) { this.result = true return [this, bareTuiQuitCmd] } if (msg.is('n', 'N', 'escape', 'ctrl+c')) { this.result = false return [this, bareTuiQuitCmd] } return [this, null] } BareTuiConfirmApp.prototype.view = function () { return bareTuiBox( this.title, this.body, this.okLabel + ' (y/enter) · ' + this.cancelLabel + ' (n/esc)' ) } function BareTuiPromptApp(opts) { opts = opts || {} this.title = opts.title || '' this.label = opts.label || opts.prompt || '> ' this.field = bareTuiTextinput.create({ prompt: this.label, placeholder: opts.placeholder || '', echoMode: opts.echoMode || 'normal', focused: true, value: opts.value || '' }) this.result = null this.cancelled = false } BareTuiPromptApp.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('enter')) { this.result = this.field.value return [this, bareTuiQuitCmd] } if (msg.is('escape', 'ctrl+c')) { this.cancelled = true this.result = null return [this, bareTuiQuitCmd] } var pair = this.field.update(msg) this.field = pair[0] return [this, pair[1]] } BareTuiPromptApp.prototype.view = function () { return bareTuiBox(this.title, this.field.view(), 'enter accept · esc cancel') } function BareTuiChooseApp(opts) { opts = opts || {} this.title = opts.title || 'Choose' var items = opts.options || opts.items || [] this.list = bareTuiList.create({ items: items, height: opts.height || Math.min(8, Math.max(3, items.length)), filterable: opts.filterable === true, title: '' }) this.result = null this.cancelled = false } BareTuiChooseApp.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('enter')) { this.result = this.list.selectedItem() return [this, bareTuiQuitCmd] } if (msg.is('escape', 'q', 'ctrl+c')) { this.cancelled = true this.result = null return [this, bareTuiQuitCmd] } var pair = this.list.update(msg) this.list = pair[0] return [this, pair[1]] } BareTuiChooseApp.prototype.view = function () { return bareTuiBox(this.title, this.list.view(), 'enter select · q/esc cancel') } function bareTuiConfirm(ctx, opts) { var app = new BareTuiConfirmApp(opts) return bareTuiRunProgram(ctx, app, opts).then(function () { return app.result }) } function bareTuiPrompt(ctx, opts) { var app = new BareTuiPromptApp(opts) return bareTuiRunProgram(ctx, app, opts).then(function () { return app.result }) } function bareTuiChoose(ctx, opts) { var app = new BareTuiChooseApp(opts) return bareTuiRunProgram(ctx, app, opts).then(function () { return app.result }) } /* src/widgets/form.js */ /** Declarative form: fields + focus + validate. Emits form.submit / form.cancel. */ function bareTuiFormFieldValue(field) { if (!field) return undefined var c = field.control if (c && typeof c.value === 'function') return c.value() if (c && c.value != null && typeof c.value !== 'function') return c.value if (c && c.checked != null) return !!c.checked if (field.checked != null) return !!field.checked return field._value } function BareTuiFormField(def) { def = def || {} this.name = def.name || '' this.label = def.label || this.name this.description = def.description || def.help || '' this.required = !!def.required this.requiredMessage = def.requiredMessage || 'required' this.validate = def.validate || null this.type = def.type || 'text' this.hidden = !!def.hidden this.readonly = !!def.readonly this.error = '' this.control = def.control || null } BareTuiFormField.prototype.focus = function () { if (this.control && typeof this.control.focus === 'function') this.control.focus() return this } BareTuiFormField.prototype.blur = function () { if (this.control && typeof this.control.blur === 'function') this.control.blur() return this } BareTuiFormField.prototype.value = function () { return bareTuiFormFieldValue(this) } BareTuiFormField.prototype.check = function () { this.error = '' if (this.hidden) return true var v = this.value() var empty = v == null || v === '' || v === false if (this.required && empty) { this.error = this.requiredMessage return false } if ( this.type === 'number' && v != null && v !== '' && typeof v === 'number' && !isFinite(v) ) { this.error = 'not a number' return false } if (typeof this.validate === 'function') { var r = this.validate(v) if (r === false) { this.error = 'invalid' return false } if (typeof r === 'string' && r) { this.error = r return false } } return true } BareTuiFormField.prototype.update = function (msg) { if ( this.readonly || !this.control || typeof this.control.update !== 'function' ) { return [this, null] } var pair = this.control.update(msg) this.control = pair[0] return [this, pair[1]] } BareTuiFormField.prototype.view = function () { var lines = [] var lab = this.label || this.name if (lab) lines.push(lab + (this.required ? ' *' : '')) if (this.description) lines.push(this.description) if (this.control && typeof this.control.view === 'function') lines.push(this.control.view()) if (this.control && typeof this.control.menuView === 'function') { var menu = this.control.menuView() if (menu) lines.push(menu) } if (this.error) lines.push('! ' + this.error) return lines.join('\n') } function bareTuiFormBuildControl(def) { var type = def.type || 'text' if (type === 'textarea') { return bareTuiTextarea.create({ value: def.value || '', placeholder: def.placeholder || '', rows: def.rows, height: def.rows || 4, focused: !!def.autofocus }) } if (type === 'checkbox' || type === 'confirm') { return bareTuiCheckbox.create({ label: '', checked: !!def.value || !!def.checked, focused: !!def.autofocus }) } if (type === 'radio') { return bareTuiRadio.create({ options: def.options || [], selected: def.selected || 0, focused: !!def.autofocus }) } if (type === 'select') { return bareTuiSelect.create({ options: def.options || [], placeholder: def.placeholder || '', selected: def.selected || 0, focused: !!def.autofocus }) } if (type === 'multiselect') { return bareTuiList.create({ items: def.options || [], height: def.height || 6, filterable: false }) } var echo = def.echoMode || (type === 'password' ? 'password' : 'normal') return bareTuiTextinput.create({ value: def.value != null ? String(def.value) : '', placeholder: def.placeholder || '', echoMode: echo, focused: !!def.autofocus }) } function bareTuiFormFromDef(def) { if (def instanceof BareTuiFormField) return def if (def && def.control) return new BareTuiFormField(def) def = def || {} var control = bareTuiFormBuildControl(def) var field = new BareTuiFormField(def) field.control = control if (field.type === 'number') { field.value = function () { var raw = field.control && field.control.value if (raw == null || raw === '') return null var n = parseFloat(raw) return isFinite(n) ? n : NaN } } if (field.type === 'checkbox' || field.type === 'confirm') { field.value = function () { return !!(field.control && field.control.checked) } } if (field.type === 'radio' || field.type === 'select') { field.value = function () { return field.control && typeof field.control.value === 'function' ? field.control.value() : null } } return field } function bareTuiFormText(opts) { opts = opts || {} opts.type = opts.type || 'text' return bareTuiFormFromDef(opts) } function bareTuiFormTextarea(opts) { opts = opts || {} opts.type = 'textarea' return bareTuiFormFromDef(opts) } function bareTuiFormNumber(opts) { opts = opts || {} opts.type = 'number' return bareTuiFormFromDef(opts) } function bareTuiFormSelect(opts) { opts = opts || {} opts.type = 'select' return bareTuiFormFromDef(opts) } function bareTuiFormRadio(opts) { opts = opts || {} opts.type = 'radio' return bareTuiFormFromDef(opts) } function bareTuiFormConfirm(opts) { opts = opts || {} opts.type = 'confirm' return bareTuiFormFromDef(opts) } function BareTuiForm(opts) { opts = opts || {} this.title = opts.title || '' this.fields = [] var raw = opts.fields || [] var i for (i = 0; i < raw.length; i++) this.fields.push(bareTuiFormFromDef(raw[i])) this.index = 0 this._sync() } BareTuiForm.prototype._visible = function () { var out = [] var i for (i = 0; i < this.fields.length; i++) { if (!this.fields[i].hidden) out.push(this.fields[i]) } return out } BareTuiForm.prototype._sync = function () { var vis = this._visible() if (this.index >= vis.length) this.index = Math.max(0, vis.length - 1) var i for (i = 0; i < vis.length; i++) { if (i === this.index) vis[i].focus() else vis[i].blur() } } BareTuiForm.prototype.value = function () { var o = {} var i for (i = 0; i < this.fields.length; i++) { var f = this.fields[i] if (f.hidden || !f.name) continue o[f.name] = f.value() } return o } BareTuiForm.prototype.errors = function () { var o = {} var i for (i = 0; i < this.fields.length; i++) { if (this.fields[i].error) o[this.fields[i].name] = this.fields[i].error } return o } BareTuiForm.prototype._submit = function () { var ok = true var i for (i = 0; i < this.fields.length; i++) { if (!this.fields[i].check()) ok = false } if (!ok) return [this, null] var values = this.value() return [ this, function () { return { type: 'form.submit', values: values } } ] } BareTuiForm.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('ctrl+c')) { return [ this, function () { return { type: 'form.cancel' } } ] } if (msg.is('ctrl+s')) return this._submit() var vis = this._visible() if (msg.is('shift+tab')) { this.index = (this.index - 1 + vis.length) % Math.max(1, vis.length) this._sync() return [this, null] } if (msg.is('tab')) { this.index = (this.index + 1) % Math.max(1, vis.length) this._sync() return [this, null] } if (msg.is('enter')) { var cur = vis[this.index] if (cur && (cur.type === 'select' || cur.type === 'textarea')) { var p0 = cur.update(msg) this._replace(cur, p0[0]) return [this, p0[1]] } if (this.index < vis.length - 1) { this.index++ this._sync() return [this, null] } return this._submit() } var focused = vis[this.index] if (!focused) return [this, null] var pair = focused.update(msg) this._replace(focused, pair[0]) return [this, pair[1]] } BareTuiForm.prototype._replace = function (oldF, next) { var i for (i = 0; i < this.fields.length; i++) { if (this.fields[i] === oldF) this.fields[i] = next } } BareTuiForm.prototype.view = function () { var lines = [] if (this.title) lines.push(this.title, '') var vis = this._visible() var i for (i = 0; i < vis.length; i++) { lines.push(vis[i].view()) lines.push('') } lines.push('tab next · ctrl+s submit · ctrl+c cancel') return lines.join('\n') } function bareTuiFormCreate(opts) { return new BareTuiForm(opts) } function bareTuiFormRun(ctx, form, opts) { var host = { form: form, result: undefined, update: function (msg) { if (msg && msg.type === 'form.submit') { this.result = msg.values return [this, bareTuiQuitCmd] } if (msg && msg.type === 'form.cancel') { this.result = null return [this, bareTuiQuitCmd] } var pair = this.form.update(msg) this.form = pair[0] return [this, pair[1]] }, view: function () { return this.form.view() } } return bareTuiRunProgram(ctx, host, opts).then(function () { return host.result }) } var bareTuiForm = { create: bareTuiFormCreate, run: null, text: bareTuiFormText, textarea: bareTuiFormTextarea, number: bareTuiFormNumber, select: bareTuiFormSelect, radio: bareTuiFormRadio, confirm: bareTuiFormConfirm } /* src/widgets/extras.js */ /** Extra widgets: tabs, modal, dialog, tree, statusbar, split, toast, markdown. */ function BareTuiTabs(opts) { opts = opts || {} this.tabs = opts.tabs ? opts.tabs.slice() : [] this.index = opts.index || 0 } BareTuiTabs.prototype.active = function () { return this.tabs[this.index] || null } BareTuiTabs.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('left', 'h')) this.index = Math.max(0, this.index - 1) else if (msg.is('right', 'l')) this.index = Math.min(this.tabs.length - 1, this.index + 1) else if (msg.name && /^[1-9]$/.test(msg.name)) { var n = parseInt(msg.name, 10) - 1 if (n < this.tabs.length) this.index = n } else { var tab = this.active() if (tab && tab.model && typeof tab.model.update === 'function') { var pair = tab.model.update(msg) tab.model = pair[0] return [this, pair[1]] } } return [this, null] } BareTuiTabs.prototype.view = function () { var bar = [] var i for (i = 0; i < this.tabs.length; i++) { var title = this.tabs[i].title || String(i + 1) if (i === this.index) title = '[' + title + ']' bar.push(title) } var body = '' var tab = this.active() if (tab) { if (tab.model && typeof tab.model.view === 'function') body = tab.model.view() else if (tab.body != null) body = String(tab.body) else if (tab.view) body = typeof tab.view === 'function' ? tab.view() : String(tab.view) } return bar.join(' ') + '\n' + body } function BareTuiModal(opts) { opts = opts || {} this.child = opts.child || null this.open = !!opts.open this.title = opts.title || '' } BareTuiModal.prototype.show = function () { this.open = true return this } BareTuiModal.prototype.hide = function () { this.open = false return this } BareTuiModal.prototype.update = function (msg) { if (this.open && msg && msg.type === 'key' && msg.is('escape')) { this.open = false return [this, null] } if (this.child && typeof this.child.update === 'function') { var pair = this.child.update(msg) this.child = pair[0] return [this, pair[1]] } return [this, null] } BareTuiModal.prototype.view = function () { if (this.child && typeof this.child.view === 'function') return this.child.view() return '' } BareTuiModal.prototype.frame = function () { var inner = this.view() if (!this.open) return inner return (this.title || 'dialog') + '\n' + inner + '\n(esc close)' } BareTuiModal.prototype.overlay = function (size) { if (!this.open) return null var inner = '' if (this.child && typeof this.child.view === 'function') inner = this.child.view() var body = (this.title || 'dialog') + '\n' + inner + '\n(esc close)' var boxed = body var h = bareTuiBlockHeight(boxed) var w = bareTuiBlockWidth(boxed) var rows = (size && size.height) || 24 var cols = (size && size.width) || 80 return { row: Math.max(0, Math.floor((rows - h) / 2)), col: Math.max(0, Math.floor((cols - w) / 2)), text: boxed } } function BareTuiDialog(opts) { opts = opts || {} this.title = opts.title || '' this.body = opts.body || opts.message || '' this.buttons = opts.buttons || ['OK'] this.index = opts.index || 0 this.choice = null } BareTuiDialog.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] if (msg.is('left', 'h')) this.index = Math.max(0, this.index - 1) else if (msg.is('right', 'l')) this.index = Math.min(this.buttons.length - 1, this.index + 1) else if (msg.is('enter', 'space')) { this.choice = this.buttons[this.index] var choice = this.choice return [ this, function () { return { type: 'dialog.choose', value: choice } } ] } else if (msg.is('escape', 'ctrl+c')) { this.choice = null return [ this, function () { return { type: 'dialog.choose', value: null } } ] } return [this, null] } BareTuiDialog.prototype.view = function () { var btns = [] var i for (i = 0; i < this.buttons.length; i++) { var b = this.buttons[i] btns.push(i === this.index ? '[' + b + ']' : ' ' + b + ' ') } return ( (this.title ? this.title + '\n' : '') + this.body + '\n' + btns.join(' ') ) } function BareTuiTree(opts) { opts = opts || {} this.items = opts.items ? opts.items.slice() : [] this.cursor = 0 } function bareTuiTreeFlat(items, depth, out) { var i for (i = 0; i < items.length; i++) { var it = items[i] out.push({ item: it, depth: depth }) if (it.open && it.children && it.children.length) { bareTuiTreeFlat(it.children, depth + 1, out) } } } BareTuiTree.prototype._flat = function () { var out = [] bareTuiTreeFlat(this.items, 0, out) return out } BareTuiTree.prototype.selectedItem = function () { var flat = this._flat() return flat[this.cursor] ? flat[this.cursor].item : null } BareTuiTree.prototype.update = function (msg) { if (!msg || msg.type !== 'key') return [this, null] var flat = this._flat() if (msg.is('up', 'k')) this.cursor = Math.max(0, this.cursor - 1) else if (msg.is('down', 'j')) this.cursor = Math.min(flat.length - 1, this.cursor + 1) else if (msg.is('right', 'l', 'enter')) { var it = this.selectedItem() if (it && it.children) it.open = true } else if (msg.is('left', 'h')) { var it2 = this.selectedItem() if (it2 && it2.children) it2.open = false } return [this, null] } BareTuiTree.prototype.view = function () { var flat = this._flat() var lines = [] var i for (i = 0; i < flat.length; i++) { var it = flat[i].item var pad = '' var d for (d = 0; d < flat[i].depth; d++) pad += ' ' var mark = it.children ? (it.open ? '▾ ' : '▸ ') : ' ' var line = pad + mark + (it.title || it.name || '') if (i === this.cursor) line = bareTuiAnsi.modifierReverse + line + bareTuiAnsi.modifierNotReverse lines.push(line) } return lines.join('\n') } function BareTuiStatusbar(opts) { opts = opts || {} this.width = opts.width || 0 this.left = opts.left || '' this.center = opts.center || '' this.right = opts.right || '' } BareTuiStatusbar.prototype.set = function (part, text) { if (part === 'left') this.left = text else if (part === 'center') this.center = text else if (part === 'right') this.right = text return this } BareTuiStatusbar.prototype.update = function () { return [this, null] } BareTuiStatusbar.prototype.view = function () { var w = this.width || 80 var l = String(this.left) var c = String(this.center) var r = String(this.right) var inner = w - bareTuiLineWidth(l) - bareTuiLineWidth(r) if (inner < 1) return bareTuiTruncate(l + ' ' + r, w) var leftPad = Math.max(0, Math.floor((inner - bareTuiLineWidth(c)) / 2)) var rightPad = Math.max(0, inner - bareTuiLineWidth(c) - leftPad) return l + bareTuiRepeat(' ', leftPad) + c + bareTuiRepeat(' ', rightPad) + r } function BareTuiSplit(opts) { opts = opts || {} this.direction = opts.direction === 'column' ? 'column' : 'row' this.ratio = opts.ratio == null ? 0.5 : opts.ratio this.a = opts.left || opts.a || null this.b = opts.right || opts.b || null this.focusPane = opts.focus || 'a' this.width = opts.width || 0 this.height = opts.height || 0 } BareTuiSplit.prototype.update = function (msg) { if (msg && msg.type === 'key' && msg.is('ctrl+w')) { this.focusPane = this.focusPane === 'a' ? 'b' : 'a' return [this, null] } var pane = this.focusPane === 'b' ? this.b : this.a if (pane && typeof pane.update === 'function') { var pair = pane.update(msg) if (this.focusPane === 'b') this.b = pair[0] else this.a = pair[0] return [this, pair[1]] } return [this, null] } BareTuiSplit.prototype.view = function () { var va = this.a && typeof this.a.view === 'function' ? this.a.view() : String(this.a || '') var vb = this.b && typeof this.b.view === 'function' ? this.b.view() : String(this.b || '') if (this.direction === 'column') { return bareTuiJoinVertical(0, va, vb) } return bareTuiJoinHorizontal(0, va, ' │ ', vb) } function BareTuiToast(opts) { opts = opts || {} this.text = '' this.ttl = opts.ttl || 2000 this.visible = false this.id = bareTuiNextWidgetId++ this.tag = 0 } BareTuiToast.prototype.show = function (text, ms) { this.text = String(text || '') this.visible = true if (ms) this.ttl = ms this.tag++ return this._tick() } BareTuiToast.prototype._tick = function () { var id = this.id var tag = this.tag var ms = this.ttl return bareTuiTick(ms, function () { return { type: 'toast.hide', id: id, tag: tag } }) } BareTuiToast.prototype.update = function (msg) { if (!msg || msg.type !== 'toast.hide') return [this, null] if (msg.id !== this.id || msg.tag !== this.tag) return [this, null] this.visible = false this.text = '' return [this, null] } BareTuiToast.prototype.view = function () { return this.visible ? this.text : '' } function bareTuiInlineMd(s) { var out = '' var i = 0 while (i < s.length) { if (s.slice(i, i + 2) === '**') { var end = s.indexOf('**', i + 2) if (end > i) { out += bareTuiAnsi.sgr(1) + s.slice(i + 2, end) + bareTuiAnsi.reset i = end + 2 continue } } if (s.charAt(i) === '`') { var e2 = s.indexOf('`', i + 1) if (e2 > i) { out += bareTuiAnsi.modifierDim + s.slice(i + 1, e2) + bareTuiAnsi.reset i = e2 + 1 continue } } out += s.charAt(i) i++ } return out } function bareTuiRenderMarkdown(src) { var lines = String(src || '').split('\n') var out = [] var fence = false var i for (i = 0; i < lines.length; i++) { var line = lines[i] if (line.slice(0, 3) === '```') { fence = !fence continue } if (fence) { out.push(bareTuiAnsi.modifierDim + line + bareTuiAnsi.reset) continue } if (/^#{1,3} /.test(line)) { var text = line.replace(/^#{1,3} /, '') out.push(bareTuiAnsi.sgr(1) + text + bareTuiAnsi.reset) continue } if (/^[-*] /.test(line)) { out.push('• ' + bareTuiInlineMd(line.slice(2))) continue } if ( line.charAt(0) === '>' && (line.charAt(1) === ' ' || line.length === 1) ) { out.push(bareTuiAnsi.modifierDim + line.slice(2) + bareTuiAnsi.reset) continue } out.push(bareTuiInlineMd(line)) } return out.join('\n') } function BareTuiMarkdown(opts) { opts = opts || {} this.source = opts.source || opts.text || '' this.viewport = bareTuiViewport.create({ width: opts.width || 0, height: opts.height || 12 }) this.viewport.setContent(bareTuiRenderMarkdown(this.source)) } BareTuiMarkdown.prototype.setSource = function (src) { this.source = src this.viewport.setContent(bareTuiRenderMarkdown(src)) return this } BareTuiMarkdown.prototype.update = function (msg) { var pair = this.viewport.update(msg) this.viewport = pair[0] return [this, pair[1]] } BareTuiMarkdown.prototype.view = function () { return this.viewport.view() } var bareTuiTabs = { create: function (opts) { return new BareTuiTabs(opts) } } var bareTuiModal = { create: function (opts) { return new BareTuiModal(opts) } } var bareTuiDialog = { create: function (opts) { return new BareTuiDialog(opts) } } var bareTuiTree = { create: function (opts) { return new BareTuiTree(opts) } } var bareTuiStatusbar = { create: function (opts) { return new BareTuiStatusbar(opts) } } var bareTuiSplit = { create: function (opts) { return new BareTuiSplit(opts) } } var bareTuiToast = { create: function (opts) { return new BareTuiToast(opts) } } var bareTuiMarkdown = { create: function (opts) { return new BareTuiMarkdown(opts) }, render: bareTuiRenderMarkdown } /* src/zz-export.js */ /** Public ctx.tui / ctx.sdk surface. */ function bareTuiBuildPublic(ctx) { return { version: BARE_TUI_VERSION, isTTY: function (opts) { return bareTuiIsTTY(ctx, opts) }, buffer: { create: bareTuiGridCreate, fill: bareTuiGridFill, blit: bareTuiGridBlit, plain: bareTuiGridPlain }, size: function (opts) { return bareTuiSize(ctx, opts) }, ansi: bareTuiAnsi, key: { matches: bareTuiKeyMatches, binding: bareTuiKeyBinding }, decode: bareTuiDecodeBytes, createDecoder: bareTuiCreateDecoder, acquire: function (opts) { return bareTuiAcquire(ctx, opts) }, release: function (opts) { return bareTuiRelease(ctx, opts) }, withSession: function (fn, opts) { return bareTuiWithSession(ctx, fn, opts) }, screen: { enter: function (opts) { return bareTuiScreenEnter(ctx, opts) }, leave: function (opts) { return bareTuiScreenLeave(ctx, opts) } }, style: bareTuiMakeStyleFactory(ctx), theme: function (opts) { return bareTuiResolveTheme(ctx, opts) }, run: function (model, opts) { return bareTuiRunProgram(ctx, model, opts) }, create: function (model, opts) { return bareTuiCreateProgram(ctx, model, opts) }, send: function (msg) { return bareTuiSendActive(ctx, msg) }, quit: bareTuiQuitCmd, tick: bareTuiTick, every: bareTuiEvery, batch: bareTuiBatch, sequence: bareTuiSequence, suspend: bareTuiSuspend, spinner: bareTuiSpinner, textinput: bareTuiTextinput, autocomplete: bareTuiAutocomplete, textarea: bareTuiTextarea, viewport: bareTuiViewport, list: bareTuiList, table: bareTuiTable, help: bareTuiHelp, progress: bareTuiProgress, paginator: bareTuiPaginator, stopwatch: bareTuiStopwatch, timer: bareTuiTimer, checkbox: bareTuiCheckbox, radio: bareTuiRadio, select: bareTuiSelect, focus: bareTuiFocus, filepicker: { create: function (opts) { return bareTuiFilepickerCreate(ctx, opts) }, mock: bareTuiFilepickerMock }, form: { create: bareTuiFormCreate, run: function (form, opts) { return bareTuiFormRun(ctx, form, opts) }, text: bareTuiFormText, textarea: bareTuiFormTextarea, number: bareTuiFormNumber, select: bareTuiFormSelect, radio: bareTuiFormRadio, confirm: bareTuiFormConfirm }, confirm: (function () { var fn = function (opts) { return bareTuiConfirm(ctx, opts) } fn.create = function (opts) { return new BareTuiConfirmApp(opts) } return fn })(), prompt: (function () { var fn = function (opts) { return bareTuiPrompt(ctx, opts) } fn.create = function (opts) { return new BareTuiPromptApp(opts) } return fn })(), choose: (function () { var fn = function (opts) { return bareTuiChoose(ctx, opts) } fn.create = function (opts) { return new BareTuiChooseApp(opts) } return fn })(), tabs: bareTuiTabs, modal: bareTuiModal, dialog: bareTuiDialog, tree: bareTuiTree, statusbar: bareTuiStatusbar, split: bareTuiSplit, toast: bareTuiToast, markdown: bareTuiMarkdown } } function bareTuiBuildSdk(ctx, tui) { return { tui: tui, theme: { name: function (opts) { return tui.theme(opts).name }, tokens: function (opts) { return tui.theme(opts).tokens }, apply: function (name) { if (name && ctx && ctx.env) ctx.env.BARE_OS_THEME = String(name) if (ctx && typeof ctx.bareOsApplyTheme === 'function') { return Promise.resolve(ctx.bareOsApplyTheme()) } return Promise.resolve() } }, env: { get: function (key) { if (!ctx || !ctx.env) return '' var v = ctx.env[key] return v == null ? '' : String(v) }, has: function (key) { return !!( ctx && ctx.env && ctx.env[key] != null && String(ctx.env[key]) !== '' ) }, term: function () { return this.get('TERM') || 'unknown' }, colorDepth: function () { return tui.theme().depth }, noColor: function () { return tui.theme().noColor } }, proc: { read: function (name) { var path = String(name || '') if (path.indexOf('/') < 0) path = '/proc/bare_os/' + path if (!ctx || !ctx.vfs || typeof ctx.vfs.readFile !== 'function') { return Promise.resolve(null) } return Promise.resolve(ctx.vfs.readFile(path)).then(function (buf) { if (!buf) return null if (ctx.b4a && typeof ctx.b4a.toString === 'function') return ctx.b4a.toString(buf) if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8').decode(buf) } return String(buf) }) } }, ipc: { pushJson: function (name, obj) { if ( !ctx || !ctx.bareOsIpc || typeof ctx.bareOsIpc.pushJson !== 'function' ) { return false } ctx.bareOsIpc.pushJson(name, obj) return true }, takeJson: function (name) { if ( !ctx || !ctx.bareOsIpc || typeof ctx.bareOsIpc.takeJson !== 'function' ) { return Promise.resolve(null) } return ctx.bareOsIpc.takeJson(name) } }, app: { run: function (model, opts) { return tui.run(model, opts) }, confirm: function (opts) { return tui.confirm(opts) }, prompt: function (opts) { return tui.prompt(opts) }, select: function (opts) { return tui.choose(opts) }, form: function (spec, opts) { var f = tui.form.create(spec) return tui.form.run(f, opts) } }, vfs: { list: function (path) { if (!ctx || !ctx.vfs || typeof ctx.vfs.readdir !== 'function') { return Promise.resolve([]) } return ctx.vfs.readdir(path) }, readText: function (path) { if (!ctx || !ctx.vfs || typeof ctx.vfs.readFile !== 'function') { return Promise.resolve(null) } return Promise.resolve(ctx.vfs.readFile(path)).then(function (buf) { if (!buf) return null if (ctx.b4a && typeof ctx.b4a.toString === 'function') return ctx.b4a.toString(buf) if (typeof TextDecoder !== 'undefined') { return new TextDecoder('utf-8').decode(buf) } return String(buf) }) }, writeText: function (path, text) { if (!ctx || !ctx.vfs || typeof ctx.vfs.writeFile !== 'function') { return Promise.resolve(false) } var s = String(text == null ? '' : text) var buf if (ctx.b4a && typeof ctx.b4a.from === 'function') buf = ctx.b4a.from(s) else if (typeof TextEncoder !== 'undefined') buf = new TextEncoder().encode(s) else buf = s return Promise.resolve(ctx.vfs.writeFile(path, buf)).then(function () { return true }) } }, tty: { isTTY: function (opts) { return tui.isTTY(opts) }, size: function (opts) { return tui.size(opts) }, acquire: function (opts) { return tui.acquire(opts) }, release: function (opts) { return tui.release(opts) }, withSession: function (fn, opts) { return tui.withSession(fn, opts) } } } } function bareTuiAttachToCtx(ctx) { var tui = bareTuiBuildPublic(ctx) var sdk = bareTuiBuildSdk(ctx, tui) return { tui: tui, sdk: sdk } } return bareTuiAttachToCtx(ctx); })