diff --git a/doc/source/netscript/advancedfunctions/autocomplete.rst b/doc/source/netscript/advancedfunctions/autocomplete.rst new file mode 100644 index 000000000..c64020a82 --- /dev/null +++ b/doc/source/netscript/advancedfunctions/autocomplete.rst @@ -0,0 +1,36 @@ +autocomplete() Netscript Function +============================ + +.. warning:: This feature is not officially supported yet and the API might change. + +.. js:function:: autocomplete(data, args) + + :RAM cost: 0 GB + :param Object data: general data about the game you might want to autocomplete. + :param string[] args: current arguments. + + data is an object with the following properties:: + + { + servers: list of all servers in the game. + txts: list of all text files on the current server. + scripts: list of all scripts on the current server. + } + + Example: + + .. code-block:: javascript + + export function autocomplete(data, args) { + return [...data.servers]; // This script autocompletes the list of servers. + return [...data.servers, ...data.scripts]; // Autocomplete servers and scripts + return ["low", "medium", "high"]; // Autocomplete 3 specific strings. + } + + Terminal: + + .. code-block:: javascript + + $ run demo.ns mega\t + // results in + $ run demo.ns megacorp diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 2caa77aea..be4f43db8 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -1,5 +1,4 @@ import { vsprintf, sprintf } from "sprintf-js"; -import * as libarg from "arg"; import { getRamCost } from "./Netscript/RamCostGenerator"; import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; @@ -58,7 +57,6 @@ import { killWorkerScript } from "./Netscript/killWorkerScript"; import { workerScripts } from "./Netscript/WorkerScripts"; import { WorkerScript } from "./Netscript/WorkerScript"; import { makeRuntimeRejectMsg, netscriptDelay, resolveNetscriptRequestedThreads } from "./NetscriptEvaluator"; -import { Interpreter } from "./ThirdParty/JSInterpreter"; import { Router } from "./ui/GameRoot"; import { numeralWrapper } from "./ui/numeralFormat"; @@ -84,44 +82,12 @@ import { INetscriptFormulas, NetscriptFormulas } from "./NetscriptFunctions/Form import { INetscriptAugmentations, NetscriptAugmentations } from "./NetscriptFunctions/Augmentations"; import { INetscriptStockMarket, NetscriptStockMarket } from "./NetscriptFunctions/StockMarket"; +import { toNative } from "./NetscriptFunctions/toNative"; + import { dialogBoxCreate } from "./ui/React/DialogBox"; import { SnackbarEvents } from "./ui/React/Snackbar"; import { Locations } from "./Locations/Locations"; - -const defaultInterpreter = new Interpreter("", () => undefined); - -// the acorn interpreter has a bug where it doesn't convert arrays correctly. -// so we have to more or less copy it here. -function toNative(pseudoObj: any): any { - if (pseudoObj == null) return null; - if ( - !pseudoObj.hasOwnProperty("properties") || - !pseudoObj.hasOwnProperty("getter") || - !pseudoObj.hasOwnProperty("setter") || - !pseudoObj.hasOwnProperty("proto") - ) { - return pseudoObj; // it wasn't a pseudo object anyway. - } - - let nativeObj: any; - if (pseudoObj.hasOwnProperty("class") && pseudoObj.class === "Array") { - nativeObj = []; - const length = defaultInterpreter.getProperty(pseudoObj, "length"); - for (let i = 0; i < length; i++) { - if (defaultInterpreter.hasProperty(pseudoObj, i)) { - nativeObj[i] = toNative(defaultInterpreter.getProperty(pseudoObj, i)); - } - } - } else { - // Object. - nativeObj = {}; - for (const key in pseudoObj.properties) { - const val = pseudoObj.properties[key]; - nativeObj[key] = toNative(val); - } - } - return nativeObj; -} +import { Flags } from "./NetscriptFunctions/Flags"; interface NS extends INetscriptExtra, INetscriptAugmentations, INetscriptStockMarket { [key: string]: any; @@ -363,6 +329,9 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { return Factions[name]; }; + console.log(workerScript); + console.log(workerScript.args); + const hack = function (ip: any, manual: any, { threads: requestedThreads, stock }: any = {}): Promise { if (ip === undefined) { throw makeRuntimeErrorMsg("hack", "Takes 1 argument."); @@ -1846,7 +1815,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { throw makeRuntimeErrorMsg("read", `Could not find port: ${port}. This is a bug. Report to dev.`); } const x = iport.read(); - console.log(x); return x; } else if (isString(port)) { // Read from script or text file @@ -1895,7 +1863,6 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { throw makeRuntimeErrorMsg("peek", `Could not find port: ${port}. This is a bug. Report to dev.`); } const x = iport.peek(); - console.log(x); return x; }, clear: function (port: any): any { @@ -3284,38 +3251,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS { } workerScript.atExit = f; }, - flags: function (data: any): any { - data = toNative(data); - // We always want the help flag. - const args: { - [key: string]: any; - } = {}; - - for (const d of data) { - let t: any = String; - if (typeof d[1] === "number") { - t = Number; - } else if (typeof d[1] === "boolean") { - t = Boolean; - } else if (Array.isArray(d[1])) { - t = [String]; - } - const numDashes = d[0].length > 1 ? 2 : 1; - args["-".repeat(numDashes) + d[0]] = t; - } - const ret = libarg(args, { argv: workerScript.args }); - for (const d of data) { - if (!ret.hasOwnProperty("--" + d[0]) || !ret.hasOwnProperty("-" + d[0])) ret[d[0]] = d[1]; - } - for (const key of Object.keys(ret)) { - if (!key.startsWith("-")) continue; - const value = ret[key]; - delete ret[key]; - const numDashes = key.length === 2 ? 1 : 2; - ret[key.slice(numDashes)] = value; - } - return ret; - }, + flags: Flags(workerScript.args), ...extra, }; diff --git a/src/NetscriptFunctions/Flags.ts b/src/NetscriptFunctions/Flags.ts new file mode 100644 index 000000000..37fbda386 --- /dev/null +++ b/src/NetscriptFunctions/Flags.ts @@ -0,0 +1,37 @@ +import { toNative } from "./toNative"; +import * as libarg from "arg"; + +export function Flags(vargs: string[]): any { + return function (data: any): any { + data = toNative(data); + // We always want the help flag. + const args: { + [key: string]: any; + } = {}; + + for (const d of data) { + let t: any = String; + if (typeof d[1] === "number") { + t = Number; + } else if (typeof d[1] === "boolean") { + t = Boolean; + } else if (Array.isArray(d[1])) { + t = [String]; + } + const numDashes = d[0].length > 1 ? 2 : 1; + args["-".repeat(numDashes) + d[0]] = t; + } + const ret = libarg(args, { argv: vargs }); + for (const d of data) { + if (!ret.hasOwnProperty("--" + d[0]) || !ret.hasOwnProperty("-" + d[0])) ret[d[0]] = d[1]; + } + for (const key of Object.keys(ret)) { + if (!key.startsWith("-")) continue; + const value = ret[key]; + delete ret[key]; + const numDashes = key.length === 2 ? 1 : 2; + ret[key.slice(numDashes)] = value; + } + return ret; + }; +} diff --git a/src/NetscriptFunctions/toNative.ts b/src/NetscriptFunctions/toNative.ts new file mode 100644 index 000000000..1f43fb03c --- /dev/null +++ b/src/NetscriptFunctions/toNative.ts @@ -0,0 +1,36 @@ +import { Interpreter } from "../ThirdParty/JSInterpreter"; + +const defaultInterpreter = new Interpreter("", () => undefined); + +// the acorn interpreter has a bug where it doesn't convert arrays correctly. +// so we have to more or less copy it here. +export function toNative(pseudoObj: any): any { + if (pseudoObj == null) return null; + if ( + !pseudoObj.hasOwnProperty("properties") || + !pseudoObj.hasOwnProperty("getter") || + !pseudoObj.hasOwnProperty("setter") || + !pseudoObj.hasOwnProperty("proto") + ) { + return pseudoObj; // it wasn't a pseudo object anyway. + } + + let nativeObj: any; + if (pseudoObj.hasOwnProperty("class") && pseudoObj.class === "Array") { + nativeObj = []; + const length = defaultInterpreter.getProperty(pseudoObj, "length"); + for (let i = 0; i < length; i++) { + if (defaultInterpreter.hasProperty(pseudoObj, i)) { + nativeObj[i] = toNative(defaultInterpreter.getProperty(pseudoObj, i)); + } + } + } else { + // Object. + nativeObj = {}; + for (const key in pseudoObj.properties) { + const val = pseudoObj.properties[key]; + nativeObj[key] = toNative(val); + } + } + return nativeObj; +} diff --git a/src/NetscriptJSEvaluator.ts b/src/NetscriptJSEvaluator.ts index 1e70e5cf2..6f615914a 100644 --- a/src/NetscriptJSEvaluator.ts +++ b/src/NetscriptJSEvaluator.ts @@ -8,7 +8,21 @@ function makeScriptBlob(code: string): Blob { return new Blob([code], { type: "text/javascript" }); } -export function compile(script: Script, scripts: Script[]): void {} +export function compile(script: Script, scripts: Script[]): void { + if (!shouldCompile(script, scripts)) return; + // The URL at the top is the one we want to import. It will + // recursively import all the other modules in the urlStack. + // + // Webpack likes to turn the import into a require, which sort of + // but not really behaves like import. Particularly, it cannot + // load fully dynamic content. So we hide the import from webpack + // by placing it inside an eval call. + script.markUpdated(); + const uurls = _getScriptUrls(script, scripts, []); + script.url = uurls[uurls.length - 1].url; + script.module = new Promise((resolve) => resolve(eval("import(uurls[uurls.length - 1].url)"))); + script.dependencies = uurls; +} // Begin executing a user JS script, and return a promise that resolves // or rejects when the script finishes. @@ -19,23 +33,9 @@ export function compile(script: Script, scripts: Script[]): void {} // When the promise returned by this resolves, we'll have finished // running the main function of the script. export async function executeJSScript(scripts: Script[] = [], workerScript: WorkerScript): Promise { - let uurls: ScriptUrl[] = []; const script = workerScript.getScript(); if (script === null) throw new Error("script is null"); - if (shouldCompile(script, scripts)) { - // The URL at the top is the one we want to import. It will - // recursively import all the other modules in the urlStack. - // - // Webpack likes to turn the import into a require, which sort of - // but not really behaves like import. Particularly, it cannot - // load fully dynamic content. So we hide the import from webpack - // by placing it inside an eval call. - script.markUpdated(); - uurls = _getScriptUrls(script, scripts, []); - script.url = uurls[uurls.length - 1].url; - script.module = new Promise((resolve) => resolve(eval("import(uurls[uurls.length - 1].url)"))); - script.dependencies = uurls; - } + compile(script, scripts); const loadedModule = await script.module; const ns = workerScript.env.vars; diff --git a/src/Terminal/determineAllPossibilitiesForTabCompletion.ts b/src/Terminal/determineAllPossibilitiesForTabCompletion.ts index ea4ef3337..0dab7788b 100644 --- a/src/Terminal/determineAllPossibilitiesForTabCompletion.ts +++ b/src/Terminal/determineAllPossibilitiesForTabCompletion.ts @@ -7,6 +7,9 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { GetServer, GetAllServers } from "../Server/AllServers"; import { ParseCommand, ParseCommands } from "./Parser"; import { isScriptFilename } from "../Script/isScriptFilename"; +import { compile } from "../NetscriptJSEvaluator"; +import { Flags } from "../NetscriptFunctions/Flags"; +import * as libarg from "arg"; // An array of all Terminal commands const commands = [ @@ -298,11 +301,27 @@ export async function determineAllPossibilitiesForTabCompletion( if (filename.endsWith(".script")) return; // Doesn't work with ns1. const script = currServ.scripts.find((script) => script.filename === filename); if (!script) return; // Doesn't exist. - // TODO compile if needs be. - if (!script.module) return; + if (!script.module) { + compile(script, currServ.scripts); + } const loadedModule = await script.module; if (!loadedModule.autocomplete) return; // Doesn't have an autocomplete function. - return loadedModule.autocomplete(); + + const runArgs = { "--tail": Boolean, "-t": Number }; + const flags = libarg(runArgs, { + permissive: true, + argv: command.slice(2), + }); + + return loadedModule.autocomplete( + { + servers: GetAllServers().map((server) => server.hostname), + scripts: currServ.scripts.map((script) => script.filename), + txts: currServ.textFiles.map((txt) => txt.fn), + flags: Flags(flags._), + }, + flags._, + ); } const pos = await scriptAutocomplete(); if (pos) return pos;