resolved merge issue

This commit is contained in:
BuildTools 2021-12-20 00:48:03 -06:00
parent 3255768259
commit 6dc7dc42c5
4 changed files with 473 additions and 459 deletions

@ -22,6 +22,7 @@
"@types/escodegen": "^0.0.7", "@types/escodegen": "^0.0.7",
"@types/numeral": "0.0.25", "@types/numeral": "0.0.25",
"@types/react": "^17.0.21", "@types/react": "^17.0.21",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"@types/react-resizable": "^1.7.3", "@types/react-resizable": "^1.7.3",
"acorn": "^8.4.1", "acorn": "^8.4.1",
@ -41,6 +42,7 @@
"numeral": "2.0.6", "numeral": "2.0.6",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"react-resizable": "^3.0.4", "react-resizable": "^3.0.4",

@ -16,6 +16,7 @@ import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculatio
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes"; import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { CursorPositions } from "../CursorPositions"; import { CursorPositions } from "../CursorPositions";
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { NetscriptFunctions } from "../../NetscriptFunctions"; import { NetscriptFunctions } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript";
@ -33,14 +34,26 @@ import Link from "@mui/material/Link";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import { PromptEvent } from "../../ui/React/PromptManager";
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts"; import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
interface IProps {
filename: string;
code: string;
hostname: string;
player: IPlayer;
router: IRouter;
}
// TODO: try to removve global symbols
let symbolsLoaded = false; let symbolsLoaded = false;
let symbols: string[] = []; let symbols: string[] = [];
export function SetupTextEditor(): void { export function SetupTextEditor(): void {
const ns = NetscriptFunctions({} as WorkerScript); const ns = NetscriptFunctions({} as WorkerScript);
// Populates symbols for text editor
function populate(ns: any): string[] { function populate(ns: any): string[] {
let symbols: string[] = []; let symbols: string[] = [];
const keys = Object.keys(ns); const keys = Object.keys(ns);
@ -53,36 +66,19 @@ export function SetupTextEditor(): void {
symbols.push(key); symbols.push(key);
} }
} }
return symbols; return symbols;
} }
symbols = populate(ns); symbols = populate(ns);
const exclude = ["heart", "break", "exploit", "bypass", "corporation", "alterReality"]; const exclude = ["heart", "break", "exploit", "bypass", "corporation", "alterReality"];
symbols = symbols.filter((symbol: string) => !exclude.includes(symbol)).sort(); symbols = symbols.filter((symbol: string) => !exclude.includes(symbol)).sort();
} }
interface IProps {
filename: string;
code: string;
hostname: string;
player: IPlayer;
router: IRouter;
}
/*
*/
// 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/
// Holds all the data for a open script // Holds all the data for a open script
class openScript { class OpenScript {
fileName: string; fileName: string;
code: string; code: string;
hostname: string; hostname: string;
@ -98,17 +94,23 @@ class openScript {
} }
} }
const openScripts = new Array<openScript>(); // Holds all open scripts // Called every time script editor is opened
let currentScript = {} as openScript; // Script currently being viewed
export function Root(props: IProps): React.ReactElement { export function Root(props: IProps): React.ReactElement {
const editorRef = useRef<IStandaloneCodeEditor | null>(null); const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const monacoRef = useRef<Monaco | null>(null); const monacoRef = useRef<Monaco | null>(null);
const [filename] = useState(props.filename);
const [code] = useState<string>(props.code); const [openScripts, setOpenScripts] = useState<OpenScript[]>(
const [decorations, setDecorations] = useState<string[]>([]); window.localStorage.getItem('scriptEditorOpenScripts') !== null ? JSON.parse(window.localStorage.getItem('scriptEditorOpenScripts')!) : []
);
const [currentScript, setCurrentScript] = useState<OpenScript | null>(
window.localStorage.getItem('scriptEditorCurrentScript') !== null ? JSON.parse(window.localStorage.getItem('scriptEditorCurrentScript')!) : null
);
const [ram, setRAM] = useState("RAM: ???"); const [ram, setRAM] = useState("RAM: ???");
const [updatingRam, setUpdatingRam] = useState(false); const [updatingRam, setUpdatingRam] = useState(false);
const [decorations, setDecorations] = useState<string[]>([]);
const [optionsOpen, setOptionsOpen] = useState(false); const [optionsOpen, setOptionsOpen] = useState(false);
const [options, setOptions] = useState<Options>({ const [options, setOptions] = useState<Options>({
theme: Settings.MonacoTheme, theme: Settings.MonacoTheme,
@ -116,6 +118,46 @@ export function Root(props: IProps): React.ReactElement {
fontSize: Settings.MonacoFontSize, fontSize: Settings.MonacoFontSize,
}); });
useEffect(() => {
// Save currentScript
window.localStorage.setItem('scriptEditorCurrentScript', JSON.stringify(currentScript, (key, value) => {
if (key == 'model') return undefined;
return value;
}));
// Save openScripts
window.localStorage.setItem('scriptEditorOpenScripts', JSON.stringify(openScripts, (key, value) => {
if (key == 'model') return undefined;
return value;
}))
}, [currentScript, openScripts])
useEffect(() => {
if (currentScript !== null) {
updateRAM(currentScript.code);
}
}, []);
useEffect(() => {
function maybeSave(event: KeyboardEvent): void {
if (Settings.DisableHotkeys) return;
//Ctrl + b
if (event.keyCode == 66 && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
save();
}
}
document.addEventListener("keydown", maybeSave);
return () => document.removeEventListener("keydown", maybeSave);
});
// Generates a new model for the script
function regenerateModel(script: OpenScript): void {
if (monacoRef.current !== null) {
script.model = monacoRef.current.editor.createModel(script.code, "javascript");
}
}
const debouncedSetRAM = useMemo( const debouncedSetRAM = useMemo(
() => () =>
debounce((s) => { debounce((s) => {
@ -125,7 +167,218 @@ export function Root(props: IProps): React.ReactElement {
[], [],
); );
async function updateRAM(newCode: string): Promise<void> {
setUpdatingRam(true);
const codeCopy = newCode + "";
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage > 0) {
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
return;
}
switch (ramUsage) {
case RamCalculationErrorCode.ImportError: {
debouncedSetRAM("RAM: Import Error");
break;
}
case RamCalculationErrorCode.URLImportError: {
debouncedSetRAM("RAM: HTTP Import Error");
break;
}
case RamCalculationErrorCode.SyntaxError:
default: {
debouncedSetRAM("RAM: Syntax Error");
break;
}
}
return new Promise<void>(() => undefined);
}
// Formats the code
function beautify(): void {
if (editorRef.current === null) return;
editorRef.current.getAction("editor.action.formatDocument").run();
}
// Before the editor is mounted
function beforeMount(monaco: any): void {
if (symbolsLoaded) return;
// Setup monaco auto completion
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 () {
// We have to improve the default js language otherwise theme sucks
const l = await monaco.languages
.getLanguages()
.find((l: any) => l.id === "javascript")
.loader();
l.language.tokenizer.root.unshift(["ns", { token: "ns" }]);
for (const symbol of symbols) l.language.tokenizer.root.unshift([symbol, { token: "netscriptfunction" }]);
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" }]);
})();
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);
}
// When the editor is mounted
function onMount(editor: IStandaloneCodeEditor, monaco: Monaco) {
editorRef.current = editor;
monacoRef.current = monaco;
if (editorRef.current === null || monacoRef.current === null) return;
if (props.filename) {
// Check if file is already opened
let openScriptIndex = openScripts.findIndex(script => script.fileName === props.filename && script.hostname === props.hostname);
if (openScriptIndex !== -1) {
// Script is already opened
if (openScripts[openScriptIndex].model === undefined || openScripts[openScriptIndex].model === null || openScripts[openScriptIndex].model.isDisposed()) {
regenerateModel(openScripts[openScriptIndex]);
}
setCurrentScript(openScripts[openScriptIndex]);
editorRef.current.setModel(openScripts[openScriptIndex].model);
editorRef.current.setPosition(openScripts[openScriptIndex].lastPosition);
editorRef.current.revealLineInCenter(openScripts[openScriptIndex].lastPosition.lineNumber);
updateRAM(openScripts[openScriptIndex].code);
} else {
// Open script
var newScript = new OpenScript(props.filename, props.code, props.hostname, new monacoRef.current.Position(0, 0), monacoRef.current.editor.createModel(props.code, 'javascript'));
setOpenScripts(oldArray => [...oldArray, newScript]);
setCurrentScript({ ...newScript });
editorRef.current.setModel(newScript.model);
updateRAM(newScript.code);
}
} else if (currentScript !== null) {
// Open currentscript
regenerateModel(currentScript);
editorRef.current.setModel(currentScript.model);
editorRef.current.setPosition(currentScript.lastPosition);
editorRef.current.revealLineInCenter(currentScript.lastPosition.lineNumber);
updateRAM(currentScript.code);
}
}
function infLoop(newCode: string): void {
if (editorRef.current === null || currentScript === null) return;
if (!currentScript.fileName.endsWith(".ns") && !currentScript.fileName.endsWith(".js")) return;
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);
}
}
// When the code is updated within the editor
function updateCode(newCode?: string) {
if (newCode === undefined) return;
updateRAM(newCode);
if (editorRef.current !== null) {
var newPos = editorRef.current.getPosition();
if (newPos === null) return;
setCurrentScript(oldScript => ({ ...oldScript!, code: newCode, lastPosition: newPos! }))
if (currentScript !== null) {
let curIndex = openScripts.findIndex(script => script.fileName === currentScript.fileName && script.hostname === currentScript.hostname);
let newArr = [...openScripts];
let tempScript = currentScript;
tempScript.code = newCode;
newArr[curIndex] = tempScript;
setOpenScripts([...newArr]);
}
try {
infLoop(newCode);
} catch (err) { }
}
}
function saveScript(scriptToSave: OpenScript): void {
const server = GetServer(scriptToSave.hostname);
if (server === null) throw new Error("Server should not be null but it is.");
if (isScriptFilename(scriptToSave.fileName)) {
//If the current script already exists on the server, overwrite it
for (let i = 0; i < server.scripts.length; i++) {
if (scriptToSave.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(
scriptToSave.fileName,
scriptToSave.code,
props.player.currentServer,
server.scripts,
);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
return;
}
}
//If the current script does NOT exist, create a new one
const script = new Script();
script.saveScript(scriptToSave.fileName, scriptToSave.code, props.player.currentServer, server.scripts);
server.scripts.push(script);
} else if (scriptToSave.fileName.endsWith(".txt")) {
for (let i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === scriptToSave.fileName) {
server.textFiles[i].write(scriptToSave.code);
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
return;
}
}
const textFile = new TextFile(scriptToSave.fileName, scriptToSave.code);
server.textFiles.push(textFile);
} else {
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)");
return;
}
if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
}
function save(): void { function save(): void {
if (currentScript === null) {
console.log("currentScript is null when it shouldn't be. Unabel to save script");
return;
}
// this is duplicate code with saving later. // this is duplicate code with saving later.
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) { if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial //Make sure filename + code properly follow tutorial
@ -172,6 +425,7 @@ export function Root(props: IProps): React.ReactElement {
server.scripts, server.scripts,
); );
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
return; return;
} }
} }
@ -185,6 +439,7 @@ export function Root(props: IProps): React.ReactElement {
if (server.textFiles[i].fn === currentScript.fileName) { if (server.textFiles[i].fn === currentScript.fileName) {
server.textFiles[i].write(currentScript.code); server.textFiles[i].write(currentScript.code);
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
return; return;
} }
} }
@ -196,407 +451,100 @@ export function Root(props: IProps): React.ReactElement {
} }
if (Settings.SaveGameOnFileSave) saveObject.saveGame(); if (Settings.SaveGameOnFileSave) saveObject.saveGame();
props.router.toTerminal();
} }
function beautify(): void { function reorder(list: Array<OpenScript>, startIndex: number, endIndex: number) {
if (editorRef.current === null) return; const result = Array.from(list);
editorRef.current.getAction("editor.action.formatDocument").run(); const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
} }
function infLoop(newCode: string): void { function onDragEnd(result: any) {
if (editorRef.current === null) return; console.log(openScripts);
if (!currentScript.fileName.endsWith(".ns") && !currentScript.fileName.endsWith(".js")) return; // Dropped outside of the list
const awaitWarning = checkInfiniteLoop(newCode); if (!result.destination) {
if (awaitWarning !== -1) { result
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);
}
}
function updateCode(newCode?: string): void {
if (newCode === undefined) return;
updateRAM(newCode);
currentScript.code = newCode;
try {
if (editorRef.current !== null) {
infLoop(newCode);
}
} catch (err) {}
}
// calculate it once the first time the file is loaded.
useEffect(() => {
updateRAM(currentScript.code);
}, []);
async function updateRAM(newCode: string): Promise<void> {
setUpdatingRam(true);
const codeCopy = newCode + "";
const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);
if (ramUsage > 0) {
debouncedSetRAM("RAM: " + numeralWrapper.formatRAM(ramUsage));
return; return;
} }
switch (ramUsage) {
case RamCalculationErrorCode.ImportError: { const items = reorder(openScripts, result.source.index, result.destination.index);
debouncedSetRAM("RAM: Import Error");
break; setOpenScripts(items);
}
case RamCalculationErrorCode.URLImportError: {
debouncedSetRAM("RAM: HTTP Import Error");
break;
}
case RamCalculationErrorCode.SyntaxError:
default: {
debouncedSetRAM("RAM: Syntax Error");
break;
}
}
return new Promise<void>(() => undefined);
} }
useEffect(() => { function onTabClick(index: number) {
function maybeSave(event: KeyboardEvent): void {
if (Settings.DisableHotkeys) return;
// CTRL/CMD + S
if (event.code == `KeyS` && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
event.stopPropagation();
save();
}
}
document.addEventListener("keydown", maybeSave);
return () => document.removeEventListener("keydown", maybeSave);
});
// Generates a new model for the script
function regenerateModel(script: openScript): void {
if (monacoRef.current !== null) {
script.model = monacoRef.current.editor.createModel(script.code, "javascript");
}
}
// Sets the currently viewed script
function setCurrentScript(script: openScript): void {
// Update last position
if (editorRef.current !== null) {
if (currentScript !== null) { if (currentScript !== null) {
const currentPosition = editorRef.current.getPosition(); // Save currentScript to openScripts
if (currentPosition !== null) { let curIndex = openScripts.findIndex(script => script.fileName === currentScript.fileName && script.hostname === currentScript.hostname);
currentScript.lastPosition = currentPosition; openScripts[curIndex] = currentScript;
}
setCurrentScript({ ...openScripts[index] });
if (editorRef.current !== null && openScripts[index] !== null) {
if (openScripts[index].model === undefined || openScripts[index].model.isDisposed()) {
regenerateModel(openScripts[index]);
}
editorRef.current.setModel(openScripts[index].model);
editorRef.current.setPosition(openScripts[index].lastPosition);
editorRef.current.revealLineInCenter(openScripts[index].lastPosition.lineNumber);
updateRAM(openScripts[index].code);
} }
} }
editorRef.current.setModel(script.model); async function onTabClose(index: number) {
currentScript = script; // See if the script on the server is up to date
editorRef.current.setPosition(currentScript.lastPosition); let closingScript = openScripts[index];
editorRef.current.revealLine(currentScript.lastPosition.lineNumber); let savedOpenScripts: Array<OpenScript> = JSON.parse(window.localStorage.getItem('scriptEditorOpenScripts')!);
updateRAM(currentScript.code); let savedScriptIndex = savedOpenScripts.findIndex(script => script.fileName === closingScript.fileName && script.hostname === closingScript.hostname);
} let savedScriptCode = '';
if (savedScriptIndex !== -1) {
savedScriptCode = savedOpenScripts[savedScriptIndex].code;
} }
// Gets a currently opened script console.log(savedScriptCode)
function getOpenedScript(fileName: string, hostname: string): openScript | null {
for (const script of openScripts) { let serverScriptIndex = GetServer(closingScript.hostname)?.scripts.findIndex(script => script.filename === closingScript.fileName);
if (script.fileName === fileName && script.hostname === hostname) { if (serverScriptIndex === -1 || savedScriptCode !== GetServer(closingScript.hostname)?.scripts[serverScriptIndex as number].code) {
return script; PromptEvent.emit({
txt: 'Do you want to save changes to ' + closingScript.fileName + '?',
resolve: (result: boolean) => {
if(result) {
// Save changes
closingScript.code = savedScriptCode;
saveScript(closingScript);
} }
} }
})
}
return null; if (openScripts.length > 1) {
setOpenScripts(oldScripts => oldScripts.filter((value, i) => i !== index));
let indexOffset = -1;
if (openScripts[index + indexOffset] === undefined) {
indexOffset = 1;
} }
function saveScript(script: openScript): void { // Change current script if we closed it
const server = GetServer(script.hostname); setCurrentScript(openScripts[index + indexOffset]);
if (server === null) throw new Error("Server should not be null but it is."); if (editorRef.current !== null) {
let found = false; if (openScripts[index + indexOffset].model === undefined || openScripts[index + indexOffset].model === null || openScripts[index + indexOffset].model.isDisposed()) {
for (let i = 0; i < server.scripts.length; i++) { regenerateModel(openScripts[index + indexOffset]);
if (script.fileName == server.scripts[i].filename) {
server.scripts[i].saveScript(script.fileName, script.code, script.hostname, server.scripts);
found = true;
}
} }
if (!found) { editorRef.current.setModel(openScripts[index + indexOffset].model);
const newScript = new Script(); editorRef.current.setPosition(openScripts[index + indexOffset].lastPosition);
newScript.saveScript(script.fileName, script.code, script.hostname, server.scripts); editorRef.current.revealLineInCenter(openScripts[index + indexOffset].lastPosition.lineNumber)
server.scripts.push(newScript);
}
}
function onMount(editor: IStandaloneCodeEditor, monaco: Monaco): void {
editorRef.current = editor;
monacoRef.current = monaco;
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();
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) {
const newScript = new openScript(
filename,
code,
props.player.getCurrentServer().hostname,
new monaco.Position(0, 0),
monacoRef.current.editor.createModel(code, "javascript"),
);
setCurrentScript(newScript);
openScripts.push(newScript);
} }
} else { } else {
// Script Editor was opened by the sidebar button // No more scripts are open
if (currentScript.model !== undefined) { setOpenScripts([]);
if (currentScript.model.isDisposed()) { setCurrentScript(null);
// Create new model, old one was disposed of
regenerateModel(currentScript);
}
setCurrentScript(currentScript);
} else {
// Create a new temporary file
if (monacoRef.current !== null) {
const newScript = new openScript(
"newfile.script",
"",
props.player.getCurrentServer().hostname,
new monaco.Position(0, 0),
monacoRef.current.editor.createModel("", "javascript"),
);
setCurrentScript(newScript);
openScripts.push(newScript);
}
}
}
}
}
function beforeMount(monaco: any): void {
if (symbolsLoaded) return;
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 () {
// We have to improve the default js language otherwise theme sucks
const l = await monaco.languages
.getLanguages()
.find((l: any) => l.id === "javascript")
.loader();
l.language.tokenizer.root.unshift(["ns", { token: "ns" }]);
for (const symbol of symbols) l.language.tokenizer.root.unshift([symbol, { token: "netscriptfunction" }]);
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" }]);
})();
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);
}
// Change tab highlight from old tab to new tab
function changeTabButtonColor(
oldButtonFileName: string,
oldButtonHostname: string,
newButtonFileName: string,
newButtonHostname: string,
): void {
const oldTabButton = document.getElementById("tabButton" + oldButtonFileName + oldButtonHostname);
if (oldTabButton !== null) {
oldTabButton.style.backgroundColor = "";
}
const oldTabCloseButton = document.getElementById("tabCloseButton" + oldButtonFileName + oldButtonHostname);
if (oldTabCloseButton !== null) {
oldTabCloseButton.style.backgroundColor = "";
}
const newTabButton = document.getElementById("tabButton" + newButtonFileName + newButtonHostname);
if (newTabButton !== null) {
newTabButton.style.backgroundColor = "#666";
}
const newTabCloseButton = document.getElementById("tabCloseButton" + newButtonFileName + newButtonHostname);
if (newTabCloseButton !== null) {
newTabCloseButton.style.backgroundColor = "#666";
}
}
// Called when a script tab was clicked
function onTabButtonClick(e: React.MouseEvent<HTMLButtonElement>): void {
const valSplit = e.currentTarget.value.split(":");
const fileName = valSplit[0];
const hostname = valSplit[1];
// Change tab highlight from old tab to new tab
changeTabButtonColor(currentScript.fileName, currentScript.hostname, fileName, hostname);
// 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
function onCloseButtonClick(e: React.MouseEvent<HTMLButtonElement>): void {
const valSplit = e.currentTarget.value.split(":");
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) {
const newScript = new openScript(
"newfile.script",
"",
props.player.getCurrentServer().hostname,
new monacoRef.current.Position(0, 0),
monacoRef.current.editor.createModel("", "javascript"),
);
setCurrentScript(newScript);
openScripts.push(newScript);
// Modify button for temp file
const parent = e.currentTarget.parentElement;
if (parent !== null) {
(parent.children[0] as HTMLButtonElement).value = "newfile.script:home";
(parent.children[0] as HTMLButtonElement).textContent = "newfile.script";
e.currentTarget.value = "newfile.script:home";
}
}
} else {
if (openScripts[0].model.isDisposed()) {
regenerateModel(openScripts[0]);
}
changeTabButtonColor(
currentScript.fileName,
currentScript.hostname,
openScripts[0].fileName,
openScripts[0].hostname,
);
setCurrentScript(openScripts[0]);
}
}
// Generate a button for each open script
const scriptButtons = [];
for (let i = 0; i < openScripts.length; i++) {
if (openScripts[i].fileName !== "") {
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
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>,
);
} else {
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>,
);
}
} }
} }
@ -605,16 +553,66 @@ export function Root(props: IProps): React.ReactElement {
const p = 11000 / -window.innerHeight + 100; const p = 11000 / -window.innerHeight + 100;
return ( return (
<> <>
<Box display="flex" flexDirection="row" alignItems="center" paddingBottom="5px"> <div style={{ display: currentScript !== null ? 'block' : 'none', height: '100%', width: '100%' }}>
{scriptButtons} <DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId='tabs' direction='horizontal'>
{(provided, snapshot) => (
<Box
maxWidth="1640px"
display="flex"
flexDirection="row"
//overflow="auto"
alignItems="center"
whiteSpace="nowrap"
ref={provided.innerRef}
{...provided.droppableProps}
style={{ backgroundColor: snapshot.isDraggingOver ? '#1F2022' : Settings.theme.backgroundprimary, overflowX: 'scroll' }}
>
{openScripts.map(({ fileName, hostname }, index) => (
<Draggable key={fileName + hostname} draggableId={fileName + hostname} index={index} disableInteractiveElementBlocking={true}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
marginRight: '5px',
flexShrink: 0
}}
>
<Button
id={"tabButton" + fileName + hostname}
onClick={() => onTabClick(index)}
style={{ background: currentScript?.fileName === openScripts[index].fileName ? Settings.theme.secondarydark : '' }}
>
{fileName}
</Button>
<Button
id={"tabCloseButton" + fileName + hostname}
onClick={() => onTabClose(index)}
style={{ maxWidth: "20px", minWidth: "20px", background: currentScript?.fileName === openScripts[index].fileName ? Settings.theme.secondarydark : '' }}
>
x
</Button>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</Box> </Box>
)}
</Droppable>
</DragDropContext>
<div style={{ paddingBottom: '5px' }} />
<Editor <Editor
beforeMount={beforeMount} beforeMount={beforeMount}
onMount={onMount} onMount={onMount}
loading={<Typography>Loading script editor!</Typography>} loading={<Typography>Loading script editor!</Typography>}
height={p + "%"} height={p + "%"}
defaultLanguage="javascript" defaultLanguage="javascript"
defaultValue={code} defaultValue={''}
onChange={updateCode} onChange={updateCode}
theme={options.theme} theme={options.theme}
options={{ ...options, glyphMargin: true }} options={{ ...options, glyphMargin: true }}
@ -624,7 +622,7 @@ export function Root(props: IProps): React.ReactElement {
<Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}> <Typography color={updatingRam ? "secondary" : "primary"} sx={{ mx: 1 }}>
{ram} {ram}
</Typography> </Typography>
<Button onClick={save}>Save (CTRL/CMD + S)</Button> <Button onClick={save}>Save & Close (Ctrl/Cmd + b)</Button>
<Typography sx={{ mx: 1 }}> <Typography sx={{ mx: 1 }}>
{" "} {" "}
Documentation:{" "} Documentation:{" "}
@ -658,6 +656,10 @@ export function Root(props: IProps): React.ReactElement {
Settings.MonacoFontSize = options.fontSize; Settings.MonacoFontSize = options.fontSize;
}} }}
/> />
</div>
<div style={{ display: currentScript !== null ? 'none' : 'flex', height: '100%', width: '100%', justifyContent: 'center', alignItems: 'center' }}>
<p style={{ color: Settings.theme.primary, fontSize: '20px', textAlign: 'center' }}><h1>No open files</h1><h5>Use "nano [File Name]" in the terminal to open files</h5></p>
</div>
</> </>
); )
} }

