Upgrade monaco and bump game version to 2.3.0dev (#369)

This commit is contained in:
Snarling 2023-02-23 21:43:29 -05:00 committed by GitHub
parent 211e2bcb95
commit 71051cde9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1658 additions and 317294 deletions

7
.gitignore vendored

@ -11,9 +11,14 @@ Netburner.txt
/test/*.css /test/*.css
/input/bitburner.api.json /input/bitburner.api.json
dist/bitburner.d.ts dist/bitburner.d.ts
dist/images
.cypress .cypress
# folder for bundles images / fonts that are generated by webpack
dist/assets
# Monaco bundle files
dist/*.worker.*
# tmp folder for build and electron # tmp folder for build and electron
.app .app
.package .package

File diff suppressed because one or more lines are too long

@ -1,415 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
define('vs/basic-languages/typescript/typescript',["require", "exports", "../fillers/monaco-editor-core"], function (require, exports, monaco_editor_core_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.language = exports.conf = void 0;
exports.conf = {
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
onEnterRules: [
{
// e.g. /** | */
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
afterText: /^\s*\*\/$/,
action: {
indentAction: monaco_editor_core_1.languages.IndentAction.IndentOutdent,
appendText: ' * '
}
},
{
// e.g. /** ...|
beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
action: {
indentAction: monaco_editor_core_1.languages.IndentAction.None,
appendText: ' * '
}
},
{
// e.g. * ...|
beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
action: {
indentAction: monaco_editor_core_1.languages.IndentAction.None,
appendText: '* '
}
},
{
// e.g. */|
beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/,
action: {
indentAction: monaco_editor_core_1.languages.IndentAction.None,
removeText: 1
}
}
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"', notIn: ['string'] },
{ open: "'", close: "'", notIn: ['string', 'comment'] },
{ open: '`', close: '`', notIn: ['string', 'comment'] },
{ open: '/**', close: ' */', notIn: ['string'] }
],
folding: {
markers: {
start: new RegExp('^\\s*//\\s*#?region\\b'),
end: new RegExp('^\\s*//\\s*#?endregion\\b')
}
}
};
exports.language = {
// Set defaultToken to invalid to see what you do not tokenize yet
defaultToken: 'invalid',
tokenPostfix: '.ts',
keywords: [
// Should match the keys of textToKeywordObj in
// https://github.com/microsoft/TypeScript/blob/master/src/compiler/scanner.ts
'abstract',
'any',
'as',
'asserts',
'bigint',
'boolean',
'break',
'case',
'catch',
'class',
'continue',
'const',
'constructor',
'debugger',
'declare',
'default',
'delete',
'do',
'else',
'enum',
'export',
'extends',
'false',
'finally',
'for',
'from',
'function',
'get',
'if',
'implements',
'import',
'in',
'infer',
'instanceof',
'interface',
'is',
'keyof',
'let',
'module',
'namespace',
'never',
'new',
'null',
'number',
'object',
'package',
'private',
'protected',
'public',
'override',
'readonly',
'require',
'global',
'return',
'set',
'static',
'string',
'super',
'switch',
'symbol',
'this',
'throw',
'true',
'try',
'type',
'typeof',
'undefined',
'unique',
'unknown',
'var',
'void',
'while',
'with',
'yield',
'async',
'await',
'of'
],
operators: [
'<=',
'>=',
'==',
'!=',
'===',
'!==',
'=>',
'+',
'-',
'**',
'*',
'/',
'%',
'++',
'--',
'<<',
'</',
'>>',
'>>>',
'&',
'|',
'^',
'!',
'~',
'&&',
'||',
'??',
'?',
':',
'=',
'+=',
'-=',
'*=',
'**=',
'/=',
'%=',
'<<=',
'>>=',
'>>>=',
'&=',
'|=',
'^=',
'@'
],
// we include these common regular expressions
symbols: /[=><!~?:&|+\-*\/\^%]+/,
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
digits: /\d+(_+\d+)*/,
octaldigits: /[0-7]+(_+[0-7]+)*/,
binarydigits: /[0-1]+(_+[0-1]+)*/,
hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
regexpctl: /[(){}\[\]\$\^|\-*+?\.]/,
regexpesc: /\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,
// The main tokenizer for our languages
tokenizer: {
root: [[/[{}]/, 'delimiter.bracket'], { include: 'common' }],
common: [
// identifiers and keywords
[
/[a-z_$][\w$]*/,
{
cases: {
'@keywords': 'keyword',
'@default': 'identifier'
}
}
],
[/[A-Z][\w\$]*/, 'type.identifier'],
// [/[A-Z][\w\$]*/, 'identifier'],
// whitespace
{ include: '@whitespace' },
// regular expression: ensure it is terminated before beginning (otherwise it is an opeator)
[
/\/(?=([^\\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,
{ token: 'regexp', bracket: '@open', next: '@regexp' }
],
// delimiters and operators
[/[()\[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[/!(?=([^=]|$))/, 'delimiter'],
[
/@symbols/,
{
cases: {
'@operators': 'delimiter',
'@default': ''
}
}
],
// numbers
[/(@digits)[eE]([\-+]?(@digits))?/, 'number.float'],
[/(@digits)\.(@digits)([eE][\-+]?(@digits))?/, 'number.float'],
[/0[xX](@hexdigits)n?/, 'number.hex'],
[/0[oO]?(@octaldigits)n?/, 'number.octal'],
[/0[bB](@binarydigits)n?/, 'number.binary'],
[/(@digits)n?/, 'number'],
// delimiter: after number because of .\d floats
[/[;,.]/, 'delimiter'],
// strings
[/"([^"\\]|\\.)*$/, 'string.invalid'],
[/'([^'\\]|\\.)*$/, 'string.invalid'],
[/"/, 'string', '@string_double'],
[/'/, 'string', '@string_single'],
[/`/, 'string', '@string_backtick']
],
whitespace: [
[/[ \t\r\n]+/, ''],
[/\/\*\*(?!\/)/, 'comment.doc', '@jsdoc'],
[/\/\*/, 'comment', '@comment'],
[/\/\/.*$/, 'comment']
],
comment: [
[/[^\/*]+/, 'comment'],
[/\*\//, 'comment', '@pop'],
[/[\/*]/, 'comment']
],
jsdoc: [
[/[^\/*]+/, 'comment.doc'],
[/\*\//, 'comment.doc', '@pop'],
[/[\/*]/, 'comment.doc']
],
// We match regular expression quite precisely
regexp: [
[
/(\{)(\d+(?:,\d*)?)(\})/,
['regexp.escape.control', 'regexp.escape.control', 'regexp.escape.control']
],
[
/(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/,
['regexp.escape.control', { token: 'regexp.escape.control', next: '@regexrange' }]
],
[/(\()(\?:|\?=|\?!)/, ['regexp.escape.control', 'regexp.escape.control']],
[/[()]/, 'regexp.escape.control'],
[/@regexpctl/, 'regexp.escape.control'],
[/[^\\\/]/, 'regexp'],
[/@regexpesc/, 'regexp.escape'],
[/\\\./, 'regexp.invalid'],
[
/(\/)([dgimsuy]*)/,
[{ token: 'regexp', bracket: '@close', next: '@pop' }, 'keyword.other']
]
],
regexrange: [
[/-/, 'regexp.escape.control'],
[/\^/, 'regexp.invalid'],
[/@regexpesc/, 'regexp.escape'],
[/[^\]]/, 'regexp'],
[
/\]/,
{
token: 'regexp.escape.control',
next: '@pop',
bracket: '@close'
}
]
],
string_double: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, 'string', '@pop']
],
string_single: [
[/[^\\']+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/'/, 'string', '@pop']
],
string_backtick: [
[/\$\{/, { token: 'delimiter.bracket', next: '@bracketCounting' }],
[/[^\\`$]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/`/, 'string', '@pop']
],
bracketCounting: [
[/\{/, 'delimiter.bracket', '@bracketCounting'],
[/\}/, 'delimiter.bracket', '@pop'],
{ include: 'common' }
]
}
};
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
define('vs/basic-languages/javascript/javascript',["require", "exports", "../typescript/typescript"], function (require, exports, typescript_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.language = exports.conf = void 0;
exports.conf = typescript_1.conf;
exports.language = {
// Set defaultToken to invalid to see what you do not tokenize yet
defaultToken: 'invalid',
tokenPostfix: '.js',
keywords: [
'break',
'case',
'catch',
'class',
'continue',
'const',
'constructor',
'debugger',
'default',
'delete',
'do',
'else',
'export',
'extends',
'false',
'finally',
'for',
'from',
'function',
'get',
'if',
'import',
'in',
'instanceof',
'let',
'new',
'null',
'return',
'set',
'super',
'switch',
'symbol',
'this',
'throw',
'true',
'try',
'typeof',
'undefined',
'var',
'void',
'while',
'with',
'yield',
'async',
'await',
'of'
],
typeKeywords: [],
operators: typescript_1.language.operators,
symbols: typescript_1.language.symbols,
escapes: typescript_1.language.escapes,
digits: typescript_1.language.digits,
octaldigits: typescript_1.language.octaldigits,
binarydigits: typescript_1.language.binarydigits,
hexdigits: typescript_1.language.hexdigits,
regexpctl: typescript_1.language.regexpctl,
regexpesc: typescript_1.language.regexpesc,
tokenizer: typescript_1.language.tokenizer
};
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -12,5 +12,7 @@ module.exports = {
"\\!!raw-loader!.*$": "<rootDir>/test/__mocks__/rawLoader.js", "\\!!raw-loader!.*$": "<rootDir>/test/__mocks__/rawLoader.js",
"@player": "<rootDir>/src/Player", "@player": "<rootDir>/src/Player",
"@nsdefs": "<rootDir>/src/ScriptEditor/NetscriptDefinitions", "@nsdefs": "<rootDir>/src/ScriptEditor/NetscriptDefinitions",
"^monaco-editor$": "<rootDir>/test/__mocks__/monacoMock.js",
"^monaco-vim$": "<rootDir>/test/__mocks__/monacoMock.js",
}, },
}; };

3581
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
{ {
"name": "bitburner", "name": "bitburner",
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"version": "2.2.2", "version": "2.3.0dev",
"main": "electron-main.js", "main": "electron-main.js",
"author": { "author": {
"name": "Daniel Xie, Olivier Gagnon, et al." "name": "Daniel Xie, Olivier Gagnon, et al."
@ -13,7 +13,6 @@
"@emotion/react": "^11.4.1", "@emotion/react": "^11.4.1",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.10.5",
"@material-ui/core": "^4.12.4", "@material-ui/core": "^4.12.4",
"@monaco-editor/react": "^4.4.6",
"@mui/icons-material": "^5.11.0", "@mui/icons-material": "^5.11.0",
"@mui/material": "~5.10.15", "@mui/material": "~5.10.15",
"@mui/styles": "^5.11.2", "@mui/styles": "^5.11.2",
@ -34,7 +33,7 @@
"jszip": "^3.10.1", "jszip": "^3.10.1",
"material-ui-color": "^1.2.0", "material-ui-color": "^1.2.0",
"material-ui-popup-state": "^1.5.3", "material-ui-popup-state": "^1.5.3",
"monaco-editor": "^0.34.1", "monaco-vim": "^0.3.5",
"notistack": "^2.0.8", "notistack": "^2.0.8",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
@ -57,7 +56,7 @@
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/escodegen": "^0.0.7", "@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/jest": "^29.2.5", "@types/jest": "^29.4.0",
"@types/jquery": "^3.5.16", "@types/jquery": "^3.5.16",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/numeral": "^2.0.2", "@types/numeral": "^2.0.2",
@ -69,6 +68,7 @@
"@typescript-eslint/parser": "^5.48.0", "@typescript-eslint/parser": "^5.48.0",
"babel-jest": "^29.3.1", "babel-jest": "^29.3.1",
"babel-loader": "^9.1.2", "babel-loader": "^9.1.2",
"css-loader": "^6.7.3",
"electron": "^22.2.1", "electron": "^22.2.1",
"electron-packager": "^17.1.1", "electron-packager": "^17.1.1",
"eslint": "^8.31.0", "eslint": "^8.31.0",
@ -76,16 +76,18 @@
"fork-ts-checker-webpack-plugin": "^7.2.14", "fork-ts-checker-webpack-plugin": "^7.2.14",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"jest": "^29.3.1", "jest": "^29.4.3",
"jest-environment-jsdom": "^29.3.1", "jest-environment-jsdom": "^29.4.3",
"jsdom": "^20.0.3", "jsdom": "^20.0.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mini-css-extract-plugin": "^2.7.2", "monaco-editor": "^0.35.0",
"monaco-editor-webpack-plugin": "^7.0.1",
"prettier": "^2.8.1", "prettier": "^2.8.1",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"source-map": "^0.7.4", "source-map": "^0.7.4",
"start-server-and-test": "^1.15.2", "start-server-and-test": "^1.15.2",
"style-loader": "^3.3.1",
"typescript": "^4.9.4", "typescript": "^4.9.4",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",

@ -87,7 +87,7 @@ export const CONSTANTS: {
Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG
LatestUpdate: string; LatestUpdate: string;
} = { } = {
VersionString: "2.2.2", VersionString: "2.3.0",
VersionNumber: 30, VersionNumber: 30,
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
@ -228,71 +228,24 @@ export const CONSTANTS: {
Donations: 41, Donations: 41,
LatestUpdate: ` LatestUpdate: `
v2.2.2 - 21 Feb 2022 v2.3 Dev
PLANNED 2.3 BREAKING CHANGES: Major bugfix (Will backport these changes to 2.2.2 if possible)
* Fix an issue that prevented the Electron API server from communicating with the VSCode plugin. (credit to u/AnyGiraffe4367 on reddit)
Other changes
* Monaco script editor updated to a newer version. Also many internal code changes to support this. (@Snarling)
* The SF9.3 bonus is also given to the player when inside of BN9. (@Zelow79)
* Sleeve shock recovery now scales with intelligence. (@Tyasuh)
* Nerf noodle bar
Planned changes:
* 2.3 will include a large planned rework to corporation. This may cause api breaks for any corporation scripts, and there will be large changes in how the corporation mechanic functions. * 2.3 will include a large planned rework to corporation. This may cause api breaks for any corporation scripts, and there will be large changes in how the corporation mechanic functions.
* Enum changes, potentially causing API break with some enums. Enums will be more usable and there will be more of them.
NETSCRIPT API: * Constants rework - interenal game constants will be reorganized and will be provided to the player as different categories of constants.
* Added ns.formatNumber, ns.formatRam, and ns.formatPercent, which allow formatting these types of numbers the same way the game does (@Snarling, See UI section). * Improve type validation on ns functions.
* Deprecated ns.nFormat. Likely to be removed in 2.3. Now just directly wraps numeral.format (@Snarling) * Add more Script Editor configuration options (font family, ligatures, etc).
* EXPERIMENTAL CHANGE (may be reverted next patch): BasicHGWOptions now allows specifying a number of additionalMsec. This should allow easier and more reliable coordination * Further deprecation of ns1. Removal of more documentation, add ingame notice to prompt player to update scripts to .js.
of completion times for hack, grow, and weaken. Since this is an experimental change, be prepared for a possible API break next patch if you use this functionality. (@d0sboots)
- Corporation API
* Fix bugs with ns.corporation.setAutoJobAssignment. (@zerbosh and @croy)
- Formulas API
* Added ns.formulas.hacking.growThreads function (@d0sboots)
- Sleeve API
* ns.sleeve.getTask now also includes cyclesWorked for the task types where this applies. (@Zelow79)
* Added ns.sleeve.setToIdle function (@Zelow79)
- Unsupported API
* Added ns.printRaw - allows printing custom React content to script logs. Use at your own risk, misuse is very likely to cause a crash. (@d0sboots)
ELECTRON (STEAM) VERSION:
* Fix security issue where player scripts were allowed to access any part of the player's filesystem. Now access is limited to the game's 'dist' folder. (@Snarling)
SCRIPTS:
* Fix an issue where multiple copies of the same script could be launched with same args/same server (@Mughur)
* Followup changes to API wrapping from 2.2.1 changes. (@d0sboots)
UI:
* Add new number formatting code to replace internal use of unmaintained package numeral.js. Added several Numeric Display options. (@Snarling)
* Removed ingame donation section. (@hydroflame)
* Improve some bladeburner number formatting (@Zelow79)
* Added IronMan theme (@MattiYT)
* Factions that have not been joined yet will show how many unowned augments they have available. (@Zelow79)
* Added more features to dev menu (@Zelow79 and @Snarling)
CORPORATION:
* Reverted previous change to employee needs. Now they will trend up on their own again. (@d0sboots)
* Improvements to how Market TA II works (@d0sboots)
* ns.corporation.getOffice return value now includes a totalExperience property. (@Snarling)
HACKNET:
* Hacknet servers are now named hacknet-server-# instead of hacknet-node-#. (@Tyasuh)
* Fix bug related to renaming hacknet servers (@Mughur)
GRAFTING:
* Bladeburner augs can be grafted if player is in Bladeburner faction (@Tyasuh)
DOCUMENTATION
* Many documentation updates (@Mughur, @d0sboots, @Snarling, @teauxfu).
* Official non-markdown docs are at http://bitburner-official.readthedocs.io/
* Official dev version markdown docs are at https://github.com/bitburner-official/bitburner-src/blob/dev/markdown/bitburner.ns.md
* Official stable version markdown docs are at https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.ns.md
* Dev version documentation is now kept up to date as changes are made. (@Snarling)
CODEBASE:
* Updated many dependencies (@d0sboots)
* Updated lots of the build processes and GitHub workflows. (@Snarling)
* Internal refactoring of how BitNode multipliers are stored (@d0sboots)
* Added some extra helper function (useRerender hook, positiveInteger ns argument validator). (@Snarling)
MISC:
* Nerf noodle bar * Nerf noodle bar
`, `,
}; };

@ -0,0 +1,54 @@
import * as monaco from "monaco-editor";
import * as React from "react";
import { useEffect, useRef } from "react";
export type Monaco = typeof monaco;
type EditorProps = {
/** css height of editor */
height: string;
/** Editor options */
options: monaco.editor.IEditorOptions;
/** 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({ height, options, beforeMount, onMount, onChange }: EditorProps) {
const containerDiv = useRef<HTMLDivElement | null>(null);
const editor = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const subscription = useRef<monaco.IDisposable | null>(null);
useEffect(() => {
if (!containerDiv.current) return;
// Before initializing monaco editor
beforeMount();
// Initialize monaco editor
editor.current = monaco.editor.create(containerDiv.current, {
value: "",
automaticLayout: true,
language: "javascript",
...options,
});
// After initializing monaco editor
onMount(editor.current);
subscription.current = editor.current.onDidChangeModelContent(() => {
onChange(editor.current?.getValue());
});
// Unmounting
return () => {
editor.current?.dispose();
const model = editor.current?.getModel();
model?.dispose();
subscription.current?.dispose();
};
}, []);
return <div ref={containerDiv} style={{ height: height, width: "100%" }} />;
}

@ -1,6 +1,8 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import Editor, { Monaco } from "@monaco-editor/react"; import { Editor } from "./Editor";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
// @ts-expect-error This library does not have types.
import * as MonacoVim from "monaco-vim";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
type ITextModel = monaco.editor.ITextModel; type ITextModel = monaco.editor.ITextModel;
@ -20,8 +22,7 @@ import { formatRam } from "../../ui/formatNumber";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import SearchIcon from "@mui/icons-material/Search"; import SearchIcon from "@mui/icons-material/Search";
import { NetscriptFunctions } from "../../NetscriptFunctions"; import { ns, enums } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial"; import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
import { debounce } from "lodash"; import { debounce } from "lodash";
@ -46,6 +47,7 @@ import { Modal } from "../../ui/React/Modal";
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts"; import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
import { TextField, Tooltip } from "@mui/material"; import { TextField, Tooltip } from "@mui/material";
import { useRerender } from "../../ui/React/hooks"; import { useRerender } from "../../ui/React/hooks";
import { NetscriptExtra } from "../../NetscriptFunctions/Extra";
interface IProps { interface IProps {
// Map of filename -> code // Map of filename -> code
@ -56,31 +58,19 @@ interface IProps {
// TODO: try to remove global symbols // TODO: try to remove global symbols
let symbolsLoaded = false; let symbolsLoaded = false;
let symbols: string[] = []; const apiKeys: string[] = [];
export function SetupTextEditor(): void { export function SetupTextEditor(): void {
const ns = NetscriptFunctions({ args: [] } as unknown as WorkerScript); // Function for populating apiKeys using a given layer of the API.
const api = { args: [], pid: 1, enums, ...ns };
// Populates symbols for text editor const hiddenAPI = NetscriptExtra();
function populate(ns: any): string[] { function populate(apiLayer: object = api) {
let symbols: string[] = []; for (const [apiKey, apiValue] of Object.entries(apiLayer)) {
const keys = Object.keys(ns); if (apiLayer === api && apiKey in hiddenAPI) continue;
for (const key of keys) { apiKeys.push(apiKey);
if (typeof ns[key] === "object") { if (typeof apiValue === "object") populate(apiValue);
symbols.push(key);
symbols = symbols.concat(populate(ns[key]));
}
if (typeof ns[key] === "function") {
symbols.push(key);
}
} }
return symbols;
} }
populate();
symbols = populate(ns);
const exclude = ["heart", "break", "exploit", "bypass", "corporation", "alterReality"];
symbols = symbols.filter((symbol: string) => !exclude.includes(symbol)).sort();
} }
// Holds all the data for a open script // Holds all the data for a open script
@ -109,8 +99,8 @@ let currentScript: OpenScript | null = null;
export function Root(props: IProps): React.ReactElement { export function Root(props: IProps): React.ReactElement {
const rerender = useRerender(); const rerender = useRerender();
const editorRef = useRef<IStandaloneCodeEditor | null>(null); const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const monacoRef = useRef<Monaco | null>(null);
const vimStatusRef = useRef<HTMLElement>(null); const vimStatusRef = useRef<HTMLElement>(null);
// monaco-vim does not have types, so this is an any
const [vimEditor, setVimEditor] = useState<any>(null); const [vimEditor, setVimEditor] = useState<any>(null);
const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null); const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null);
const [filter, setFilter] = useState(""); const [filter, setFilter] = useState("");
@ -170,54 +160,51 @@ export function Root(props: IProps): React.ReactElement {
useEffect(() => { useEffect(() => {
// setup monaco-vim // setup monaco-vim
if (options.vim && editor && !vimEditor) { if (options.vim && editor && !vimEditor) {
// Using try/catch because MonacoVim does not have types.
try { try {
// This library is not typed setVimEditor(MonacoVim.initVimMode(editor, vimStatusRef.current));
// @ts-expect-error MonacoVim.VimMode.Vim.defineEx("write", "w", function () {
window.require(["monaco-vim"], function (MonacoVim: any) { // your own implementation on what you want to do when :w is pressed
setVimEditor(MonacoVim.initVimMode(editor, vimStatusRef.current)); save();
MonacoVim.VimMode.Vim.defineEx("write", "w", function () {
// your own implementation on what you want to do when :w is pressed
save();
});
MonacoVim.VimMode.Vim.defineEx("quit", "q", function () {
Router.toPage(Page.Terminal);
});
const saveNQuit = (): void => {
save();
Router.toPage(Page.Terminal);
};
// "wqriteandquit" & "xriteandquit" are not typos, prefix must be found in full string
MonacoVim.VimMode.Vim.defineEx("wqriteandquit", "wq", saveNQuit);
MonacoVim.VimMode.Vim.defineEx("xriteandquit", "x", saveNQuit);
// Setup "go to next tab" and "go to previous tab". This is a little more involved
// since these aren't Ex commands (they run in normal mode, not after typing `:`)
MonacoVim.VimMode.Vim.defineAction("nextTabs", function (_cm: any, args: { repeat?: number }) {
const nTabs = args.repeat ?? 1;
// Go to the next tab (to the right). Wraps around when at the rightmost tab
const currIndex = currentTabIndex();
if (currIndex !== undefined) {
const nextIndex = (currIndex + nTabs) % openScripts.length;
onTabClick(nextIndex);
}
});
MonacoVim.VimMode.Vim.defineAction("prevTabs", function (_cm: any, args: { repeat?: number }) {
const nTabs = args.repeat ?? 1;
// Go to the previous tab (to the left). Wraps around when at the leftmost tab
const currIndex = currentTabIndex();
if (currIndex !== undefined) {
let nextIndex = currIndex - nTabs;
while (nextIndex < 0) {
nextIndex += openScripts.length;
}
onTabClick(nextIndex);
}
});
MonacoVim.VimMode.Vim.mapCommand("gt", "action", "nextTabs", {}, { context: "normal" });
MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" });
editor.focus();
}); });
MonacoVim.VimMode.Vim.defineEx("quit", "q", function () {
Router.toPage(Page.Terminal);
});
const saveNQuit = (): void => {
save();
Router.toPage(Page.Terminal);
};
// "wqriteandquit" & "xriteandquit" are not typos, prefix must be found in full string
MonacoVim.VimMode.Vim.defineEx("wqriteandquit", "wq", saveNQuit);
MonacoVim.VimMode.Vim.defineEx("xriteandquit", "x", saveNQuit);
// Setup "go to next tab" and "go to previous tab". This is a little more involved
// since these aren't Ex commands (they run in normal mode, not after typing `:`)
MonacoVim.VimMode.Vim.defineAction("nextTabs", function (_cm: any, args: { repeat?: number }) {
const nTabs = args.repeat ?? 1;
// Go to the next tab (to the right). Wraps around when at the rightmost tab
const currIndex = currentTabIndex();
if (currIndex !== undefined) {
const nextIndex = (currIndex + nTabs) % openScripts.length;
onTabClick(nextIndex);
}
});
MonacoVim.VimMode.Vim.defineAction("prevTabs", function (_cm: any, args: { repeat?: number }) {
const nTabs = args.repeat ?? 1;
// Go to the previous tab (to the left). Wraps around when at the leftmost tab
const currIndex = currentTabIndex();
if (currIndex !== undefined) {
let nextIndex = currIndex - nTabs;
while (nextIndex < 0) {
nextIndex += openScripts.length;
}
onTabClick(nextIndex);
}
});
MonacoVim.VimMode.Vim.mapCommand("gt", "action", "nextTabs", {}, { context: "normal" });
MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" });
editor.focus();
} catch {} } catch {}
} else if (!options.vim) { } else if (!options.vim) {
// When vim mode is disabled // When vim mode is disabled
@ -232,9 +219,7 @@ export function Root(props: IProps): React.ReactElement {
// Generates a new model for the script // Generates a new model for the script
function regenerateModel(script: OpenScript): void { function regenerateModel(script: OpenScript): void {
if (monacoRef.current !== null) { script.model = monaco.editor.createModel(script.code, script.isTxt ? "plaintext" : "javascript");
script.model = monacoRef.current.editor.createModel(script.code, script.isTxt ? "plaintext" : "javascript");
}
} }
const debouncedUpdateRAM = debounce((newCode: string) => { const debouncedUpdateRAM = debounce((newCode: string) => {
@ -284,7 +269,7 @@ export function Root(props: IProps): React.ReactElement {
// Formats the code // Formats the code
function beautify(): void { function beautify(): void {
if (editorRef.current === null) return; if (editorRef.current === null) return;
editorRef.current.getAction("editor.action.formatDocument").run(); editorRef.current.getAction("editor.action.formatDocument")?.run();
} }
// How to load function definition in monaco // How to load function definition in monaco
@ -295,35 +280,19 @@ export function Root(props: IProps): React.ReactElement {
// https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39 // https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39
// https://blog.checklyhq.com/customizing-monaco/ // https://blog.checklyhq.com/customizing-monaco/
// Before the editor is mounted // Before the editor is mounted
function beforeMount(monaco: any): void { function beforeMount(): void {
if (symbolsLoaded) return; if (symbolsLoaded) return;
// Setup monaco auto completion // Setup monaco auto completion
symbolsLoaded = true; symbolsLoaded = true;
monaco.languages.registerCompletionItemProvider("javascript", {
provideCompletionItems: () => {
const suggestions = [];
for (const symbol of symbols) {
suggestions.push({
label: symbol,
kind: monaco.languages.CompletionItemKind.Function,
insertText: symbol,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
});
}
return { suggestions: suggestions };
},
});
(async function () { (async function () {
// We have to improve the default js language otherwise theme sucks // We have to improve the default js language otherwise theme sucks
const l = await monaco.languages const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript");
.getLanguages() // Unsupported function is not exposed in monaco public API.
.find((l: any) => l.id === "javascript") const l = await (jsLanguage as any).loader();
.loader();
// replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side // 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 // 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" }]); l.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]);
for (const symbol of symbols) for (const symbol of apiKeys)
l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]);
const otherKeywords = ["let", "const", "var", "function"]; const otherKeywords = ["let", "const", "var", "function"];
const otherKeyvars = ["true", "false", "null", "undefined"]; const otherKeyvars = ["true", "false", "null", "undefined"];
@ -345,15 +314,14 @@ export function Root(props: IProps): React.ReactElement {
} }
// When the editor is mounted // When the editor is mounted
function onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void { function onMount(editor: IStandaloneCodeEditor): void {
// Required when switching between site navigation (e.g. from Script Editor -> Terminal and back) // 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. // the `useEffect()` for vim mode is called before editor is mounted.
setEditor(editor); setEditor(editor);
editorRef.current = editor; editorRef.current = editor;
monacoRef.current = monaco;
if (editorRef.current === null || monacoRef.current === null) return; if (!editorRef.current) return;
if (!props.files && currentScript !== null) { if (!props.files && currentScript !== null) {
// Open currentscript // Open currentscript
@ -395,8 +363,8 @@ export function Root(props: IProps): React.ReactElement {
filename, filename,
code, code,
props.hostname, props.hostname,
new monacoRef.current.Position(0, 0), new monaco.Position(0, 0),
monacoRef.current.editor.createModel(code, filename.endsWith(".txt") ? "plaintext" : "javascript"), monaco.editor.createModel(code, filename.endsWith(".txt") ? "plaintext" : "javascript"),
); );
openScripts.push(newScript); openScripts.push(newScript);
currentScript = newScript; currentScript = newScript;
@ -866,12 +834,8 @@ export function Root(props: IProps): React.ReactElement {
<Editor <Editor
beforeMount={beforeMount} beforeMount={beforeMount}
onMount={onMount} onMount={onMount}
loading={<Typography>Loading script editor!</Typography>}
height={`calc(100vh - ${130 + (options.vim ? 34 : 0)}px)`} height={`calc(100vh - ${130 + (options.vim ? 34 : 0)}px)`}
defaultLanguage="javascript"
defaultValue={""}
onChange={updateCode} onChange={updateCode}
theme={options.theme}
options={{ ...options, glyphMargin: true }} options={{ ...options, glyphMargin: true }}
/> />
@ -921,7 +885,7 @@ export function Root(props: IProps): React.ReactElement {
open={optionsOpen} open={optionsOpen}
onClose={() => { onClose={() => {
sanitizeTheme(Settings.EditorTheme); sanitizeTheme(Settings.EditorTheme);
monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptionsOpen(false); setOptionsOpen(false);
}} }}
options={{ options={{
@ -933,7 +897,7 @@ export function Root(props: IProps): React.ReactElement {
}} }}
save={(options: Options) => { save={(options: Options) => {
sanitizeTheme(Settings.EditorTheme); sanitizeTheme(Settings.EditorTheme);
monacoRef.current?.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme)); monaco.editor.defineTheme("customTheme", makeTheme(Settings.EditorTheme));
setOptions(options); setOptions(options);
Settings.MonacoTheme = options.theme; Settings.MonacoTheme = options.theme;
Settings.MonacoInsertSpaces = options.insertSpaces; Settings.MonacoInsertSpaces = options.insertSpaces;

@ -1,5 +1,5 @@
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import type { Monaco } from "@monaco-editor/react"; import type { Monaco } from "./Editor";
export interface IScriptEditorTheme { export interface IScriptEditorTheme {
base: "vs" | "vs-dark" | "hc-black"; base: "vs" | "vs-dark" | "hc-black";

@ -13,21 +13,7 @@
<meta name="msapplication-TileColor" content="#000000" /> <meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-config" content="dist/browserconfig.xml" /> <meta name="msapplication-config" content="dist/browserconfig.xml" />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
<!-- MONACO JS -->
<link
rel="stylesheet"
data-name="vs/editor/editor.main"
href="dist/ext/monaco-editor/min/vs/editor/editor.main.css"
/>
<script>
var require = { paths: { vs: "dist/ext/monaco-editor/min/vs", "monaco-vim": "dist/ext/monaco-vim" } };
</script>
<script src="dist/ext/monaco-editor/min/vs/loader.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="dist/ext/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="dist/ext/monaco-vim.js"></script>
<style> <style>
html, body { html, body {
margin: 0; margin: 0;

@ -16,5 +16,5 @@
"strict": true, "strict": true,
"target": "es2019" "target": "es2019"
}, },
"include": ["src/**/*", "electron/**/*", ".eslintrc.js"] "include": ["src/**/*", "electron/**/*", ".eslintrc.js", "node_modules/monaco-editor/monaco.d.ts"]
} }

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const webpack = require("webpack"); const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin"); const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
@ -96,13 +96,11 @@ module.exports = (env, argv) => {
return { return {
plugins: [ plugins: [
new MonacoWebpackPlugin({ languages: ["javascript", "typescript"] }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
"process.env.NODE_ENV": isDevelopment ? '"development"' : '"production"', "process.env.NODE_ENV": isDevelopment ? '"development"' : '"production"',
}), }),
new HtmlWebpackPlugin(htmlConfig), new HtmlWebpackPlugin(htmlConfig),
new MiniCssExtractPlugin({
filename: "[name].css",
}),
new ForkTsCheckerWebpackPlugin({ new ForkTsCheckerWebpackPlugin({
typescript: { typescript: {
diagnosticOptions: { diagnosticOptions: {
@ -137,6 +135,7 @@ module.exports = (env, argv) => {
output: { output: {
path: path.resolve(__dirname, outputDirectory), path: path.resolve(__dirname, outputDirectory),
filename: "[name].bundle.js", filename: "[name].bundle.js",
assetModuleFilename: "assets/[hash][ext][query]",
}, },
module: { module: {
rules: [ rules: [
@ -151,18 +150,10 @@ module.exports = (env, argv) => {
}, },
}, },
}, },
{ test: /\.(ttf|png|jpe?g|gif|jp2|webp)$/, type: "asset/resource" },
{ {
test: /\.s?css$/, test: /\.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], use: ["style-loader", "css-loader"],
},
{
test: /\.(png|jpe?g|gif|jp2|webp)$/,
loader: "file-loader",
options: {
name: "[contenthash].[ext]",
outputPath: "images",
publicPath: `${outputDirectory}/images`,
},
}, },
], ],
}, },