From b0918d7bd31d303cef471b0d79bc6984cc758e96 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sat, 11 May 2019 19:20:20 -0700 Subject: [PATCH] Fixed numerous reported bugs. Refactored some of the directory-related code. Added documentation for MasonDs changes to hack/grow/weaken --- doc/source/basicgameplay/stockmarket.rst | 13 +++-- doc/source/netscript/basicfunctions/grow.rst | 6 +++ doc/source/netscript/basicfunctions/hack.rst | 6 +++ .../netscript/basicfunctions/weaken.rst | 8 ++- .../singularityfunctions/workForFaction.rst | 2 +- src/Constants.ts | 7 +++ src/Gang.js | 22 ++++---- src/Hacking/netscriptCanHack.ts | 1 - src/NetscriptFunctions.js | 23 ++++++-- src/NetscriptWorker.js | 14 ++--- src/Script/RamCalculationErrorCodes.ts | 5 ++ src/Script/RamCalculations.js | 15 +++--- src/Script/Script.ts | 2 +- src/Script/ScriptHelpers.js | 17 +++++- src/Server/BaseServer.ts | 15 +++--- src/Terminal.js | 2 +- src/Terminal/DirectoryHelpers.ts | 47 +++------------- src/Terminal/DirectoryServerHelpers.ts | 53 +++++++++++++++++++ ...termineAllPossibilitiesForTabCompletion.ts | 2 +- 19 files changed, 172 insertions(+), 88 deletions(-) create mode 100644 src/Script/RamCalculationErrorCodes.ts create mode 100644 src/Terminal/DirectoryServerHelpers.ts diff --git a/doc/source/basicgameplay/stockmarket.rst b/doc/source/basicgameplay/stockmarket.rst index b84cd8894..49b86e3bc 100644 --- a/doc/source/basicgameplay/stockmarket.rst +++ b/doc/source/basicgameplay/stockmarket.rst @@ -134,10 +134,15 @@ A Stop Order to buy will execute if the stock's price <= order's price A Stop Order to sell will execute if the stock's price >= order's price. -.. note:: Limit and Stop orders do **not** take into account the fact that - transactions can influence a stock's price. If a stock's price - changes mid-transaction, a limit/stop order will continue to execute - even if its conditions are no longer met. +.. note:: Stop Orders do **not** take into account the fact that transactions can + influence a stock's price. Limit Orders, however, do take this into account. + + For example, assume you have a Limit Order set to purchase a stock at + $5. Then, the stock's price drops to $5 and your Limit Order executes. + However, the transaction causes the stock's price to increase before + the order finishes executing all of the shares. Your Limit Order will + stop executing, and will continue only when the stock's price drops back to + $5 or below. Automating the Stock Market --------------------------- diff --git a/doc/source/netscript/basicfunctions/grow.rst b/doc/source/netscript/basicfunctions/grow.rst index 9ab819462..ddd828c78 100644 --- a/doc/source/netscript/basicfunctions/grow.rst +++ b/doc/source/netscript/basicfunctions/grow.rst @@ -4,6 +4,11 @@ grow() Netscript Function .. js:function:: grow(hostname/ip) :param string hostname/ip: IP or hostname of the target server to grow + :param object options: Optional parameters for configuring function behavior. Properties: + + * threads (*number*) - Number of threads to use for this function. + Must be less than or equal to the number of threads the script is running with. + :returns: The number by which the money on the server was multiplied for the growth :RAM cost: 0.15 GB @@ -19,3 +24,4 @@ grow() Netscript Function Example:: grow("foodnstuff"); + grow("foodnstuff", { threads: 5 }); // Only use 5 threads to grow diff --git a/doc/source/netscript/basicfunctions/hack.rst b/doc/source/netscript/basicfunctions/hack.rst index a62c85a3d..6f49954c1 100644 --- a/doc/source/netscript/basicfunctions/hack.rst +++ b/doc/source/netscript/basicfunctions/hack.rst @@ -4,6 +4,11 @@ hack() Netscript Function .. js:function:: hack(hostname/ip) :param string hostname/ip: IP or hostname of the target server to hack + :param object options: Optional parameters for configuring function behavior. Properties: + + * threads (*number*) - Number of threads to use for this function. + Must be less than or equal to the number of threads the script is running with. + :returns: The amount of money stolen if the hack is successful, and zero otherwise :RAM cost: 0.1 GB @@ -20,3 +25,4 @@ hack() Netscript Function hack("foodnstuff"); hack("10.1.2.3"); + hack("foodnstuff", { threads: 5 }); // Only use 5 threads to hack diff --git a/doc/source/netscript/basicfunctions/weaken.rst b/doc/source/netscript/basicfunctions/weaken.rst index e1eebc1db..4d13e1a1a 100644 --- a/doc/source/netscript/basicfunctions/weaken.rst +++ b/doc/source/netscript/basicfunctions/weaken.rst @@ -1,9 +1,14 @@ weaken() Netscript Function =========================== -.. js:function:: weaken(hostname/ip) +.. js:function:: weaken(hostname/ip, options={}) :param string hostname/ip: IP or hostname of the target server to weaken + :param object options: Optional parameters for configuring function behavior. Properties: + + * threads (*number*) - Number of threads to use for this function. + Must be less than or equal to the number of threads the script is running with. + :returns: The amount by which the target server's security level was decreased. This is equivalent to 0.05 multiplied by the number of script threads :RAM cost: 0.15 GB @@ -18,3 +23,4 @@ weaken() Netscript Function Example:: weaken("foodnstuff"); + weaken("foodnstuff", { threads: 5 }); // Only use 5 threads to weaken diff --git a/doc/source/netscript/singularityfunctions/workForFaction.rst b/doc/source/netscript/singularityfunctions/workForFaction.rst index e544a78da..e30bf9ed4 100644 --- a/doc/source/netscript/singularityfunctions/workForFaction.rst +++ b/doc/source/netscript/singularityfunctions/workForFaction.rst @@ -5,7 +5,7 @@ workForFaction() Netscript Function :param string factionName: Name of faction to work for. CASE-SENSITIVE :param string workType: - Type of work to perform for the faction + Type of work to perform for the faction: * hacking/hacking contracts/hackingcontracts * field/fieldwork/field work diff --git a/src/Constants.ts b/src/Constants.ts index 07d52ee41..05b4f5612 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -233,9 +233,12 @@ export let CONSTANTS: IMap = { * Re-sleeves can no longer have the NeuroFlux Governor augmentation ** This is just a temporary patch until the mechanic gets re-worked + * hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD) + * Adjusted RAM costs of Netscript Singularity functions (mostly increased) * Netscript Singularity functions no longer cost extra RAM outside of BitNode-4 * Corporation employees no longer have an "age" stat + * Gang Wanted level gain rate capped at 100 (per employee) * Bug Fix: Corporation employees stats should no longer become negative * Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios * Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server @@ -245,5 +248,9 @@ export let CONSTANTS: IMap = { * Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes * Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads * Bug Fix: Faction UI should now automatically update reputation + * Bug Fix: Fixed purchase4SMarketData() + * Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from "script") + * Bug Fix: Terminal 'wget' command now correctly evaluates directory paths + * Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in ` } diff --git a/src/Gang.js b/src/Gang.js index 39c960c2d..db42bd646 100644 --- a/src/Gang.js +++ b/src/Gang.js @@ -682,21 +682,25 @@ GangMember.prototype.calculateRespectGain = function(gang) { GangMember.prototype.calculateWantedLevelGain = function(gang) { const task = this.getTask(); - if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) {return 0;} - var statWeight = (task.hackWeight/100) * this.hack + - (task.strWeight/100) * this.str + - (task.defWeight/100) * this.def + - (task.dexWeight/100) * this.dex + - (task.agiWeight/100) * this.agi + - (task.chaWeight/100) * this.cha; + if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) { return 0; } + let statWeight = (task.hackWeight / 100) * this.hack + + (task.strWeight / 100) * this.str + + (task.defWeight / 100) * this.def + + (task.dexWeight / 100) * this.dex + + (task.agiWeight / 100) * this.agi + + (task.chaWeight / 100) * this.cha; statWeight -= (3.5 * task.difficulty); if (statWeight <= 0) { return 0; } const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100; if (isNaN(territoryMult) || territoryMult <= 0) { return 0; } if (task.baseWanted < 0) { - return 0.5 * task.baseWanted * statWeight * territoryMult; + return 0.4 * task.baseWanted * statWeight * territoryMult; } else { - return 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8)); + const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8)); + + // Put an arbitrary cap on this to prevent wanted level from rising too fast if the + // denominator is very small. Might want to rethink formula later + return Math.max(100, calc); } } diff --git a/src/Hacking/netscriptCanHack.ts b/src/Hacking/netscriptCanHack.ts index f2b8fc6a2..204d959a5 100644 --- a/src/Hacking/netscriptCanHack.ts +++ b/src/Hacking/netscriptCanHack.ts @@ -6,7 +6,6 @@ */ import { IReturnStatus } from "../types"; -//import { HacknetServer } from "../Hacknet/HacknetServer"; import { IPlayer } from "../PersonObjects/IPlayer"; import { Server } from "../Server/Server"; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 553aebb17..56a642a91 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -110,6 +110,7 @@ import { getStockMarket4SDataCost, getStockMarket4STixApiCost } from "./StockMarket/StockMarketCosts"; +import { isValidFilePath } from "./Terminal/DirectoryHelpers"; import { TextFile, getTextFile, createTextFile } from "./TextFile"; import { @@ -1013,8 +1014,14 @@ function NetscriptFunctions(workerScript) { }); return res; } - if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) && - !scriptname.endsWith("txt")) { + + // Invalid file type + if (!isValidFilePath(scriptname)) { + throw makeRuntimeRejectMsg(workerScript, `Error: scp() failed due to invalid filename: ${scriptname}`); + } + + // Invalid file name + if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) && !scriptname.endsWith("txt")) { throw makeRuntimeRejectMsg(workerScript, "ERROR: scp() does not work with this file type. It only works for .script, .lit, and .txt files"); } @@ -1724,6 +1731,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.shouldLog("purchase4SMarketData")) { workerScript.log("Purchased 4S Market Data"); } + displayStockMarketContent(); return true; }, purchase4SMarketDataTixApi : function() { @@ -1749,6 +1757,7 @@ function NetscriptFunctions(workerScript) { if (workerScript.shouldLog("purchase4SMarketDataTixApi")) { workerScript.log("Purchased 4S Market Data TIX API"); } + displayStockMarketContent(); return true; }, getPurchasedServerLimit : function() { @@ -1919,8 +1928,12 @@ function NetscriptFunctions(workerScript) { } return port.write(data); } else if (isString(port)) { // Write to script or text file - var fn = port; - var server = workerScript.getServer(); + const fn = port; + if (!isValidFilePath(fn)) { + throw makeRuntimeRejectMsg(workerScript, `write() failed due to invalid filepath: ${fn}`); + } + + const server = workerScript.getServer(); if (server == null) { throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in write(). This is a bug please contact game dev"); } @@ -2251,7 +2264,7 @@ function NetscriptFunctions(workerScript) { }, wget: async function(url, target, ip=workerScript.serverIp) { if (!isScriptFilename(target) && !target.endsWith(".txt")) { - workerSript.log(`ERROR: wget() failed because of an invalid target file: ${target}. Target file must be a script or text file`); + workerScript.log(`ERROR: wget() failed because of an invalid target file: ${target}. Target file must be a script or text file`); return Promise.resolve(false); } var s = safeGetServer(ip, "wget"); diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index d5eae7f47..b60c8c944 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -178,7 +178,6 @@ function startNetscript1Script(workerScript) { fnArgs.push(arguments[i]); } } - console.log(fnArgs); let cb = arguments[arguments.length-1]; let fnPromise = entry.apply(null, fnArgs); fnPromise.then(function(res) { @@ -289,7 +288,7 @@ function startNetscript1Script(workerScript) { */ function processNetscript1Imports(code, workerScript) { //allowReserved prevents 'import' from throwing error in ES5 - var ast = parse(code, {ecmaVersion:6, allowReserved:true, sourceType:"module"}); + const ast = parse(code, { ecmaVersion: 6, allowReserved: true, sourceType: "module" }); var server = workerScript.getServer(); if (server == null) { @@ -305,10 +304,10 @@ function processNetscript1Imports(code, workerScript) { return null; } - var generatedCode = ""; //Generated Javascript Code - var hasImports = false; + let generatedCode = ""; // Generated Javascript Code + let hasImports = false; - //Walk over the tree and process ImportDeclaration nodes + // Walk over the tree and process ImportDeclaration nodes walk.simple(ast, { ImportDeclaration: (node) => { hasImports = true; @@ -323,7 +322,7 @@ function processNetscript1Imports(code, workerScript) { let scriptAst = parse(script.code, {ecmaVersion:5, allowReserved:true, sourceType:"module"}); if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") { - //import * as namespace from script + // import * as namespace from script let namespace = node.specifiers[0].local.name; let fnNames = []; //Names only let fnDeclarations = []; //FunctionDeclaration Node objects @@ -335,7 +334,7 @@ function processNetscript1Imports(code, workerScript) { }); //Now we have to generate the code that would create the namespace - generatedCode = + generatedCode += "var " + namespace + ";\n" + "(function (namespace) {\n"; @@ -406,6 +405,7 @@ function processNetscript1Imports(code, workerScript) { //Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5); code = generatedCode + code; + var res = { code: code, lineOffset: lineOffset diff --git a/src/Script/RamCalculationErrorCodes.ts b/src/Script/RamCalculationErrorCodes.ts new file mode 100644 index 000000000..36da465bc --- /dev/null +++ b/src/Script/RamCalculationErrorCodes.ts @@ -0,0 +1,5 @@ +export enum RamCalculationErrorCode { + SyntaxError = -1, + ImportError = -2, + URLImportError = -3, +} diff --git a/src/Script/RamCalculations.js b/src/Script/RamCalculations.js index 9a14beb5b..3013b8c2f 100644 --- a/src/Script/RamCalculations.js +++ b/src/Script/RamCalculations.js @@ -1,6 +1,8 @@ // Calculate a script's RAM usage import * as walk from "acorn-walk"; +import { RamCalculationErrorCode } from "./RamCalculationErrorCodes"; + import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator"; import { parse, Node } from "../../utils/acorn"; @@ -71,12 +73,12 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { } } catch(e) { console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); - return -1; + return RamCalculationErrorCode.URLImportError; } } else { if (!Array.isArray(otherScripts)) { console.warn(`parseOnlyRamCalculate() not called with array of scripts`); - return -1; + return RamCalculationErrorCode.ImportError; } let script = null; @@ -89,8 +91,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { } if (script == null) { - console.warn("Invalid script"); - return -1; // No such script on the server. + return RamCalculationErrorCode.ImportError; // No such script on the server } code = script.code; @@ -191,7 +192,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { // console.info("parse or eval error: ", error); // This is not unexpected. The user may be editing a script, and it may be in // a transitory invalid state. - return -1; + return RamCalculationErrorCode.SyntaxError; } } @@ -310,8 +311,8 @@ export async function calculateRamUsage(codeCopy, otherScripts) { } catch (e) { console.error(`Failed to parse script for RAM calculations:`); console.error(e); - return -1; + return RamCalculationErrorCode.SyntaxError; } - return -1; + return RamCalculationErrorCode.SyntaxError; } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index 0dcc4f022..f700b0ef1 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -89,7 +89,7 @@ export class Script { // Updates the script's RAM usage based on its code async updateRamUsage(otherScripts: Script[]) { var res = await calculateRamUsage(this.code, otherScripts); - if (res !== -1) { + if (res > 0) { this.ramUsage = roundToTwo(res); } } diff --git a/src/Script/ScriptHelpers.js b/src/Script/ScriptHelpers.js index 557ee5b37..c30c6170f 100644 --- a/src/Script/ScriptHelpers.js +++ b/src/Script/ScriptHelpers.js @@ -1,5 +1,6 @@ import { Script } from "./Script"; +import { RamCalculationErrorCode } from "./RamCalculationErrorCodes"; import { calculateRamUsage } from "./RamCalculations"; import { isScriptFilename } from "./ScriptHelpersTS"; @@ -190,10 +191,22 @@ export async function updateScriptEditorContent() { var codeCopy = code.repeat(1); var ramUsage = await calculateRamUsage(codeCopy, Player.getCurrentServer().scripts); - if (ramUsage !== -1) { + if (ramUsage > 0) { scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB"; } else { - scriptEditorRamText.innerText = "RAM: Syntax Error"; + switch (ramUsage) { + case RamCalculationErrorCode.ImportError: + scriptEditorRamText.innerText = "RAM: Import Error"; + break; + case RamCalculationErrorCode.URLImportError: + scriptEditorRamText.innerText = "RAM: HTTP Import Error"; + break; + case RamCalculationErrorCode.SyntaxError: + default: + scriptEditorRamText.innerText = "RAM: Syntax Error"; + break; + } + } } diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index b787bd807..74f297189 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -5,6 +5,7 @@ import { CodingContract } from "../CodingContracts"; import { Message } from "../Message/Message"; import { RunningScript } from "../Script/RunningScript"; import { Script } from "../Script/Script"; +import { isValidFilePath } from "../Terminal/DirectoryHelpers"; import { TextFile } from "../TextFile"; import { IReturnStatus } from "../types"; @@ -223,10 +224,10 @@ export class BaseServer { * Overwrites existing files. Creates new files if the script does not eixst */ writeToScriptFile(fn: string, code: string) { - var ret = {success: false, overwritten: false}; - if (!isScriptFilename(fn)) { return ret; } + var ret = { success: false, overwritten: false }; + if (!isValidFilePath(fn) || !isScriptFilename(fn)) { return ret; } - //Check if the script already exists, and overwrite it if it does + // Check if the script already exists, and overwrite it if it does for (let i = 0; i < this.scripts.length; ++i) { if (fn === this.scripts[i].filename) { let script = this.scripts[i]; @@ -239,7 +240,7 @@ export class BaseServer { } } - //Otherwise, create a new script + // Otherwise, create a new script const newScript = new Script(fn, code, this.ip, this.scripts); this.scripts.push(newScript); ret.success = true; @@ -250,9 +251,9 @@ export class BaseServer { // Overwrites existing files. Creates new files if the text file does not exist writeToTextFile(fn: string, txt: string) { var ret = { success: false, overwritten: false }; - if (!fn.endsWith("txt")) { return ret; } + if (!isValidFilePath(fn) || !fn.endsWith("txt")) { return ret; } - //Check if the text file already exists, and overwrite if it does + // Check if the text file already exists, and overwrite if it does for (let i = 0; i < this.textFiles.length; ++i) { if (this.textFiles[i].fn === fn) { ret.overwritten = true; @@ -262,7 +263,7 @@ export class BaseServer { } } - //Otherwise create a new text file + // Otherwise create a new text file var newFile = new TextFile(fn, txt); this.textFiles.push(newFile); ret.success = true; diff --git a/src/Terminal.js b/src/Terminal.js index 728d96892..b88832954 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -1475,7 +1475,7 @@ let Terminal = { } let url = commandArray[1]; - let target = commandArray[2]; + let target = Terminal.getFilepath(commandArray[2]); if (!isScriptFilename(target) && !target.endsWith(".txt")) { return post(`wget failed: Invalid target file. Target file must be script or text file`); } diff --git a/src/Terminal/DirectoryHelpers.ts b/src/Terminal/DirectoryHelpers.ts index cd70b23e0..ae2d425b7 100644 --- a/src/Terminal/DirectoryHelpers.ts +++ b/src/Terminal/DirectoryHelpers.ts @@ -1,9 +1,12 @@ /** * Helper functions that implement "directory" functionality in the Terminal. - * These aren't real directories, they're more of a pseudo-directory implementation + * These aren't "real" directories, it's more of a pseudo-directory implementation + * that uses mainly string manipulation. + * + * This file contains functions that deal only with that string manipulation. + * Functions that need to access/process Server-related things can be + * found in ./DirectoryServerHelpers.ts */ -import { HacknetServer } from "../Hacknet/HacknetServer"; -import { Server } from "../Server/Server"; /** * Removes leading forward slash ("/") from a string. @@ -149,44 +152,6 @@ export function getAllParentDirectories(path: string): string { return t_path.slice(0, lastSlash + 1); } -/** - * Given a directory (by the full directory path) and a server, returns all - * subdirectories of that directory. This is only for FIRST-LEVEl/immediate subdirectories - */ -export function getSubdirectories(serv: Server | HacknetServer, dir: string): string[] { - const res: string[] = []; - - if (!isValidDirectoryPath(dir)) { return res; } - - let t_dir = dir; - if (!t_dir.endsWith("/")) { t_dir += "/"; } - - function processFile(fn: string) { - if (t_dir === "/" && isInRootDirectory(fn)) { - const subdir = getFirstParentDirectory(fn); - if (subdir !== "/" && !res.includes(subdir)) { - res.push(subdir); - } - } else if (fn.startsWith(t_dir)) { - const remaining = fn.slice(t_dir.length); - const subdir = getFirstParentDirectory(remaining); - if (subdir !== "/" && !res.includes(subdir)) { - res.push(subdir); - } - } - } - - for (const script of serv.scripts) { - processFile(script.filename); - } - - for (const txt of serv.textFiles) { - processFile(txt.fn); - } - - return res; -} - /** * Checks if a file path refers to a file in the root directory. */ diff --git a/src/Terminal/DirectoryServerHelpers.ts b/src/Terminal/DirectoryServerHelpers.ts new file mode 100644 index 000000000..69a77f89f --- /dev/null +++ b/src/Terminal/DirectoryServerHelpers.ts @@ -0,0 +1,53 @@ +/** + * Helper functions that implement "directory" functionality in the Terminal. + * These aren't "real" directories, it's more of a pseudo-directory implementation + * that uses mainly string manipulation. + * + * This file contains function that deal with Server-related directory things. + * Functions that deal with the string manipulation can be found in + * ./DirectoryHelpers.ts + */ +import { + isValidDirectoryPath, + isInRootDirectory, + getFirstParentDirectory, +} from "./DirectoryHelpers"; +import { BaseServer } from "../Server/BaseServer"; + +/** + * Given a directory (by the full directory path) and a server, returns all + * subdirectories of that directory. This is only for FIRST-LEVEl/immediate subdirectories + */ +export function getSubdirectories(serv: BaseServer, dir: string): string[] { + const res: string[] = []; + + if (!isValidDirectoryPath(dir)) { return res; } + + let t_dir = dir; + if (!t_dir.endsWith("/")) { t_dir += "/"; } + + function processFile(fn: string) { + if (t_dir === "/" && isInRootDirectory(fn)) { + const subdir = getFirstParentDirectory(fn); + if (subdir !== "/" && !res.includes(subdir)) { + res.push(subdir); + } + } else if (fn.startsWith(t_dir)) { + const remaining = fn.slice(t_dir.length); + const subdir = getFirstParentDirectory(remaining); + if (subdir !== "/" && !res.includes(subdir)) { + res.push(subdir); + } + } + } + + for (const script of serv.scripts) { + processFile(script.filename); + } + + for (const txt of serv.textFiles) { + processFile(txt.fn); + } + + return res; +} diff --git a/src/Terminal/determineAllPossibilitiesForTabCompletion.ts b/src/Terminal/determineAllPossibilitiesForTabCompletion.ts index fcce578eb..c91e549eb 100644 --- a/src/Terminal/determineAllPossibilitiesForTabCompletion.ts +++ b/src/Terminal/determineAllPossibilitiesForTabCompletion.ts @@ -1,8 +1,8 @@ import { evaluateDirectoryPath, getAllParentDirectories, - getSubdirectories, } from "./DirectoryHelpers"; +import { getSubdirectories } from "./DirectoryServerHelpers"; import { Aliases,