From d2dc99b6ff7221e7226e75d49da9c59ec4e42c99 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sat, 30 Sep 2017 16:54:24 +0200 Subject: [PATCH] Add various things to the demo --- js/term/demo.js | 178 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 136 insertions(+), 42 deletions(-) diff --git a/js/term/demo.js b/js/term/demo.js index af542fa..f5175d7 100644 --- a/js/term/demo.js +++ b/js/term/demo.js @@ -143,7 +143,7 @@ class ScrollingTerminal { this.cursor = { x: 0, y: 0, style: 1, visible: true } this.trackMouse = false this.theme = 0 - this.rainbow = false + this.rainbow = this.superRainbow = false this.parser.reset() this.clear() } @@ -291,6 +291,9 @@ class ScrollingTerminal { data += this.buttonLabels.map(x => x + '\x01').join('') return data } + getTitle () { + return 'TESPTerm Web UI Demo\x01' + } getCursor () { let data = 'C' data += encodeAsCodePoint(this.cursor.y) @@ -319,10 +322,15 @@ class ScrollingTerminal { let x = index % this.width let y = Math.floor(index / this.width) // C instead of F in mask and 1 << 8 in attrs to change attr bits 1 and 2 - style[0] = getRainbowColor((x + y) / 10 + Date.now() / 1000) + let t = (x + y) / 10 + Date.now() / 1000 + if (this.superRainbow) { + t = (x * y + Date.now()) / 100 + Date.now() / 1000 + } + style[0] = getRainbowColor(t) style[1] = 0 + if (this.superRainbow) style[1] = getRainbowColor(t / 10) style[2] = style[2] | 1 - if (style[2] & (1 << 1)) style[2] ^= (1 << 1) + if (!this.superRainbow && style[2] & (1 << 1)) style[2] ^= (1 << 1) index++ } @@ -353,6 +361,7 @@ class ScrollingTerminal { let topics = 0 let topicData = [] let screenOpts = this.getScreenOpts() + let title = this.getTitle() let buttons = this.getButtons() let cursor = this.getCursor() let screen = this.serializeScreen() @@ -360,6 +369,10 @@ class ScrollingTerminal { this._screenOpts = screenOpts topicData.push(screenOpts) } + if (this._title !== title) { + this._title = title + topicData.push(title) + } if (this._buttons !== buttons) { this._buttons = buttons topicData.push(buttons) @@ -528,7 +541,7 @@ let demoshIndex = { } return new Promise((resolve, reject) => { const self = this - let x = 14 + let x = 13 let cycles = 0 let loop = function () { for (let y = 0; y < splash.length; y++) { @@ -536,7 +549,7 @@ let demoshIndex = { if (dx > 0) drawCell(dx, y) } - if (++x < 69) { + if (++x < 70) { if (++cycles >= 3) { setTimeout(loop, 50) cycles = 0 @@ -690,15 +703,40 @@ let demoshIndex = { } }, rainbow: class ToggleRainbow extends Process { - constructor (shell) { + constructor (shell, options = {}) { super() this.shell = shell + this.su = options.su + this.abort = false } - run () { - this.shell.terminal.rainbow = !this.shell.terminal.rainbow - this.emit('write', '') + write (data, newLine = true) { + if (data === 'y') { + this.shell.terminal.rainbow = !this.shell.terminal.rainbow + this.shell.terminal.superRainbow = true + demoData._didWarnRainbow = true + } else { + this.emit('write', data) + } + if (newLine) this.emit('write', '\x1b[0;32m\x1b[G\x1b[79PRainbow!\n') this.destroy() } + run () { + if (this.su && !this.shell.terminal.rainbow) { + if (!demoData._didWarnRainbow) { + this.emit('write', '\x1b[31;1mWarning: flashy colors. Continue? [y/N] ') + } else { + this.write('y', false) + } + } else { + this.shell.terminal.rainbow = !this.shell.terminal.rainbow + this.shell.terminal.superRainbow = false + this.destroy() + } + } + destroy () { + this.abort = true + super.destroy() + } }, mouse: class ShowMouse extends Process { constructor (shell) { @@ -767,43 +805,19 @@ let demoshIndex = { constructor (shell) { super() this.shell = shell + this.didDestroy = false } run (...args) { if (args.length === 0) { - this.emit('write', '\x1b[31mUsage: sudo \x1b[m\r\n') - this.destroy() - } else if (args.length === 4 && args.join(' ').toLowerCase() === 'make me a sandwich') { - const b = '\x1b[33m' - const r = '\x1b[0m' - const l = '\x1b[32m' - const c = '\x1b[38;5;229m' - const h = '\x1b[38;5;225m' - this.emit('write', - ` ${b}_.---._\r\n` + - ` _.-~ ~-._\r\n` + - ` _.-~ ~-._\r\n` + - ` _.-~ ~---._\r\n` + - ` _.-~ ~\\\r\n` + - ` .-~ _.;\r\n` + - ` :-._ _.-~ ./\r\n` + - ` \`-._~-._ _..__.-~ _.-~\r\n` + - ` ${c}/ ${b}~-._~-._ / .__..--${c}~-${l}---._\r\n` + - `${c} \\_____(_${b};-._\\. _.-~_/${c} ~)${l}.. . \\\r\n` + - `${l} /(_____ ${b}\\\`--...--~_.-~${c}______..-+${l}_______)\r\n` + - `${l} .(_________/${b}\`--...--~/${l} _/ ${h} ${b}/\\\r\n` + - `${b} /-._${h} \\_ ${l}(___./_..-~${h}__.....${b}__..-~./\r\n` + - `${b} \`-._~-._${h} ~\\--------~ .-~${b}_..__.-~ _.-~\r\n` + - `${b} ~-._~-._ ${h}~---------\` ${b}/ .__..--~\r\n` + - `${b} ~-._\\. _.-~_/\r\n` + - `${b} \\\`--...--~_.-~\r\n` + - `${b} \`--...--~${r}\r\n`) + this.emit('write', '\x1b[31mUsage: sudo [command]\x1b[m\r\n') this.destroy() } else { let name = args.shift() if (this.shell.index[name]) { let Process = this.shell.index[name] if (Process instanceof Function) { - let child = new Process(this) + let child = this.child = new Process(this.shell, { su: true }) + this.on('in', data => child.write(data)) let write = data => this.emit('write', data) child.on('write', write) child.on('exit', code => { @@ -821,12 +835,50 @@ let demoshIndex = { } } } + destroy () { + if (this.didDestroy) return + this.didDestroy = true + this.child.destroy() + super.destroy() + } }, make: class Make extends Process { + constructor (shell, options = {}) { + super() + this.su = options.su + } run (...args) { if (args.length === 0) this.emit('write', '\x1b[31mmake: *** No targets specified. Stop.\x1b[0m\r\n') else if (args.length === 3 && args.join(' ').toLowerCase() === 'me a sandwich') { - this.emit('write', '\x1b[31mmake: me a sandwich : Permission denied\x1b[0m\r\n') + console.log(this) + if (this.su) { + const b = '\x1b[33m' + const r = '\x1b[0m' + const l = '\x1b[32m' + const c = '\x1b[38;5;229m' + const h = '\x1b[38;5;225m' + this.emit('write', + ` ${b}_.---._\r\n` + + ` _.-~ ~-._\r\n` + + ` _.-~ ~-._\r\n` + + ` _.-~ ~---._\r\n` + + ` _.-~ ~\\\r\n` + + ` .-~ _.;\r\n` + + ` :-._ _.-~ ./\r\n` + + ` \`-._~-._ _..__.-~ _.-~\r\n` + + ` ${c}/ ${b}~-._~-._ / .__..--${c}~-${l}---._\r\n` + + `${c} \\_____(_${b};-._\\. _.-~_/${c} ~)${l}.. . \\\r\n` + + `${l} /(_____ ${b}\\\`--...--~_.-~${c}______..-+${l}_______)\r\n` + + `${l} .(_________/${b}\`--...--~/${l} _/ ${h} ${b}/\\\r\n` + + `${b} /-._${h} \\_ ${l}(___./_..-~${h}__.....${b}__..-~./\r\n` + + `${b} \`-._~-._${h} ~\\--------~ .-~${b}_..__.-~ _.-~\r\n` + + `${b} ~-._~-._ ${h}~---------\` ${b}/ .__..--~\r\n` + + `${b} ~-._\\. _.-~_/\r\n` + + `${b} \\\`--...--~_.-~\r\n` + + `${b} \`--...--~${r}\r\n`) + } else { + this.emit('write', '\x1b[31mmake: me a sandwich : Permission denied\x1b[0m\r\n') + } } else { this.emit('write', `\x1b[31mmake: *** No rule to make target '${args.join(' ').toLowerCase()}'. Stop.\x1b[0m\r\n`) } @@ -852,6 +904,12 @@ let demoshIndex = { } } } +let autocompleteIndex = { + 'local-echo': 'local-echo [--suppress-note]', + theme: 'theme [n]', + cursor: 'cursor [block|line|bar] [--steady]', + sudo: 'sudo [command]' +} class DemoShell { constructor (terminal, printInfo) { @@ -861,6 +919,7 @@ class DemoShell { this.history = [] this.historyIndex = 0 this.cursorPos = 0 + this.lastInputLength = 0 this.child = null this.index = demoshIndex @@ -887,8 +946,29 @@ class DemoShell { this.history[0] = current this.historyIndex = 0 } + getCompleted (visual = false) { + if (this.history[0]) { + let input = this.history[0] + let prefix = '' + if (input.startsWith('sudo ')) { + let newInput = input.replace(/^(sudo\s+)+/, '') + prefix = input.substr(0, input.length - newInput.length) + input = newInput + } + for (let name in this.index) { + if (name.startsWith(input) && name !== input) { + if (visual && name in autocompleteIndex) return prefix + autocompleteIndex[name] + return prefix + name + } + } + return null + } + return null + } handleParsed (action, ...args) { + this.terminal.write(`\x1b[${this.lastInputLength - this.cursorPos}P`) this.terminal.write('\b\x1b[P'.repeat(this.cursorPos)) + if (action === 'write') { this.copyFromHistoryIndex() this.history[0] = this.history[0].substr(0, this.cursorPos) + args[0] + this.history[0].substr(this.cursorPos) @@ -899,7 +979,11 @@ class DemoShell { this.cursorPos-- if (this.cursorPos < 0) this.cursorPos = 0 } else if (action === 'tab') { - console.warn('TAB not implemented') // TODO completion + let completed = this.getCompleted() + if (completed) { + this.history[0] = completed + this.cursorPos = this.history[0].length + } } else if (action === 'move-cursor-x') { this.cursorPos = Math.max(0, Math.min(this.history[this.historyIndex].length, this.cursorPos + args[0])) } else if (action === 'delete-line') { @@ -922,17 +1006,27 @@ class DemoShell { this.terminal.write(this.history[this.historyIndex]) this.terminal.write('\b'.repeat(this.history[this.historyIndex].length)) this.terminal.moveForward(this.cursorPos) - this.terminal.write('') // dummy. Apply the moveFoward + + this.lastInputLength = this.cursorPos + + let completed = this.getCompleted(true) + if (this.historyIndex === 0 && completed) { + // show closest match faintly + let rest = completed.substr(this.history[0].length) + this.terminal.write(`\x1b[2m${rest}\x1b[m${'\b'.repeat(rest.length)}`) + this.lastInputLength += rest.length + } if (action === 'return') { - this.terminal.write('\r\n') + this.terminal.write('\n') this.parse(this.history[this.historyIndex]) } } parse (input) { if (input === 'help') input = 'info' // TODO: basic chaining (i.e. semicolon) - this.run(input) + if (input) this.run(input) + else this.prompt() } run (command) { let parts = ['']