@ -11,7 +11,7 @@ export function rm(
server: BaseServer, server: BaseServer,
args: (string | number | boolean)[], args: (string | number | boolean)[],
): void { ): void {
if (args.length !== 1) { if (args.length < 1) {
terminal.error("Incorrect number of arguments. Usage: rm [program/script]"); terminal.error("Incorrect number of arguments. Usage: rm [program/script]");
return; return;
} }
@ -25,6 +25,14 @@ export function rm(
try { try {
delTarget = terminal.getFilepath(args[0] + ""); delTarget = terminal.getFilepath(args[0] + "");
status = server.removeFile(delTarget); status = server.removeFile(delTarget);
// Check if we should remove it from both the text and script arrays
if(args[1] === '-tas') {
let fileIndex = server.scripts.findIndex(script => script.filename === (args[0] + ""));
if(fileIndex !== -1) {
server.scripts.splice(fileIndex, 1);
}
}
} catch (err) { } catch (err) {
status = { status = {
res: false, res: false,

@ -43,8 +43,10 @@ export function PromptManager(): React.ReactElement {
{prompt != null && ( {prompt != null && (
<Modal open={true} onClose={close}> <Modal open={true} onClose={close}>
<Typography>{prompt.txt}</Typography> <Typography>{prompt.txt}</Typography>
<Button onClick={yes}>Yes</Button> <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}>
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button>
<Button onClick={no}>No</Button> <Button onClick={no}>No</Button>
</div>
</Modal> </Modal>
)} )}
</> </>