From 2de22f93568ba143898313625923dd2177b9eab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 18 Aug 2019 17:36:06 +0200 Subject: [PATCH] extract @vt entries from docs --- bin/extract_vtfeatures.js | 181 ++++++++++++++++++++++++++++++++++++++ package.json | 4 +- src/InputHandler.ts | 14 +++ yarn.lock | 5 ++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 bin/extract_vtfeatures.js diff --git a/bin/extract_vtfeatures.js b/bin/extract_vtfeatures.js new file mode 100644 index 000000000..60f3200e5 --- /dev/null +++ b/bin/extract_vtfeatures.js @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + * + * Script to extract vt features documented in docstrings. + */ +const fs = require('fs'); +const Mustache = require('mustache'); + +/** + * regexp to fetch all comments + * Fetches all multiline comments and single lines containing '// @vt:'. + */ +const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mug; + +// expected - "@vt: type name "sequence" "short description" +/** + * regexp to parse the @vt line + * expected data - "@vt: <"name"> "" "" + */ +const REX_VT_LINE = /^[@]vt\:\s*(\w+)\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/; + +// known vt command types +const TYPES = [ + 'C0', + 'C1', + 'ESC', + 'CSI', + 'DCS', + 'OSC', + 'APC', + 'PM', + 'SOS' +]; + +const MARKDOWN_TMPL = ` +# Supported VT features by xterm.js +Version: {{version}} +### C0 + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#C0}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/C0}} + + +### C1 + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#C1}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/C1}} + + +### CSI + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#CSI}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/CSI}} + + +### DCS + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#DCS}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/DCS}} + + +### ESC + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#ESC}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/ESC}} + + +### OSC + +| Mnemonic | Name | Sequence | Short Description | Status | +| -------- | ---- | -------- | ----------------- | ------ | +{{#OSC}} +| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{shortDescription}} | {{status}} | +{{/OSC}} +` + +function parseMultiLine(filename, s) { + if (!~s.indexOf('@vt:')) { + return; + } + const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, '')); + let grabLine = false; + const longDescription = []; + let feature; + for (const line of lines) { + if (grabLine) { + if (!line) { + break; + } + longDescription.push(line); + } + if (~line.indexOf('@vt:')) { + feature = parseSingleLine(filename, line); + grabLine = true; + } + } + if (feature) { + feature.longDescription = longDescription; + return feature; + } +} + +function parseSingleLine(filename, s) { + const line = s.trim(); + const match = line.match(REX_VT_LINE); + if (match !== null) { + if (!~TYPES.indexOf(match[2])) { + throw new Error(`unkown vt-command type "${match[2]}" specified in "${filename}"`); + } + return { + status: match[1], + type: match[2], + mnemonic: match[3], + name: match[4], + sequence: match[5], + shortDescription: match[6], + longDescription: [], + source: filename + }; + } +} + +function postProcessData(features) { + const featureTable = {}; + for (const feature of features) { + if (featureTable[feature.type] === undefined) { + featureTable[feature.type] = []; + } + featureTable[feature.type].push(feature); + } + for (const entry in featureTable) { + featureTable[entry].sort((a, b) => a.sequence.slice(-1) > b.sequence.slice(-1)); + } + // console.error(featureTable); + featureTable.version = require('../package.json').version; + console.log(Mustache.render(MARKDOWN_TMPL, featureTable)); +} + +function main(filenames) { + // console.error(filenames); + let leftToProcess = filenames.length; + const features = []; + for (const filename of filenames) { + fs.readFile(filename, 'utf-8', (err, data) => { + let match; + while ((match = REX_COMMENTS.exec(data)) !== null) { + if (match.index === REX_COMMENTS.lastIndex) { + REX_COMMENTS.lastIndex++; + } + const feature = match[1] + ? parseMultiLine(filename, match[1]) + : parseSingleLine(filename, match[2]); + if (feature) { + features.push(feature); + } + } + leftToProcess--; + if (!leftToProcess) { + postProcessData(features); + } + }); + } +} + +main(process.argv.slice(2)) diff --git a/package.json b/package.json index 68467a89c..5c5bd7890 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json", "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js", "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js", - "clean": "rm -rf lib out addons/*/lib addons/*/out" + "clean": "rm -rf lib out addons/*/lib addons/*/out", + "vtfeatures": "node bin/extract_vt.js src/**/*.ts src/*.ts" }, "devDependencies": { "@types/chai": "^3.4.34", @@ -41,6 +42,7 @@ "glob": "^7.0.5", "jsdom": "^11.11.0", "mocha": "^6.1.4", + "mustache": "^3.0.1", "node-pty": "0.7.6", "puppeteer": "^1.15.0", "source-map-loader": "^0.2.4", diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 900677728..9b5505855 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -177,13 +177,18 @@ export class InputHandler extends Disposable implements IInputHandler { * CSI handler */ this._parser.setCsiHandler({final: '@'}, params => this.insertChars(params)); + // @vt: supported CSI CUU "Cursor Up" "CSI Ps A" "Move the cursor position `Ps` times to the top (default=1)." this._parser.setCsiHandler({final: 'A'}, params => this.cursorUp(params)); + // @vt: supported CSI CUD "Cursor Down" "CSI Ps B" "Move the cursor position `Ps` times to the bottom (default=1)." this._parser.setCsiHandler({final: 'B'}, params => this.cursorDown(params)); + // @vt: supported CSI CUF "Cursor Forward" "CSI Ps C" "Move the cursor position `Ps` times to the right (default=1)." this._parser.setCsiHandler({final: 'C'}, params => this.cursorForward(params)); + // @vt: supported CSI CUB "Cursor Backward" "CSI Ps D" "Move the cursor position `Ps` times to the left (default=1)." this._parser.setCsiHandler({final: 'D'}, params => this.cursorBackward(params)); this._parser.setCsiHandler({final: 'E'}, params => this.cursorNextLine(params)); this._parser.setCsiHandler({final: 'F'}, params => this.cursorPrecedingLine(params)); this._parser.setCsiHandler({final: 'G'}, params => this.cursorCharAbsolute(params)); + // @vt: supported CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set the cursor to position [`Ps`, `Ps`]." this._parser.setCsiHandler({final: 'H'}, params => this.cursorPosition(params)); this._parser.setCsiHandler({final: 'I'}, params => this.cursorForwardTab(params)); this._parser.setCsiHandler({final: 'J'}, params => this.eraseInDisplay(params)); @@ -210,6 +215,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setCsiHandler({prefix: '?', final: 'h'}, params => this.setModePrivate(params)); this._parser.setCsiHandler({final: 'l'}, params => this.resetMode(params)); this._parser.setCsiHandler({prefix: '?', final: 'l'}, params => this.resetModePrivate(params)); + // @vt: partly CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set various text attributes." this._parser.setCsiHandler({final: 'm'}, params => this.charAttributes(params)); this._parser.setCsiHandler({final: 'n'}, params => this.deviceStatus(params)); this._parser.setCsiHandler({prefix: '?', final: 'n'}, params => this.deviceStatusPrivate(params)); @@ -222,11 +228,15 @@ export class InputHandler extends Disposable implements IInputHandler { /** * execute handler */ + // @vt: supported C0 BEL "Bell" "\x07" "Rings the bell." this._parser.setExecuteHandler(C0.BEL, () => this.bell()); + // @vt: supported C0 LF "Line Feed" "\n" "Moves the cursor one row down, scrolling if needed." this._parser.setExecuteHandler(C0.LF, () => this.lineFeed()); this._parser.setExecuteHandler(C0.VT, () => this.lineFeed()); this._parser.setExecuteHandler(C0.FF, () => this.lineFeed()); + // @vt: supported C0 CR "Carriage Return" "\r" "Moves the cursor to the beginning of the row." this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn()); + // @vt: supported C0 BS "Backspace" "\x08" "Moves the cursor one position to the left." this._parser.setExecuteHandler(C0.BS, () => this.backspace()); this._parser.setExecuteHandler(C0.HT, () => this.tab()); this._parser.setExecuteHandler(C0.SO, () => this.shiftOut()); @@ -241,9 +251,12 @@ export class InputHandler extends Disposable implements IInputHandler { * OSC handler */ // 0 - icon name + title + // @vt: partly OSC OSC0 "" "OSC 0 ; Pt BEL" "Set window title and icon name." this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data))); // 1 - icon name + // @vt: unsupported OSC OSC1 "" "OSC 1 ; Pt BEL" "Set icon name." // 2 - title + // @vt: supported OSC OSC2 "" "OSC 2 ; Pt BEL" "Set window title." this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data))); // 3 - set property X in the form "prop=value" // 4 - Change Color Number @@ -306,6 +319,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._parser.setEscHandler({intermediates: '.', final: flag}, () => this.selectCharset('.' + flag)); this._parser.setEscHandler({intermediates: '/', final: flag}, () => this.selectCharset('/' + flag)); // TODO: supported? } + // @vt: supported ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." this._parser.setEscHandler({intermediates: '#', final: '8'}, () => this.screenAlignmentPattern()); /** diff --git a/yarn.lock b/yarn.lock index a67ede71d..42ecc4cb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3133,6 +3133,11 @@ ms@2.1.1, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +mustache@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-3.0.1.tgz#873855f23aa8a95b150fb96d9836edbc5a1d248a" + integrity sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"