2021-10-11 22:59:37 +02:00
|
|
|
import React, { useState, useEffect, useRef, useMemo } from "react";
|
2021-12-17 14:36:33 +01:00
|
|
|
import Editor, { Monaco } from "@monaco-editor/react";
|
2021-08-20 07:57:32 +02:00
|
|
|
import * as monaco from "monaco-editor";
|
2021-08-27 01:14:56 +02:00
|
|
|
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
|
2021-12-17 14:36:33 +01:00
|
|
|
type ITextModel = monaco.editor.ITextModel;
|
2021-09-25 05:36:28 +02:00
|
|
|
import { OptionsModal } from "./OptionsModal";
|
2021-08-20 08:14:27 +02:00
|
|
|
import { Options } from "./Options";
|
2021-08-20 07:21:37 +02:00
|
|
|
import { isValidFilePath } from "../../Terminal/DirectoryHelpers";
|
|
|
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
2021-09-17 08:58:02 +02:00
|
|
|
import { IRouter } from "../../ui/Router";
|
2021-09-25 20:42:57 +02:00
|
|
|
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
2021-09-24 22:34:21 +02:00
|
|
|
import { isScriptFilename } from "../../Script/isScriptFilename";
|
2021-08-20 07:21:37 +02:00
|
|
|
import { Script } from "../../Script/Script";
|
|
|
|
import { TextFile } from "../../TextFile";
|
2021-10-29 05:04:26 +02:00
|
|
|
import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculations";
|
2021-08-20 07:21:37 +02:00
|
|
|
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
|
|
|
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
2021-10-08 06:21:30 +02:00
|
|
|
import { CursorPositions } from "../CursorPositions";
|
2021-10-28 05:19:19 +02:00
|
|
|
|
2021-08-21 06:17:26 +02:00
|
|
|
import { NetscriptFunctions } from "../../NetscriptFunctions";
|
|
|
|
import { WorkerScript } from "../../Netscript/WorkerScript";
|
|
|
|
import { Settings } from "../../Settings/Settings";
|
2021-09-09 05:47:34 +02:00
|
|
|
import { iTutorialNextStep, ITutorial, iTutorialSteps } from "../../InteractiveTutorial";
|
2021-10-11 22:59:37 +02:00
|
|
|
import { debounce } from "lodash";
|
2021-10-11 23:57:17 +02:00
|
|
|
import { saveObject } from "../../SaveObject";
|
2021-10-12 16:56:19 +02:00
|
|
|
import { loadThemes } from "./themes";
|
2021-12-14 02:08:58 +01:00
|
|
|
import { GetServer } from "../../Server/AllServers";
|
2021-08-21 06:17:26 +02:00
|
|
|
|
2021-09-25 05:36:28 +02:00
|
|
|
import Button from "@mui/material/Button";
|
2021-12-18 22:26:50 +01:00
|
|
|
import Tooltip from "@mui/material/Tooltip";
|
2021-09-25 05:36:28 +02:00
|
|
|
import Typography from "@mui/material/Typography";
|
|
|
|
import Link from "@mui/material/Link";
|
|
|
|
import Box from "@mui/material/Box";
|
|
|
|
import IconButton from "@mui/material/IconButton";
|
|
|
|
import SettingsIcon from "@mui/icons-material/Settings";
|
|
|
|
|
2021-10-30 18:34:14 +02:00
|
|
|
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
|
2021-10-28 05:19:19 +02:00
|
|
|
|
2021-10-10 04:59:06 +02:00
|
|
|
let symbolsLoaded = false;
|
2021-08-21 06:17:26 +02:00
|
|
|
let symbols: string[] = [];
|
2021-10-05 03:06:55 +02:00
|
|
|
export function SetupTextEditor(): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
const ns = NetscriptFunctions({} as WorkerScript);
|
2021-08-21 06:17:26 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
function populate(ns: any): string[] {
|
|
|
|
let symbols: string[] = [];
|
|
|
|
const keys = Object.keys(ns);
|
|
|
|
for (const key of keys) {
|
|
|
|
if (typeof ns[key] === "object") {
|
|
|
|
symbols.push(key);
|
|
|
|
symbols = symbols.concat(populate(ns[key]));
|
|
|
|
}
|
|
|
|
if (typeof ns[key] === "function") {
|
|
|
|
symbols.push(key);
|
|
|
|
}
|
2021-08-21 06:17:26 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
return symbols;
|
|
|
|
}
|
|
|
|
symbols = populate(ns);
|
2021-09-05 02:14:33 +02:00
|
|
|
|
2021-10-27 21:16:16 +02:00
|
|
|
const exclude = ["heart", "break", "exploit", "bypass", "corporation", "alterReality"];
|
2021-10-16 00:25:22 +02:00
|
|
|
symbols = symbols.filter((symbol: string) => !exclude.includes(symbol)).sort();
|
2021-10-05 03:06:55 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
|
|
|
interface IProps {
|
2021-09-05 01:09:30 +02:00
|
|
|
filename: string;
|
|
|
|
code: string;
|
2021-10-16 03:27:02 +02:00
|
|
|
hostname: string;
|
2021-09-05 01:09:30 +02:00
|
|
|
player: IPlayer;
|
2021-09-17 08:58:02 +02:00
|
|
|
router: IRouter;
|
2021-08-27 01:14:56 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-08-21 07:54:39 +02:00
|
|
|
/*
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2021-08-20 07:21:37 +02:00
|
|
|
// 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
|
2021-08-20 10:03:00 +02:00
|
|
|
// https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages
|
2021-08-20 19:57:32 +02:00
|
|
|
// https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39
|
2021-10-16 00:27:02 +02:00
|
|
|
// https://blog.checklyhq.com/customizing-monaco/
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-12-17 14:36:33 +01:00
|
|
|
// Holds all the data for a open script
|
|
|
|
class openScript {
|
|
|
|
fileName: string;
|
|
|
|
code: string;
|
|
|
|
hostname: string;
|
|
|
|
lastPosition: monaco.Position;
|
|
|
|
model: ITextModel;
|
|
|
|
|
|
|
|
constructor(fileName: string, code: string, hostname: string, lastPosition: monaco.Position, model: ITextModel) {
|
|
|
|
this.fileName = fileName;
|
|
|
|
this.code = code;
|
|
|
|
this.hostname = hostname;
|
|
|
|
this.lastPosition = lastPosition;
|
|
|
|
this.model = model;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-18 22:26:50 +01:00
|
|
|
const openScripts = new Array<openScript>(); // Holds all open scripts
|
|
|
|
let currentScript = {} as openScript; // Script currently being viewed
|
2021-08-23 08:09:49 +02:00
|
|
|
|
2021-08-20 07:21:37 +02:00
|
|
|
export function Root(props: IProps): React.ReactElement {
|
2021-09-05 01:09:30 +02:00
|
|
|
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
|
2021-12-17 14:36:33 +01:00
|
|
|
const monacoRef = useRef<Monaco | null>(null);
|
2021-12-19 19:04:34 +01:00
|
|
|
const [filename] = useState(props.filename);
|
|
|
|
const [code] = useState<string>(props.code);
|
2021-10-29 05:04:26 +02:00
|
|
|
const [decorations, setDecorations] = useState<string[]>([]);
|
2021-09-05 01:09:30 +02:00
|
|
|
const [ram, setRAM] = useState("RAM: ???");
|
2021-10-11 22:59:37 +02:00
|
|
|
const [updatingRam, setUpdatingRam] = useState(false);
|
2021-09-25 05:36:28 +02:00
|
|
|
const [optionsOpen, setOptionsOpen] = useState(false);
|
2021-09-05 01:09:30 +02:00
|
|
|
const [options, setOptions] = useState<Options>({
|
|
|
|
theme: Settings.MonacoTheme,
|
|
|
|
insertSpaces: Settings.MonacoInsertSpaces,
|
2021-10-05 03:06:55 +02:00
|
|
|
fontSize: Settings.MonacoFontSize,
|
2021-09-05 01:09:30 +02:00
|
|
|
});
|
|
|
|
|
2021-10-11 22:59:37 +02:00
|
|
|
const debouncedSetRAM = useMemo(
|
|
|
|
() =>
|
|
|
|
debounce((s) => {
|
|
|
|
setRAM(s);
|
|
|
|
setUpdatingRam(false);
|
|
|
|
}, 300),
|
|
|
|
[],
|
|
|
|
);
|
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
function save(): void {
|
2021-09-19 06:46:39 +02:00
|
|
|
// this is duplicate code with saving later.
|
2021-09-09 05:47:34 +02:00
|
|
|
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
|
2021-09-05 01:09:30 +02:00
|
|
|
//Make sure filename + code properly follow tutorial
|
2021-12-17 14:36:33 +01:00
|
|
|
if (currentScript.fileName !== "n00dles.script") {
|
2021-10-16 03:27:02 +02:00
|
|
|
dialogBoxCreate("Leave the script name as 'n00dles.script'!");
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-12-17 14:36:33 +01:00
|
|
|
if (currentScript.code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) {
|
2021-09-05 01:09:30 +02:00
|
|
|
dialogBoxCreate("Please copy and paste the code from the tutorial!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Save the script
|
2021-12-17 14:36:33 +01:00
|
|
|
saveScript(currentScript);
|
2021-09-19 06:46:39 +02:00
|
|
|
|
|
|
|
iTutorialNextStep();
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-09-19 06:46:39 +02:00
|
|
|
props.router.toTerminal();
|
|
|
|
return;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-12-17 14:36:33 +01:00
|
|
|
if (currentScript.fileName == "") {
|
2021-09-05 01:09:30 +02:00
|
|
|
dialogBoxCreate("You must specify a filename!");
|
|
|
|
return;
|
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-12-17 14:36:33 +01:00
|
|
|
if (!isValidFilePath(currentScript.fileName)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
dialogBoxCreate(
|
|
|
|
"Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.",
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-12-17 14:36:33 +01:00
|
|
|
const server = GetServer(currentScript.hostname);
|
2021-09-09 05:47:34 +02:00
|
|
|
if (server === null) throw new Error("Server should not be null but it is.");
|
2021-12-17 14:36:33 +01:00
|
|
|
if (isScriptFilename(currentScript.fileName)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
//If the current script already exists on the server, overwrite it
|
|
|
|
for (let i = 0; i < server.scripts.length; i++) {
|
2021-12-17 14:36:33 +01:00
|
|
|
if (currentScript.fileName == server.scripts[i].filename) {
|
2021-12-18 22:26:50 +01:00
|
|
|
server.scripts[i].saveScript(
|
|
|
|
currentScript.fileName,
|
|
|
|
currentScript.code,
|
|
|
|
props.player.currentServer,
|
|
|
|
server.scripts,
|
|
|
|
);
|
2021-10-12 00:47:05 +02:00
|
|
|
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
2021-08-20 07:21:37 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
//If the current script does NOT exist, create a new one
|
|
|
|
const script = new Script();
|
2021-12-17 14:36:33 +01:00
|
|
|
script.saveScript(currentScript.fileName, currentScript.code, props.player.currentServer, server.scripts);
|
2021-09-05 01:09:30 +02:00
|
|
|
server.scripts.push(script);
|
2021-12-17 14:36:33 +01:00
|
|
|
} else if (currentScript.fileName.endsWith(".txt")) {
|
2021-09-05 01:09:30 +02:00
|
|
|
for (let i = 0; i < server.textFiles.length; ++i) {
|
2021-12-17 14:36:33 +01:00
|
|
|
if (server.textFiles[i].fn === currentScript.fileName) {
|
|
|
|
server.textFiles[i].write(currentScript.code);
|
2021-10-12 00:47:05 +02:00
|
|
|
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
2021-08-20 07:21:37 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-12-17 14:36:33 +01:00
|
|
|
const textFile = new TextFile(currentScript.fileName, currentScript.code);
|
2021-09-05 01:09:30 +02:00
|
|
|
server.textFiles.push(textFile);
|
|
|
|
} else {
|
2021-09-09 05:47:34 +02:00
|
|
|
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-10-11 23:57:17 +02:00
|
|
|
|
2021-10-12 00:47:05 +02:00
|
|
|
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
function beautify(): void {
|
|
|
|
if (editorRef.current === null) return;
|
2021-10-23 20:40:46 +02:00
|
|
|
editorRef.current.getAction("editor.action.formatDocument").run();
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-10-29 07:23:15 +02:00
|
|
|
function infLoop(newCode: string): void {
|
|
|
|
if (editorRef.current === null) return;
|
2021-12-17 14:36:33 +01:00
|
|
|
if (!currentScript.fileName.endsWith(".ns") && !currentScript.fileName.endsWith(".js")) return;
|
2021-10-29 07:23:15 +02:00
|
|
|
const awaitWarning = checkInfiniteLoop(newCode);
|
|
|
|
if (awaitWarning !== -1) {
|
|
|
|
const newDecorations = editorRef.current.deltaDecorations(decorations, [
|
|
|
|
{
|
|
|
|
range: {
|
|
|
|
startLineNumber: awaitWarning,
|
|
|
|
startColumn: 1,
|
|
|
|
endLineNumber: awaitWarning,
|
|
|
|
endColumn: 10,
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
isWholeLine: true,
|
|
|
|
glyphMarginClassName: "myGlyphMarginClass",
|
|
|
|
glyphMarginHoverMessage: {
|
|
|
|
value: "Possible infinite loop, await something.",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
setDecorations(newDecorations);
|
|
|
|
} else {
|
|
|
|
const newDecorations = editorRef.current.deltaDecorations(decorations, []);
|
|
|
|
setDecorations(newDecorations);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
function updateCode(newCode?: string): void {
|
|
|
|
if (newCode === undefined) return;
|
2021-10-11 22:59:37 +02:00
|
|
|
updateRAM(newCode);
|
2021-12-17 14:36:33 +01:00
|
|
|
currentScript.code = newCode;
|
2021-11-02 21:10:01 +01:00
|
|
|
try {
|
|
|
|
if (editorRef.current !== null) {
|
|
|
|
infLoop(newCode);
|
|
|
|
}
|
2021-12-18 22:26:50 +01:00
|
|
|
} catch (err) {}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-10-11 22:59:37 +02:00
|
|
|
// calculate it once the first time the file is loaded.
|
|
|
|
useEffect(() => {
|
2021-12-17 14:36:33 +01:00
|
|
|
updateRAM(currentScript.code);
|
2021-10-11 22:59:37 +02:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
async function updateRAM(newCode: string): Promise<void> {
|
|
|
|
setUpdatingRam(true);
|
|
|
|
const codeCopy = newCode + "";
|
2021-09-09 05:47:34 +02:00
|
|
|
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (ramUsage > 0) {
|
2021-10-11 22:59:37 +02:00
|
|
|
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
2021-08-20 07:21:37 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
switch (ramUsage) {
|
|
|
|
case RamCalculationErrorCode.ImportError: {
|
2021-10-11 22:59:37 +02:00
|
|
|
debouncedSetRAM("RAM: Import Error");
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case RamCalculationErrorCode.URLImportError: {
|
2021-10-11 22:59:37 +02:00
|
|
|
debouncedSetRAM("RAM: HTTP Import Error");
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case RamCalculationErrorCode.SyntaxError:
|
|
|
|
default: {
|
2021-10-11 22:59:37 +02:00
|
|
|
debouncedSetRAM("RAM: Syntax Error");
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
return new Promise<void>(() => undefined);
|
|
|
|
}
|
2021-08-20 07:21:37 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
useEffect(() => {
|
2021-09-06 21:06:08 +02:00
|
|
|
function maybeSave(event: KeyboardEvent): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (Settings.DisableHotkeys) return;
|
2021-12-20 05:34:27 +01:00
|
|
|
|
|
|
|
// CTRL/CMD + S
|
|
|
|
if (event.code == `KeyS` && (event.ctrlKey || event.metaKey)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
event.preventDefault();
|
2021-12-20 05:34:27 +01:00
|
|
|
event.stopPropagation();
|
2021-09-05 01:09:30 +02:00
|
|
|
save();
|
|
|
|
}
|
2021-08-20 07:57:32 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
document.addEventListener("keydown", maybeSave);
|
|
|
|
return () => document.removeEventListener("keydown", maybeSave);
|
|
|
|
});
|
2021-08-20 07:57:32 +02:00
|
|
|
|
2021-12-17 14:36:33 +01:00
|
|
|
// Generates a new model for the script
|
2021-12-18 22:26:50 +01:00
|
|
|
function regenerateModel(script: openScript): void {
|
2021-12-17 14:36:33 +01:00
|
|
|
if (monacoRef.current !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
script.model = monacoRef.current.editor.createModel(script.code, "javascript");
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets the currently viewed script
|
2021-12-18 22:26:50 +01:00
|
|
|
function setCurrentScript(script: openScript): void {
|
2021-12-17 14:36:33 +01:00
|
|
|
// Update last position
|
|
|
|
if (editorRef.current !== null) {
|
|
|
|
if (currentScript !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
const currentPosition = editorRef.current.getPosition();
|
2021-12-17 14:36:33 +01:00
|
|
|
if (currentPosition !== null) {
|
|
|
|
currentScript.lastPosition = currentPosition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
editorRef.current.setModel(script.model);
|
|
|
|
currentScript = script;
|
|
|
|
editorRef.current.setPosition(currentScript.lastPosition);
|
|
|
|
editorRef.current.revealLine(currentScript.lastPosition.lineNumber);
|
|
|
|
updateRAM(currentScript.code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets a currently opened script
|
2021-12-18 22:26:50 +01:00
|
|
|
function getOpenedScript(fileName: string, hostname: string): openScript | null {
|
2021-12-17 14:36:33 +01:00
|
|
|
for (const script of openScripts) {
|
|
|
|
if (script.fileName === fileName && script.hostname === hostname) {
|
|
|
|
return script;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-12-18 22:26:50 +01:00
|
|
|
function saveScript(script: openScript): void {
|
2021-12-17 14:36:33 +01:00
|
|
|
const server = GetServer(script.hostname);
|
|
|
|
if (server === null) throw new Error("Server should not be null but it is.");
|
|
|
|
let found = false;
|
|
|
|
for (let i = 0; i < server.scripts.length; i++) {
|
|
|
|
if (script.fileName == server.scripts[i].filename) {
|
|
|
|
server.scripts[i].saveScript(script.fileName, script.code, script.hostname, server.scripts);
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
const newScript = new Script();
|
|
|
|
newScript.saveScript(script.fileName, script.code, script.hostname, server.scripts);
|
|
|
|
server.scripts.push(newScript);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
editorRef.current = editor;
|
2021-12-17 14:36:33 +01:00
|
|
|
monacoRef.current = monaco;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (editorRef.current === null) return;
|
|
|
|
const position = CursorPositions.getCursor(filename);
|
|
|
|
if (position.row !== -1)
|
|
|
|
editorRef.current.setPosition({
|
|
|
|
lineNumber: position.row,
|
|
|
|
column: position.column,
|
|
|
|
});
|
|
|
|
editorRef.current.focus();
|
2021-12-17 14:36:33 +01:00
|
|
|
|
|
|
|
const script = getOpenedScript(filename, props.player.getCurrentServer().hostname);
|
|
|
|
|
|
|
|
// Check if script is already opened, if so switch to that model
|
|
|
|
if (script !== null) {
|
|
|
|
if (script.model.isDisposed()) {
|
|
|
|
regenerateModel(script);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentScript(script);
|
|
|
|
} else {
|
|
|
|
if (filename !== undefined) {
|
|
|
|
// Create new model
|
|
|
|
if (monacoRef.current !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
const newScript = new openScript(
|
|
|
|
filename,
|
|
|
|
code,
|
|
|
|
props.player.getCurrentServer().hostname,
|
|
|
|
new monaco.Position(0, 0),
|
|
|
|
monacoRef.current.editor.createModel(code, "javascript"),
|
|
|
|
);
|
2021-12-17 14:36:33 +01:00
|
|
|
setCurrentScript(newScript);
|
|
|
|
openScripts.push(newScript);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Script Editor was opened by the sidebar button
|
|
|
|
if (currentScript.model !== undefined) {
|
|
|
|
if (currentScript.model.isDisposed()) {
|
|
|
|
// Create new model, old one was disposed of
|
|
|
|
regenerateModel(currentScript);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentScript(currentScript);
|
|
|
|
} else {
|
|
|
|
// Create a new temporary file
|
|
|
|
if (monacoRef.current !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
const newScript = new openScript(
|
|
|
|
"newfile.script",
|
|
|
|
"",
|
|
|
|
props.player.getCurrentServer().hostname,
|
|
|
|
new monaco.Position(0, 0),
|
|
|
|
monacoRef.current.editor.createModel("", "javascript"),
|
|
|
|
);
|
2021-12-17 14:36:33 +01:00
|
|
|
setCurrentScript(newScript);
|
|
|
|
openScripts.push(newScript);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-08-20 07:57:32 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
function beforeMount(monaco: any): void {
|
2021-10-10 04:59:06 +02:00
|
|
|
if (symbolsLoaded) return;
|
|
|
|
symbolsLoaded = true;
|
2021-09-05 01:09:30 +02:00
|
|
|
monaco.languages.registerCompletionItemProvider("javascript", {
|
|
|
|
provideCompletionItems: () => {
|
|
|
|
const suggestions = [];
|
|
|
|
for (const symbol of symbols) {
|
|
|
|
suggestions.push({
|
|
|
|
label: symbol,
|
|
|
|
kind: monaco.languages.CompletionItemKind.Function,
|
|
|
|
insertText: symbol,
|
2021-09-09 05:47:34 +02:00
|
|
|
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
2021-09-05 01:09:30 +02:00
|
|
|
});
|
2021-08-21 07:54:39 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
return { suggestions: suggestions };
|
|
|
|
},
|
|
|
|
});
|
2021-10-16 00:25:22 +02:00
|
|
|
(async function () {
|
2021-10-16 00:58:10 +02:00
|
|
|
// We have to improve the default js language otherwise theme sucks
|
2021-10-16 00:25:22 +02:00
|
|
|
const l = await monaco.languages
|
|
|
|
.getLanguages()
|
|
|
|
.find((l: any) => l.id === "javascript")
|
|
|
|
.loader();
|
|
|
|
l.language.tokenizer.root.unshift(["ns", { token: "ns" }]);
|
2021-11-06 22:46:55 +01:00
|
|
|
for (const symbol of symbols) l.language.tokenizer.root.unshift([symbol, { token: "netscriptfunction" }]);
|
2021-10-16 00:58:10 +02:00
|
|
|
const otherKeywords = ["let", "const", "var", "function"];
|
|
|
|
const otherKeyvars = ["true", "false", "null", "undefined"];
|
|
|
|
otherKeywords.forEach((k) => l.language.tokenizer.root.unshift([k, { token: "otherkeywords" }]));
|
|
|
|
otherKeyvars.forEach((k) => l.language.tokenizer.root.unshift([k, { token: "otherkeyvars" }]));
|
|
|
|
l.language.tokenizer.root.unshift(["this", { token: "this" }]);
|
2021-10-16 00:25:22 +02:00
|
|
|
})();
|
|
|
|
|
2021-10-28 05:19:19 +02:00
|
|
|
const source = (libSource + "").replace(/export /g, "");
|
|
|
|
monaco.languages.typescript.javascriptDefaults.addExtraLib(source, "netscript.d.ts");
|
|
|
|
monaco.languages.typescript.typescriptDefaults.addExtraLib(source, "netscript.d.ts");
|
2021-10-12 16:56:19 +02:00
|
|
|
loadThemes(monaco);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-12-17 14:36:33 +01:00
|
|
|
|
|
|
|
// Change tab highlight from old tab to new tab
|
2021-12-18 22:26:50 +01:00
|
|
|
function changeTabButtonColor(
|
|
|
|
oldButtonFileName: string,
|
|
|
|
oldButtonHostname: string,
|
|
|
|
newButtonFileName: string,
|
|
|
|
newButtonHostname: string,
|
|
|
|
): void {
|
|
|
|
const oldTabButton = document.getElementById("tabButton" + oldButtonFileName + oldButtonHostname);
|
2021-12-17 14:36:33 +01:00
|
|
|
if (oldTabButton !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
oldTabButton.style.backgroundColor = "";
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
2021-12-18 22:26:50 +01:00
|
|
|
const oldTabCloseButton = document.getElementById("tabCloseButton" + oldButtonFileName + oldButtonHostname);
|
2021-12-17 14:36:33 +01:00
|
|
|
if (oldTabCloseButton !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
oldTabCloseButton.style.backgroundColor = "";
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
2021-12-18 22:26:50 +01:00
|
|
|
const newTabButton = document.getElementById("tabButton" + newButtonFileName + newButtonHostname);
|
2021-12-17 14:36:33 +01:00
|
|
|
if (newTabButton !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
newTabButton.style.backgroundColor = "#666";
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
|
2021-12-18 22:26:50 +01:00
|
|
|
const newTabCloseButton = document.getElementById("tabCloseButton" + newButtonFileName + newButtonHostname);
|
2021-12-17 14:36:33 +01:00
|
|
|
if (newTabCloseButton !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
newTabCloseButton.style.backgroundColor = "#666";
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when a script tab was clicked
|
2021-12-18 22:26:50 +01:00
|
|
|
function onTabButtonClick(e: React.MouseEvent<HTMLButtonElement>): void {
|
|
|
|
const valSplit = e.currentTarget.value.split(":");
|
2021-12-17 14:36:33 +01:00
|
|
|
const fileName = valSplit[0];
|
|
|
|
const hostname = valSplit[1];
|
|
|
|
|
|
|
|
// Change tab highlight from old tab to new tab
|
2021-12-18 22:26:50 +01:00
|
|
|
changeTabButtonColor(currentScript.fileName, currentScript.hostname, fileName, hostname);
|
2021-12-17 14:36:33 +01:00
|
|
|
|
|
|
|
// Update current script
|
|
|
|
const clickedScript = getOpenedScript(fileName, hostname);
|
|
|
|
|
|
|
|
if (clickedScript !== null) {
|
|
|
|
if (clickedScript.model.isDisposed()) {
|
|
|
|
regenerateModel(clickedScript);
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentScript(clickedScript);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when a script tab close button was clicked
|
2021-12-18 22:26:50 +01:00
|
|
|
function onCloseButtonClick(e: React.MouseEvent<HTMLButtonElement>): void {
|
|
|
|
const valSplit = e.currentTarget.value.split(":");
|
2021-12-17 14:36:33 +01:00
|
|
|
const fileName = valSplit[0];
|
|
|
|
const hostname = valSplit[1];
|
|
|
|
|
|
|
|
const scriptToClose = getOpenedScript(fileName, hostname);
|
|
|
|
|
|
|
|
// Save and remove script from openScripts
|
|
|
|
if (scriptToClose !== null) {
|
|
|
|
saveScript(scriptToClose);
|
|
|
|
|
|
|
|
openScripts.splice(openScripts.indexOf(scriptToClose), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (openScripts.length === 0) {
|
|
|
|
// No other scripts are open, create a new temporary file
|
|
|
|
if (monacoRef.current !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
const newScript = new openScript(
|
|
|
|
"newfile.script",
|
|
|
|
"",
|
|
|
|
props.player.getCurrentServer().hostname,
|
|
|
|
new monacoRef.current.Position(0, 0),
|
|
|
|
monacoRef.current.editor.createModel("", "javascript"),
|
|
|
|
);
|
|
|
|
|
|
|
|
setCurrentScript(newScript);
|
2021-12-17 14:36:33 +01:00
|
|
|
openScripts.push(newScript);
|
|
|
|
|
|
|
|
// Modify button for temp file
|
2021-12-18 22:26:50 +01:00
|
|
|
const parent = e.currentTarget.parentElement;
|
2021-12-17 14:36:33 +01:00
|
|
|
if (parent !== null) {
|
2021-12-18 22:26:50 +01:00
|
|
|
(parent.children[0] as HTMLButtonElement).value = "newfile.script:home";
|
|
|
|
(parent.children[0] as HTMLButtonElement).textContent = "newfile.script";
|
|
|
|
e.currentTarget.value = "newfile.script:home";
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (openScripts[0].model.isDisposed()) {
|
|
|
|
regenerateModel(openScripts[0]);
|
|
|
|
}
|
|
|
|
|
2021-12-18 22:26:50 +01:00
|
|
|
changeTabButtonColor(
|
|
|
|
currentScript.fileName,
|
|
|
|
currentScript.hostname,
|
|
|
|
openScripts[0].fileName,
|
|
|
|
openScripts[0].hostname,
|
|
|
|
);
|
2021-12-17 14:36:33 +01:00
|
|
|
|
|
|
|
setCurrentScript(openScripts[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a button for each open script
|
|
|
|
const scriptButtons = [];
|
|
|
|
for (let i = 0; i < openScripts.length; i++) {
|
2021-12-18 22:26:50 +01:00
|
|
|
if (openScripts[i].fileName !== "") {
|
2021-12-17 14:36:33 +01:00
|
|
|
const fileName2 = openScripts[i].fileName;
|
|
|
|
const hostname = openScripts[i].hostname;
|
|
|
|
if (openScripts[i].fileName === currentScript.fileName && openScripts[i].hostname === currentScript.hostname) {
|
|
|
|
// Set special background color for current script tab button
|
2021-12-18 22:26:50 +01:00
|
|
|
scriptButtons.push(
|
|
|
|
<Tooltip
|
|
|
|
title={
|
|
|
|
<Typography>
|
|
|
|
{hostname}:~/{fileName2}
|
|
|
|
</Typography>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div key={fileName2 + hostname} style={{ paddingRight: "5px" }}>
|
|
|
|
<Button style={{ backgroundColor: "#666" }} value={fileName2 + ":" + hostname} onClick={onTabButtonClick}>
|
|
|
|
{openScripts[i].fileName}
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
value={fileName2 + ":" + hostname}
|
|
|
|
onClick={onCloseButtonClick}
|
|
|
|
style={{ maxWidth: "20px", minWidth: "20px", backgroundColor: "#666" }}
|
|
|
|
>
|
|
|
|
x
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</Tooltip>,
|
|
|
|
);
|
2021-12-17 14:36:33 +01:00
|
|
|
} else {
|
2021-12-18 22:26:50 +01:00
|
|
|
scriptButtons.push(
|
|
|
|
<div id={"scriptEditorTab" + fileName2 + hostname} key={"tabButton" + i} style={{ paddingRight: "5px" }}>
|
|
|
|
<Button
|
|
|
|
id={"tabButton" + openScripts[i].fileName + openScripts[i].hostname}
|
|
|
|
value={fileName2 + ":" + hostname}
|
|
|
|
onClick={onTabButtonClick}
|
|
|
|
>
|
|
|
|
{openScripts[i].fileName}
|
|
|
|
</Button>
|
|
|
|
<Button
|
|
|
|
id={"tabCloseButton" + openScripts[i].fileName + openScripts[i].hostname}
|
|
|
|
value={fileName2 + ":" + hostname}
|
|
|
|
onClick={onCloseButtonClick}
|
|
|
|
style={{ maxWidth: "20px", minWidth: "20px" }}
|
|
|
|
>
|
|
|
|
x
|
|
|
|
</Button>
|
|
|
|
</div>,
|
|
|
|
);
|
2021-12-17 14:36:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-15 22:14:27 +02:00
|
|
|
// 370px 71%, 725px 85.1%, 1085px 90%, 1300px 91.7%
|
|
|
|
// fuck around in desmos until you find a function
|
|
|
|
const p = 11000 / -window.innerHeight + 100;
|
2021-09-05 01:09:30 +02:00
|
|
|
return (
|
2021-09-17 08:04:44 +02:00
|
|
|
<>
|
2021-12-18 22:26:50 +01:00
|
|
|
<Box display="flex" flexDirection="row" alignItems="center" paddingBottom="5px">
|
2021-12-17 14:36:33 +01:00
|
|
|
{scriptButtons}
|
2021-09-25 05:36:28 +02:00
|
|
|
</Box>
|
2021-09-05 01:09:30 +02:00
|
|
|
<Editor
|
|
|
|
beforeMount={beforeMount}
|
|
|
|
onMount={onMount}
|
2021-10-01 19:08:37 +02:00
|
|
|
loading={<Typography>Loading script editor!</Typography>}
|
2021-10-15 22:14:27 +02:00
|
|
|
height={p + "%"}
|
2021-10-10 04:59:06 +02:00
|
|
|
defaultLanguage="javascript"
|
2021-09-05 01:09:30 +02:00
|
|
|
defaultValue={code}
|
|
|
|
onChange={updateCode}
|
|
|
|
theme={options.theme}
|
2021-10-29 05:04:26 +02:00
|
|
|
options={{ ...options, glyphMargin: true }}
|
2021-09-05 01:09:30 +02:00
|
|
|
/>
|
2021-09-25 05:36:28 +02:00
|
|
|
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
|
|
|
|
<Button onClick={beautify}>Beautify</Button>
|
2021-12-18 22:26:50 +01:00
|
|
|
<Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
|
2021-10-11 22:59:37 +02:00
|
|
|
{ram}
|
|
|
|
</Typography>
|
2021-12-20 05:34:27 +01:00
|
|
|
<Button onClick={save}>Save (CTRL/CMD + S)</Button>
|
2021-12-18 14:11:59 +01:00
|
|
|
<Typography sx={{ mx: 1 }}>
|
2021-11-18 01:56:17 +01:00
|
|
|
{" "}
|
|
|
|
Documentation:{" "}
|
|
|
|
<Link target="_blank" href="https://bitburner.readthedocs.io/en/latest/index.html">
|
|
|
|
Basic
|
|
|
|
</Link>{" "}
|
|
|
|
|
|
|
|
|
<Link target="_blank" href="https://github.com/danielyxie/bitburner/blob/dev/markdown/bitburner.ns.md">
|
|
|
|
Full
|
|
|
|
</Link>
|
|
|
|
</Typography>
|
2021-12-18 22:26:50 +01:00
|
|
|
<IconButton style={{ marginLeft: "auto" }} onClick={() => setOptionsOpen(true)}>
|
2021-12-17 14:36:33 +01:00
|
|
|
<>
|
|
|
|
<SettingsIcon />
|
|
|
|
options
|
|
|
|
</>
|
|
|
|
</IconButton>
|
2021-09-25 05:36:28 +02:00
|
|
|
</Box>
|
|
|
|
<OptionsModal
|
|
|
|
open={optionsOpen}
|
|
|
|
onClose={() => setOptionsOpen(false)}
|
|
|
|
options={{
|
|
|
|
theme: Settings.MonacoTheme,
|
|
|
|
insertSpaces: Settings.MonacoInsertSpaces,
|
2021-10-05 03:06:55 +02:00
|
|
|
fontSize: Settings.MonacoFontSize,
|
2021-09-25 05:36:28 +02:00
|
|
|
}}
|
|
|
|
save={(options: Options) => {
|
|
|
|
setOptions(options);
|
|
|
|
Settings.MonacoTheme = options.theme;
|
|
|
|
Settings.MonacoInsertSpaces = options.insertSpaces;
|
2021-10-05 03:06:55 +02:00
|
|
|
Settings.MonacoFontSize = options.fontSize;
|
2021-09-25 05:36:28 +02:00
|
|
|
}}
|
|
|
|
/>
|
2021-09-17 08:04:44 +02:00
|
|
|
</>
|
2021-09-05 01:09:30 +02:00
|
|
|
);
|
2021-12-18 22:26:50 +01:00
|
|
|
}
|