From 5245917c88918e23179bb872eec5b0e344b713d1 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Sat, 30 Sep 2017 14:35:24 +0200 Subject: [PATCH] Update demo for new protocol, add `themes` command --- js/term/demo.js | 272 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 188 insertions(+), 84 deletions(-) diff --git a/js/term/demo.js b/js/term/demo.js index 0ea9e33..39dc9ec 100644 --- a/js/term/demo.js +++ b/js/term/demo.js @@ -36,30 +36,41 @@ class ANSIParser { this.handler('insert-blanks', numOr1) } else if (type === 'q') this.handler('set-cursor-style', numOr1) else if (type === 'm') { - if (!numbers.length || numbers[0] === 0) { + if (!numbers.length) { this.handler('reset-style') return } - let type = numbers[0] - if (type === 1) this.handler('add-attrs', 1) // bold - else if (type === 2) this.handler('add-attrs', 1 << 1) // faint - else if (type === 3) this.handler('add-attrs', 1 << 2) // italic - else if (type === 4) this.handler('add-attrs', 1 << 3) // underline - else if (type === 5 || type === 6) this.handler('add-attrs', 1 << 4) // blink - else if (type === 7) this.handler('add-attrs', -1) // invert - else if (type === 9) this.handler('add-attrs', 1 << 6) // strike - else if (type === 20) this.handler('add-attrs', 1 << 5) // fraktur - else if (type >= 30 && type <= 37) this.handler('set-color-fg', type % 10) - else if (type >= 40 && type <= 47) this.handler('set-color-bg', type % 10) - else if (type === 39) this.handler('reset-color-fg') - else if (type === 49) this.handler('reset-color-bg') - else if (type >= 90 && type <= 98) this.handler('set-color-fg', (type % 10) + 8) - else if (type >= 100 && type <= 108) this.handler('set-color-bg', (type % 10) + 8) - else if (type === 38 || type === 48) { - if (numbers[1] === 5) { - let color = (numbers[2] | 0) & 0xFF - if (type === 38) this.handler('set-color-fg', color) - if (type === 48) this.handler('set-color-bg', color) + let type + while ((type = numbers.shift())) { + if (type === 0) this.handler('reset-style') + else if (type === 1) this.handler('add-attrs', 1 << 2) // bold + else if (type === 2) this.handler('add-attrs', 1 << 9) // faint + else if (type === 3) this.handler('add-attrs', 1 << 6) // italic + else if (type === 4) this.handler('add-attrs', 1 << 3) // underline + else if (type === 5 || type === 6) this.handler('add-attrs', 1 << 5) // blink + else if (type === 7) this.handler('add-attrs', 1 << 4) // invert + else if (type === 9) this.handler('add-attrs', 1 << 7) // strike + else if (type === 20) this.handler('add-attrs', 1 << 10) // fraktur + else if (type >= 30 && type <= 37) this.handler('set-color-fg', type % 10) + else if (type >= 40 && type <= 47) this.handler('set-color-bg', type % 10) + else if (type === 39) this.handler('reset-color-fg') + else if (type === 49) this.handler('reset-color-bg') + else if (type >= 90 && type <= 98) this.handler('set-color-fg', (type % 10) + 8) + else if (type >= 100 && type <= 108) this.handler('set-color-bg', (type % 10) + 8) + else if (type === 38 || type === 48) { + let mode = numbers.shift() + if (mode === 2) { + let r = numbers.shift() + let g = numbers.shift() + let b = numbers.shift() + let color = (r << 16 | g << 8 | b) + 256 + if (type === 38) this.handler('set-color-fg', color) + if (type === 48) this.handler('set-color-bg', color) + } else if (mode === 5) { + let color = (numbers.shift() | 0) & 0xFF + if (type === 38) this.handler('set-color-fg', color) + if (type === 48) this.handler('set-color-bg', color) + } } } } else if (type === 'h' || type === 'l') { @@ -101,7 +112,7 @@ class ANSIParser { if (!this.joinChunks) this.reset() } } -const TERM_DEFAULT_STYLE = 0 +const TERM_DEFAULT_STYLE = [0, 0, 0] const TERM_MIN_DRAW_DELAY = 10 let getRainbowColor = t => { @@ -117,19 +128,20 @@ class ScrollingTerminal { this.height = 25 this.termScreen = screen this.parser = new ANSIParser((...args) => this.handleParsed(...args)) + this.buttonLabels = ['', '', '^C', '', 'Help'] this.reset() this._lastLoad = Date.now() - this.termScreen.load(this.serialize()) + this.loadTimer() window.showPage() } reset () { - this.style = TERM_DEFAULT_STYLE + this.style = TERM_DEFAULT_STYLE.slice() this.cursor = { x: 0, y: 0, style: 1, visible: true } this.trackMouse = false - this.theme = -1 + this.theme = 0 this.rainbow = false this.parser.reset() this.clear() @@ -137,13 +149,13 @@ class ScrollingTerminal { clear () { this.screen = [] for (let i = 0; i < this.width * this.height; i++) { - this.screen.push([' ', this.style]) + this.screen.push([' ', this.style.slice()]) } } scroll () { this.screen.splice(0, this.width) for (let i = 0; i < this.width; i++) { - this.screen.push([' ', TERM_DEFAULT_STYLE]) + this.screen.push([' ', TERM_DEFAULT_STYLE.slice()]) } this.cursor.y-- } @@ -152,7 +164,7 @@ class ScrollingTerminal { if (this.cursor.y >= this.height) this.scroll() } writeChar (character) { - this.screen[this.cursor.y * this.width + this.cursor.x] = [character, this.style] + this.screen[this.cursor.y * this.width + this.cursor.x] = [character, this.style.slice()] this.cursor.x++ if (this.cursor.x >= this.width) { this.cursor.x = 0 @@ -181,12 +193,12 @@ class ScrollingTerminal { } deleteChar () { // FIXME unused? this.moveBack() - this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE]) + this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE.slice()]) this.screen.splice(this.cursor.y * this.width + this.cursor.x, 1) } deleteForward (n) { n = Math.min(this.width, n) - for (let i = 0; i < n; i++) this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE]) + for (let i = 0; i < n; i++) this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE.slice()]) this.screen.splice(this.cursor.y * this.width + this.cursor.x, n) } clampCursor () { @@ -205,11 +217,12 @@ class ScrollingTerminal { } else if (action === 'clear') { this.clear() } else if (action === 'bell') { - this.termScreen.load('B') + this.termScreen.load('U\x01B') } else if (action === 'back') { this.moveBack() } else if (action === 'new-line') { this.newLine() + this.cursor.x = 0 } else if (action === 'return') { this.cursor.x = 0 } else if (action === 'set-cursor') { @@ -231,17 +244,21 @@ class ScrollingTerminal { } else if (action === 'set-cursor-style') { this.cursor.style = Math.max(0, Math.min(6, args[0])) } else if (action === 'reset-style') { - this.style = TERM_DEFAULT_STYLE + this.style = TERM_DEFAULT_STYLE.slice() } else if (action === 'add-attrs') { - this.style |= (args[0] << 16) + this.style[2] |= args[0] } else if (action === 'set-color-fg') { - this.style = (this.style & 0xFFFFFF00) | (1 << 8 << 16) | args[0] + this.style[0] = args[0] + this.style[2] |= 1 } else if (action === 'set-color-bg') { - this.style = (this.style & 0xFFFF00FF) | (1 << 9 << 16) | (args[0] << 8) + this.style[1] = args[0] + this.style[2] |= 1 << 1 } else if (action === 'reset-color-fg') { - this.style = this.style & 0xFFFEFF00 + this.style[0] = 0 + if (this.style[2] & 1) this.style[2] ^= 1 } else if (action === 'reset-color-bg') { - this.style = this.style & 0xFFFD00FF + this.style[1] = 0 + if (this.style[2] & (1 << 1)) this.style[2] ^= (1 << 1) } else if (action === 'hide-cursor') { this.cursor.visible = false } else if (action === 'show-cursor') { @@ -250,65 +267,119 @@ class ScrollingTerminal { } write (text) { this.parser.write(text) - this.scheduleLoad() } - serialize () { - let serialized = 'S' - serialized += String.fromCodePoint(this.height + 1) + String.fromCodePoint(this.width + 1) - serialized += String.fromCodePoint(this.cursor.y + 1) + String.fromCodePoint(this.cursor.x + 1) - + getScreenOpts () { + let data = 'O' + data += String.fromCodePoint(26) + data += String.fromCodePoint(81) + data += String.fromCodePoint(this.theme + 1) + data += String.fromCodePoint(8) + data += String.fromCodePoint(1) + data += String.fromCodePoint(1) + data += String.fromCodePoint(1) let attributes = +this.cursor.visible attributes |= (3 << 5) * +this.trackMouse // track mouse controls both attributes |= 3 << 7 // buttons/links always visible attributes |= (this.cursor.style << 9) - serialized += String.fromCodePoint(attributes + 1) + data += String.fromCodePoint(attributes + 1) + return data + } + getButtons () { + let data = 'B' + data += String.fromCodePoint(6) + data += this.buttonLabels.map(x => x + '\x01').join('') + return data + } + getCursor () { + let data = 'C' + data += String.fromCodePoint(this.cursor.y + 1) + data += String.fromCodePoint(this.cursor.x + 1) + data += String.fromCodePoint(1) + return data + } + encodeColor (color) { + if (color < 256) { + return String.fromCodePoint(color + 1) + } else { + color -= 256 + return String.fromCodePoint(((color & 0xFFF) | 0x10000) + 1) + String.fromCodePoint((color >> 12) + 1) + } + } + serializeScreen () { + let serialized = 'S' + serialized += String.fromCodePoint(1) + String.fromCodePoint(1) + serialized += String.fromCodePoint(this.height + 1) + String.fromCodePoint(this.width + 1) - let lastStyle = null + let lastStyle = [null, null, null] let index = 0 for (let cell of this.screen) { - let style = cell[1] + let style = cell[1].slice() if (this.rainbow) { 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 8 and 9 - style = (style & 0xFFFC0000) | (1 << 8 << 16) | getRainbowColor((x + y) / 10 + Date.now() / 1000) + // 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) + style[1] = 0 + style[2] = style[2] | 1 + if (style[2] & (1 << 1)) style[2] ^= (1 << 1) index++ } - if (style !== lastStyle) { - let foreground = style & 0xFF - let background = (style >> 8) & 0xFF - let attributes = (style >> 16) & 0xFFFF - let setForeground = foreground !== (lastStyle & 0xFF) - let setBackground = background !== ((lastStyle >> 8) & 0xFF) - let setAttributes = attributes !== ((lastStyle >> 16) & 0xFFFF) - - if (setForeground && setBackground) serialized += '\x03' + String.fromCodePoint((style & 0xFFFF) + 1) - else if (setForeground) serialized += '\x05' + String.fromCodePoint(foreground + 1) - else if (setBackground) serialized += '\x06' + String.fromCodePoint(background + 1) - if (setAttributes) serialized += '\x04' + String.fromCodePoint(attributes + 1) - lastStyle = style - } + + let foreground = style[0] + let background = style[1] + let attributes = style[2] + let setForeground = foreground !== lastStyle[0] + let setBackground = background !== lastStyle[1] + let setAttributes = attributes !== lastStyle[2] + + if (setForeground && setBackground) { + if (foreground < 256 && background < 256) { + serialized += '\x03' + String.fromCodePoint(((background << 8) | foreground) + 1) + } else { + serialized += '\x05' + this.encodeColor(foreground) + serialized += '\x06' + this.encodeColor(background) + } + } else if (setForeground) serialized += '\x05' + this.encodeColor(foreground) + else if (setBackground) serialized += '\x06' + this.encodeColor(background) + if (setAttributes) serialized += '\x04' + String.fromCodePoint(attributes + 1) + lastStyle = style + serialized += cell[0] } return serialized } - scheduleLoad () { - clearTimeout(this._scheduledLoad) - if (this._lastLoad < Date.now() - TERM_MIN_DRAW_DELAY) { - this.termScreen.load(this.serialize(), { theme: this.theme }) - this.theme = -1 // prevent useless theme setting next time - } else { - this._scheduledLoad = setTimeout(() => { - this.termScreen.load(this.serialize()) - }, TERM_MIN_DRAW_DELAY - this._lastLoad) - } - } - rainbowTimer () { - if (!this.rainbow) return - clearInterval(this._rainbowTimer) - this._rainbowTimer = setInterval(() => { - if (this.rainbow) this.scheduleLoad() - }, 50) + getUpdate () { + let topics = 0 + let topicData = [] + let screenOpts = this.getScreenOpts() + let buttons = this.getButtons() + let cursor = this.getCursor() + let screen = this.serializeScreen() + if (this._screenOpts !== screenOpts) { + this._screenOpts = screenOpts + topicData.push(screenOpts) + } + if (this._buttons !== buttons) { + this._buttons = buttons + topicData.push(buttons) + } + if (this._cursor !== cursor) { + this._cursor = cursor + topicData.push(cursor) + } + if (this._screen !== screen) { + this._screen = screen + topicData.push(screen) + } + if (!topicData.length) return '' + return 'U' + String.fromCodePoint(topics + 1) + topicData.join('') + } + loadTimer () { + clearInterval(this._loadTimer) + this._loadTimer = setInterval(() => { + let update = this.getUpdate() + if (update) this.termScreen.load(update) + }, 30) } } @@ -329,7 +400,7 @@ let demoData = { buttons: { 1: '', 2: '', - 3: '', + 3: (terminal, shell) => shell.write('\x03'), 4: '', 5: function (terminal, shell) { if (shell.child) shell.child.destroy() @@ -466,7 +537,7 @@ let demoshIndex = { if (++x < 69) { if (++cycles >= 3) { - setTimeout(loop, 20) + setTimeout(loop, 50) cycles = 0 } else loop() } else { @@ -557,7 +628,7 @@ let demoshIndex = { let theme = +args[0] | 0 const maxnum = themes.length if (!args.length || !Number.isFinite(theme) || theme < 0 || theme >= maxnum) { - this.emit('write', `\x1b[31mUsage: theme [0–${maxnum - 1}]\r\n`) + this.emit('write', `\x1b[31mUsage: theme [0–${maxnum - 1}]\n`) this.destroy() return } @@ -568,6 +639,40 @@ let demoshIndex = { this.destroy() } }, + themes: class ShowThemes extends Process { + color (hex) { + hex = parseInt(hex.substr(1), 16) + let r = hex >> 16 + let g = (hex >> 8) & 0xFF + let b = hex & 0xFF + this.emit('write', `\x1b[48;2;${r};${g};${b}m`) + if (((r + g + b) / 3) > 127) { + this.emit('write', '\x1b[38;5;16m') + } else { + this.emit('write', '\x1b[38;5;255m') + } + } + run (...args) { + for (let i in themes) { + let theme = themes[i] + + let name = ` ${i}`.substr(-2) + + this.emit('write', `Theme ${name}: `) + + for (let col = 0; col < 16; col++) { + let text = ` ${col}`.substr(-2) + this.color(theme[col]) + this.emit('write', text) + this.emit('write', '\x1b[m ') + } + + this.emit('write', '\n') + } + + this.destroy() + } + }, cursor: class SetCursor extends Process { run (...args) { let steady = args.includes('--steady') @@ -590,7 +695,6 @@ let demoshIndex = { } run () { this.shell.terminal.rainbow = !this.shell.terminal.rainbow - this.shell.terminal.rainbowTimer() this.emit('write', '') this.destroy() } @@ -618,7 +722,7 @@ let demoshIndex = { } render () { this.emit('write', '\x1b[m\x1b[2J\x1b[1;1H') - this.emit('write', '\x1b[97m\x1b[1mMouse Demo\r\n\x1b[mMouse movement, clicking and scrolling!') + this.emit('write', '\x1b[97m\x1b[1mMouse Demo\r\n\x1b[mMouse movement, clicking, and scrolling!') // render random data for scrolling for (let y = 0; y < 23; y++) {