diff --git a/package-lock.json b/package-lock.json index 922232ed7..41a7723fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,30 +10,30 @@ "hasInstallScript": true, "license": "SEE LICENSE IN license.txt", "dependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.10.5", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", "@material-ui/core": "^4.12.4", - "@mui/icons-material": "^5.11.0", + "@mui/icons-material": "^5.14.12", "@mui/material": "^5.14.12", - "@mui/styles": "^5.11.2", - "@mui/system": "^5.0.3", - "@types/estree": "^1.0.0", - "@types/react-syntax-highlighter": "^15.5.7", - "acorn": "^8.8.1", + "@mui/styles": "^5.14.12", + "@mui/system": "^5.14.12", + "@types/estree": "^1.0.2", + "@types/react-syntax-highlighter": "^15.5.8", + "acorn": "^8.10.0", "acorn-walk": "^8.2.0", "arg": "^5.0.2", "bcryptjs": "^2.4.3", - "better-react-mathjax": "^2.0.2", + "better-react-mathjax": "^2.0.3", "buffer": "^6.0.3", "clsx": "^1.2.1", - "date-fns": "^2.29.3", - "escodegen": "^2.0.0", + "date-fns": "^2.30.0", + "escodegen": "^2.1.0", "file-saver": "^2.0.5", - "jquery": "^3.6.3", + "jquery": "^3.7.1", "js-sha256": "^0.9.0", "jszip": "^3.10.1", "material-ui-color": "^1.2.0", - "material-ui-popup-state": "^1.5.3", + "material-ui-popup-state": "^1.9.3", "monaco-vim": "^0.3.5", "notistack": "^2.0.8", "numeral": "^2.0.6", @@ -41,62 +41,62 @@ "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", "react-dom": "^17.0.2", - "react-draggable": "^4.4.4", + "react-draggable": "^4.4.6", "react-markdown": "^8.0.7", - "react-resizable": "^3.0.4", + "react-resizable": "^3.0.5", "react-syntax-highlighter": "^15.5.0", "remark-gfm": "^3.0.1", - "sprintf-js": "^1.1.2" + "sprintf-js": "^1.1.3" }, "devDependencies": { - "@babel/core": "^7.20.12", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@microsoft/api-documenter": "^7.21.3", - "@microsoft/api-extractor": "^7.34.2", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", - "@types/bcryptjs": "^2.4.2", + "@babel/core": "^7.23.0", + "@babel/preset-env": "^7.22.20", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", + "@microsoft/api-documenter": "^7.23.9", + "@microsoft/api-extractor": "^7.38.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@types/bcryptjs": "^2.4.4", "@types/escodegen": "^0.0.7", "@types/file-saver": "^2.0.5", - "@types/jest": "^29.4.0", - "@types/jquery": "^3.5.16", - "@types/lodash": "^4.14.191", - "@types/numeral": "^2.0.2", - "@types/react": "^17.0.52", - "@types/react-beautiful-dnd": "^13.1.3", - "@types/react-dom": "^17.0.20", - "@types/react-resizable": "^3.0.3", - "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.48.0", - "babel-jest": "^29.3.1", - "babel-loader": "^9.1.2", - "css-loader": "^6.7.3", - "electron": "^26.2.4", - "electron-packager": "^17.1.1", + "@types/jest": "^29.5.5", + "@types/jquery": "^3.5.22", + "@types/lodash": "^4.14.199", + "@types/numeral": "^2.0.3", + "@types/react": "^17.0.67", + "@types/react-beautiful-dnd": "^13.1.5", + "@types/react-dom": "^17.0.21", + "@types/react-resizable": "^3.0.5", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "babel-jest": "^29.7.0", + "babel-loader": "^9.1.3", + "css-loader": "^6.8.1", + "electron": "^26.3.0", + "electron-packager": "^17.1.2", "eslint": "^8.51.0", - "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "^7.2.14", - "html-webpack-plugin": "^5.5.0", + "fork-ts-checker-webpack-plugin": "^7.3.0", + "html-webpack-plugin": "^5.5.3", "http-server": "^14.1.1", - "jest": "^29.4.3", - "jest-environment-jsdom": "^29.4.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "jsdom": "^20.0.3", "lodash": "^4.17.21", "monaco-editor": "^0.35.0", - "monaco-editor-webpack-plugin": "^7.0.1", - "prettier": "^2.8.1", + "monaco-editor-webpack-plugin": "^7.1.0", + "prettier": "^2.8.8", "raw-loader": "^4.0.2", "react-refresh": "^0.14.0", "source-map": "^0.7.4", - "start-server-and-test": "^1.15.2", - "style-loader": "^3.3.1", - "typescript": "^5.2.2", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.11.1" + "start-server-and-test": "^1.15.4", + "style-loader": "^3.3.3", + "typescript": "^4.9.5", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1" }, "engines": { "node": ">=14" @@ -2345,9 +2345,9 @@ "dev": true }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -4440,9 +4440,9 @@ } }, "node_modules/@types/jquery": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.21.tgz", - "integrity": "sha512-BgCalH4OX2JcjbnD9E86L+WeN6IcCGZle+Iy0I6eOMuraAfcMb0Z1x9E51rC2Dqq+m8KexEoDBW6GIJxUPmLEg==", + "version": "3.5.22", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.22.tgz", + "integrity": "sha512-ISQFeUK5GwRftLK4PVvKTWEVCxZ2BpaqBz0TWkIq5w4vGojxZP9+XkqgcPjxoqmPeew+HLyWthCBvK7GdF5NYA==", "dev": true, "dependencies": { "@types/sizzle": "*" @@ -4500,9 +4500,9 @@ "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==" }, "node_modules/@types/node": { - "version": "18.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", - "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==", + "version": "18.18.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.4.tgz", + "integrity": "sha512-t3rNFBgJRugIhackit2mVcLfF6IRc0JE4oeizPQL8Zrm8n2WY/0wOdpOPhdtG0V9Q2TlW/axbF1MJ6z+Yj/kKQ==", "dev": true }, "node_modules/@types/numeral": { @@ -4582,9 +4582,9 @@ } }, "node_modules/@types/react-syntax-highlighter": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.7.tgz", - "integrity": "sha512-bo5fEO5toQeyCp0zVHBeggclqf5SQ/Z5blfFmjwO5dkMVGPgmiwZsJh9nu/Bo5L7IHTuGWrja6LxJVE2uB5ZrQ==", + "version": "15.5.8", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.8.tgz", + "integrity": "sha512-GT1PLGzhF3ilGaQiCHFDShxDBb014s01MQi0nWfXJ23efjWfUrZ2i0g4tH1JGdfnIGBtQDge/k3ON3fLoAuU/w==", "dependencies": { "@types/react": "*" } @@ -4695,9 +4695,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.26", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz", - "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==", + "version": "17.0.28", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.28.tgz", + "integrity": "sha512-N3e3fkS86hNhtk6BEnc0rj3zcehaxx8QWhCROJkqpl5Zaoi7nAic3jH8q94jVD3zu5LGk+PUB6KAiDmimYOEQw==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -5962,9 +5962,9 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "node_modules/better-react-mathjax": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.2.tgz", - "integrity": "sha512-GSU/c+LhnMVClkduWpO7fr9Ac2KMOxWs/ePwdFgpffidK3Zg9Vyd3scO6XZbd2QgMUcKnpjEG6hE1sM/EFekSQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz", + "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==", "dependencies": { "mathjax-full": "^3.2.2" }, @@ -7582,9 +7582,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.542", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.542.tgz", - "integrity": "sha512-6+cpa00G09N3sfh2joln4VUXHquWrOFx3FLZqiVQvl45+zS9DskDBTPvob+BhvFRmTBkyDSk0vvLMMRo/qc6mQ==", + "version": "1.4.544", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.544.tgz", + "integrity": "sha512-54z7squS1FyFRSUqq/knOFSptjjogLZXbKcYk3B0qkE1KZzvqASwRZnY2KzZQJqIYLVD38XZeoiMRflYSwyO4w==", "dev": true }, "node_modules/emittery": { @@ -8109,9 +8109,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -8704,12 +8704,12 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", "dev": true, "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, @@ -12706,9 +12706,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -12733,13 +12733,13 @@ } }, "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", "dev": true, "dependencies": { "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" + "shell-quote": "^1.8.1" } }, "node_modules/lazy-ass": { @@ -16469,9 +16469,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz", - "integrity": "sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", "dev": true }, "node_modules/spdy": { @@ -17586,9 +17586,9 @@ } }, "node_modules/v8-to-istanbul": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.2.tgz", - "integrity": "sha512-ZGBe7VAivuuoQXTeckpbYKTdtjXGcm3ZUHXC0PAk0CzFyuYvwi73a58iEKI3GkGD1c3EHc+EgfR1w5pgbfzJlQ==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", diff --git a/package.json b/package.json index 0ca335581..e30d4c148 100644 --- a/package.json +++ b/package.json @@ -10,30 +10,30 @@ "url": "https://github.com/bitburner-official/bitburner-src/issues" }, "dependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.10.5", + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", "@material-ui/core": "^4.12.4", - "@mui/icons-material": "^5.11.0", + "@mui/icons-material": "^5.14.12", "@mui/material": "^5.14.12", - "@mui/styles": "^5.11.2", - "@mui/system": "^5.0.3", - "@types/estree": "^1.0.0", - "@types/react-syntax-highlighter": "^15.5.7", - "acorn": "^8.8.1", + "@mui/styles": "^5.14.12", + "@mui/system": "^5.14.12", + "@types/estree": "^1.0.2", + "@types/react-syntax-highlighter": "^15.5.8", + "acorn": "^8.10.0", "acorn-walk": "^8.2.0", "arg": "^5.0.2", "bcryptjs": "^2.4.3", - "better-react-mathjax": "^2.0.2", + "better-react-mathjax": "^2.0.3", "buffer": "^6.0.3", "clsx": "^1.2.1", - "date-fns": "^2.29.3", - "escodegen": "^2.0.0", + "date-fns": "^2.30.0", + "escodegen": "^2.1.0", "file-saver": "^2.0.5", - "jquery": "^3.6.3", + "jquery": "^3.7.1", "js-sha256": "^0.9.0", "jszip": "^3.10.1", "material-ui-color": "^1.2.0", - "material-ui-popup-state": "^1.5.3", + "material-ui-popup-state": "^1.9.3", "monaco-vim": "^0.3.5", "notistack": "^2.0.8", "numeral": "^2.0.6", @@ -41,63 +41,63 @@ "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", "react-dom": "^17.0.2", - "react-draggable": "^4.4.4", + "react-draggable": "^4.4.6", "react-markdown": "^8.0.7", - "react-resizable": "^3.0.4", + "react-resizable": "^3.0.5", "react-syntax-highlighter": "^15.5.0", "remark-gfm": "^3.0.1", - "sprintf-js": "^1.1.2" + "sprintf-js": "^1.1.3" }, "description": "A cyberpunk-themed incremental game", "devDependencies": { - "@babel/core": "^7.20.12", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@microsoft/api-documenter": "^7.21.3", - "@microsoft/api-extractor": "^7.34.2", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.10", - "@types/bcryptjs": "^2.4.2", + "@babel/core": "^7.23.0", + "@babel/preset-env": "^7.22.20", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", + "@microsoft/api-documenter": "^7.23.9", + "@microsoft/api-extractor": "^7.38.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@types/bcryptjs": "^2.4.4", "@types/escodegen": "^0.0.7", "@types/file-saver": "^2.0.5", - "@types/jest": "^29.4.0", - "@types/jquery": "^3.5.16", - "@types/lodash": "^4.14.191", - "@types/numeral": "^2.0.2", - "@types/react": "^17.0.52", - "@types/react-beautiful-dnd": "^13.1.3", - "@types/react-dom": "^17.0.20", - "@types/react-resizable": "^3.0.3", - "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.48.0", - "babel-jest": "^29.3.1", - "babel-loader": "^9.1.2", - "css-loader": "^6.7.3", - "electron": "^26.2.4", - "electron-packager": "^17.1.1", + "@types/jest": "^29.5.5", + "@types/jquery": "^3.5.22", + "@types/lodash": "^4.14.199", + "@types/numeral": "^2.0.3", + "@types/react": "^17.0.67", + "@types/react-beautiful-dnd": "^13.1.5", + "@types/react-dom": "^17.0.21", + "@types/react-resizable": "^3.0.5", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "babel-jest": "^29.7.0", + "babel-loader": "^9.1.3", + "css-loader": "^6.8.1", + "electron": "^26.3.0", + "electron-packager": "^17.1.2", "eslint": "^8.51.0", - "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "file-loader": "^6.2.0", - "fork-ts-checker-webpack-plugin": "^7.2.14", - "html-webpack-plugin": "^5.5.0", + "fork-ts-checker-webpack-plugin": "^7.3.0", + "html-webpack-plugin": "^5.5.3", "http-server": "^14.1.1", - "jest": "^29.4.3", - "jest-environment-jsdom": "^29.4.3", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "jsdom": "^20.0.3", "lodash": "^4.17.21", "monaco-editor": "^0.35.0", - "monaco-editor-webpack-plugin": "^7.0.1", - "prettier": "^2.8.1", + "monaco-editor-webpack-plugin": "^7.1.0", + "prettier": "^2.8.8", "raw-loader": "^4.0.2", "react-refresh": "^0.14.0", "source-map": "^0.7.4", - "start-server-and-test": "^1.15.2", - "style-loader": "^3.3.1", + "start-server-and-test": "^1.15.4", + "style-loader": "^3.3.3", "typescript": "^5.2.2", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", - "webpack-dev-server": "^4.11.1" + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1" }, "engines": { "node": ">=14" diff --git a/src/ScriptEditor/ScriptEditor.ts b/src/ScriptEditor/ScriptEditor.ts new file mode 100644 index 000000000..eb753480e --- /dev/null +++ b/src/ScriptEditor/ScriptEditor.ts @@ -0,0 +1,70 @@ +import type { ContentFilePath } from "../Paths/ContentFile"; + +import { EventEmitter } from "../utils/EventEmitter"; +import * as monaco from "monaco-editor"; +import { loadThemes, makeTheme, sanitizeTheme } from "./ui/themes"; +import libSource from "!!raw-loader!./NetscriptDefinitions.d.ts"; +import { Settings } from "../Settings/Settings"; +import { NetscriptExtra } from "../NetscriptFunctions/Extra"; +import * as enums from "../Enums"; +import { ns } from "../NetscriptFunctions"; + +/** Event emitter used for tracking when changes have been made to a content file. */ +export const fileEditEvents = new EventEmitter<[hostname: string, filename: ContentFilePath]>(); + +export class ScriptEditor { + // TODO: This will store info about currently open scripts. + // Among other things, this will allow informing the script editor of changes made elsewhere, even if the script editor is not being rendered. + // openScripts: OpenScript[] = []; + + // Currently, this object is only used for initialization. + isInitialized = false; + initialize() { + if (this.isInitialized) return; + this.isInitialized = true; + // populate API keys for adding tokenization + const apiKeys: string[] = []; + const api = { args: [], pid: 1, enums, ...ns }; + const hiddenAPI = NetscriptExtra(); + function populate(apiLayer: object = api) { + for (const [apiKey, apiValue] of Object.entries(apiLayer)) { + if (apiLayer === api && apiKey in hiddenAPI) continue; + apiKeys.push(apiKey); + if (typeof apiValue === "object") populate(apiValue); + } + } + populate(); + // Add api keys to tokenization + (async function () { + // We have to improve the default js language otherwise theme sucks + const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript"); + // Unsupported function is not exposed in monaco public API. + const l = await (jsLanguage as any).loader(); + // replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side + // this prevents the highlighter from highlighting pieces of variables that start with a reserved token name + l.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]); + for (const symbol of apiKeys) + l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); + const otherKeywords = ["let", "const", "var", "function"]; + const otherKeyvars = ["true", "false", "null", "undefined"]; + otherKeywords.forEach((k) => + l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]), + ); + otherKeyvars.forEach((k) => + l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]), + ); + l.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]); + })(); + + // Add ts definitions for API + const source = (libSource + "").replace(/export /g, ""); + monaco.languages.typescript.javascriptDefaults.addExtraLib(source, "netscript.d.ts"); + monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts"); + // Load themes + loadThemes(monaco.editor.defineTheme); + sanitizeTheme(Settings.EditorTheme); + monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); + } +} + +export const scriptEditor = new ScriptEditor(); diff --git a/src/ScriptEditor/ui/Editor.tsx b/src/ScriptEditor/ui/Editor.tsx index 22fafb0ab..65055715e 100644 --- a/src/ScriptEditor/ui/Editor.tsx +++ b/src/ScriptEditor/ui/Editor.tsx @@ -3,17 +3,16 @@ import React, { useEffect, useRef } from "react"; import * as monaco from "monaco-editor"; import { useScriptEditorContext } from "./ScriptEditorContext"; +import { scriptEditor } from "../ScriptEditor"; interface EditorProps { - /** Function to be ran prior to mounting editor */ - beforeMount: () => void; /** Function to be ran after mounting editor */ onMount: (editor: monaco.editor.IStandaloneCodeEditor) => void; /** Function to be ran every time the code is updated */ onChange: (newCode?: string) => void; } -export function Editor({ beforeMount, onMount, onChange }: EditorProps) { +export function Editor({ onMount, onChange }: EditorProps) { const containerDiv = useRef(null); const editorRef = useRef(null); const subscription = useRef(null); @@ -23,7 +22,7 @@ export function Editor({ beforeMount, onMount, onChange }: EditorProps) { useEffect(() => { if (!containerDiv.current) return; // Before initializing monaco editor - beforeMount(); + scriptEditor.initialize(); // Initialize monaco editor editorRef.current = monaco.editor.create(containerDiv.current, { diff --git a/src/ScriptEditor/ui/OpenScript.ts b/src/ScriptEditor/ui/OpenScript.ts index 0c86e03b9..30859c833 100644 --- a/src/ScriptEditor/ui/OpenScript.ts +++ b/src/ScriptEditor/ui/OpenScript.ts @@ -1,6 +1,5 @@ -import type { editor, Position } from "monaco-editor"; - import type { ContentFilePath } from "../../Paths/ContentFile"; +import { editor, Position } from "monaco-editor"; type ITextModel = editor.ITextModel; @@ -12,6 +11,8 @@ export class OpenScript { lastPosition: Position; model: ITextModel; isTxt: boolean; + // TODO: Adding actual external update notifications for the OpenScript class + // hasExternalUpdate = false; constructor(path: ContentFilePath, code: string, hostname: string, lastPosition: Position, model: ITextModel) { this.path = path; @@ -21,4 +22,8 @@ export class OpenScript { this.model = model; this.isTxt = path.endsWith(".txt"); } + + regenerateModel(): void { + this.model = editor.createModel(this.code, this.isTxt ? "plaintext" : "javascript"); + } } diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index 8e286680f..f32a9c443 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -1,3 +1,5 @@ +import type { ContentFilePath } from "../../Paths/ContentFile"; + import React, { useEffect, useRef } from "react"; import * as monaco from "monaco-editor"; @@ -8,23 +10,17 @@ type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; import { Router } from "../../ui/GameRoot"; import { Page } from "../../ui/Router"; import { dialogBoxCreate } from "../../ui/React/DialogBox"; -import { ScriptFilePath } from "../../Paths/ScriptFilePath"; import { checkInfiniteLoop } from "../../Script/RamCalculations"; -import { ns, enums } from "../../NetscriptFunctions"; import { Settings } from "../../Settings/Settings"; import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial"; import { debounce } from "lodash"; import { saveObject } from "../../SaveObject"; -import { loadThemes, makeTheme, sanitizeTheme } from "./themes"; import { GetServer } from "../../Server/AllServers"; import { PromptEvent } from "../../ui/React/PromptManager"; -import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts"; import { useRerender } from "../../ui/React/hooks"; -import { NetscriptExtra } from "../../NetscriptFunctions/Extra"; -import { TextFilePath } from "../../Paths/TextFilePath"; import { dirty, getServerCode } from "./utils"; import { OpenScript } from "./OpenScript"; @@ -33,31 +29,14 @@ import { Toolbar } from "./Toolbar"; import { NoOpenScripts } from "./NoOpenScripts"; import { ScriptEditorContextProvider, useScriptEditorContext } from "./ScriptEditorContext"; import { useVimEditor } from "./useVimEditor"; +import { useCallback } from "react"; interface IProps { // Map of filename -> code - files: Map; + files: Map; hostname: string; vim: boolean; } - -// TODO: try to remove global symbols -let symbolsLoaded = false; -const apiKeys: string[] = []; -export function SetupTextEditor(): void { - // Function for populating apiKeys using a given layer of the API. - const api = { args: [], pid: 1, enums, ...ns }; - const hiddenAPI = NetscriptExtra(); - function populate(apiLayer: object = api) { - for (const [apiKey, apiValue] of Object.entries(apiLayer)) { - if (apiLayer === api && apiKey in hiddenAPI) continue; - apiKeys.push(apiKey); - if (typeof apiValue === "object") populate(apiValue); - } - } - populate(); -} - const openScripts: OpenScript[] = []; let currentScript: OpenScript | null = null; @@ -77,6 +56,42 @@ function Root(props: IProps): React.ReactElement { currentScript = openScripts[0] ?? null; } + const save = useCallback(() => { + if (currentScript === null) { + console.error("currentScript is null when it shouldn't be. Unable to save script"); + return; + } + // this is duplicate code with saving later. + if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) { + //Make sure filename + code properly follow tutorial + if (currentScript.path !== "n00dles.script" && currentScript.path !== "n00dles.js") { + dialogBoxCreate("Don't change the script name for now."); + return; + } + const cleanCode = currentScript.code.replace(/\s/g, ""); + const ns1 = "while(true){hack('n00dles');}"; + const ns2 = `exportasyncfunctionmain(ns){while(true){awaitns.hack('n00dles');}}`; + if (!cleanCode.includes(ns1) && !cleanCode.includes(ns2)) { + dialogBoxCreate("Please copy and paste the code from the tutorial!"); + return; + } + + //Save the script + saveScript(currentScript); + Router.toPage(Page.Terminal); + + iTutorialNextStep(); + + return; + } + + const server = GetServer(currentScript.hostname); + if (server === null) throw new Error("Server should not be null but it is."); + server.writeToContentFile(currentScript.path, currentScript.code); + if (Settings.SaveGameOnFileSave) saveObject.saveGame(); + rerender(); + }, [rerender]); + useEffect(() => { function keydown(event: KeyboardEvent): void { if (Settings.DisableHotkeys) return; @@ -95,12 +110,7 @@ function Root(props: IProps): React.ReactElement { } document.addEventListener("keydown", keydown); return () => document.removeEventListener("keydown", keydown); - }); - - // Generates a new model for the script - function regenerateModel(script: OpenScript): void { - script.model = monaco.editor.createModel(script.code, script.isTxt ? "plaintext" : "javascript"); - } + }, [save]); function infLoop(newCode: string): void { if (editorRef.current === null || currentScript === null) return; @@ -142,56 +152,15 @@ function Root(props: IProps): React.ReactElement { debouncedCodeParsing(newCode); }; - // How to load function definition in monaco - // https://github.com/Microsoft/monaco-editor/issues/1415 - // https://microsoft.github.io/monaco-editor/api/modules/monaco.languages.html - // https://www.npmjs.com/package/@monaco-editor/react#development-playground - // https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages - // https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39 - // https://blog.checklyhq.com/customizing-monaco/ - // Before the editor is mounted - function beforeMount(): void { - if (symbolsLoaded) return; - // Setup monaco auto completion - symbolsLoaded = true; - (async function () { - // We have to improve the default js language otherwise theme sucks - const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript"); - // Unsupported function is not exposed in monaco public API. - const l = await (jsLanguage as any).loader(); - // replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side - // this prevents the highlighter from highlighting pieces of variables that start with a reserved token name - l.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]); - for (const symbol of apiKeys) - l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); - const otherKeywords = ["let", "const", "var", "function"]; - const otherKeyvars = ["true", "false", "null", "undefined"]; - otherKeywords.forEach((k) => - l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]), - ); - otherKeyvars.forEach((k) => - l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]), - ); - l.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]); - })(); - - const source = (libSource + "").replace(/export /g, ""); - monaco.languages.typescript.javascriptDefaults.addExtraLib(source, "netscript.d.ts"); - monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts"); - loadThemes(monaco.editor.defineTheme); - sanitizeTheme(Settings.EditorTheme); - monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); - } - // When the editor is mounted function onMount(editor: IStandaloneCodeEditor): void { // Required when switching between site navigation (e.g. from Script Editor -> Terminal and back) // the `useEffect()` for vim mode is called before editor is mounted. editorRef.current = editor; - if (!props.files && currentScript !== null) { + if (props.files.size === 0 && currentScript !== null) { // Open currentscript - regenerateModel(currentScript); + currentScript.regenerateModel(); editorRef.current.setModel(currentScript.model); editorRef.current.setPosition(currentScript.lastPosition); editorRef.current.revealLineInCenter(currentScript.lastPosition.lineNumber); @@ -199,42 +168,35 @@ function Root(props: IProps): React.ReactElement { editorRef.current.focus(); return; } - if (props.files) { - const files = props.files; + const files = props.files; - if (!files.size) { - editorRef.current.focus(); - return; - } - - for (const [filename, code] of files) { - // Check if file is already opened - const openScript = openScripts.find((script) => script.path === filename && script.hostname === props.hostname); - if (openScript) { - // Script is already opened - if (openScript.model === undefined || openScript.model === null || openScript.model.isDisposed()) { - regenerateModel(openScript); - } - - currentScript = openScript; - editorRef.current.setModel(openScript.model); - editorRef.current.setPosition(openScript.lastPosition); - editorRef.current.revealLineInCenter(openScript.lastPosition.lineNumber); - parseCode(openScript.code); - } else { - // Open script - const newScript = new OpenScript( - filename, - code, - props.hostname, - new monaco.Position(0, 0), - monaco.editor.createModel(code, filename.endsWith(".txt") ? "plaintext" : "javascript"), - ); - openScripts.push(newScript); - currentScript = newScript; - editorRef.current.setModel(newScript.model); - parseCode(newScript.code); + for (const [filename, code] of files) { + // Check if file is already opened + const openScript = openScripts.find((script) => script.path === filename && script.hostname === props.hostname); + if (openScript) { + // Script is already opened + if (openScript.model === undefined || openScript.model === null || openScript.model.isDisposed()) { + openScript.regenerateModel(); } + + currentScript = openScript; + editorRef.current.setModel(openScript.model); + editorRef.current.setPosition(openScript.lastPosition); + editorRef.current.revealLineInCenter(openScript.lastPosition.lineNumber); + parseCode(openScript.code); + } else { + // Open script + const newScript = new OpenScript( + filename, + code, + props.hostname, + new monaco.Position(0, 0), + monaco.editor.createModel(code, filename.endsWith(".txt") ? "plaintext" : "javascript"), + ); + openScripts.push(newScript); + currentScript = newScript; + editorRef.current.setModel(newScript.model); + parseCode(newScript.code); } } @@ -263,42 +225,6 @@ function Root(props: IProps): React.ReactElement { if (Settings.SaveGameOnFileSave) saveObject.saveGame(); } - function save(): void { - if (currentScript === null) { - console.error("currentScript is null when it shouldn't be. Unable to save script"); - return; - } - // this is duplicate code with saving later. - if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) { - //Make sure filename + code properly follow tutorial - if (currentScript.path !== "n00dles.script" && currentScript.path !== "n00dles.js") { - dialogBoxCreate("Don't change the script name for now."); - return; - } - const cleanCode = currentScript.code.replace(/\s/g, ""); - const ns1 = "while(true){hack('n00dles');}"; - const ns2 = `exportasyncfunctionmain(ns){while(true){awaitns.hack('n00dles');}}`; - if (!cleanCode.includes(ns1) && !cleanCode.includes(ns2)) { - dialogBoxCreate("Please copy and paste the code from the tutorial!"); - return; - } - - //Save the script - saveScript(currentScript); - Router.toPage(Page.Terminal); - - iTutorialNextStep(); - - return; - } - - const server = GetServer(currentScript.hostname); - if (server === null) throw new Error("Server should not be null but it is."); - server.writeToContentFile(currentScript.path, currentScript.code); - if (Settings.SaveGameOnFileSave) saveObject.saveGame(); - rerender(); - } - function currentTabIndex(): number | undefined { if (currentScript) return openScripts.findIndex((openScript) => currentScript === openScript); return undefined; @@ -316,8 +242,8 @@ function Root(props: IProps): React.ReactElement { currentScript = openScripts[index]; if (editorRef.current !== null && openScripts[index] !== null) { - if (currentScript.model === undefined || currentScript.model.isDisposed()) { - regenerateModel(currentScript); + if (!currentScript.model || currentScript.model.isDisposed()) { + currentScript.regenerateModel(); } editorRef.current.setModel(currentScript.model); editorRef.current.setPosition(currentScript.lastPosition); @@ -361,7 +287,7 @@ function Root(props: IProps): React.ReactElement { currentScript = openScripts[index + indexOffset]; if (editorRef.current !== null) { if (currentScript.model.isDisposed() || !currentScript.model) { - regenerateModel(currentScript); + currentScript.regenerateModel(); } editorRef.current.setModel(currentScript.model); editorRef.current.setPosition(currentScript.lastPosition); @@ -393,7 +319,7 @@ function Root(props: IProps): React.ReactElement { if (editorRef.current !== null && openScript !== null) { if (openScript.model === undefined || openScript.model.isDisposed()) { - regenerateModel(openScript); + openScript.regenerateModel(); } editorRef.current.setModel(openScript.model); @@ -464,7 +390,7 @@ function Root(props: IProps): React.ReactElement { onTabUpdate={onTabUpdate} />
- + {VimStatus} diff --git a/src/TextFile.ts b/src/TextFile.ts index 5964bcac7..7754d016f 100644 --- a/src/TextFile.ts +++ b/src/TextFile.ts @@ -1,4 +1,3 @@ -import { dialogBoxCreate } from "./ui/React/DialogBox"; import { BaseServer } from "./Server/BaseServer"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "./utils/JSONReviver"; import { TextFilePath } from "./Paths/TextFilePath"; @@ -25,11 +24,6 @@ export class TextFile implements ContentFile { this.text = txt; } - /** Concatenates the raw values to the end of current content. */ - append(txt: string): void { - this.text += txt; - } - /** Serves the file to the user as a downloadable resource through the browser. */ download(): void { const file: Blob = new Blob([this.text], { type: "text/plain" }); @@ -41,30 +35,15 @@ export class TextFile implements ContentFile { a.click(); setTimeout(() => { document.body.removeChild(a); - window.URL.revokeObjectURL(url); + URL.revokeObjectURL(url); }, 0); } - /** Retrieve the content of the file. */ - read(): string { - return this.text; - } - - /** Shows the content to the user via the game's dialog box. */ - show(): void { - dialogBoxCreate(`${this.filename}\n\n${this.text}`); - } - /** Serialize the current file to a JSON save state. */ toJSON(): IReviverValue { return Generic_toJSON("TextFile", this); } - /** Replaces the current content with the text provided. */ - write(txt: string): void { - this.text = txt; - } - deleteFromServer(server: BaseServer): boolean { if (!server.textFiles.has(this.filename)) return false; server.textFiles.delete(this.filename); diff --git a/src/engine.tsx b/src/engine.tsx index 361a6f02a..de8d1a5a5 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -9,7 +9,6 @@ import { staneksGift } from "./CotMG/Helper"; import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers"; import { Router } from "./ui/GameRoot"; import { Page } from "./ui/Router"; -import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot"; import "./PersonObjects/Player/PlayerObject"; // For side-effect of creating Player import { @@ -377,7 +376,6 @@ const Engine: { // Start interactive tutorial iTutorialStart(); } - SetupTextEditor(); }, start: function () {