diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts
index d77d413f3..22eed0e28 100644
--- a/src/Terminal/HelpText.ts
+++ b/src/Terminal/HelpText.ts
@@ -3,49 +3,70 @@ import { IMap } from "../types";
export const TerminalHelpText: string[] = [
"Type 'help name' to learn more about the command ",
- " ",
- ' alias [-g] [name="value"] Create or display Terminal aliases',
- " analyze Get information about the current machine ",
- " backdoor Install a backdoor on the current machine ",
- " buy [-l/-a/program] Purchase a program through the Dark Web",
- " cat [file] Display a .msg, .lit, or .txt file",
- " cd [dir] Change to a new directory",
- " check [script] [args...] Print a script's logs to Terminal",
- " clear Clear all text on the terminal ",
- " cls See 'clear' command ",
- " connect [hostname] Connects to a remote server",
- " cp [src] [dst] Copy a file",
- " download [script/text file] Downloads scripts or text files to your computer",
- " expr [math expression] Evaluate a mathematical expression",
- " free Check the machine's memory (RAM) usage",
- " grow Spoof money in a servers bank account, increasing the amount available.",
- " hack Hack the current machine",
- " help [command] Display this help text, or the help text for a command",
- " home Connect to home computer",
- " hostname Displays the hostname of the machine",
- " kill [script/pid] [args...] Stops the specified script on the current server ",
- " killall Stops all running scripts on the current machine",
- " ls [dir] [| grep pattern] Displays all files on the machine",
- " 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 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",
- " scan Prints all immediately-available network connections",
- " scan-analyze [d] [-a] Prints info for all servers up to d nodes away",
- " scp [file ...] [server] Copies a file to a destination server",
- " sudov Shows whether you have root access on this computer",
- " 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 one or more scripts or text files in vim mode",
- " weaken Reduce the security of the current machine",
- " wget [url] [target file] Retrieves code/text from a web server",
- " ",
+ "",
+ 'alias [-g] [name="value"] Create or display Terminal aliases',
+ "analyze Get information about the current machine ",
+ "backdoor Install a backdoor on the current machine ",
+ "buy [-l/program] Purchase a program through the Dark Web",
+ "cat [file] Display a .msg, .lit, or .txt file",
+ "cd [dir] Change to a new directory",
+ "check [script] [args...] Print a script's logs to Terminal",
+ "clear Clear all text on the terminal ",
+ "cls See 'clear' command ",
+ "connect [hostname] Connects to a remote server",
+ "cp [src] [dst] Copy a file",
+ "download [script/text file] Downloads scripts or text files to your computer",
+ "expr [math expression] Evaluate a mathematical expression",
+ "free Check the machine's memory (RAM) usage",
+ "grow Spoof money in a servers bank account, increasing the amount available.",
+ "hack Hack the current machine",
+ "help [command] Display this help text, or the help text for a command",
+ "home Connect to home computer",
+ "hostname Displays the hostname of the machine",
+ "kill [script/pid] [args...] Stops the specified script on the current server ",
+ "killall Stops all running scripts on the current machine",
+ "ls [dir] [| grep pattern] Displays all files on the machine",
+ "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 ...] | [glob] 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",
+ "scan Prints all immediately-available network connections",
+ "scan-analyze [d] [-a] Prints info for all servers up to d nodes away",
+ "scp [file ...] [server] Copies a file to a destination server",
+ "sudov Shows whether you have root access on this computer",
+ "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 ...] | [glob] Text editor - Open up and edit one or more scripts or text files in vim mode",
+ "weaken Reduce the security of the current machine",
+ "wget [url] [target file] Retrieves code/text from a web server",
];
+const TemplatedHelpTexts: IMap<(command: string) => string[]> = {
+ scriptEditor: (command) => {
+ return [
+ `${command} [file ...] | [glob]`,
+ ` `,
+ `Opens up the specified file(s) in the Script Editor. Only scripts (.js, .ns, .script) or text files (.txt) `,
+ `can be edited using the Script Editor. If a file does not exist a new one will be created`,
+ ` `,
+ `If provided a glob as the only argument, ${command} can spider directories and open all matching `,
+ `files at once. ${command} cannot create files using globs, so your scripts must already exist.`,
+ ` `,
+ `Examples:`,
+ ` `,
+ `${command} test.js`,
+ `${command} test.js test2.js`,
+ ` `,
+ `${command} test.*`,
+ `${command} /my-dir/*.js`,
+ ]
+ }
+}
+
export const HelpTexts: IMap = {
alias: [
'Usage: alias [-g] [name="value"] ',
@@ -322,16 +343,8 @@ export const HelpTexts: IMap = {
" mv myScript.js myOldScript.js",
" ",
],
- nano: [
- "Usage: nano [file ...]",
- " ",
- "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",
- " ",
- ],
+ nano: TemplatedHelpTexts.scriptEditor('nano'),
ps: ["Usage: ps", " ", "Prints all scripts that are running on the current server", " "],
-
rm: [
"Usage: rm [file]",
" ",
@@ -433,14 +446,7 @@ export const HelpTexts: IMap = {
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
" ",
],
- vim: [
- "Usage: vim [file ...]",
- " ",
- "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",
- " ",
- ],
+ vim: TemplatedHelpTexts.scriptEditor('vim'),
weaken: [
"Usage: weaken",
" ",
diff --git a/src/Terminal/commands/common/editor.ts b/src/Terminal/commands/common/editor.ts
index bf5c23960..8d6b6c7c5 100644
--- a/src/Terminal/commands/common/editor.ts
+++ b/src/Terminal/commands/common/editor.ts
@@ -1,9 +1,12 @@
import { ITerminal } from "../../ITerminal";
+import { removeLeadingSlash, removeTrailingSlash } from '../../DirectoryHelpers'
import { IRouter, ScriptEditorRouteOptions } from "../../../ui/Router";
import { IPlayer } from "../../../PersonObjects/IPlayer";
import { BaseServer } from "../../../Server/BaseServer";
import { isScriptFilename } from "../../../Script/isScriptFilename";
import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
+import { Script } from "../../../Script/Script";
+import { isEmpty } from "lodash";
interface EditorParameters {
terminal: ITerminal;
@@ -22,6 +25,74 @@ export async function main(ns) {
}`;
+interface ISimpleScriptGlob {
+ glob: string;
+ preGlob: string;
+ postGlob: string;
+ globError: string;
+ globMatches: string[];
+ globAgainst: Script[];
+}
+
+function containsSimpleGlob(filename: string): boolean {
+ return filename.includes("*");
+}
+
+function detectSimpleScriptGlob(
+ args: EditorParameters["args"],
+ player: IPlayer,
+ terminal: ITerminal,
+): ISimpleScriptGlob | null {
+ if (args.length == 1 && containsSimpleGlob(`${args[0]}`)) {
+ const filename = `${args[0]}`;
+ const scripts = player.getCurrentServer().scripts;
+ const parsedGlob = parseSimpleScriptGlob(filename, scripts, terminal);
+ return parsedGlob;
+ }
+ return null;
+}
+
+function parseSimpleScriptGlob(globString: string, globDatabase: Script[], terminal: ITerminal): ISimpleScriptGlob {
+ const parsedGlob: ISimpleScriptGlob = {
+ glob: globString,
+ preGlob: "",
+ postGlob: "",
+ globError: "",
+ globMatches: [],
+ globAgainst: globDatabase,
+ };
+
+ // Ensure deep globs are minified to simple globs, which act as deep globs in this impl
+ globString = globString.replace("**", "*");
+
+ // Ensure only a single glob is present
+ if (globString.split("").filter((c) => c == "*").length !== 1) {
+ parsedGlob.globError = "Only a single glob is supported per command.\nexample: `nano my-dir/*.js`";
+ return parsedGlob;
+ }
+
+ // Split arg around glob, normalize preGlob path
+ [parsedGlob.preGlob, parsedGlob.postGlob] = globString.split("*");
+ parsedGlob.preGlob = removeLeadingSlash(parsedGlob.preGlob);
+
+ // Add CWD to preGlob path
+ const cwd = removeTrailingSlash(terminal.cwd())
+ parsedGlob.preGlob = `${cwd}/${parsedGlob.preGlob}`
+
+ // For every script on the current server, filter matched scripts per glob values & persist
+ globDatabase.forEach((script) => {
+ const filename = script.filename.startsWith('/') ? script.filename : `/${script.filename}`
+ if (filename.startsWith(parsedGlob.preGlob) && filename.endsWith(parsedGlob.postGlob)) {
+ parsedGlob.globMatches.push(filename);
+ }
+ });
+
+ // Rebuild glob for potential error reporting
+ parsedGlob.glob = `${parsedGlob.preGlob}*${parsedGlob.postGlob}`
+
+ return parsedGlob;
+}
+
export function commonEditor(
command: string,
{ terminal, router, player, args }: EditorParameters,
@@ -32,8 +103,15 @@ export function commonEditor(
return;
}
+ let filesToLoadOrCreate = args;
try {
- const files = args.map((arg) => {
+ const globSearch = detectSimpleScriptGlob(args, player, terminal);
+ if (globSearch) {
+ if (isEmpty(globSearch.globError) === false) throw new Error(globSearch.globError);
+ filesToLoadOrCreate = globSearch.globMatches;
+ }
+
+ const files = filesToLoadOrCreate.map((arg) => {
const filename = `${arg}`;
if (isScriptFilename(filename)) {
@@ -55,12 +133,18 @@ export function commonEditor(
if (filename.endsWith(".txt")) {
const filepath = terminal.getFilepath(filename);
const txt = terminal.getTextFile(player, filename);
- return [filepath, txt == null ? "" : txt.text];
+ return [filepath, txt === null ? "" : txt.text];
}
- throw new Error(`Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`);
+ throw new Error(
+ `Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`,
+ );
});
+ if (globSearch && files.length === 0) {
+ throw new Error(`Could not find any valid files to open with ${command} using glob: \`${globSearch.glob}\``)
+ }
+
router.toScriptEditor(Object.fromEntries(files), scriptEditorRouteOptions);
} catch (e) {
terminal.error(`${e}`);