/** TEA summon TUI. Cell buffer; engine lives in summon-*.js. */ function bareSummonTuiRunOpts() { return { buffer: 'cell' } } function bareSummonCreateTuiApp(ctx, session) { var tui = ctx.tui var size0 = tui && typeof tui.size === 'function' ? tui.size() : {} return { session: session, width: size0.width || 80, height: size0.height || 24, mode: 'browse', help: false, quitConfirm: false, status: '', num: '', linkFocus: 0, input: tui.textinput.create({ prompt: '', focused: true, charLimit: 800 }), viewport: tui.viewport.create({ width: size0.width || 80, height: Math.max(6, (size0.height || 24) - 5) }), init: function () { var self = this self.session.cols = Math.max(20, (self.width || 80) - 2) var tab = bareSummonCurrent(self.session) if (tab && tab.url) self._go(tab.url, { replace: true }) else self._sync() return null }, _tab: function () { return bareSummonCurrent(this.session) }, _sync: function () { var tab = this._tab() var cols = Math.max(20, (this.width || 80) - 2) this.session.cols = cols if (tab && tab.doc && (!tab.layout || tab.layout.cols !== cols)) { bareSummonRelayout(this.session) tab = this._tab() } this.viewport.width = cols this.viewport.height = Math.max(4, (this.height || 24) - 5) var lines = [] if (this.mode === 'source') { lines = String((tab && tab.source) || '').split('\n') } else if (tab && tab.reader && tab.doc) { lines = String(bareSummonReaderText(tab.doc) || '').split('\n') } else if (tab && tab.layout && tab.layout.plain) { lines = tab.layout.plain.slice() } if (tab && tab.error) lines.unshift('error: ' + tab.error) if (tab && tab.loading) lines.unshift('loading…') this.viewport.setContent(lines.join('\n')) var nlinks = tab && tab.layout && tab.layout.links ? tab.layout.links.length : 0 if (this.linkFocus >= nlinks) this.linkFocus = Math.max(0, nlinks - 1) }, _go: function (url, opts) { var self = this var tab = this._tab() if (tab) tab.loading = true this.status = 'loading…' this.mode = 'browse' this.num = '' bareSummonNavigate(this.session, url, opts || {}) .then(function () { self.status = '' self._sync() if (tui && typeof tui.send === 'function') { tui.send({ type: 'summon.loaded' }) } }) .catch(function (err) { var cur = self._tab() if (cur) { cur.error = (err && err.message) || String(err) cur.loading = false } self.status = 'error' self._sync() if (tui && typeof tui.send === 'function') { tui.send({ type: 'summon.loaded' }) } }) }, _follow: function (id) { var tab = this._tab() var links = (tab && tab.layout && tab.layout.links) || [] var hit = null var i for (i = 0; i < links.length; i++) { if (links[i].id === id) hit = links[i] } if (!hit) { this.status = 'no link ' + (id + 1) return } this._go(hit.href) }, _promptSubmit: function () { var line = String(this.input.value || '').trim() this.input.reset() var mode = this.mode this.mode = 'browse' if (!line) return if (mode === 'goto') this._go(line) else if (mode === 'find') { var tab = this._tab() if (tab) tab.find = line this._find(1) } }, _find: function (dir) { var tab = this._tab() var q = tab && tab.find ? String(tab.find) : '' if (!q) { this.status = 'no find' return } var lines = this.viewport.lines || [] var start = this.viewport.yOffset + (dir > 0 ? 1 : -1) var i if (dir > 0) { for (i = start; i < lines.length; i++) { if ( String(lines[i] || '') .toLowerCase() .indexOf(q.toLowerCase()) >= 0 ) { this.viewport.setYOffset(i) this.status = 'find ' + q return } } } else { for (i = start; i >= 0; i--) { if ( String(lines[i] || '') .toLowerCase() .indexOf(q.toLowerCase()) >= 0 ) { this.viewport.setYOffset(i) this.status = 'find ' + q return } } } this.status = 'not found: ' + q }, update: function (msg) { if (msg && msg.type === 'resize') { this.width = msg.width || this.width this.height = msg.height || this.height this._sync() return [this, null] } if (msg && msg.type === 'summon.loaded') { this._sync() return [this, null] } if (this.help) { if (msg && msg.type === 'key') this.help = false return [this, null] } if (this.quitConfirm) { if (tui.key.matches(msg, 'y', 'Y')) return [this, tui.quit] if (msg && msg.type === 'key') this.quitConfirm = false return [this, null] } if (this.mode === 'goto' || this.mode === 'find') { if (tui.key.matches(msg, 'escape', 'ctrl+c')) { this.mode = 'browse' this.input.reset() return [this, null] } if (tui.key.matches(msg, 'enter')) { this._promptSubmit() return [this, null] } var pair = this.input.update(msg) this.input = pair[0] return [this, pair[1]] } if (tui.key.matches(msg, 'ctrl+c', 'q')) { this.quitConfirm = true return [this, null] } if (tui.key.matches(msg, 'f10')) return [this, tui.quit] if (tui.key.matches(msg, '?')) { this.help = true return [this, null] } if (tui.key.matches(msg, 'g')) { this.mode = 'goto' this.input.reset() this.input.setValue && this.input.setValue('') return [this, null] } if (tui.key.matches(msg, '/')) { this.mode = 'find' this.input.reset() return [this, null] } if (tui.key.matches(msg, 'n')) { this._find(1) return [this, null] } if (tui.key.matches(msg, 'N')) { this._find(-1) return [this, null] } if (tui.key.matches(msg, 'r')) { this._go(this._tab().url, { replace: true }) return [this, null] } if (tui.key.matches(msg, 'R')) { var tabR = this._tab() tabR.reader = !tabR.reader this._sync() return [this, null] } if (tui.key.matches(msg, 'J')) { this.session.config.javascript = this.session.config.javascript === false this.status = this.session.config.javascript === false ? 'js off' : 'js on' if (this._tab().url) this._go(this._tab().url, { replace: true }) return [this, null] } if (tui.key.matches(msg, 's')) { this.mode = this.mode === 'source' ? 'browse' : 'source' this._sync() return [this, null] } if (tui.key.matches(msg, 'H')) { this._go('about:summon') return [this, null] } if (tui.key.matches(msg, 'o')) { this._go('about:bookmarks') return [this, null] } if (tui.key.matches(msg, 'a')) { var tabA = this._tab() bareSummonAddBookmark(this.session, tabA.url, tabA.title) this.status = 'bookmarked' if (typeof bareSummonSavePersisted === 'function') { bareSummonSavePersisted(this.session) } return [this, null] } if (tui.key.matches(msg, 'left', 'h')) { var selfB = this bareSummonBack(this.session).then(function () { selfB._sync() if (tui && typeof tui.send === 'function') tui.send({ type: 'summon.loaded' }) }) return [this, null] } if (tui.key.matches(msg, 'u')) { var selfF = this bareSummonForward(this.session).then(function () { selfF._sync() if (tui && typeof tui.send === 'function') tui.send({ type: 'summon.loaded' }) }) return [this, null] } if (tui.key.matches(msg, 't')) { bareSummonNewTab(this.session, 'about:summon') this._go('about:summon', { replace: true }) return [this, null] } if (tui.key.matches(msg, 'w')) { bareSummonCloseTab(this.session) this._sync() return [this, null] } if (tui.key.matches(msg, ']')) { bareSummonCycleTab(this.session, 1) this._sync() return [this, null] } if (tui.key.matches(msg, '[')) { bareSummonCycleTab(this.session, -1) this._sync() return [this, null] } if (tui.key.matches(msg, 'tab')) { var tabT = this._tab() var n = tabT && tabT.layout && tabT.layout.links ? tabT.layout.links.length : 0 if (n) this.linkFocus = (this.linkFocus + 1) % n return [this, null] } if (tui.key.matches(msg, 'enter')) { if (this.num) { var id = parseInt(this.num, 10) - 1 this.num = '' if (id >= 0) this._follow(id) return [this, null] } this._follow(this.linkFocus) return [this, null] } if ( msg && msg.type === 'key' && msg.name && msg.name.length === 1 && msg.name >= '0' && msg.name <= '9' ) { this.num += msg.name this.status = '#' + this.num return [this, null] } if (tui.key.matches(msg, 'j', 'down')) { this.viewport.scrollDown(1) return [this, null] } if (tui.key.matches(msg, 'k', 'up')) { this.viewport.scrollUp(1) return [this, null] } if (tui.key.matches(msg, ' ', 'pagedown')) { this.viewport.scrollDown(this.viewport.height || 10) return [this, null] } if (tui.key.matches(msg, 'pageup')) { this.viewport.scrollUp(this.viewport.height || 10) return [this, null] } if (tui.key.matches(msg, 'home')) { this.viewport.gotoTop() return [this, null] } if (tui.key.matches(msg, 'end')) { this.viewport.gotoBottom() return [this, null] } return [this, null] }, view: function () { var cols = Math.max(40, this.width || 80) var rows = Math.max(12, this.height || 24) var st = tui.style var tab = this._tab() var ntab = this.session.tabs.length var idx = this.session.current + 1 var url = (tab && tab.url) || '' var title = (tab && tab.title) || '' var jsOn = this.session.config && this.session.config.javascript !== false var scripts = ' js:' + (jsOn ? 'on' : 'off') + (tab && tab.js && tab.js.ran ? '+' + tab.js.ran : '') + (tab && tab.scriptsBlocked ? '/' + tab.scriptsBlocked : '') var titleRaw = ' summon [' + idx + '/' + ntab + '] ' + (title || url) + (tab && tab.reader ? ' reader' : '') + (this.mode === 'source' ? ' source' : '') + scripts + ' ' + cols + 'x' + rows + ' ' var bar = st ? st() .foreground('brightwhite') .background('blue') .width(cols) .render(titleRaw) : titleRaw var locRaw = ' ' + url + ' ' + (tab && tab.status ? tab.status : '') + ' ' var loc = st ? st().dim().width(cols).render(locRaw) : locRaw var body = this.viewport.view() var prompt = '' if (this.mode === 'goto') { prompt = 'Go: ' + (this.input.view ? this.input.view() : this.input.value || '') } else if (this.mode === 'find') { prompt = 'Find: ' + (this.input.view ? this.input.view() : this.input.value || '') } else { prompt = (this.status || '') + (this.num ? ' #' + this.num : '') + (this.linkFocus >= 0 && tab && tab.layout && tab.layout.links[this.linkFocus] ? ' → ' + tab.layout.links[this.linkFocus].href : '') } var footRaw = 'g go Enter follow 12+Enter link h back j/k /find a mark o marks q quit ?' var foot = st ? st().dim().width(cols).render(footRaw) : footRaw var promptLine = st ? st() .width(cols) .render(prompt || ' ') : prompt || ' ' var lines = [bar, loc] .concat(String(body).split('\n')) .concat([promptLine, foot]) while (lines.length < rows) lines.push('') var out = [] var i for (i = 0; i < rows; i++) { out.push( st ? st.truncate(lines[i] || '', cols) : String(lines[i] || '').slice(0, cols) ) } return out.join('\n') }, overlay: function (size) { if (!this.help && !this.quitConfirm) return null var cols = (size && size.width) || this.width || 80 var rows = (size && size.height) || this.height || 24 var st = tui.style var body = this.quitConfirm ? 'Leave summon?\n\nY yes any other key cancel' : 'summon — text web browser\n\n' + 'g URL go Enter follow focused / numbered link\n' + 'h/left back u forward r reload R reader s source\n' + 'j/k scroll / find n/N next/prev a bookmark o marks\n' + 't new tab w close [ ] tabs H home digits+Enter link #\n' + 'J toggle page JS (Bare VM / sandbox) HTTP via ctx.httpFetch\n\n' + 'Press any key to close.' var boxed = st ? st() .border(st.borders.rounded) .padding(1, 2) .background('black') .render(body) : body var h = st ? st.height(boxed) : boxed.split('\n').length var w = st ? st.width(boxed) : 56 return { row: Math.max(0, Math.floor((rows - h) / 2)), col: Math.max(0, Math.floor((cols - w) / 2)), text: boxed } } } }