From bb2f8e883c15be93dd6bbd20a35cc8a3a803ac8a Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 20 Dec 2021 12:42:47 -0500 Subject: [PATCH] feat: Support opening multiple files from command line --- src/ScriptEditor/ui/ScriptEditorRoot.tsx | 79 ++++++++++++----------- src/Terminal/HelpText.ts | 12 ++-- src/Terminal/commands/common/editor.ts | 81 ++++++++++++------------ src/ui/GameRoot.tsx | 19 ++---- src/ui/Router.ts | 2 +- 5 files changed, 99 insertions(+), 94 deletions(-) diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index b19ac4d72..da13bc8a1 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -39,8 +39,8 @@ import { PromptEvent } from "../../ui/React/PromptManager"; import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts"; interface IProps { - filename: string; - code: string; + // Map of filename -> code + files: Record; hostname: string; player: IPlayer; router: IRouter; @@ -307,47 +307,54 @@ export function Root(props: IProps): React.ReactElement { if (editorRef.current === null || monacoRef.current === null) return; - if (props.filename) { - // Check if file is already opened - const 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]); - } + const files = Object.entries(props.files); - 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 - const 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) { + if (!files.length && 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); + editorRef.current.focus(); + return; + } + + if (!files.length) { + editorRef.current.focus(); + return; + } + + for (const [filename, code] of files) { + // Check if file is already opened + const openScript = openScripts.find( + (script) => script.fileName === filename && script.hostname === props.hostname, + ); + if (openScript) { + // Script is already opened + if (openScript.model === undefined || openScript.model === null || openScript.model.isDisposed()) { + regenerateModel(openScript); + } + + setCurrentScript(openScript); + editorRef.current.setModel(openScript.model); + editorRef.current.setPosition(openScript.lastPosition); + editorRef.current.revealLineInCenter(openScript.lastPosition.lineNumber); + updateRAM(openScript.code); + } else { + // Open script + const newScript = new OpenScript( + filename, + code, + props.hostname, + new monacoRef.current.Position(0, 0), + monacoRef.current.editor.createModel(code, "javascript"), + ); + setOpenScripts((oldArray) => [...oldArray, newScript]); + setCurrentScript({ ...newScript }); + editorRef.current.setModel(newScript.model); + updateRAM(newScript.code); + } } editorRef.current.focus(); diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts index 8ebb92c11..4c0e15452 100644 --- a/src/Terminal/HelpText.ts +++ b/src/Terminal/HelpText.ts @@ -29,7 +29,7 @@ export const TerminalHelpText: string[] = [ "lscpu Displays the number of CPU cores on the machine", "mem [script] [-t] [n] Displays the amount of RAM required to run the script", "mv [src] [dest] Move/rename a text or script file", - "nano [file] Text editor - Open up and edit a script or text file", + "nano [file ...] Text editor - Open up and edit one or more scripts or text files", "ps Display all scripts that are currently running", "rm [file] Delete a file from the server", "run [name] [-t n] [--tail] [args...] Execute a program or script", @@ -40,7 +40,7 @@ export const TerminalHelpText: string[] = [ "tail [script] [args...] Displays dynamic logs for the specified script", "top Displays all running scripts and their RAM usage", "unalias [alias name] Deletes the specified alias", - "vim [file] Text editor - Open up and edit a script or text file in vim mode", + "vim [file ...] Text editor - Open up and edit one or more scripts or text files in vim mode", "weaken [server] Reduce the security of a server", "wget [url] [target file] Retrieves code/text from a web server", ]; @@ -305,9 +305,9 @@ export const HelpTexts: IMap = { "mv myScript.js myOldScript.js", ], nano: [ - "nano [file name]", + "nano [file ...]", " ", - "Opens up the specified file in the Text Editor. Only scripts (.script) or text files (.txt) can be ", + "Opens up the specified file(s) in the Text Editor. Only scripts (.script) or text files (.txt) can be ", "edited using the Text Editor. If the file does not already exist, then a new, empty one ", "will be created", ], @@ -404,9 +404,9 @@ export const HelpTexts: IMap = { "It is not necessary to differentiate between global and non-global aliases when using 'unalias'", ], vim: [ - "vim [file name]", + "vim [file ...]", " ", - "Opens up the specified file in the Text Editor in vim mode. Only scripts (.script) or text files (.txt) can be ", + "Opens up the specified file(s) in the Text Editor in vim mode. Only scripts (.script) or text files (.txt) can be ", "edited using the Text Editor. If the file does not already exist, then a new, empty one ", "will be created", ], diff --git a/src/Terminal/commands/common/editor.ts b/src/Terminal/commands/common/editor.ts index cc551e9b5..a76237e85 100644 --- a/src/Terminal/commands/common/editor.ts +++ b/src/Terminal/commands/common/editor.ts @@ -1,5 +1,5 @@ import { ITerminal } from "../../ITerminal"; -import { IRouter, ScriptEditorRouteOptions} from "../../../ui/Router"; +import { IRouter, ScriptEditorRouteOptions } from "../../../ui/Router"; import { IPlayer } from "../../../PersonObjects/IPlayer"; import { BaseServer } from "../../../Server/BaseServer"; import { isScriptFilename } from "../../../Script/isScriptFilename"; @@ -13,53 +13,56 @@ interface EditorParameters { args: (string | number | boolean)[]; } +function isNs2(filename: string): boolean { + return filename.endsWith(".ns") || filename.endsWith(".js"); +} -export function commonEditor(command: string, { - terminal, - router, - player, - server, - args, -}: EditorParameters, scriptEditorRouteOptions?: ScriptEditorRouteOptions): void { - if (args.length !== 1) { +const newNs2Template = `/** @param {NS} ns **/ +export async function main(ns) { + +}`; + +export function commonEditor( + command: string, + { terminal, router, player, server, args }: EditorParameters, + scriptEditorRouteOptions?: ScriptEditorRouteOptions, +): void { + if (args.length < 1) { terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`); return; } try { - const filename = args[0] + ""; - if (isScriptFilename(filename)) { - const filepath = terminal.getFilepath(filename); - const script = terminal.getScript(player, filename); - if (script == null) { - let code = ""; - if (filename.endsWith(".ns") || filename.endsWith(".js")) { - code = `/** @param {NS} ns **/ -export async function main(ns) { - -}`; + const files = args.map((arg) => { + const filename = `${arg}`; + + if (isScriptFilename(filename)) { + const filepath = terminal.getFilepath(filename); + const script = terminal.getScript(player, filename); + const fileIsNs2 = isNs2(filename); + const code = script !== null ? script.code : fileIsNs2 ? newNs2Template : ""; + + if (code === newNs2Template) { + CursorPositions.saveCursor(filename, { + row: 3, + column: 5, + }); } - CursorPositions.saveCursor(filename, { - row: 3, - column: 5, - }); - router.toScriptEditor(filepath, code, scriptEditorRouteOptions); - } else { - router.toScriptEditor(filepath, script.code, scriptEditorRouteOptions); + + return [filepath, code]; } - } else if (filename.endsWith(".txt")) { - const filepath = terminal.getFilepath(filename); - const txt = terminal.getTextFile(player, filename); - if (txt == null) { - router.toScriptEditor(filepath, "", scriptEditorRouteOptions); - } else { - router.toScriptEditor(filepath, txt.text, scriptEditorRouteOptions); + + if (filename.endsWith(".txt")) { + const filepath = terminal.getFilepath(filename); + const txt = terminal.getTextFile(player, filename); + return [filepath, txt == null ? "" : txt.text]; } - } else { - terminal.error("Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with vim"); - return; - } + + throw new Error(`Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`); + }); + + router.toScriptEditor(Object.fromEntries(files), scriptEditorRouteOptions); } catch (e) { - terminal.error(e + ""); + terminal.error(`${e}`); } } diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 7362da33e..4c37690a7 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -95,10 +95,6 @@ const useStyles = makeStyles((theme: Theme) => }), ); -let filename = ""; -let code = ""; -let vim = false; - export let Router: IRouter = { page: () => { throw new Error("Router called before initialization"); @@ -197,6 +193,7 @@ function determineStartPage(player: IPlayer): Page { export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement { const classes = useStyles(); + const [{files, vim}, setEditorOptions] = useState({files: {}, vim: false}) const [page, setPage] = useState(determineStartPage(player)); const setRerender = useState(0)[1]; const [faction, setFaction] = useState( @@ -247,10 +244,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme toHacknetNodes: () => setPage(Page.Hacknet), toMilestones: () => setPage(Page.Milestones), toResleeves: () => setPage(Page.Resleeves), - toScriptEditor: (fn: string, c: string, options?: ScriptEditorRouteOptions) => { - filename = fn; - code = c; - vim = !!options?.vim; + toScriptEditor: (files: Record, options?: ScriptEditorRouteOptions) => { + setEditorOptions({ + files, + vim: !!options?.vim, + }); setPage(Page.ScriptEditor); }, toSleeves: () => setPage(Page.Sleeves), @@ -292,8 +290,6 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme }; useEffect(() => { - filename = ""; - code = ""; if (page !== Page.Terminal) window.scrollTo(0, 0); }); @@ -332,8 +328,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme ) : page === Page.ScriptEditor ? ( , options?: ScriptEditorRouteOptions): void; toSleeves(): void; toStockMarket(): void; toTerminal(): void;