Files
bare-operating-system/packages/bare-os-seeder/kernel/lib/bare-os/tui.js
T
Raven Scott ddebf42f1c
Release rolling / release (push) Successful in 9m38s
TUI Updates p2
2026-08-12 22:55:38 -04:00

4841 lines
125 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(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 Glossstyle 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<Msg> | 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);
})