From da9a37e12512bbf1b37dfb12de135448ba995cd3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Sep 2018 14:05:33 -0400 Subject: [PATCH] use blessed for dev mode terminal output --- package-lock.json | 22 ++++++--- package.json | 2 + src/cli/dev.ts | 111 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 105 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55ca862..3e38241 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "sapper", - "version": "0.19.0", + "version": "0.19.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,6 +10,15 @@ "integrity": "sha1-MU+BaPUK5IoDLP2tX9tDb0ZKl6w=", "dev": true }, + "@types/blessed": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@types/blessed/-/blessed-0.1.10.tgz", + "integrity": "sha512-lCpkGnCq2lj9RBPwh/RH/ZJegYV6JdyyRHmURIW1DwMdtNhRRxYeHllqaMu8K6bDf6zhO7PpHsmEqlYMDPlmhw==", + "requires": { + "@types/events": "*", + "@types/node": "*" + } + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -19,8 +28,7 @@ "@types/events": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", - "dev": true + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" }, "@types/glob": { "version": "5.0.35", @@ -57,8 +65,7 @@ "@types/node": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.4.tgz", - "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==", - "dev": true + "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==" }, "@types/rimraf": { "version": "2.0.2", @@ -1010,6 +1017,11 @@ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, + "blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk=" + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", diff --git a/package.json b/package.json index 0337bef..4724be8 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "test": "test" }, "dependencies": { + "@types/blessed": "^0.1.10", + "blessed": "^0.1.81", "html-minifier": "^3.5.16", "shimport": "^0.0.10", "source-map-support": "^0.5.6", diff --git a/src/cli/dev.ts b/src/cli/dev.ts index ecaa4a3..2e990fb 100644 --- a/src/cli/dev.ts +++ b/src/cli/dev.ts @@ -1,11 +1,78 @@ import * as path from 'path'; import colors from 'kleur'; import * as child_process from 'child_process'; +import * as blessed from 'blessed'; import prettyMs from 'pretty-ms'; import { dev as _dev } from '../api/dev'; import * as events from '../api/interfaces'; export function dev(opts: { port: number, open: boolean, bundler?: string }) { + const screen = blessed.screen({ + smartCSR: true + }); + + const status_box = blessed.box({ + width: '100%', + height: '50%', + scrollable: true + }); + + let mouse_is_down = false; + let dragging = false; + + screen.on('mousedown', data => { + if (mouse_is_down) { + if (dragging) { + divider.top = data.y; + status_box.height = data.y; + log_box.height = screen.height - (data.y + 1); + screen.render(); + } + } else { + if (data.y === divider.top) { + dragging = true; + } + + mouse_is_down = true; + } + }); + + screen.on('mouseup', data => { + mouse_is_down = false; + }); + + const log_box = blessed.box({ + bottom: '0', + width: '100%', + height: '50%', + scrollable: true + }); + + const divider = blessed.line({ + top: '50%', + orientation: 'horizontal' + }); + + screen.append(status_box); + screen.append(log_box); + screen.append(divider); + screen.render(); + + screen.key(['escape', 'q', 'C-c'], function(ch, key) { + return process.exit(0); + }); + + const append_log = data => { + log_box.setContent(log_box.getContent() + data); + screen.render(); + }; + + const append_status = line => { + const lines = status_box.getLines(); + status_box.insertLine(lines.length, line); + screen.render(); + }; + try { const watcher = _dev(opts); @@ -13,68 +80,62 @@ export function dev(opts: { port: number, open: boolean, bundler?: string }) { watcher.on('ready', (event: events.ReadyEvent) => { if (first) { - console.log(colors.bold.cyan(`> Listening on http://localhost:${event.port}`)); + append_status(colors.bold.cyan(`> Listening on http://localhost:${event.port}`)); if (opts.open) child_process.exec(`open http://localhost:${event.port}`); first = false; } - // TODO clear screen? - - event.process.stdout.on('data', data => { - process.stdout.write(data); - }); - - event.process.stderr.on('data', data => { - process.stderr.write(data); - }); + event.process.stdout.on('data', append_log); + event.process.stderr.on('data', append_log); }); watcher.on('invalid', (event: events.InvalidEvent) => { const changed = event.changed.map(filename => path.relative(process.cwd(), filename)).join(', '); - console.log(`\n${colors.bold.cyan(changed)} changed. rebuilding...`); + status_box.setContent(''); + append_status(`\n${colors.bold.cyan(changed)} changed. rebuilding...`); }); watcher.on('error', (event: events.ErrorEvent) => { - console.log(colors.red(`✗ ${event.type}`)); - console.log(colors.red(event.message)); + append_status(colors.red(`✗ ${event.type}`)); + append_status(colors.red(event.message)); }); watcher.on('fatal', (event: events.FatalEvent) => { - console.log(colors.bold.red(`> ${event.message}`)); - if (event.log) console.log(event.log); + append_status(colors.bold.red(`> ${event.message}`)); + if (event.log) append_status(event.log); }); watcher.on('build', (event: events.BuildEvent) => { if (event.errors.length) { - console.log(colors.bold.red(`✗ ${event.type}`)); + append_status(colors.bold.red(`✗ ${event.type}`)); event.errors.filter(e => !e.duplicate).forEach(error => { - if (error.file) console.log(colors.bold(error.file)); - console.log(error.message); + if (error.file) append_status(colors.bold(error.file)); + append_status(error.message); }); const hidden = event.errors.filter(e => e.duplicate).length; if (hidden > 0) { - console.log(`${hidden} duplicate ${hidden === 1 ? 'error' : 'errors'} hidden\n`); + append_status(`${hidden} duplicate ${hidden === 1 ? 'error' : 'errors'} hidden\n`); } } else if (event.warnings.length) { - console.log(colors.bold.yellow(`• ${event.type}`)); + append_status(colors.bold.yellow(`• ${event.type}`)); event.warnings.filter(e => !e.duplicate).forEach(warning => { - if (warning.file) console.log(colors.bold(warning.file)); - console.log(warning.message); + if (warning.file) append_status(colors.bold(warning.file)); + append_status(warning.message); }); const hidden = event.warnings.filter(e => e.duplicate).length; if (hidden > 0) { - console.log(`${hidden} duplicate ${hidden === 1 ? 'warning' : 'warnings'} hidden\n`); + append_status(`${hidden} duplicate ${hidden === 1 ? 'warning' : 'warnings'} hidden\n`); } } else { - console.log(`${colors.bold.green(`✔ ${event.type}`)} ${colors.gray(`(${prettyMs(event.duration)})`)}`); + append_status(`${colors.bold.green(`✔ ${event.type}`)} ${colors.gray(`(${prettyMs(event.duration)})`)}`); } }); } catch (err) { - console.log(colors.bold.red(`> ${err.message}`)); + append_status(colors.bold.red(`> ${err.message}`)); process.exit(1); } } \ No newline at end of file