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}`);