4841 lines
125 KiB
JavaScript
4841 lines
125 KiB
JavaScript
(function (ctx) {
|
||
"use strict";
|
||
/* src/00-prelude.js */
|
||
/** Bare OS TUI concat prelude. Guest scripts receive this via ctx only. */
|
||
var BARE_TUI_VERSION = '1.3.0'
|
||
var bareTuiSessions = []
|
||
var bareTuiNextWidgetId = 1
|
||
|
||
/* src/ansi/codes.js */
|
||
/** CSI / SGR / DEC private-mode sequences for the guest TUI. */
|
||
|
||
var BARE_TUI_ESC = '\x1b'
|
||
var BARE_TUI_CSI = '\x1b['
|
||
|
||
function bareTuiSgr(n) {
|
||
return BARE_TUI_CSI + String(n) + 'm'
|
||
}
|
||
|
||
var bareTuiAnsi = {
|
||
esc: BARE_TUI_ESC,
|
||
csi: BARE_TUI_CSI,
|
||
reset: bareTuiSgr(0),
|
||
cursorHide: BARE_TUI_CSI + '?25l',
|
||
cursorShow: BARE_TUI_CSI + '?25h',
|
||
home: BARE_TUI_CSI + 'H',
|
||
eraseDisplay: BARE_TUI_CSI + '2J',
|
||
eraseDisplayEnd: BARE_TUI_CSI + 'J',
|
||
eraseScrollback: BARE_TUI_CSI + '3J',
|
||
eraseLine: BARE_TUI_CSI + '2K',
|
||
eraseLineEnd: BARE_TUI_CSI + 'K',
|
||
enterAltScreen: BARE_TUI_CSI + '?1049h',
|
||
leaveAltScreen: BARE_TUI_CSI + '?1049l',
|
||
enableMouseBasic: BARE_TUI_CSI + '?1000h' + BARE_TUI_CSI + '?1006h',
|
||
enableMouseDrag:
|
||
BARE_TUI_CSI + '?1000h' + BARE_TUI_CSI + '?1002h' + BARE_TUI_CSI + '?1006h',
|
||
enableMouseAll:
|
||
BARE_TUI_CSI + '?1000h' + BARE_TUI_CSI + '?1003h' + BARE_TUI_CSI + '?1006h',
|
||
disableMouse:
|
||
BARE_TUI_CSI +
|
||
'?1006l' +
|
||
BARE_TUI_CSI +
|
||
'?1003l' +
|
||
BARE_TUI_CSI +
|
||
'?1002l' +
|
||
BARE_TUI_CSI +
|
||
'?1000l',
|
||
enableBracketPaste: BARE_TUI_CSI + '?2004h',
|
||
disableBracketPaste: BARE_TUI_CSI + '?2004l',
|
||
enableFocus: BARE_TUI_CSI + '?1004h',
|
||
disableFocus: BARE_TUI_CSI + '?1004l',
|
||
syncOutputBegin: BARE_TUI_CSI + '?2026h',
|
||
syncOutputEnd: BARE_TUI_CSI + '?2026l',
|
||
modifierReverse: bareTuiSgr(7),
|
||
modifierNotReverse: bareTuiSgr(27),
|
||
modifierDim: bareTuiSgr(2),
|
||
cursorTo: function (row, col) {
|
||
var r = (row | 0) + 1
|
||
var c = (col | 0) + 1
|
||
if (r < 1) r = 1
|
||
if (c < 1) c = 1
|
||
return BARE_TUI_CSI + r + ';' + c + 'H'
|
||
},
|
||
cursorUp: function (n) {
|
||
return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'A'
|
||
},
|
||
cursorDown: function (n) {
|
||
return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'B'
|
||
},
|
||
cursorForward: function (n) {
|
||
return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'C'
|
||
},
|
||
cursorBack: function (n) {
|
||
return BARE_TUI_CSI + (n == null ? 1 : n | 0) + 'D'
|
||
},
|
||
sgr: bareTuiSgr,
|
||
fg256: function (n) {
|
||
return BARE_TUI_CSI + '38;5;' + (n | 0) + 'm'
|
||
},
|
||
bg256: function (n) {
|
||
return BARE_TUI_CSI + '48;5;' + (n | 0) + 'm'
|
||
},
|
||
fgRgb: function (r, g, b) {
|
||
return (
|
||
BARE_TUI_CSI + '38;2;' + (r | 0) + ';' + (g | 0) + ';' + (b | 0) + 'm'
|
||
)
|
||
},
|
||
bgRgb: function (r, g, b) {
|
||
return (
|
||
BARE_TUI_CSI + '48;2;' + (r | 0) + ';' + (g | 0) + ';' + (b | 0) + 'm'
|
||
)
|
||
}
|
||
}
|
||
|
||
/* src/input/decoder.js */
|
||
/** Byte-queue key / mouse / paste decoder. Produces TEA-style messages. */
|
||
|
||
function bareTuiUtf8Decode(bytes) {
|
||
try {
|
||
if (typeof TextDecoder !== 'undefined') {
|
||
return new TextDecoder('utf-8', { fatal: false }).decode(
|
||
new Uint8Array(bytes)
|
||
)
|
||
}
|
||
} catch (e) {
|
||
/* fall through */
|
||
}
|
||
var s = ''
|
||
for (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i])
|
||
return s
|
||
}
|
||
|
||
function bareTuiUtf8TrailCount(b1) {
|
||
if (b1 >= 0xc0 && b1 < 0xe0) return 1
|
||
if (b1 >= 0xe0 && b1 < 0xf0) return 2
|
||
if (b1 >= 0xf0) return 3
|
||
return 0
|
||
}
|
||
|
||
function bareTuiKeyToString() {
|
||
var parts = []
|
||
if (this.ctrl) parts.push('ctrl')
|
||
if (this.meta) parts.push('alt')
|
||
if (this.shift && this.name && this.name.length > 1) parts.push('shift')
|
||
parts.push(this.name === 'return' ? 'enter' : this.name)
|
||
return parts.join('+')
|
||
}
|
||
|
||
function bareTuiKeyIs() {
|
||
var str = this.toString()
|
||
for (var i = 0; i < arguments.length; i++) {
|
||
var chord = arguments[i]
|
||
if (chord === 'esc') chord = 'escape'
|
||
if (chord === str || chord === this.name) return true
|
||
if (chord === 'enter' && (str === 'enter' || this.name === 'return'))
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
function bareTuiMakeKey(name, seq, ctrl, meta, shift) {
|
||
return {
|
||
type: 'key',
|
||
name: name,
|
||
sequence: seq || '',
|
||
ctrl: !!ctrl,
|
||
meta: !!meta,
|
||
shift: !!shift,
|
||
toString: bareTuiKeyToString,
|
||
is: bareTuiKeyIs
|
||
}
|
||
}
|
||
|
||
function bareTuiKeyMatches(msg) {
|
||
if (!msg || msg.type !== 'key' || typeof msg.is !== 'function') return false
|
||
var chords = []
|
||
for (var i = 1; i < arguments.length; i++) {
|
||
var item = arguments[i]
|
||
if (item && typeof item === 'object' && item.keys && item.keys.length) {
|
||
for (var k = 0; k < item.keys.length; k++) chords.push(item.keys[k])
|
||
} else {
|
||
chords.push(item)
|
||
}
|
||
}
|
||
return msg.is.apply(msg, chords)
|
||
}
|
||
|
||
function bareTuiKeyBinding(opts) {
|
||
var o = opts || {}
|
||
var keys = o.keys
|
||
if (!keys) keys = []
|
||
if (typeof keys === 'string') keys = [keys]
|
||
return { keys: keys.slice(), help: o.help || null }
|
||
}
|
||
|
||
var BARE_TUI_CSI_NAMES = {
|
||
A: 'up',
|
||
B: 'down',
|
||
C: 'right',
|
||
D: 'left',
|
||
H: 'home',
|
||
F: 'end',
|
||
Z: 'tab',
|
||
P: 'f1',
|
||
Q: 'f2',
|
||
R: 'f3',
|
||
S: 'f4'
|
||
}
|
||
|
||
var BARE_TUI_TILDE_NAMES = {
|
||
1: 'home',
|
||
2: 'insert',
|
||
3: 'delete',
|
||
4: 'end',
|
||
5: 'pageup',
|
||
6: 'pagedown',
|
||
7: 'home',
|
||
8: 'end',
|
||
11: 'f1',
|
||
12: 'f2',
|
||
13: 'f3',
|
||
14: 'f4',
|
||
15: 'f5',
|
||
17: 'f6',
|
||
18: 'f7',
|
||
19: 'f8',
|
||
20: 'f9',
|
||
21: 'f10',
|
||
23: 'f11',
|
||
24: 'f12'
|
||
}
|
||
|
||
function bareTuiParseCsiPayload(payload) {
|
||
if (!payload) return null
|
||
if (payload === 'I') return { type: 'focus', gained: true }
|
||
if (payload === 'O') return { type: 'focus', gained: false }
|
||
|
||
if (payload.charAt(0) === '<') {
|
||
var m = /^<(\d+);(\d+);(\d+)([Mm])$/.exec(payload)
|
||
if (!m) return { type: 'unknown', sequence: payload }
|
||
var btn = parseInt(m[1], 10)
|
||
var x = parseInt(m[2], 10)
|
||
var y = parseInt(m[3], 10)
|
||
var release = m[4] === 'm'
|
||
var motion = (btn & 32) !== 0
|
||
var wheel = (btn & 64) !== 0
|
||
var code = btn & 3
|
||
var button =
|
||
code === 0
|
||
? 'left'
|
||
: code === 1
|
||
? 'middle'
|
||
: code === 2
|
||
? 'right'
|
||
: 'none'
|
||
var action = 'press'
|
||
if (wheel) action = (btn & 1) === 0 ? 'scrollup' : 'scrolldown'
|
||
else if (release) action = 'release'
|
||
else if (motion) action = 'drag'
|
||
return {
|
||
type: 'mouse',
|
||
action: action,
|
||
button: button,
|
||
x: x,
|
||
y: y,
|
||
ctrl: (btn & 16) !== 0,
|
||
meta: (btn & 8) !== 0,
|
||
shift: (btn & 4) !== 0
|
||
}
|
||
}
|
||
|
||
var last = payload.charAt(payload.length - 1)
|
||
var body = payload.slice(0, payload.length - 1)
|
||
var mods = 1
|
||
var num = ''
|
||
var semi = body.indexOf(';')
|
||
if (last === '~') {
|
||
if (semi >= 0) {
|
||
num = body.slice(0, semi)
|
||
mods = parseInt(body.slice(semi + 1), 10) || 1
|
||
} else {
|
||
num = body
|
||
}
|
||
if (num === '200') return { type: 'paste-start' }
|
||
if (num === '201') return { type: 'paste-end' }
|
||
var tname = BARE_TUI_TILDE_NAMES[num]
|
||
if (!tname) return { type: 'unknown', sequence: payload }
|
||
return bareTuiMakeKey(
|
||
tname,
|
||
payload,
|
||
!!((mods - 1) & 4),
|
||
!!((mods - 1) & 2),
|
||
!!((mods - 1) & 1)
|
||
)
|
||
}
|
||
var letter = last
|
||
if (semi >= 0) {
|
||
var parts = body.split(';')
|
||
if (parts.length >= 2) mods = parseInt(parts[1], 10) || 1
|
||
}
|
||
var lname = BARE_TUI_CSI_NAMES[letter]
|
||
if (!lname) return { type: 'unknown', sequence: payload }
|
||
var shift = !!((mods - 1) & 1)
|
||
if (letter === 'Z') shift = true
|
||
return bareTuiMakeKey(
|
||
lname,
|
||
payload,
|
||
!!((mods - 1) & 4),
|
||
!!((mods - 1) & 2),
|
||
shift
|
||
)
|
||
}
|
||
|
||
function bareTuiTryConsume(q) {
|
||
if (!q.length) return null
|
||
var b1 = q[0]
|
||
|
||
if (b1 === 27) {
|
||
if (q.length < 2) return { type: 'incomplete' }
|
||
var b2 = q[1]
|
||
if (b2 === 91) {
|
||
var i = 2
|
||
while (i < q.length) {
|
||
var b = q[i]
|
||
if (b >= 0x40 && b <= 0x7e) {
|
||
var seq = String.fromCharCode.apply(null, q.slice(2, i + 1))
|
||
q.splice(0, i + 1)
|
||
return bareTuiParseCsiPayload(seq)
|
||
}
|
||
i++
|
||
}
|
||
return { type: 'incomplete' }
|
||
}
|
||
if (b2 === 79) {
|
||
if (q.length < 3) return { type: 'incomplete' }
|
||
var b3 = q[2]
|
||
q.splice(0, 3)
|
||
var ss3 = String.fromCharCode(b3)
|
||
var ssName = {
|
||
A: 'up',
|
||
B: 'down',
|
||
C: 'right',
|
||
D: 'left',
|
||
H: 'home',
|
||
F: 'end',
|
||
P: 'f1',
|
||
Q: 'f2',
|
||
R: 'f3',
|
||
S: 'f4'
|
||
}[ss3]
|
||
if (!ssName) return { type: 'unknown', sequence: 'O' + ss3 }
|
||
return bareTuiMakeKey(ssName, 'O' + ss3, false, false, false)
|
||
}
|
||
q.splice(0, 2)
|
||
if (b2 === 27) return bareTuiMakeKey('escape', '\x1b', false, false, false)
|
||
if (b2 >= 1 && b2 <= 26) {
|
||
return bareTuiMakeKey(
|
||
String.fromCharCode(96 + b2),
|
||
String.fromCharCode(b2),
|
||
true,
|
||
true,
|
||
false
|
||
)
|
||
}
|
||
return bareTuiMakeKey(
|
||
String.fromCharCode(b2),
|
||
String.fromCharCode(b2),
|
||
false,
|
||
true,
|
||
false
|
||
)
|
||
}
|
||
|
||
if (b1 === 3) {
|
||
q.shift()
|
||
return bareTuiMakeKey('c', '\x03', true, false, false)
|
||
}
|
||
if (b1 === 4) {
|
||
q.shift()
|
||
return bareTuiMakeKey('d', '\x04', true, false, false)
|
||
}
|
||
if (b1 === 8 || b1 === 127) {
|
||
q.shift()
|
||
return bareTuiMakeKey(
|
||
'backspace',
|
||
String.fromCharCode(b1),
|
||
false,
|
||
false,
|
||
false
|
||
)
|
||
}
|
||
if (b1 === 13 || b1 === 10) {
|
||
q.shift()
|
||
return bareTuiMakeKey('enter', String.fromCharCode(b1), false, false, false)
|
||
}
|
||
if (b1 === 9) {
|
||
q.shift()
|
||
return bareTuiMakeKey('tab', '\t', false, false, false)
|
||
}
|
||
if (b1 === 32) {
|
||
q.shift()
|
||
return bareTuiMakeKey('space', ' ', false, false, false)
|
||
}
|
||
if (b1 === 27) {
|
||
q.shift()
|
||
return bareTuiMakeKey('escape', '\x1b', false, false, false)
|
||
}
|
||
if (b1 < 32) {
|
||
q.shift()
|
||
if (b1 === 0) return bareTuiMakeKey('space', '\x00', true, false, false)
|
||
return bareTuiMakeKey(
|
||
String.fromCharCode(96 + b1),
|
||
String.fromCharCode(b1),
|
||
true,
|
||
false,
|
||
false
|
||
)
|
||
}
|
||
|
||
if (b1 >= 0xc0) {
|
||
var trail = bareTuiUtf8TrailCount(b1)
|
||
if (q.length < trail + 1) return { type: 'incomplete' }
|
||
var ub = q.splice(0, trail + 1)
|
||
var ch = bareTuiUtf8Decode(ub)
|
||
return bareTuiMakeKey(ch, ch, false, false, false)
|
||
}
|
||
|
||
q.shift()
|
||
var one = String.fromCharCode(b1)
|
||
return bareTuiMakeKey(one, one, false, false, false)
|
||
}
|
||
|
||
function bareTuiCreateDecoder() {
|
||
var q = []
|
||
var paste = null
|
||
return {
|
||
push: function (chunk) {
|
||
var i
|
||
if (chunk == null) return
|
||
if (typeof chunk === 'string') {
|
||
for (i = 0; i < chunk.length; ) {
|
||
var cp = chunk.codePointAt(i)
|
||
i += cp > 0xffff ? 2 : 1
|
||
if (cp < 0x80) q.push(cp)
|
||
else if (cp < 0x800) {
|
||
q.push(0xc0 | (cp >> 6))
|
||
q.push(0x80 | (cp & 63))
|
||
} else if (cp < 0x10000) {
|
||
q.push(0xe0 | (cp >> 12))
|
||
q.push(0x80 | ((cp >> 6) & 63))
|
||
q.push(0x80 | (cp & 63))
|
||
} else {
|
||
q.push(0xf0 | (cp >> 18))
|
||
q.push(0x80 | ((cp >> 12) & 63))
|
||
q.push(0x80 | ((cp >> 6) & 63))
|
||
q.push(0x80 | (cp & 63))
|
||
}
|
||
}
|
||
return
|
||
}
|
||
if (chunk instanceof Uint8Array || Array.isArray(chunk)) {
|
||
for (i = 0; i < chunk.length; i++) q.push(chunk[i] & 255)
|
||
return
|
||
}
|
||
if (typeof chunk === 'number') q.push(chunk & 255)
|
||
},
|
||
flushEscape: function () {
|
||
if (q.length === 1 && q[0] === 27) {
|
||
q.shift()
|
||
return bareTuiMakeKey('escape', '\x1b', false, false, false)
|
||
}
|
||
return null
|
||
},
|
||
take: function () {
|
||
var out = []
|
||
for (;;) {
|
||
if (!q.length) break
|
||
if (paste !== null) {
|
||
var end = -1
|
||
var j
|
||
for (j = 0; j <= q.length - 6; j++) {
|
||
if (
|
||
q[j] === 27 &&
|
||
q[j + 1] === 91 &&
|
||
q[j + 2] === 50 &&
|
||
q[j + 3] === 48 &&
|
||
q[j + 4] === 49 &&
|
||
q[j + 5] === 126
|
||
) {
|
||
end = j
|
||
break
|
||
}
|
||
}
|
||
if (end < 0) break
|
||
var inner = q.splice(0, end)
|
||
q.splice(0, 6)
|
||
paste = null
|
||
out.push({ type: 'paste', text: bareTuiUtf8Decode(inner) })
|
||
continue
|
||
}
|
||
var ev = bareTuiTryConsume(q)
|
||
if (!ev || ev.type === 'incomplete') break
|
||
if (ev.type === 'paste-start') {
|
||
paste = []
|
||
continue
|
||
}
|
||
if (ev.type === 'paste-end') continue
|
||
out.push(ev)
|
||
}
|
||
return out
|
||
},
|
||
pending: function () {
|
||
return q.length
|
||
}
|
||
}
|
||
}
|
||
|
||
function bareTuiDecodeBytes(chunk) {
|
||
var d = bareTuiCreateDecoder()
|
||
d.push(chunk)
|
||
var evs = d.take()
|
||
var esc = d.flushEscape()
|
||
if (esc) evs.push(esc)
|
||
return evs
|
||
}
|
||
|
||
/* src/adapter/session.js */
|
||
/** Session adapter: Fish suspend, raw mode, stream resolve. Widgets never call this. */
|
||
|
||
function bareTuiEnv(ctx) {
|
||
return ctx && ctx.env && typeof ctx.env === 'object' ? ctx.env : {}
|
||
}
|
||
|
||
function bareTuiGetSession(ctx) {
|
||
var i
|
||
for (i = 0; i < bareTuiSessions.length; i++) {
|
||
if (bareTuiSessions[i].ctx === ctx) return bareTuiSessions[i]
|
||
}
|
||
var s = {
|
||
ctx: ctx,
|
||
depth: 0,
|
||
didSuspend: false,
|
||
didRaw: false,
|
||
stdin: null
|
||
}
|
||
bareTuiSessions.push(s)
|
||
return s
|
||
}
|
||
|
||
function bareTuiResolveStdin(ctx, opts) {
|
||
var o = opts || {}
|
||
if (o.input) return o.input
|
||
if (ctx && ctx.replStdin) return ctx.replStdin
|
||
if (ctx && ctx.stdin) return ctx.stdin
|
||
var p = typeof globalThis !== 'undefined' ? globalThis.process : null
|
||
return p && p.stdin ? p.stdin : null
|
||
}
|
||
|
||
function bareTuiResolveStdout(ctx, opts) {
|
||
var o = opts || {}
|
||
if (o.output) return o.output
|
||
if (ctx && ctx.replStdout) return ctx.replStdout
|
||
if (ctx && ctx.stdout) return ctx.stdout
|
||
var p = typeof globalThis !== 'undefined' ? globalThis.process : null
|
||
return p && p.stdout ? p.stdout : null
|
||
}
|
||
|
||
function bareTuiWriteRaw(ctx, opts, chunk) {
|
||
var out = bareTuiResolveStdout(ctx, opts)
|
||
var s = typeof chunk === 'string' ? chunk : String(chunk)
|
||
if (out && typeof out.write === 'function') {
|
||
out.write(s)
|
||
return
|
||
}
|
||
if (ctx && typeof ctx.writeScreen === 'function') ctx.writeScreen(s)
|
||
}
|
||
|
||
function bareTuiIsTTY(ctx, opts) {
|
||
var o = opts || {}
|
||
if (o.isTTY === true) return true
|
||
if (o.isTTY === false) return false
|
||
if (ctx && ctx.bareOsStdoutCaptured) return false
|
||
var stdin = bareTuiResolveStdin(ctx, o)
|
||
var stdout = bareTuiResolveStdout(ctx, o)
|
||
var inTty = !!(stdin && stdin.isTTY)
|
||
var outTty = !!(stdout && stdout.isTTY)
|
||
return inTty || outTty
|
||
}
|
||
|
||
function bareTuiSize(ctx, opts) {
|
||
var o = opts || {}
|
||
if (o.width > 0 && o.height > 0) {
|
||
return { width: o.width | 0, height: o.height | 0 }
|
||
}
|
||
var stdout = bareTuiResolveStdout(ctx, o)
|
||
var env = bareTuiEnv(ctx)
|
||
var w =
|
||
(stdout && stdout.columns) ||
|
||
parseInt(env.COLUMNS || '', 10) ||
|
||
o.width ||
|
||
80
|
||
var h =
|
||
(stdout && stdout.rows) || parseInt(env.LINES || '', 10) || o.height || 24
|
||
if (!isFinite(w) || w < 1) w = 80
|
||
if (!isFinite(h) || h < 1) h = 24
|
||
return { width: w | 0, height: h | 0 }
|
||
}
|
||
|
||
function bareTuiNoAltScreen(ctx, opts) {
|
||
var o = opts || {}
|
||
if (o.altScreen === false) return true
|
||
var env = bareTuiEnv(ctx)
|
||
var v = env.BARE_OS_TUI_NO_ALTSCREEN
|
||
return v != null && String(v) !== ''
|
||
}
|
||
|
||
function bareTuiAcquire(ctx, opts) {
|
||
var sess = bareTuiGetSession(ctx)
|
||
sess.depth++
|
||
if (sess.depth !== 1) return sess
|
||
if (ctx && typeof ctx.suspendReplForSubprocess === 'function') {
|
||
ctx.suspendReplForSubprocess()
|
||
sess.didSuspend = true
|
||
}
|
||
var stdin = bareTuiResolveStdin(ctx, opts)
|
||
sess.stdin = stdin
|
||
if (stdin && typeof stdin.setRawMode === 'function') {
|
||
try {
|
||
stdin.setRawMode(true)
|
||
sess.didRaw = true
|
||
} catch (e) {
|
||
sess.didRaw = false
|
||
}
|
||
}
|
||
if (stdin && typeof stdin.resume === 'function') {
|
||
try {
|
||
stdin.resume()
|
||
} catch (e2) {
|
||
/* ignore */
|
||
}
|
||
}
|
||
return sess
|
||
}
|
||
|
||
function bareTuiRelease(ctx, opts) {
|
||
var sess = bareTuiGetSession(ctx)
|
||
if (sess.depth < 1) return sess
|
||
sess.depth--
|
||
if (sess.depth !== 0) return sess
|
||
var stdin = sess.stdin || bareTuiResolveStdin(ctx, opts)
|
||
if (sess.didRaw && stdin && typeof stdin.setRawMode === 'function') {
|
||
try {
|
||
stdin.setRawMode(false)
|
||
} catch (e) {
|
||
/* ignore */
|
||
}
|
||
}
|
||
sess.didRaw = false
|
||
if (
|
||
sess.didSuspend &&
|
||
ctx &&
|
||
typeof ctx.resumeReplAfterSubprocess === 'function'
|
||
) {
|
||
try {
|
||
ctx.resumeReplAfterSubprocess()
|
||
} catch (e2) {
|
||
/* ignore */
|
||
}
|
||
}
|
||
sess.didSuspend = false
|
||
sess.stdin = null
|
||
return sess
|
||
}
|
||
|
||
async function bareTuiWithSession(ctx, fn, opts) {
|
||
bareTuiAcquire(ctx, opts)
|
||
var entered = false
|
||
try {
|
||
bareTuiScreenEnter(ctx, opts)
|
||
entered = true
|
||
return await fn()
|
||
} finally {
|
||
if (entered) {
|
||
try {
|
||
bareTuiScreenLeave(ctx, opts)
|
||
} catch (e) {
|
||
/* ignore */
|
||
}
|
||
}
|
||
bareTuiRelease(ctx, opts)
|
||
}
|
||
}
|
||
|
||
/* src/screen/screen.js */
|
||
/** Alternate-screen / cursor / mouse / paste enter-leave. Always pair with adapter. */
|
||
|
||
function bareTuiMouseMode(opts) {
|
||
var m = opts && opts.mouse
|
||
if (m === true || m === 'basic') return 'basic'
|
||
if (m === 'drag' || m === 'motion') return 'drag'
|
||
if (m === 'all') return 'all'
|
||
return null
|
||
}
|
||
|
||
function bareTuiScreenEnter(ctx, opts) {
|
||
var o = opts || {}
|
||
var parts = []
|
||
if (!bareTuiNoAltScreen(ctx, o)) parts.push(bareTuiAnsi.enterAltScreen)
|
||
else parts.push(bareTuiAnsi.home + bareTuiAnsi.eraseDisplay)
|
||
parts.push(bareTuiAnsi.cursorHide)
|
||
parts.push(bareTuiAnsi.home)
|
||
if (o.bracketedPaste !== false) parts.push(bareTuiAnsi.enableBracketPaste)
|
||
if (o.focus) parts.push(bareTuiAnsi.enableFocus)
|
||
var mouse = bareTuiMouseMode(o)
|
||
if (mouse === 'basic') parts.push(bareTuiAnsi.enableMouseBasic)
|
||
else if (mouse === 'drag') parts.push(bareTuiAnsi.enableMouseDrag)
|
||
else if (mouse === 'all') parts.push(bareTuiAnsi.enableMouseAll)
|
||
bareTuiWriteRaw(ctx, o, parts.join(''))
|
||
}
|
||
|
||
function bareTuiScreenLeave(ctx, opts) {
|
||
var o = opts || {}
|
||
var parts = []
|
||
parts.push(bareTuiAnsi.disableMouse)
|
||
parts.push(bareTuiAnsi.disableFocus)
|
||
parts.push(bareTuiAnsi.disableBracketPaste)
|
||
parts.push(bareTuiAnsi.cursorShow)
|
||
parts.push(bareTuiAnsi.reset)
|
||
if (!bareTuiNoAltScreen(ctx, o)) parts.push(bareTuiAnsi.leaveAltScreen)
|
||
bareTuiWriteRaw(ctx, o, parts.join(''))
|
||
}
|
||
|
||
/* src/theme/theme.js */
|
||
/** Resolve BARE_OS_THEME / BARE_OS_COLOR_* / NO_COLOR into TUI tokens. */
|
||
|
||
var BARE_TUI_THEME_FALLBACK = {
|
||
default: {
|
||
accent: 'cyan',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'blue'
|
||
},
|
||
nord: {
|
||
accent: 'cyan',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'blue'
|
||
},
|
||
dracula: {
|
||
accent: 'magenta',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'magenta'
|
||
},
|
||
solarized_dark: {
|
||
accent: 'blue',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'cyan'
|
||
},
|
||
gruvbox_dark: {
|
||
accent: 'yellow',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'yellow'
|
||
},
|
||
catppuccin_mocha: {
|
||
accent: 'blue',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'magenta'
|
||
},
|
||
tokyo_night: {
|
||
accent: 'blue',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'blue'
|
||
},
|
||
github_dark: {
|
||
accent: 'cyan',
|
||
muted: 'gray',
|
||
error: 'red',
|
||
ok: 'green',
|
||
border: 'blue'
|
||
}
|
||
}
|
||
|
||
function bareTuiColorDepth(env) {
|
||
var raw = env && env.BARE_OS_COLOR_DEPTH
|
||
var d = raw == null ? '' : String(raw).toLowerCase()
|
||
if (d === '16' || d === '8' || d === 'ansi') return '16'
|
||
if (d === '256' || d === '8bit') return '256'
|
||
return 'truecolor'
|
||
}
|
||
|
||
function bareTuiNoColor(env) {
|
||
if (!env) return false
|
||
if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return true
|
||
if (String(env.TERM || '').toLowerCase() === 'dumb') return true
|
||
return false
|
||
}
|
||
|
||
function bareTuiThemeName(env, opts) {
|
||
var o = opts || {}
|
||
if (o.theme && typeof o.theme === 'string') return String(o.theme)
|
||
var e = env || {}
|
||
var raw = e.BARE_OS_THEME
|
||
var name = raw == null || raw === '' ? 'default' : String(raw)
|
||
return name.toLowerCase().trim().replace(/\s+/g, '_')
|
||
}
|
||
|
||
function bareTuiEnvColor(env, short) {
|
||
if (!env) return ''
|
||
var key = 'BARE_OS_COLOR_' + String(short).toUpperCase()
|
||
var v = env[key]
|
||
return v == null ? '' : String(v)
|
||
}
|
||
|
||
function bareTuiResolveTheme(ctx, opts) {
|
||
var env = bareTuiEnv(ctx)
|
||
var name = bareTuiThemeName(env, opts)
|
||
var fb = BARE_TUI_THEME_FALLBACK[name] || BARE_TUI_THEME_FALLBACK.default
|
||
var noColor = bareTuiNoColor(env)
|
||
return {
|
||
name: name,
|
||
noColor: noColor,
|
||
depth: bareTuiColorDepth(env),
|
||
tokens: {
|
||
accent: bareTuiEnvColor(env, 'command') || fb.accent,
|
||
muted: bareTuiEnvColor(env, 'ghost') || fb.muted,
|
||
error: bareTuiEnvColor(env, 'envunset') || fb.error,
|
||
ok: bareTuiEnvColor(env, 'prompt') || fb.ok,
|
||
border: fb.border,
|
||
path: bareTuiEnvColor(env, 'path') || 'yellow',
|
||
search: bareTuiEnvColor(env, 'search') || fb.accent,
|
||
status: bareTuiEnvColor(env, 'prompt') || fb.ok
|
||
}
|
||
}
|
||
}
|
||
|
||
/* src/layout/style.js */
|
||
/** Lip Gloss–style chainable style + ANSI-aware width. */
|
||
|
||
var BARE_TUI_ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g
|
||
var BARE_TUI_ANSI_STICKY = /\x1b\[[0-9;?]*[A-Za-z]/y
|
||
|
||
function bareTuiStripAnsi(str) {
|
||
return String(str).replace(BARE_TUI_ANSI_RE, '')
|
||
}
|
||
|
||
function bareTuiIsZeroWidth(cp) {
|
||
return (
|
||
(cp >= 0x0300 && cp <= 0x036f) ||
|
||
(cp >= 0x1ab0 && cp <= 0x1aff) ||
|
||
(cp >= 0x1dc0 && cp <= 0x1dff) ||
|
||
(cp >= 0x20d0 && cp <= 0x20ff) ||
|
||
(cp >= 0xfe20 && cp <= 0xfe2f) ||
|
||
cp === 0x200b ||
|
||
(cp >= 0x200c && cp <= 0x200f) ||
|
||
cp === 0xfeff
|
||
)
|
||
}
|
||
|
||
function bareTuiIsWide(cp) {
|
||
return (
|
||
(cp >= 0x1100 && cp <= 0x115f) ||
|
||
(cp >= 0x2e80 && cp <= 0x303e) ||
|
||
(cp >= 0x3041 && cp <= 0x33ff) ||
|
||
(cp >= 0x3400 && cp <= 0x4dbf) ||
|
||
(cp >= 0x4e00 && cp <= 0x9fff) ||
|
||
(cp >= 0xa000 && cp <= 0xa4cf) ||
|
||
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
||
(cp >= 0xf900 && cp <= 0xfaff) ||
|
||
(cp >= 0xfe30 && cp <= 0xfe4f) ||
|
||
(cp >= 0xff00 && cp <= 0xff60) ||
|
||
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
||
(cp >= 0x1f300 && cp <= 0x1faff) ||
|
||
(cp >= 0x20000 && cp <= 0x3fffd)
|
||
)
|
||
}
|
||
|
||
function bareTuiCharWidth(cp) {
|
||
if (cp === 0) return 0
|
||
if (cp < 32 || (cp >= 0x7f && cp < 0xa0)) return 0
|
||
if (bareTuiIsZeroWidth(cp)) return 0
|
||
if (bareTuiIsWide(cp)) return 2
|
||
return 1
|
||
}
|
||
|
||
function bareTuiLineWidth(line) {
|
||
var w = 0
|
||
var plain = bareTuiStripAnsi(line)
|
||
for (var i = 0; i < plain.length; ) {
|
||
var cp = plain.codePointAt(i)
|
||
w += bareTuiCharWidth(cp)
|
||
i += cp > 0xffff ? 2 : 1
|
||
}
|
||
return w
|
||
}
|
||
|
||
function bareTuiBlockWidth(str) {
|
||
var w = 0
|
||
var lines = String(str).split('\n')
|
||
for (var i = 0; i < lines.length; i++)
|
||
w = Math.max(w, bareTuiLineWidth(lines[i]))
|
||
return w
|
||
}
|
||
|
||
function bareTuiBlockHeight(str) {
|
||
return String(str).split('\n').length
|
||
}
|
||
|
||
function bareTuiTruncate(str, w) {
|
||
if (w <= 0) return ''
|
||
str = String(str)
|
||
var out = ''
|
||
var used = 0
|
||
var sawAnsi = false
|
||
var i = 0
|
||
while (i < str.length) {
|
||
if (str.charAt(i) === '\x1b') {
|
||
BARE_TUI_ANSI_STICKY.lastIndex = i
|
||
var m = BARE_TUI_ANSI_STICKY.exec(str)
|
||
if (m) {
|
||
out += m[0]
|
||
sawAnsi = true
|
||
i = BARE_TUI_ANSI_STICKY.lastIndex
|
||
continue
|
||
}
|
||
}
|
||
var cp = str.codePointAt(i)
|
||
var ch = String.fromCodePoint(cp)
|
||
var cw = bareTuiCharWidth(cp)
|
||
if (used + cw > w) break
|
||
out += ch
|
||
used += cw
|
||
i += ch.length
|
||
}
|
||
if (sawAnsi) out += bareTuiAnsi.reset
|
||
return out
|
||
}
|
||
|
||
function bareTuiPadLine(line, w, pos) {
|
||
if (pos == null) pos = 0
|
||
var lw = bareTuiLineWidth(line)
|
||
if (lw > w) return bareTuiTruncate(line, w)
|
||
var space = w - lw
|
||
if (space === 0) return line
|
||
if (pos <= 0) return line + bareTuiRepeat(' ', space)
|
||
if (pos >= 1) return bareTuiRepeat(' ', space) + line
|
||
var left = Math.floor(space * pos)
|
||
return bareTuiRepeat(' ', left) + line + bareTuiRepeat(' ', space - left)
|
||
}
|
||
|
||
function bareTuiRepeat(ch, n) {
|
||
if (n <= 0) return ''
|
||
var s = ''
|
||
while (s.length < n) s += ch
|
||
return s
|
||
}
|
||
|
||
var BARE_TUI_NAMED = {
|
||
black: 30,
|
||
red: 31,
|
||
green: 32,
|
||
yellow: 33,
|
||
blue: 34,
|
||
magenta: 35,
|
||
cyan: 36,
|
||
white: 37,
|
||
default: 39,
|
||
gray: 90,
|
||
grey: 90,
|
||
brightblack: 90,
|
||
brightred: 91,
|
||
brightgreen: 92,
|
||
brightyellow: 93,
|
||
brightblue: 94,
|
||
brightmagenta: 95,
|
||
brightcyan: 96,
|
||
brightwhite: 97
|
||
}
|
||
|
||
function bareTuiHexToRgb(hex) {
|
||
var h = String(hex).replace(/^#/, '')
|
||
if (h.length === 3)
|
||
h =
|
||
h.charAt(0) +
|
||
h.charAt(0) +
|
||
h.charAt(1) +
|
||
h.charAt(1) +
|
||
h.charAt(2) +
|
||
h.charAt(2)
|
||
var n = parseInt(h, 16)
|
||
if (!isFinite(n)) return null
|
||
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
|
||
}
|
||
|
||
function bareTuiRgbTo256(r, g, b) {
|
||
if (r === g && g === b) {
|
||
if (r < 8) return 16
|
||
if (r > 248) return 231
|
||
return Math.round(((r - 8) / 247) * 24) + 232
|
||
}
|
||
return (
|
||
16 +
|
||
36 * Math.round((r / 255) * 5) +
|
||
6 * Math.round((g / 255) * 5) +
|
||
Math.round((b / 255) * 5)
|
||
)
|
||
}
|
||
|
||
function bareTuiRgbTo16(r, g, b) {
|
||
var bright = r > 180 || g > 180 || b > 180
|
||
var max = Math.max(r, g, b)
|
||
var min = Math.min(r, g, b)
|
||
if (max - min < 30) return bright ? 97 : 37
|
||
if (r >= g && r >= b)
|
||
return g > 80 && b < 80 ? (bright ? 93 : 33) : bright ? 91 : 31
|
||
if (g >= r && g >= b) return b > 80 ? (bright ? 96 : 36) : bright ? 92 : 32
|
||
return r > 80 ? (bright ? 95 : 35) : bright ? 94 : 34
|
||
}
|
||
|
||
function bareTuiColorParams(spec, bg, depth) {
|
||
if (spec === undefined || spec === null || spec === '') return []
|
||
var lead = bg ? 48 : 38
|
||
var d = depth || 'truecolor'
|
||
|
||
if (typeof spec === 'number') {
|
||
if (d === '16')
|
||
return [
|
||
bg
|
||
? spec >= 8
|
||
? spec - 8 + 100
|
||
: spec + 40
|
||
: spec >= 8
|
||
? spec - 8 + 90
|
||
: spec + 30
|
||
]
|
||
return [lead, 5, spec & 255]
|
||
}
|
||
|
||
var s = String(spec)
|
||
if (s.charAt(0) === '\x1b') return []
|
||
|
||
if (s.charAt(0) === '#') {
|
||
var rgb = bareTuiHexToRgb(s)
|
||
if (!rgb) return []
|
||
if (d === 'truecolor') return [lead, 2, rgb[0], rgb[1], rgb[2]]
|
||
if (d === '256') return [lead, 5, bareTuiRgbTo256(rgb[0], rgb[1], rgb[2])]
|
||
var n16 = bareTuiRgbTo16(rgb[0], rgb[1], rgb[2])
|
||
return [bg ? n16 + 10 : n16]
|
||
}
|
||
var name = s.toLowerCase()
|
||
if (name in BARE_TUI_NAMED)
|
||
return [bg ? BARE_TUI_NAMED[name] + 10 : BARE_TUI_NAMED[name]]
|
||
if (/^\d+$/.test(s)) {
|
||
var idx = parseInt(s, 10) & 255
|
||
if (d === '16') return [bg ? 40 : 30]
|
||
return [lead, 5, idx]
|
||
}
|
||
return []
|
||
}
|
||
|
||
function bareTuiSgrParams(params) {
|
||
return params.length ? bareTuiAnsi.csi + params.join(';') + 'm' : ''
|
||
}
|
||
|
||
var BARE_TUI_BORDERS = {
|
||
normal: {
|
||
topLeft: '\u250c',
|
||
top: '\u2500',
|
||
topRight: '\u2510',
|
||
left: '\u2502',
|
||
right: '\u2502',
|
||
bottomLeft: '\u2514',
|
||
bottom: '\u2500',
|
||
bottomRight: '\u2518'
|
||
},
|
||
rounded: {
|
||
topLeft: '\u256d',
|
||
top: '\u2500',
|
||
topRight: '\u256e',
|
||
left: '\u2502',
|
||
right: '\u2502',
|
||
bottomLeft: '\u2570',
|
||
bottom: '\u2500',
|
||
bottomRight: '\u256f'
|
||
},
|
||
thick: {
|
||
topLeft: '\u250f',
|
||
top: '\u2501',
|
||
topRight: '\u2513',
|
||
left: '\u2503',
|
||
right: '\u2503',
|
||
bottomLeft: '\u2517',
|
||
bottom: '\u2501',
|
||
bottomRight: '\u251b'
|
||
},
|
||
double: {
|
||
topLeft: '\u2554',
|
||
top: '\u2550',
|
||
topRight: '\u2557',
|
||
left: '\u2551',
|
||
right: '\u2551',
|
||
bottomLeft: '\u255a',
|
||
bottom: '\u2550',
|
||
bottomRight: '\u255d'
|
||
}
|
||
}
|
||
|
||
var BARE_TUI_POSITION = { top: 0, left: 0, center: 0.5, right: 1, bottom: 1 }
|
||
|
||
function bareTuiSides(args) {
|
||
var a = []
|
||
var i
|
||
for (i = 0; i < args.length; i++) a.push(args[i] || 0)
|
||
if (a.length <= 1) return [a[0] || 0, a[0] || 0, a[0] || 0, a[0] || 0]
|
||
if (a.length === 2) return [a[0], a[1], a[0], a[1]]
|
||
if (a.length === 3) return [a[0], a[1], a[2], a[1]]
|
||
return [a[0], a[1], a[2], a[3]]
|
||
}
|
||
|
||
function BareTuiStyle(props) {
|
||
this.props = props || {}
|
||
}
|
||
|
||
BareTuiStyle.prototype._with = function (patch) {
|
||
var next = {}
|
||
var k
|
||
for (k in this.props) {
|
||
if (Object.prototype.hasOwnProperty.call(this.props, k))
|
||
next[k] = this.props[k]
|
||
}
|
||
for (k in patch) {
|
||
if (Object.prototype.hasOwnProperty.call(patch, k)) next[k] = patch[k]
|
||
}
|
||
return new BareTuiStyle(next)
|
||
}
|
||
|
||
BareTuiStyle.prototype.bold = function (v) {
|
||
return this._with({ bold: v !== false })
|
||
}
|
||
BareTuiStyle.prototype.faint = function (v) {
|
||
return this._with({ faint: v !== false })
|
||
}
|
||
BareTuiStyle.prototype.dim = function (v) {
|
||
return this.faint(v)
|
||
}
|
||
BareTuiStyle.prototype.italic = function (v) {
|
||
return this._with({ italic: v !== false })
|
||
}
|
||
BareTuiStyle.prototype.underline = function (v) {
|
||
return this._with({ underline: v !== false })
|
||
}
|
||
BareTuiStyle.prototype.strikethrough = function (v) {
|
||
return this._with({ strikethrough: v !== false })
|
||
}
|
||
BareTuiStyle.prototype.reverse = function (v) {
|
||
return this._with({ reverse: v !== false })
|
||
}
|
||
BareTuiStyle.prototype.foreground = function (c) {
|
||
return this._with({ fg: c })
|
||
}
|
||
BareTuiStyle.prototype.background = function (c) {
|
||
return this._with({ bg: c })
|
||
}
|
||
BareTuiStyle.prototype.width = function (n) {
|
||
return this._with({ width: n })
|
||
}
|
||
BareTuiStyle.prototype.height = function (n) {
|
||
return this._with({ height: n })
|
||
}
|
||
BareTuiStyle.prototype.align = function (pos) {
|
||
return this._with({ align: pos })
|
||
}
|
||
BareTuiStyle.prototype.alignVertical = function (pos) {
|
||
return this._with({ alignV: pos })
|
||
}
|
||
BareTuiStyle.prototype.padding = function () {
|
||
return this._with({ padding: bareTuiSides(arguments) })
|
||
}
|
||
BareTuiStyle.prototype.margin = function () {
|
||
return this._with({ margin: bareTuiSides(arguments) })
|
||
}
|
||
BareTuiStyle.prototype.border = function (chars) {
|
||
var rest = []
|
||
var i
|
||
for (i = 1; i < arguments.length; i++) rest.push(arguments[i])
|
||
var on = rest.length
|
||
? bareTuiSides(rest).map(Boolean)
|
||
: [true, true, true, true]
|
||
return this._with({ border: chars, borderSides: on })
|
||
}
|
||
BareTuiStyle.prototype.borderForeground = function (c) {
|
||
return this._with({ borderFg: c })
|
||
}
|
||
|
||
BareTuiStyle.prototype._open = function () {
|
||
if (this.props.noColor) return ''
|
||
var p = this.props
|
||
var params = []
|
||
if (p.bold) params.push(1)
|
||
if (p.faint) params.push(2)
|
||
if (p.italic) params.push(3)
|
||
if (p.underline) params.push(4)
|
||
if (p.reverse) params.push(7)
|
||
if (p.strikethrough) params.push(9)
|
||
var depth = p.depth || 'truecolor'
|
||
var fg = bareTuiColorParams(p.fg, false, depth)
|
||
var bg = bareTuiColorParams(p.bg, true, depth)
|
||
var i
|
||
for (i = 0; i < fg.length; i++) params.push(fg[i])
|
||
for (i = 0; i < bg.length; i++) params.push(bg[i])
|
||
return bareTuiSgrParams(params)
|
||
}
|
||
|
||
BareTuiStyle.prototype.render = function (text) {
|
||
var p = this.props
|
||
var pad = p.padding || [0, 0, 0, 0]
|
||
var mar = p.margin || [0, 0, 0, 0]
|
||
var align = p.align || 0
|
||
var lines = String(text).split('\n')
|
||
var block =
|
||
!!p.border ||
|
||
(p.bg !== undefined && p.bg !== null) ||
|
||
!!p.width ||
|
||
align !== 0 ||
|
||
pad[0] ||
|
||
pad[1] ||
|
||
pad[2] ||
|
||
pad[3]
|
||
var contentW = p.width || bareTuiBlockWidth(lines.join('\n'))
|
||
var i
|
||
if (block) {
|
||
for (i = 0; i < lines.length; i++)
|
||
lines[i] = bareTuiPadLine(lines[i], contentW, align)
|
||
} else {
|
||
for (i = 0; i < lines.length; i++) {
|
||
if (bareTuiLineWidth(lines[i]) > contentW)
|
||
lines[i] = bareTuiTruncate(lines[i], contentW)
|
||
}
|
||
}
|
||
if (p.height)
|
||
lines = bareTuiFitHeight(lines, p.height, contentW, p.alignV || 0)
|
||
var innerW = contentW + pad[1] + pad[3]
|
||
if (pad[1] || pad[3]) {
|
||
var lp = bareTuiRepeat(' ', pad[3])
|
||
var rp = bareTuiRepeat(' ', pad[1])
|
||
for (i = 0; i < lines.length; i++) lines[i] = lp + lines[i] + rp
|
||
}
|
||
var blank = bareTuiRepeat(' ', innerW)
|
||
for (i = 0; i < pad[0]; i++) lines.unshift(blank)
|
||
for (i = 0; i < pad[2]; i++) lines.push(blank)
|
||
var open = this._open()
|
||
if (open) {
|
||
var reset = bareTuiAnsi.reset
|
||
for (i = 0; i < lines.length; i++) {
|
||
lines[i] =
|
||
open +
|
||
String(lines[i])
|
||
.split(reset)
|
||
.join(reset + open) +
|
||
reset
|
||
}
|
||
}
|
||
if (p.border)
|
||
lines = bareTuiApplyBorder(
|
||
lines,
|
||
innerW,
|
||
p.border,
|
||
p.borderSides,
|
||
p.borderFg,
|
||
p
|
||
)
|
||
if (mar[3] || mar[1]) {
|
||
var lm = bareTuiRepeat(' ', mar[3])
|
||
var rm = bareTuiRepeat(' ', mar[1])
|
||
for (i = 0; i < lines.length; i++) lines[i] = lm + lines[i] + rm
|
||
}
|
||
var fullW = bareTuiBlockWidth(lines.join('\n'))
|
||
var marginBlank = bareTuiRepeat(' ', fullW)
|
||
for (i = 0; i < mar[0]; i++) lines.unshift(marginBlank)
|
||
for (i = 0; i < mar[2]; i++) lines.push(marginBlank)
|
||
return lines.join('\n')
|
||
}
|
||
|
||
function bareTuiFitHeight(lines, h, w, posV) {
|
||
if (lines.length >= h) return lines.slice(0, h)
|
||
var extra = h - lines.length
|
||
var before = posV <= 0 ? 0 : posV >= 1 ? extra : Math.floor(extra * posV)
|
||
var blank = bareTuiRepeat(' ', w)
|
||
var out = []
|
||
var i
|
||
for (i = 0; i < before; i++) out.push(blank)
|
||
for (i = 0; i < lines.length; i++) out.push(lines[i])
|
||
for (i = 0; i < extra - before; i++) out.push(blank)
|
||
return out
|
||
}
|
||
|
||
function bareTuiApplyBorder(lines, innerW, chars, on, fg, props) {
|
||
var t = on[0]
|
||
var r = on[1]
|
||
var b = on[2]
|
||
var l = on[3]
|
||
var depth = (props && props.depth) || 'truecolor'
|
||
var noColor = props && props.noColor
|
||
var paint = function (s) {
|
||
if (noColor) return s
|
||
var params = bareTuiColorParams(fg, false, depth)
|
||
return params.length ? bareTuiSgrParams(params) + s + bareTuiAnsi.reset : s
|
||
}
|
||
var out = []
|
||
if (t) {
|
||
out.push(
|
||
paint(
|
||
(l ? chars.topLeft : '') +
|
||
bareTuiRepeat(chars.top, innerW) +
|
||
(r ? chars.topRight : '')
|
||
)
|
||
)
|
||
}
|
||
var left = l ? paint(chars.left) : ''
|
||
var right = r ? paint(chars.right) : ''
|
||
var i
|
||
for (i = 0; i < lines.length; i++) out.push(left + lines[i] + right)
|
||
if (b) {
|
||
out.push(
|
||
paint(
|
||
(l ? chars.bottomLeft : '') +
|
||
bareTuiRepeat(chars.bottom, innerW) +
|
||
(r ? chars.bottomRight : '')
|
||
)
|
||
)
|
||
}
|
||
return out
|
||
}
|
||
|
||
function bareTuiJoinHorizontal(pos) {
|
||
var blocks = []
|
||
var i
|
||
for (i = 1; i < arguments.length; i++) blocks.push(arguments[i])
|
||
var cols = []
|
||
var widths = []
|
||
var h = 0
|
||
for (i = 0; i < blocks.length; i++) {
|
||
var lines = String(blocks[i]).split('\n')
|
||
cols.push(lines)
|
||
widths.push(bareTuiBlockWidth(lines.join('\n')))
|
||
if (lines.length > h) h = lines.length
|
||
}
|
||
var padded = []
|
||
for (i = 0; i < cols.length; i++) {
|
||
var filled = []
|
||
var j
|
||
for (j = 0; j < cols[i].length; j++)
|
||
filled.push(bareTuiPadLine(cols[i][j], widths[i], 0))
|
||
var extra = h - filled.length
|
||
var before = pos <= 0 ? 0 : pos >= 1 ? extra : Math.floor(extra * pos)
|
||
var blank = bareTuiRepeat(' ', widths[i])
|
||
var col = []
|
||
for (j = 0; j < before; j++) col.push(blank)
|
||
for (j = 0; j < filled.length; j++) col.push(filled[j])
|
||
for (j = 0; j < extra - before; j++) col.push(blank)
|
||
padded.push(col)
|
||
}
|
||
var out = []
|
||
var row
|
||
for (row = 0; row < h; row++) {
|
||
var s = ''
|
||
for (i = 0; i < padded.length; i++) s += padded[i][row]
|
||
out.push(s)
|
||
}
|
||
return out.join('\n')
|
||
}
|
||
|
||
function bareTuiJoinVertical(pos) {
|
||
var blocks = []
|
||
var i
|
||
for (i = 1; i < arguments.length; i++) blocks.push(arguments[i])
|
||
var w = 0
|
||
var cols = []
|
||
for (i = 0; i < blocks.length; i++) {
|
||
var lines = String(blocks[i]).split('\n')
|
||
cols.push(lines)
|
||
var bw = bareTuiBlockWidth(lines.join('\n'))
|
||
if (bw > w) w = bw
|
||
}
|
||
var out = []
|
||
for (i = 0; i < cols.length; i++) {
|
||
var j
|
||
for (j = 0; j < cols[i].length; j++)
|
||
out.push(bareTuiPadLine(cols[i][j], w, pos))
|
||
}
|
||
return out.join('\n')
|
||
}
|
||
|
||
function bareTuiMakeStyleFactory(ctx) {
|
||
function style() {
|
||
var theme = bareTuiResolveTheme(ctx, {})
|
||
return new BareTuiStyle({ noColor: theme.noColor, depth: theme.depth })
|
||
}
|
||
style.Style = BareTuiStyle
|
||
style.borders = BARE_TUI_BORDERS
|
||
style.position = BARE_TUI_POSITION
|
||
style.joinHorizontal = bareTuiJoinHorizontal
|
||
style.joinVertical = bareTuiJoinVertical
|
||
style.width = bareTuiBlockWidth
|
||
style.height = bareTuiBlockHeight
|
||
style.truncate = bareTuiTruncate
|
||
style.stripAnsi = bareTuiStripAnsi
|
||
return style
|
||
}
|
||
|
||
/* src/buffer/grid.js */
|
||
/** Cell grid: parse ANSI text, blit overlays, serialize rows. */
|
||
|
||
var BARE_TUI_CELL_BOLD = 1
|
||
var BARE_TUI_CELL_DIM = 2
|
||
var BARE_TUI_CELL_REV = 4
|
||
|
||
function bareTuiCellEmpty() {
|
||
return { ch: ' ', fg: '', bg: '', attrs: 0 }
|
||
}
|
||
|
||
function bareTuiGridCreate(rows, cols) {
|
||
rows = Math.max(1, rows | 0)
|
||
cols = Math.max(1, cols | 0)
|
||
var cells = []
|
||
var r
|
||
for (r = 0; r < rows; r++) {
|
||
var row = []
|
||
var c
|
||
for (c = 0; c < cols; c++) row.push(bareTuiCellEmpty())
|
||
cells.push(row)
|
||
}
|
||
return { rows: rows, cols: cols, cells: cells }
|
||
}
|
||
|
||
function bareTuiGridClear(grid) {
|
||
var r, c
|
||
for (r = 0; r < grid.rows; r++) {
|
||
for (c = 0; c < grid.cols; c++) grid.cells[r][c] = bareTuiCellEmpty()
|
||
}
|
||
}
|
||
|
||
function bareTuiGridPut(grid, r, c, ch, fg, bg, attrs) {
|
||
if (r < 0 || c < 0 || r >= grid.rows || c >= grid.cols) return
|
||
grid.cells[r][c] = { ch: ch, fg: fg || '', bg: bg || '', attrs: attrs | 0 }
|
||
}
|
||
|
||
function bareTuiApplySgr(state, params) {
|
||
var i = 0
|
||
if (!params.length) params = [0]
|
||
while (i < params.length) {
|
||
var n = params[i++] | 0
|
||
if (n === 0) {
|
||
state.fg = ''
|
||
state.bg = ''
|
||
state.attrs = 0
|
||
} else if (n === 1) state.attrs |= BARE_TUI_CELL_BOLD
|
||
else if (n === 2) state.attrs |= BARE_TUI_CELL_DIM
|
||
else if (n === 22) state.attrs &= ~(BARE_TUI_CELL_BOLD | BARE_TUI_CELL_DIM)
|
||
else if (n === 7) state.attrs |= BARE_TUI_CELL_REV
|
||
else if (n === 27) state.attrs &= ~BARE_TUI_CELL_REV
|
||
else if ((n >= 30 && n <= 37) || (n >= 90 && n <= 97)) state.fg = String(n)
|
||
else if (n === 39) state.fg = ''
|
||
else if ((n >= 40 && n <= 47) || (n >= 100 && n <= 107))
|
||
state.bg = String(n)
|
||
else if (n === 49) state.bg = ''
|
||
else if (n === 38 || n === 48) {
|
||
var kind = params[i++] | 0
|
||
var key = n === 38 ? 'fg' : 'bg'
|
||
if (kind === 5) {
|
||
state[key] = n + ';5;' + (params[i++] | 0)
|
||
} else if (kind === 2) {
|
||
var r = params[i++] | 0
|
||
var g = params[i++] | 0
|
||
var b = params[i++] | 0
|
||
state[key] = n + ';2;' + r + ';' + g + ';' + b
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function bareTuiParseSgrParams(body) {
|
||
if (!body) return [0]
|
||
var parts = String(body).split(';')
|
||
var out = []
|
||
var i
|
||
for (i = 0; i < parts.length; i++) {
|
||
if (parts[i] === '') out.push(0)
|
||
else out.push(parseInt(parts[i], 10) || 0)
|
||
}
|
||
return out
|
||
}
|
||
|
||
function bareTuiGridWriteText(grid, startRow, startCol, text) {
|
||
var state = { fg: '', bg: '', attrs: 0 }
|
||
var r = startRow
|
||
var c = startCol
|
||
var i = 0
|
||
text = String(text == null ? '' : text)
|
||
while (i < text.length) {
|
||
var ch0 = text.charAt(i)
|
||
if (ch0 === '\n') {
|
||
r++
|
||
c = startCol
|
||
i++
|
||
continue
|
||
}
|
||
if (ch0 === '\r') {
|
||
i++
|
||
continue
|
||
}
|
||
if (ch0 === '\x1b') {
|
||
BARE_TUI_ANSI_STICKY.lastIndex = i
|
||
var m = BARE_TUI_ANSI_STICKY.exec(text)
|
||
if (m) {
|
||
var seq = m[0]
|
||
i = BARE_TUI_ANSI_STICKY.lastIndex
|
||
if (seq.charAt(seq.length - 1) === 'm') {
|
||
var inner = seq.slice(2, seq.length - 1)
|
||
bareTuiApplySgr(state, bareTuiParseSgrParams(inner))
|
||
}
|
||
continue
|
||
}
|
||
}
|
||
var cp = text.codePointAt(i)
|
||
var glyph = String.fromCodePoint(cp)
|
||
var cw = bareTuiCharWidth(cp)
|
||
i += glyph.length
|
||
if (cw <= 0) continue
|
||
bareTuiGridPut(grid, r, c, glyph, state.fg, state.bg, state.attrs)
|
||
if (cw === 2 && c + 1 < grid.cols) {
|
||
bareTuiGridPut(grid, r, c + 1, '', state.fg, state.bg, state.attrs)
|
||
}
|
||
c += cw
|
||
}
|
||
}
|
||
|
||
function bareTuiGridFill(grid, text) {
|
||
bareTuiGridClear(grid)
|
||
bareTuiGridWriteText(grid, 0, 0, text)
|
||
return grid
|
||
}
|
||
|
||
function bareTuiGridBlit(grid, row, col, text) {
|
||
var tmpRows = String(text == null ? '' : text).split('\n').length
|
||
var tmpCols = Math.max(1, bareTuiBlockWidth(text || ' '))
|
||
var tmp = bareTuiGridCreate(tmpRows, tmpCols)
|
||
bareTuiGridWriteText(tmp, 0, 0, text)
|
||
var r, c
|
||
for (r = 0; r < tmp.rows; r++) {
|
||
for (c = 0; c < tmp.cols; c++) {
|
||
var cell = tmp.cells[r][c]
|
||
if (cell.ch === ' ' && !cell.fg && !cell.bg && !cell.attrs) continue
|
||
bareTuiGridPut(
|
||
grid,
|
||
row + r,
|
||
col + c,
|
||
cell.ch,
|
||
cell.fg,
|
||
cell.bg,
|
||
cell.attrs
|
||
)
|
||
}
|
||
}
|
||
return grid
|
||
}
|
||
|
||
function bareTuiGridPlain(grid) {
|
||
var lines = []
|
||
var r, c
|
||
for (r = 0; r < grid.rows; r++) {
|
||
var s = ''
|
||
for (c = 0; c < grid.cols; c++) {
|
||
var ch = grid.cells[r][c].ch
|
||
if (ch !== '') s += ch
|
||
}
|
||
lines.push(s.replace(/\s+$/, ''))
|
||
}
|
||
return lines
|
||
}
|
||
|
||
function bareTuiGridRowKey(grid, r) {
|
||
var s = ''
|
||
var c
|
||
for (c = 0; c < grid.cols; c++) {
|
||
var cell = grid.cells[r][c]
|
||
s +=
|
||
cell.ch +
|
||
'\x01' +
|
||
cell.fg +
|
||
'\x01' +
|
||
cell.bg +
|
||
'\x01' +
|
||
cell.attrs +
|
||
'\x02'
|
||
}
|
||
return s
|
||
}
|
||
|
||
function bareTuiCellSgr(cell) {
|
||
var params = []
|
||
if (cell.attrs & BARE_TUI_CELL_BOLD) params.push(1)
|
||
if (cell.attrs & BARE_TUI_CELL_DIM) params.push(2)
|
||
if (cell.attrs & BARE_TUI_CELL_REV) params.push(7)
|
||
if (cell.fg) {
|
||
var fg = String(cell.fg)
|
||
if (fg.indexOf(';') >= 0) {
|
||
var fp = fg.split(';')
|
||
var fi
|
||
for (fi = 0; fi < fp.length; fi++) params.push(parseInt(fp[fi], 10) || 0)
|
||
} else params.push(parseInt(fg, 10) || 0)
|
||
}
|
||
if (cell.bg) {
|
||
var bg = String(cell.bg)
|
||
if (bg.indexOf(';') >= 0) {
|
||
var bp = bg.split(';')
|
||
var bi
|
||
for (bi = 0; bi < bp.length; bi++) params.push(parseInt(bp[bi], 10) || 0)
|
||
} else params.push(parseInt(bg, 10) || 0)
|
||
}
|
||
return params.length ? bareTuiAnsi.csi + params.join(';') + 'm' : ''
|
||
}
|
||
|
||
function bareTuiGridPaintRow(grid, r) {
|
||
var out = ''
|
||
var last = ''
|
||
var c
|
||
for (c = 0; c < grid.cols; c++) {
|
||
var cell = grid.cells[r][c]
|
||
if (cell.ch === '') continue
|
||
var sg = bareTuiCellSgr(cell)
|
||
if (sg !== last) {
|
||
out += bareTuiAnsi.reset + sg
|
||
last = sg
|
||
}
|
||
out += cell.ch
|
||
}
|
||
if (last) out += bareTuiAnsi.reset
|
||
return out
|
||
}
|
||
|
||
/* src/render/line-diff.js */
|
||
/** Line-diff renderer. Repaints only changed rows. Does not own alt-screen. */
|
||
|
||
function BareTuiRenderer(output) {
|
||
this.out = output
|
||
this.mode = 'line'
|
||
this.lastLines = null
|
||
}
|
||
|
||
BareTuiRenderer.prototype.clear = function () {
|
||
this.lastLines = null
|
||
}
|
||
|
||
BareTuiRenderer.prototype.write = function (s) {
|
||
if (!s) return
|
||
if (this.out && typeof this.out.write === 'function') this.out.write(s)
|
||
}
|
||
|
||
BareTuiRenderer.prototype.render = function (view) {
|
||
var lines = String(view).split('\n')
|
||
var s = ''
|
||
var i
|
||
if (this.lastLines === null) {
|
||
s += bareTuiAnsi.home
|
||
for (i = 0; i < lines.length; i++) {
|
||
s += bareTuiAnsi.eraseLineEnd + lines[i]
|
||
if (i < lines.length - 1) s += '\r\n'
|
||
}
|
||
s += bareTuiAnsi.eraseDisplayEnd
|
||
} else {
|
||
for (i = 0; i < lines.length; i++) {
|
||
if (lines[i] !== this.lastLines[i]) {
|
||
s += bareTuiAnsi.cursorTo(i, 0) + bareTuiAnsi.eraseLineEnd + lines[i]
|
||
}
|
||
}
|
||
if (this.lastLines.length > lines.length) {
|
||
s += bareTuiAnsi.cursorTo(lines.length, 0) + bareTuiAnsi.eraseDisplayEnd
|
||
}
|
||
}
|
||
this.lastLines = lines
|
||
this.write(s)
|
||
}
|
||
|
||
/* src/render/cell.js */
|
||
/** Dirty-row cell renderer. Base view + overlays; no layout insert. */
|
||
|
||
function BareTuiCellRenderer(output, opts) {
|
||
opts = opts || {}
|
||
this.out = output
|
||
this.mode = 'cell'
|
||
this.rows = Math.max(1, opts.rows || opts.height || 24)
|
||
this.cols = Math.max(1, opts.cols || opts.width || 80)
|
||
this.grid = bareTuiGridCreate(this.rows, this.cols)
|
||
this.lastKeys = null
|
||
}
|
||
|
||
BareTuiCellRenderer.prototype.write = function (s) {
|
||
if (!s) return
|
||
if (this.out && typeof this.out.write === 'function') this.out.write(s)
|
||
}
|
||
|
||
BareTuiCellRenderer.prototype.resize = function (rows, cols) {
|
||
rows = Math.max(1, rows | 0)
|
||
cols = Math.max(1, cols | 0)
|
||
if (rows === this.rows && cols === this.cols) return
|
||
this.rows = rows
|
||
this.cols = cols
|
||
this.grid = bareTuiGridCreate(rows, cols)
|
||
this.lastKeys = null
|
||
}
|
||
|
||
BareTuiCellRenderer.prototype.clear = function () {
|
||
this.lastKeys = null
|
||
}
|
||
|
||
BareTuiCellRenderer.prototype.render = function (view, overlays) {
|
||
bareTuiGridFill(this.grid, view == null ? '' : view)
|
||
if (overlays && overlays.length) {
|
||
var i
|
||
for (i = 0; i < overlays.length; i++) {
|
||
var ov = overlays[i]
|
||
if (!ov || ov.text == null) continue
|
||
bareTuiGridBlit(this.grid, ov.row | 0, ov.col | 0, ov.text)
|
||
}
|
||
}
|
||
var keys = []
|
||
var s = ''
|
||
var r
|
||
for (r = 0; r < this.grid.rows; r++) {
|
||
keys[r] = bareTuiGridRowKey(this.grid, r)
|
||
if (this.lastKeys && this.lastKeys[r] === keys[r]) continue
|
||
s +=
|
||
bareTuiAnsi.cursorTo(r, 0) +
|
||
bareTuiAnsi.eraseLineEnd +
|
||
bareTuiGridPaintRow(this.grid, r)
|
||
}
|
||
if (!this.lastKeys) s = bareTuiAnsi.home + s
|
||
this.lastKeys = keys
|
||
this.write(s)
|
||
}
|
||
|
||
BareTuiCellRenderer.prototype.plain = function () {
|
||
return bareTuiGridPlain(this.grid)
|
||
}
|
||
|
||
/* src/program/commands.js */
|
||
/** TEA commands. A Cmd is () => Msg | Promise<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);
|
||
})
|