diff --git a/.eslintrc b/.eslintrc index c57f4976..95838e8a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,8 @@ "NodeJS": true, "Element": true, "localStorage": true, - "location": true + "location": true, + "KeyboardEvent": true }, "settings": { "import/resolver": { diff --git a/CHANGELOG.md b/CHANGELOG.md index 4332f59b..43254798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -275,4 +275,37 @@ All notable changes to this project will be documented in this file. ## [1.3.6] - 10-09-2022 -- fix share feature in web-ui \ No newline at end of file +- fix share feature in web-ui + +## [1.3.7] - 29-09-2022 + +- minor fix for file.get_content, return empty string instead of undefined, thanks for reporting [@TopRoupi](https://github.com/TopRoupi) +- add sort key logic in sort intrinsic, thanks for reporting [@TopRoupi](https://github.com/TopRoupi) +- add missing tan intrinsics +- add same errors in basic intrinsics as in ms +- implement format_columns logic +- improve output handler logic +- user_input supports anyKey now +- add proper router intrinsics +- rework shell intriniscs for connect_service + scp +- add shell intriniscs for launch + build + ping +- add ftpShell intrinsics for put +- add computer intrinsics for connect eth + connect wifi +- update computer intrinsics for touch + create_folder +- update file intrinsics for move + copy + chmod + set_content + get_content + set_owner +- add groups to mock env +- update crypo intrinsics +- update metaxploit intrinsics +- update metalib intrinsics +- update netsession intrinsics +- more realistic usernames, passwords, vulnerability zone names +- loading bars are supported now +- deactivate breakpoint for injection during debugging in cli execution +- keep pending state after injection in interpreter +- update meta version with a few corrections +- update parser with removed ";" checks +- support nested import_code +- support outer imports using ".." +- fix [List can be different even if the same](https://github.com/ayecue/greybel-js/issues/32), thanks for reporting [@brahermoon](https://github.com/brahermoon) +- add __isa logic for maps +- minor TextMesh Pro support for output \ No newline at end of file diff --git a/README.md b/README.md index 70bd47ce..76a1700d 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ Interpreter CLI Example: greybel-execute Options: - -p, --params Execution parameters + -p, --params Execution parameters + -i, --interactive Interactive parameters ``` For Windows you can use something like [gitbash](https://gitforwindows.org/). Or just use the UI. @@ -136,12 +137,6 @@ Not yet supported: - `Wallet` - only pollyfill which "returns not yet supported" - `SubWallet` - only pollyfill which "returns not yet supported" - `Coin` - only pollyfill which "returns not yet supported" -- `Service` - only pollyfill which "returns not yet supported" -- `Metaxploit.aireplay` maxAcks are not yet supported -- `File.set_group` -- `Shell.build` -- `Shell.launch` -- `FtpShell.put` ## Debugger Pauses execution and enables you to inspect/debug your code. @@ -154,6 +149,39 @@ debugger ![Debugger UI](/assets/debugger-ui-preview.png?raw=true "Debugger UI") +## TextMesh Pro Rich Text support +[TextMesh Pro Rich Text](http://digitalnativestudios.com/textmeshpro/docs/rich-text/) is partially supported. + +### CLI +Supports: +* color +* mark +* underline +* italic +* bold +* strikethrough +* lowercase +* uppercase + +### UI +Supports: +* color +* mark +* underline +* italic +* bold +* strikethrough +* lowercase +* uppercase +* align +* cspace +* lineheight +* margin +* nobr +* pos +* size +* voffset + # REPL ``` Emulator CLI @@ -245,6 +273,9 @@ This going to be very useful if you want to use the new feature but still want y Together with the `--installer` flag in the CLI it will bundle your files for you which makes it easier to copy/paste code from your file system into the game. +### Nested import_code +Nested `import_code` is supported now as well. Each nested `import_code` will be moved to the entry file when transpiling/building. + ## Import Import will use the relative path from the file it imports to. ``` diff --git a/bin/compile b/bin/compile index ad64ae05..c53f9034 100755 --- a/bin/compile +++ b/bin/compile @@ -10,8 +10,6 @@ if (!semver.satisfies(process.version, engineVersion)) { } const build = require('../out/build').default; -const path = require('path'); -const fs = require('fs'); const program = require('commander').program; const version = require('../package.json').version; let options = {}; diff --git a/bin/execute b/bin/execute index f0158d98..64476aeb 100755 --- a/bin/execute +++ b/bin/execute @@ -1,4 +1,5 @@ #! /usr/bin/env node +const inquirer = require('inquirer'); const semver = require('semver'); const package = require('../package.json'); @@ -23,13 +24,30 @@ program .action(function (filepath, output) { options.filepath = filepath; }) - .option('-params, --params ', 'Execution parameters'); + .option('-params, --params ', 'Execution parameters') + .option('-i, --interactive', 'Interactive parameters'); program.parse(process.argv); (async function() { options = Object.assign(options, program.opts()); + if (options.interactive) { + options.params = await inquirer + .prompt({ + name: 'default', + message: 'Params:', + type: 'input', + loop: false + }) + .then((inputMap) => { + return inputMap.default.split(' '); + }) + .catch((err) => { + throw err; + }); + } + const success = await execute(options.filepath, { params: options.params }); diff --git a/package-lock.json b/package-lock.json index 434beb91..a55ed0da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,25 @@ { "name": "greybel-js", - "version": "1.3.6", + "version": "1.3.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "greybel-js", - "version": "1.3.6", + "version": "1.3.7", "dependencies": { "@babel/runtime": "^7.16.7", + "ansis": "^1.5.5", + "cli-progress": "^3.11.2", "commander": "^7.2.0", - "greybel-core": "^0.4.7", - "greybel-gh-mock-intrinsics": "^1.1.8", - "greybel-interpreter": "^1.2.0", - "greybel-intrinsics": "^1.1.8", - "greybel-transpiler": "^0.6.0", - "greyscript-core": "^0.4.1", - "greyscript-meta": "^1.1.5", + "css-color-names": "^1.0.1", + "greybel-core": "^0.4.9", + "greybel-gh-mock-intrinsics": "^1.3.2", + "greybel-interpreter": "^1.4.0", + "greybel-intrinsics": "^1.3.7", + "greybel-transpiler": "^0.6.3", + "greyscript-core": "^0.4.4", + "greyscript-meta": "^1.1.8", "inquirer": "^8.1.0", "inquirer-command-prompt": "^0.1.0", "json-formatter-js": "^2.3.4", @@ -24,6 +27,7 @@ "mkdirp": "^1.0.4", "open": "^8.2.1", "semver": "^5.3.0", + "text-mesh-transformer": "^1.0.2", "uuid": "^8.3.2" }, "bin": { @@ -42,6 +46,7 @@ "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.1.3", + "@types/cli-progress": "^3.11.0", "@types/inquirer": "^8.1.3", "@types/mkdirp": "^1.0.2", "@types/node": "^18.7.9", @@ -2157,6 +2162,15 @@ "node": ">= 6" } }, + "node_modules/@types/cli-progress": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.0.tgz", + "integrity": "sha512-XhXhBv1R/q2ahF3BM7qT5HLzJNlIL0wbcGyZVjqOTqAybAnsLisd7gy1UCyIqpL+5Iv6XhlSyzjLCnI2sIdbCg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -2566,15 +2580,16 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, + "node_modules/ansis": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-1.5.5.tgz", + "integrity": "sha512-DNctovTacxs/NfZpGo6bIGWgLd2oZsDO7RJbiYX6Ttj40LPZM1XKv9WtesH13ieOEm1GajjD+Vik2n9YnSTPdA==", "engines": { - "node": ">=4" + "node": ">=12.13" + }, + "funding": { + "type": "patreon", + "url": "https://patreon.com/biodiscus" } }, "node_modules/argparse": { @@ -2958,6 +2973,17 @@ "node": ">=4" } }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -2974,6 +3000,17 @@ "node": ">=8" } }, + "node_modules/cli-progress": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.2.tgz", + "integrity": "sha512-lCPoS6ncgX4+rJu5bS3F/iCz17kZ9MPZ6dpuTtI0KXKABkhyXIdYB3Inby1OpaGti3YlI3EeEkM9AuWpelJrVA==", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cli-spinners": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", @@ -3086,6 +3123,14 @@ "node": ">= 8" } }, + "node_modules/css-color-names": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", + "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==", + "engines": { + "node": "*" + } + }, "node_modules/csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", @@ -4395,64 +4440,74 @@ "dev": true }, "node_modules/greybel-core": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/greybel-core/-/greybel-core-0.4.7.tgz", - "integrity": "sha512-LR5xirYRtWA7s+pFbFcX+2fcjexHBaAng2ADuxqu2JlN9hEAhyBqU0Ang25GXOSc0p81MZQBgu2YgXoLKIFVaA==", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/greybel-core/-/greybel-core-0.4.9.tgz", + "integrity": "sha512-u3mKjmTSIO7O62KVs9alH0Cs2kZnVfXoqXWklx5m/3dw1LlEnSu28355dtixyozd48KG4T/h2cwSkmOthvi4LA==", "dependencies": { - "greyscript-core": "^0.4.1" + "greyscript-core": "^0.4.4" } }, "node_modules/greybel-gh-mock-intrinsics": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/greybel-gh-mock-intrinsics/-/greybel-gh-mock-intrinsics-1.1.8.tgz", - "integrity": "sha512-hZkBgn4Nl469wICfWqxnwXWoYkefvws4oI3pTUiz8wc0cabtP24hgiGWMjN9OefpjqhaEzYVWexGfjL5+sescQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/greybel-gh-mock-intrinsics/-/greybel-gh-mock-intrinsics-1.3.2.tgz", + "integrity": "sha512-mRXukmashl0pAtNptPBILWEk02A/iKqg7Z6ZGZmtN9Cb4DVZFpHTARKmDBjLwce6mP2rsIFPz5xhBY3j+TkBRQ==", "dependencies": { - "@types/random-seed": "^0.3.3", "blueimp-md5": "^2.19.0", - "greybel-interpreter": "^1.2.0", - "random-seed": "^0.3.0", - "random-username-generator": "^1.0.4" + "greybel-interpreter": "^1.4.0", + "greybel-mock-environment": "^1.2.8" } }, "node_modules/greybel-interpreter": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/greybel-interpreter/-/greybel-interpreter-1.2.0.tgz", - "integrity": "sha512-Thnu8CB/zXBNZW+S6aOHqOuXPm6w6t2XhG5HFv9WXi+TQVR6VU+bup3bMZ37LCKFF/6bbKt1ZX4BQXxohHflKA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/greybel-interpreter/-/greybel-interpreter-1.4.0.tgz", + "integrity": "sha512-0FIA8gmAOCvYI/HamCST98/QkrVNxDxFujnIk7gQ7aWArXLGKEUaGisXW3KmzVToZVeP7cMPG0P5rv4NARpJBg==", "dependencies": { - "greybel-core": "^0.4.7", - "greyscript-core": "^0.4.1" + "greybel-core": "^0.4.9", + "greyscript-core": "^0.4.4" } }, "node_modules/greybel-intrinsics": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/greybel-intrinsics/-/greybel-intrinsics-1.1.8.tgz", - "integrity": "sha512-nnABGlDPfSmk7xMTlgn3Ok9Ox6D3jVULG1yA2kIjBD8jSIdHdsc/AQ1vkUSQwkHdDsPMt7n1ovmM8gLi/Ka5fw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/greybel-intrinsics/-/greybel-intrinsics-1.3.7.tgz", + "integrity": "sha512-Xrvh+VjBNYScWpINnKx5v5wlCwsw1RsfFE8EZunCbgYeAV0d3rZSYtrx+z1L6dxLNYoPsYjV+YbL6qlCDq1ueQ==", + "dependencies": { + "greybel-interpreter": "^1.4.0" + } + }, + "node_modules/greybel-mock-environment": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/greybel-mock-environment/-/greybel-mock-environment-1.2.8.tgz", + "integrity": "sha512-Qvc56hmBVB24l5miNHWqoS9lN+og52vJIHvKBQUhJM+eUgHrjwJ3XWJ3+hx2NdXkcTz81yfltxl2C4cCWm95lQ==", "dependencies": { - "greybel-interpreter": "^1.2.0" + "@types/random-seed": "^0.3.3", + "blueimp-md5": "^2.19.0", + "greyscript-core": "^0.4.4", + "random-seed": "^0.3.0", + "random-username-generator": "^1.0.4" } }, "node_modules/greybel-transpiler": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/greybel-transpiler/-/greybel-transpiler-0.6.0.tgz", - "integrity": "sha512-c9Rzzw28e8uI8nMdL3scWwYUSGewkZyzzekuF4u80oVN7HwJEttNcZxEMjxRltNI4Yrq9vVhNvyAzIx3HHH2iA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/greybel-transpiler/-/greybel-transpiler-0.6.3.tgz", + "integrity": "sha512-nxanzCOAGR5nxDNDXbJGDACG47R8rr8cjhSj4KCju0+YhHA9NosiS/67a7ouv7rO2YkjdJfHRldl96S9/mx0EA==", "dependencies": { "blueimp-md5": "^2.19.0", - "greybel-core": "^0.4.7", - "greyscript-core": "^0.4.1" + "greybel-core": "^0.4.9", + "greyscript-core": "^0.4.4" } }, "node_modules/greyscript-core": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/greyscript-core/-/greyscript-core-0.4.1.tgz", - "integrity": "sha512-eF0GChvsxLlk8wdWON46IypJVPBif/oQyP7LES9giQQm/BF1De+ey9r9M+xrEvzxgzw8fzPXkUUl69ykBjgSZw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/greyscript-core/-/greyscript-core-0.4.4.tgz", + "integrity": "sha512-vlCLKTuYo6+qfJfMxcmj/sw2cw+bDxcDQp1Av5IYV8v4ZjSz4tUfYXCbZIwdpn3PPpy6kqkBdLso6t/66iqZ7w==", "dependencies": { - "greyscript-meta": "^1.1.5" + "greyscript-meta": "^1.1.8" } }, "node_modules/greyscript-meta": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/greyscript-meta/-/greyscript-meta-1.1.5.tgz", - "integrity": "sha512-3wBBlnP+gWwAg9ogyj0iXAk5RZXl6gIwE1XHdCrLYYB9+HVXBZ8AA8fP9QFLVIL5OFk0s0AIg8wqCXvu2H5HdQ==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/greyscript-meta/-/greyscript-meta-1.1.8.tgz", + "integrity": "sha512-ltVcWNGR4WdkZ8A02GqTPEC1hwpZvxK40VdfD9cbpSvTJ4kiA/8og8gofIDArVMd3NccrnG+BH3YtRQT2So/sQ==", "dependencies": { "react-string-replace": "^1.1.0" }, @@ -6593,6 +6648,11 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-mesh-transformer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-mesh-transformer/-/text-mesh-transformer-1.0.2.tgz", + "integrity": "sha512-gQEWhyLu2hSqklUzFF6c+VYJRx36+mCK7r6vbPVO9o7UUHlzpn0w0ebMFdHUaZwfP0b5KheS/KotBNj3SFW0Ug==" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8480,6 +8540,15 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@types/cli-progress": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.0.tgz", + "integrity": "sha512-XhXhBv1R/q2ahF3BM7qT5HLzJNlIL0wbcGyZVjqOTqAybAnsLisd7gy1UCyIqpL+5Iv6XhlSyzjLCnI2sIdbCg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -8765,13 +8834,10 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } + "ansis": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-1.5.5.tgz", + "integrity": "sha512-DNctovTacxs/NfZpGo6bIGWgLd2oZsDO7RJbiYX6Ttj40LPZM1XKv9WtesH13ieOEm1GajjD+Vik2n9YnSTPdA==" }, "argparse": { "version": "2.0.1", @@ -9036,6 +9102,16 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + } } }, "chardet": { @@ -9051,6 +9127,14 @@ "restore-cursor": "^3.1.0" } }, + "cli-progress": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.2.tgz", + "integrity": "sha512-lCPoS6ncgX4+rJu5bS3F/iCz17kZ9MPZ6dpuTtI0KXKABkhyXIdYB3Inby1OpaGti3YlI3EeEkM9AuWpelJrVA==", + "requires": { + "string-width": "^4.2.3" + } + }, "cli-spinners": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", @@ -9140,6 +9224,11 @@ "which": "^2.0.1" } }, + "css-color-names": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", + "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==" + }, "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", @@ -10117,64 +10206,74 @@ "dev": true }, "greybel-core": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/greybel-core/-/greybel-core-0.4.7.tgz", - "integrity": "sha512-LR5xirYRtWA7s+pFbFcX+2fcjexHBaAng2ADuxqu2JlN9hEAhyBqU0Ang25GXOSc0p81MZQBgu2YgXoLKIFVaA==", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/greybel-core/-/greybel-core-0.4.9.tgz", + "integrity": "sha512-u3mKjmTSIO7O62KVs9alH0Cs2kZnVfXoqXWklx5m/3dw1LlEnSu28355dtixyozd48KG4T/h2cwSkmOthvi4LA==", "requires": { - "greyscript-core": "^0.4.1" + "greyscript-core": "^0.4.4" } }, "greybel-gh-mock-intrinsics": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/greybel-gh-mock-intrinsics/-/greybel-gh-mock-intrinsics-1.1.8.tgz", - "integrity": "sha512-hZkBgn4Nl469wICfWqxnwXWoYkefvws4oI3pTUiz8wc0cabtP24hgiGWMjN9OefpjqhaEzYVWexGfjL5+sescQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/greybel-gh-mock-intrinsics/-/greybel-gh-mock-intrinsics-1.3.2.tgz", + "integrity": "sha512-mRXukmashl0pAtNptPBILWEk02A/iKqg7Z6ZGZmtN9Cb4DVZFpHTARKmDBjLwce6mP2rsIFPz5xhBY3j+TkBRQ==", "requires": { - "@types/random-seed": "^0.3.3", "blueimp-md5": "^2.19.0", - "greybel-interpreter": "^1.2.0", - "random-seed": "^0.3.0", - "random-username-generator": "^1.0.4" + "greybel-interpreter": "^1.4.0", + "greybel-mock-environment": "^1.2.8" } }, "greybel-interpreter": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/greybel-interpreter/-/greybel-interpreter-1.2.0.tgz", - "integrity": "sha512-Thnu8CB/zXBNZW+S6aOHqOuXPm6w6t2XhG5HFv9WXi+TQVR6VU+bup3bMZ37LCKFF/6bbKt1ZX4BQXxohHflKA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/greybel-interpreter/-/greybel-interpreter-1.4.0.tgz", + "integrity": "sha512-0FIA8gmAOCvYI/HamCST98/QkrVNxDxFujnIk7gQ7aWArXLGKEUaGisXW3KmzVToZVeP7cMPG0P5rv4NARpJBg==", "requires": { - "greybel-core": "^0.4.7", - "greyscript-core": "^0.4.1" + "greybel-core": "^0.4.9", + "greyscript-core": "^0.4.4" } }, "greybel-intrinsics": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/greybel-intrinsics/-/greybel-intrinsics-1.1.8.tgz", - "integrity": "sha512-nnABGlDPfSmk7xMTlgn3Ok9Ox6D3jVULG1yA2kIjBD8jSIdHdsc/AQ1vkUSQwkHdDsPMt7n1ovmM8gLi/Ka5fw==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/greybel-intrinsics/-/greybel-intrinsics-1.3.7.tgz", + "integrity": "sha512-Xrvh+VjBNYScWpINnKx5v5wlCwsw1RsfFE8EZunCbgYeAV0d3rZSYtrx+z1L6dxLNYoPsYjV+YbL6qlCDq1ueQ==", "requires": { - "greybel-interpreter": "^1.2.0" + "greybel-interpreter": "^1.4.0" + } + }, + "greybel-mock-environment": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/greybel-mock-environment/-/greybel-mock-environment-1.2.8.tgz", + "integrity": "sha512-Qvc56hmBVB24l5miNHWqoS9lN+og52vJIHvKBQUhJM+eUgHrjwJ3XWJ3+hx2NdXkcTz81yfltxl2C4cCWm95lQ==", + "requires": { + "@types/random-seed": "^0.3.3", + "blueimp-md5": "^2.19.0", + "greyscript-core": "^0.4.4", + "random-seed": "^0.3.0", + "random-username-generator": "^1.0.4" } }, "greybel-transpiler": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/greybel-transpiler/-/greybel-transpiler-0.6.0.tgz", - "integrity": "sha512-c9Rzzw28e8uI8nMdL3scWwYUSGewkZyzzekuF4u80oVN7HwJEttNcZxEMjxRltNI4Yrq9vVhNvyAzIx3HHH2iA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/greybel-transpiler/-/greybel-transpiler-0.6.3.tgz", + "integrity": "sha512-nxanzCOAGR5nxDNDXbJGDACG47R8rr8cjhSj4KCju0+YhHA9NosiS/67a7ouv7rO2YkjdJfHRldl96S9/mx0EA==", "requires": { "blueimp-md5": "^2.19.0", - "greybel-core": "^0.4.7", - "greyscript-core": "^0.4.1" + "greybel-core": "^0.4.9", + "greyscript-core": "^0.4.4" } }, "greyscript-core": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/greyscript-core/-/greyscript-core-0.4.1.tgz", - "integrity": "sha512-eF0GChvsxLlk8wdWON46IypJVPBif/oQyP7LES9giQQm/BF1De+ey9r9M+xrEvzxgzw8fzPXkUUl69ykBjgSZw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/greyscript-core/-/greyscript-core-0.4.4.tgz", + "integrity": "sha512-vlCLKTuYo6+qfJfMxcmj/sw2cw+bDxcDQp1Av5IYV8v4ZjSz4tUfYXCbZIwdpn3PPpy6kqkBdLso6t/66iqZ7w==", "requires": { - "greyscript-meta": "^1.1.5" + "greyscript-meta": "^1.1.8" } }, "greyscript-meta": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/greyscript-meta/-/greyscript-meta-1.1.5.tgz", - "integrity": "sha512-3wBBlnP+gWwAg9ogyj0iXAk5RZXl6gIwE1XHdCrLYYB9+HVXBZ8AA8fP9QFLVIL5OFk0s0AIg8wqCXvu2H5HdQ==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/greyscript-meta/-/greyscript-meta-1.1.8.tgz", + "integrity": "sha512-ltVcWNGR4WdkZ8A02GqTPEC1hwpZvxK40VdfD9cbpSvTJ4kiA/8og8gofIDArVMd3NccrnG+BH3YtRQT2So/sQ==", "requires": { "react-string-replace": "^1.1.0" } @@ -11733,6 +11832,11 @@ } } }, + "text-mesh-transformer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/text-mesh-transformer/-/text-mesh-transformer-1.0.2.tgz", + "integrity": "sha512-gQEWhyLu2hSqklUzFF6c+VYJRx36+mCK7r6vbPVO9o7UUHlzpn0w0ebMFdHUaZwfP0b5KheS/KotBNj3SFW0Ug==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 3468cde9..d22ac9e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "greybel-js", - "version": "1.3.6", + "version": "1.3.7", "engines": { "node": ">=14.0.0" }, @@ -22,14 +22,17 @@ ], "dependencies": { "@babel/runtime": "^7.16.7", + "ansis": "^1.5.5", + "cli-progress": "^3.11.2", "commander": "^7.2.0", - "greybel-core": "^0.4.7", - "greybel-gh-mock-intrinsics": "^1.1.8", - "greybel-interpreter": "^1.2.0", - "greybel-intrinsics": "^1.1.8", - "greybel-transpiler": "^0.6.0", - "greyscript-core": "^0.4.1", - "greyscript-meta": "^1.1.5", + "css-color-names": "^1.0.1", + "greybel-core": "^0.4.9", + "greybel-gh-mock-intrinsics": "^1.3.2", + "greybel-interpreter": "^1.4.0", + "greybel-intrinsics": "^1.3.7", + "greybel-transpiler": "^0.6.3", + "greyscript-core": "^0.4.4", + "greyscript-meta": "^1.1.8", "inquirer": "^8.1.0", "inquirer-command-prompt": "^0.1.0", "json-formatter-js": "^2.3.4", @@ -37,6 +40,7 @@ "mkdirp": "^1.0.4", "open": "^8.2.1", "semver": "^5.3.0", + "text-mesh-transformer": "^1.0.2", "uuid": "^8.3.2" }, "peerDependencies": { @@ -91,6 +95,7 @@ "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.1.3", + "@types/cli-progress": "^3.11.0", "@types/inquirer": "^8.1.3", "@types/mkdirp": "^1.0.2", "@types/node": "^18.7.9", diff --git a/src/build.ts b/src/build.ts index 6fa60c3c..cbd477c1 100644 --- a/src/build.ts +++ b/src/build.ts @@ -110,13 +110,6 @@ function createImportList( mainTarget: string ): any[] { const pseudoRoot = path.dirname(mainTarget) || ''; - const list = [ - { - filepath: mainTarget, - pseudoFilepath: path.basename(mainTarget), - content: parseResult[mainTarget] - } - ]; const imports = Object.entries(parseResult).map(([target, code]) => { return { filepath: target, @@ -125,7 +118,7 @@ function createImportList( }; }); - return list.concat(imports); + return imports; } async function createInstaller( @@ -252,7 +245,7 @@ export default async function build( }).parse(); const buildPath = path.resolve(output, './build'); - const targetRoot = path.dirname(target); + const targetRootSegments = path.dirname(target).split(path.sep); try { await fs.rm(buildPath, { @@ -262,9 +255,32 @@ export default async function build( await mkdirp(buildPath); + const getRelativePath = (filePath: string) => { + const pathSegments = filePath.split(path.sep); + const filtered = []; + + for (const segment of targetRootSegments) { + const current = pathSegments.shift(); + + if (current !== segment) { + break; + } + + filtered.push(current); + } + + let relativePath = filePath.replace(`${path.join(...filtered)}`, '.'); + + if (relativePath.startsWith(path.sep)) { + relativePath = relativePath.slice(1); + } + + return relativePath; + }; + await Promise.all( Object.entries(result).map(async ([file, code]) => { - const relativePath = file.replace(targetRoot, '.'); + const relativePath = getRelativePath(file); const fullPath = path.resolve(buildPath, relativePath); await mkdirp(path.dirname(fullPath)); await fs.writeFile(fullPath, code, { encoding: 'utf-8' }); diff --git a/src/execute.ts b/src/execute.ts index 1bdbe762..e19d2127 100644 --- a/src/execute.ts +++ b/src/execute.ts @@ -1,18 +1,26 @@ +import ansis from 'ansis'; +import cliProgress from 'cli-progress'; +import cssColorNames from 'css-color-names'; import { init as initGHIntrinsics } from 'greybel-gh-mock-intrinsics'; import { CustomFunction, - CustomString, - CustomValue, Debugger, Defaults, + HandlerContainer, Interpreter, - OperationContext + KeyEvent, + OperationContext, + OutputHandler } from 'greybel-interpreter'; import { init as initIntrinsics } from 'greybel-intrinsics'; import { ASTBase } from 'greyscript-core'; import inquirer from 'inquirer'; +import readline from 'readline'; +import transform, { Tag, TagRecord } from 'text-mesh-transformer'; inquirer.registerPrompt('command', require('inquirer-command-prompt')); +const hasOwnProperty = Object.prototype.hasOwnProperty; + class GrebyelPseudoDebugger extends Debugger { interpreter: Interpreter; @@ -51,6 +59,7 @@ class GrebyelPseudoDebugger extends Debugger { } try { + me.interpreter.debugger.setBreakpoint(false); await me.interpreter.injectInLastContext(line); console.log( `Execution of ${line}:${operationContext.target} was successful.` @@ -58,6 +67,8 @@ class GrebyelPseudoDebugger extends Debugger { } catch (err: any) { console.error(`Execution of ${line} failed.`); console.error(err); + } finally { + me.interpreter.debugger.setBreakpoint(true); } await iterate(); @@ -67,6 +78,259 @@ class GrebyelPseudoDebugger extends Debugger { } } +export interface NodeJSKeyEvent { + sequence: string; + name: string; + ctrl: boolean; + meta: boolean; + shift: boolean; + code?: string; +} + +export enum NodeJSKey { + Return = 'return', + Escape = 'escape', + Space = 'space', + Tab = 'tab', + Up = 'up', + Right = 'right', + Left = 'left', + Down = 'down', + Backspace = 'backspace', + Insert = 'insert', + Home = 'home', + End = 'end', + PageDown = 'pagedown', + PageUp = 'pageup', + Delete = 'delete', + F1 = 'f1', + F2 = 'f2', + F3 = 'f3', + F4 = 'f4', + F5 = 'f5', + F6 = 'f6', + F7 = 'f7', + F8 = 'f8', + F9 = 'f9', + F10 = 'f10', + F11 = 'f11', + F12 = 'f12' +} + +export function nodeJSKeyEventToKeyEvent( + nodeJSKeyEvent: NodeJSKeyEvent +): KeyEvent { + const create = (keyCode: number, code: string): KeyEvent => ({ + keyCode, + code + }); + + switch (nodeJSKeyEvent.name) { + case NodeJSKey.Return: + return create(13, 'Enter'); + case NodeJSKey.Escape: + return create(27, 'Escape'); + case NodeJSKey.Space: + return create(32, 'Space'); + case NodeJSKey.Tab: + return create(9, 'Tab'); + case NodeJSKey.Left: + return create(37, 'ArrowLeft'); + case NodeJSKey.Up: + return create(38, 'ArrowUp'); + case NodeJSKey.Right: + return create(39, 'ArrowRight'); + case NodeJSKey.Down: + return create(40, 'ArrowDown'); + case NodeJSKey.Backspace: + return create(8, 'Backspace'); + case NodeJSKey.Insert: + return create(45, 'Insert'); + case NodeJSKey.Home: + return create(36, 'Home'); + case NodeJSKey.End: + return create(35, 'End'); + case NodeJSKey.PageDown: + return create(34, 'PageDown'); + case NodeJSKey.PageUp: + return create(33, 'PageUp'); + case NodeJSKey.Delete: + return create(46, 'Delete'); + case NodeJSKey.F1: + return create(112, 'F1'); + case NodeJSKey.F2: + return create(113, 'F2'); + case NodeJSKey.F3: + return create(114, 'F3'); + case NodeJSKey.F4: + return create(115, 'F4'); + case NodeJSKey.F5: + return create(116, 'F5'); + case NodeJSKey.F6: + return create(117, 'F6'); + case NodeJSKey.F7: + return create(118, 'F7'); + case NodeJSKey.F8: + return create(119, 'F8'); + case NodeJSKey.F9: + return create(120, 'F9'); + case NodeJSKey.F10: + return create(121, 'F10'); + case NodeJSKey.F11: + return create(122, 'F11'); + case NodeJSKey.F12: + return create(123, 'F12'); + default: { + const char = nodeJSKeyEvent.sequence; + const keyCode = char.toUpperCase().charCodeAt(0); + const code = nodeJSKeyEvent.name || char; + return create(keyCode, code); + } + } +} + +function useColor(color: string | undefined, content: string): string { + if (!color) return content; + + const cssColorMap = cssColorNames as { [key: string]: string }; + + if (hasOwnProperty.call(cssColorMap, color)) { + const item = cssColorMap[color]; + color = item; + } + + return ansis.hex(color)(content); +} + +function useBgColor(color: string | undefined, content: string): string { + if (!color) return content; + + const cssColorMap = cssColorNames as { [key: string]: string }; + + if (hasOwnProperty.call(cssColorMap, color)) { + const item = cssColorMap[color]; + color = item; + } + + return ansis.bgHex(color)(content); +} + +function wrapWithTag(openTag: TagRecord, content: string): string { + switch (openTag.tag) { + case Tag.Color: + return useColor(openTag.value, content); + case Tag.Underline: + return ansis.underline(content); + case Tag.Italic: + return ansis.italic(content); + case Tag.Bold: + return ansis.bold(content); + case Tag.Strikethrough: + return ansis.strikethrough(content); + case Tag.Mark: + return useBgColor(openTag.value, content); + case Tag.Lowercase: + return content.toLowerCase(); + case Tag.Uppercase: + return content.toLowerCase(); + } + + if (openTag.value) { + return `<${openTag.tag}=${openTag.value}>${content}`; + } + + return `<${openTag.tag}>${content}`; +} + +export class CLIOutputHandler extends OutputHandler { + print(message: string) { + const transformed = transform( + message, + (openTag: TagRecord, content: string): string => { + return wrapWithTag(openTag, content); + } + ); + + console.log(transformed); + } + + clear() { + console.clear(); + } + + progress(timeout: number): Promise { + const startTime = Date.now(); + const loadingBar = new cliProgress.SingleBar( + {}, + cliProgress.Presets.shades_classic + ); + loadingBar.start(timeout, 0); + + if (!process.stdin.isTTY) { + console.warn( + 'Stdin TTY is false. Therefore the progress bar cannot be shown.' + ); + } + + return new Promise((resolve, _reject) => { + const interval = setInterval(() => { + const currentTime = Date.now(); + const elapsed = currentTime - startTime; + + if (elapsed > timeout) { + loadingBar.update(timeout); + clearInterval(interval); + loadingBar.stop(); + resolve(); + return; + } + + loadingBar.update(elapsed); + }); + }); + } + + waitForInput(isPassword: boolean): Promise { + return inquirer + .prompt({ + name: 'default', + message: 'Input:', + type: isPassword ? 'password' : 'input', + loop: false + }) + .then((inputMap) => { + return inputMap.default; + }) + .catch((err) => { + throw err; + }); + } + + waitForKeyPress(): Promise { + return new Promise((resolve, _reject) => { + readline.emitKeypressEvents(process.stdin); + + process.stdin.resume(); + + if (process.stdin.isTTY) { + process.stdin.setRawMode(true); + } else { + console.warn( + "Stdin TTY is false. Therefore anyKey isn't able to detect any input. Press enter to continue." + ); + } + + process.stdin.once( + 'keypress', + (character: string, key: NodeJSKeyEvent) => { + process.stdin.pause(); + resolve(nodeJSKeyEventToKeyEvent(key)); + } + ); + }); + } +} + export interface ExecuteOptions { api?: Map; params?: string[]; @@ -76,74 +340,12 @@ export default async function execute( target: string, options: ExecuteOptions = {} ): Promise { - const vsAPI = options.api || new Map(); - - vsAPI.set( - 'print', - CustomFunction.createExternal( - 'print', - ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - console.log(args.get('value')?.toString()); - return Promise.resolve(Defaults.Void); - } - ).addArgument('value') - ); - - vsAPI.set( - 'exit', - CustomFunction.createExternal( - 'exit', - ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - console.log(args.get('value')?.toString()); - interpreter.exit(); - return Promise.resolve(Defaults.Void); - } - ).addArgument('value') - ); - - vsAPI.set( - 'user_input', - CustomFunction.createExternal( - 'user_input', - async ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - const message = args.get('message')?.toString(); - const isPassword = args.get('isPassword')?.toTruthy(); - - return inquirer - .prompt({ - name: 'default', - message, - type: isPassword ? 'password' : 'input', - loop: false - }) - .then(function (inputMap) { - return new CustomString(inputMap.default); - }) - .catch((err) => { - throw err; - }); - } - ) - .addArgument('message') - .addArgument('isPassword') - .addArgument('anyKey') - ); - const interpreter = new Interpreter({ target, - api: initIntrinsics(initGHIntrinsics(vsAPI)) + handler: new HandlerContainer({ + outputHandler: new CLIOutputHandler() + }), + api: initIntrinsics(initGHIntrinsics()) }); interpreter.setDebugger(new GrebyelPseudoDebugger(interpreter)); diff --git a/src/repl.ts b/src/repl.ts index dd5bf33a..bd9a11cd 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -1,15 +1,17 @@ import { init as initGHIntrinsics } from 'greybel-gh-mock-intrinsics'; import { CustomFunction, - CustomString, CustomValue, Debugger, Defaults, + HandlerContainer, Interpreter, OperationContext } from 'greybel-interpreter'; import { init as initIntrinsics } from 'greybel-intrinsics'; import inquirer from 'inquirer'; + +import { CLIOutputHandler } from './execute'; inquirer.registerPrompt('command', require('inquirer-command-prompt')); class GrebyelPseudoDebugger extends Debugger { @@ -34,66 +36,27 @@ export default async function repl( const vsAPI = options.api || new Map(); let active = true; - vsAPI.set( - 'print', - CustomFunction.createExternal( - 'print', - ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - console.log(args.get('value')?.toString()); - return Promise.resolve(Defaults.Void); - } - ).addArgument('value') - ); - vsAPI.set( 'exit', CustomFunction.createExternal( 'exit', ( - _ctx: OperationContext, + ctx: OperationContext, _self: CustomValue, args: Map ): Promise => { - console.log(args.get('value')?.toString()); + ctx.handler.outputHandler.print(args.get('value')!.toString()); active = false; return Promise.resolve(Defaults.Void); } ).addArgument('value') ); - vsAPI.set( - 'user_input', - CustomFunction.createExternal( - 'user_input', - async ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - const message = args.get('message')?.toString(); - const isPassword = args.get('isPassword')?.toTruthy(); - - const result = await inquirer.prompt({ - name: 'default', - message, - type: isPassword ? 'password' : 'input', - loop: false - }); - - return new CustomString(result?.default); - } - ) - .addArgument('message') - .addArgument('isPassword') - .addArgument('anyKey') - ); - const interpreter = new Interpreter({ debugger: new GrebyelPseudoDebugger(), + handler: new HandlerContainer({ + outputHandler: new CLIOutputHandler() + }), api: initIntrinsics(initGHIntrinsics(vsAPI)) }); diff --git a/src/web.ts b/src/web.ts index 0ad0d393..6a75f46e 100644 --- a/src/web.ts +++ b/src/web.ts @@ -21,4 +21,4 @@ root.render( React.createElement(App, { initContent: content }) -); \ No newline at end of file +); diff --git a/src/web/app.tsx b/src/web/app.tsx index a286073e..0fabc72f 100644 --- a/src/web/app.tsx +++ b/src/web/app.tsx @@ -140,7 +140,7 @@ interface ExecuteOptions { } function Execute({ model, showError, instance, setDebug }: ExecuteOptions) { - const stdoutRef = useRef(null); + const stdoutRef = useRef(null); const stdinRef = useRef(null); const [stdout, setStdout] = useState(undefined); const [stdin, setStdin] = useState(undefined); @@ -262,13 +262,12 @@ function Execute({ model, showError, instance, setDebug }: ExecuteOptions) { - + > Promise; } +function wrapWithTag(openTag: TagRecord, content: string): string { + switch (openTag.tag) { + case Tag.Color: + return `${content}`; + case Tag.Underline: + return `${content}`; + case Tag.Italic: + return `${content}`; + case Tag.Bold: + return `${content}`; + case Tag.Strikethrough: + return `${content}`; + case Tag.Mark: + return `${content}`; + case Tag.Lowercase: + return `${content}`; + case Tag.Uppercase: + return `${content}`; + case Tag.Align: + return `${content}`; + case Tag.CSpace: + return `${content}`; + case Tag.LineHeight: + return `${content}`; + case Tag.Margin: + return `${content}`; + case Tag.NoBR: + return `${content}`; + case Tag.Pos: + return `${content}`; + case Tag.Size: + return `${content}`; + case Tag.VOffset: + return `${content}`; + } + + if (openTag.value) { + return `<${openTag.tag}=${openTag.value}>${content}</${openTag.tag}>`; + } + + return `<${openTag.tag}>${content}</${openTag.tag}>`; +} + export default async function execute( model: Monaco.editor.IModel, options: ExecuteOptions @@ -69,70 +114,79 @@ export default async function execute( await activeInterpreter.exit(); } - vsAPI.set( - 'print', - CustomFunction.createExternal( - 'print', - ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - stdout.write(args.get('value')?.toString()); - return Promise.resolve(Defaults.Void); - } - ).addArgument('value') - ); - - vsAPI.set( - 'exit', - CustomFunction.createExternal( - 'exit', - ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - stdout.write(args.get('value')?.toString()); - interpreter.exit(); - return Promise.resolve(Defaults.Void); - } - ).addArgument('value') - ); + const WebOutputHandler = class extends OutputHandler { + print(message: string) { + const transformed = transform( + message, + (openTag: TagRecord, content: string): string => { + return wrapWithTag(openTag, content); + } + ); - vsAPI.set( - 'user_input', - CustomFunction.createExternal( - 'user_input', - async ( - _ctx: OperationContext, - _self: CustomValue, - args: Map - ): Promise => { - const message = args.get('message')?.toString(); - const isPassword = args.get('isPassword')?.toTruthy(); + stdout.write(transformed); + } - stdout.write(message); + clear() { + stdout.clear(); + } - stdin.enable(); - stdin.focus(); - stdin.setType(isPassword ? 'password' : 'text'); + progress(timeout: number): Promise { + const startTime = Date.now(); + const max = 20; + stdout.write(`[${'-'.repeat(max)}]`); + + return new Promise((resolve, _reject) => { + const interval = setInterval(() => { + const currentTime = Date.now(); + const elapsed = currentTime - startTime; + + if (elapsed > timeout) { + stdout.updateLast(`[${'#'.repeat(max)}]`); + clearInterval(interval); + resolve(); + return; + } + + const elapsedPercentage = (100 * elapsed) / timeout; + const progress = Math.floor((elapsedPercentage * max) / 100); + const right = max - progress; + + stdout.updateLast(`[${'#'.repeat(progress)}${'-'.repeat(right)}]`); + }); + }); + } - await stdin.waitForInput(); + async waitForInput(isPassword: boolean): Promise { + stdin.enable(); + stdin.focus(); + stdin.setType(isPassword ? 'password' : 'text'); - const value = stdin.getValue(); + await stdin.waitForInput(); - stdin.clear(); - stdin.disable(); - stdin.setType('text'); + const value = stdin.getValue(); - return new CustomString(value); - } - ) - .addArgument('message') - .addArgument('isPassword') - .addArgument('anyKey') - ); + stdin.clear(); + stdin.disable(); + stdin.setType('text'); + + return value; + } + + async waitForKeyPress(): Promise { + stdin.enable(); + stdin.focus(); + + const keyEvent = await stdin.waitForKeyPress(); + + stdin.clear(); + stdin.disable(); + + return { + keyCode: keyEvent.keyCode, + code: keyEvent.code + }; + } + }; class PseudoResourceHandler extends ResourceHandler { getTargetRelativeTo(_source: string, _target: string): Promise { @@ -156,7 +210,8 @@ export default async function execute( target: 'default', debugger: new GrebyelDebugger(options.onInteract), handler: new HandlerContainer({ - resourceHandler: new PseudoResourceHandler() + resourceHandler: new PseudoResourceHandler(), + outputHandler: new WebOutputHandler() }), api: initIntrinsics(initGHIntrinsics(vsAPI)) }); diff --git a/src/web/extension/hover.ts b/src/web/extension/hover.ts index 8160701d..aa99d639 100644 --- a/src/web/extension/hover.ts +++ b/src/web/extension/hover.ts @@ -56,7 +56,9 @@ export function activate(monaco: typeof Monaco) { const argValues = args .map( (item: SignatureDefinitionArg) => - `${item.label}${item.opt ? '?' : ''}: ${formatType(item.type)}${item.default ? ` = ${item.default}` : ''}` + `${item.label}${item.opt ? '?' : ''}: ${formatType(item.type)}${ + item.default ? ` = ${item.default}` : '' + }` ) .join(', '); diff --git a/src/web/std.ts b/src/web/std.ts index d763dcf9..bf180601 100644 --- a/src/web/std.ts +++ b/src/web/std.ts @@ -39,15 +39,15 @@ export class Stdin { me.target.type = type; } - waitForInput() { + waitForInput(): Promise { const me = this; const target = me.target; const id = v4(); me.queue.unshift(id); - return new Promise((resolve: any) => { - const handler = (evt: any) => { + return new Promise((resolve) => { + const handler = (evt: KeyboardEvent) => { if (evt.keyCode === 13) { const currentId = me.queue[0]; @@ -63,6 +63,20 @@ export class Stdin { target.addEventListener('keydown', handler); }); } + + waitForKeyPress(): Promise { + const me = this; + const target = me.target; + + return new Promise((resolve) => { + const handler = (evt: KeyboardEvent) => { + target.removeEventListener('keydown', handler); + resolve(evt); + }; + + target.addEventListener('keydown', handler); + }); + } } export class Stdout { @@ -80,10 +94,16 @@ export class Stdout { me.render(); } + updateLast(value: any) { + const me = this; + me.history[me.history.length - 1] = value; + me.render(); + } + render() { const me = this; const target = me.target; - target.value = me.history.join('\n'); + target.innerHTML = me.history.join('
'); target.scrollTop = target.scrollHeight; } @@ -91,7 +111,7 @@ export class Stdout { const me = this; const target = me.target; me.history = []; - target.value = ''; + target.innerHTML = ''; target.scrollTop = target.scrollHeight; } } diff --git a/web/index.css b/web/index.css index 6bb8061e..552704bd 100644 --- a/web/index.css +++ b/web/index.css @@ -134,8 +134,13 @@ body { } .editor-console-stdout { - width: 100%; height: 200px; + background: #000; + color: green; + padding: 5px; + overflow: auto; + border: 1px solid #FFF; + position: relative; } .debugger-popup-bg {