bitburner-src/src/Terminal/determineAllPossibilitiesForTabCompletion.ts

410 lines
10 KiB
TypeScript
Raw Normal View History

2021-09-09 05:47:34 +02:00
import { evaluateDirectoryPath, getAllParentDirectories } from "./DirectoryHelpers";
import { getSubdirectories } from "./DirectoryServerHelpers";
2019-04-20 07:27:33 +02:00
2021-11-16 05:49:33 +01:00
import { Aliases, GlobalAliases, substituteAliases } from "../Alias";
import { DarkWebItems } from "../DarkWeb/DarkWebItems";
2021-09-05 01:09:30 +02:00
import { IPlayer } from "../PersonObjects/IPlayer";
2021-10-07 22:56:01 +02:00
import { GetServer, GetAllServers } from "../Server/AllServers";
2021-10-15 04:36:28 +02:00
import { ParseCommand, ParseCommands } from "./Parser";
2022-01-14 22:25:30 +01:00
import { HelpTexts } from "./HelpText";
2021-10-15 04:36:28 +02:00
import { isScriptFilename } from "../Script/isScriptFilename";
2021-10-15 18:47:43 +02:00
import { compile } from "../NetscriptJSEvaluator";
import { Flags } from "../NetscriptFunctions/Flags";
import { AutocompleteData } from "../ScriptEditor/NetscriptDefinitions";
2021-10-15 18:47:43 +02:00
import * as libarg from "arg";
// An array of all Terminal commands
const commands = [
2021-09-05 01:09:30 +02:00
"alias",
"analyze",
"backdoor",
"cat",
"cd",
"check",
"clear",
"cls",
"connect",
2021-10-12 04:35:00 +02:00
"cp",
2021-09-05 01:09:30 +02:00
"download",
"expr",
"free",
"grow",
2021-09-05 01:09:30 +02:00
"hack",
"help",
"home",
"hostname",
"ifconfig",
"kill",
"killall",
"ls",
"lscpu",
"mem",
"mv",
"nano",
"ps",
"rm",
"run",
"scan-analyze",
"scan",
2021-09-05 01:09:30 +02:00
"scp",
"sudov",
"tail",
"theme",
"top",
2021-12-17 18:48:34 +01:00
"vim",
"weaken",
];
2021-10-15 04:36:28 +02:00
export async function determineAllPossibilitiesForTabCompletion(
2021-09-05 01:09:30 +02:00
p: IPlayer,
input: string,
index: number,
currPath = "",
2021-10-15 04:36:28 +02:00
): Promise<string[]> {
2021-11-16 05:49:33 +01:00
input = substituteAliases(input);
2021-09-05 01:09:30 +02:00
let allPos: string[] = [];
allPos = allPos.concat(Object.keys(GlobalAliases));
const currServ = p.getCurrentServer();
const homeComputer = p.getHomeComputer();
let parentDirPath = "";
let evaledParentDirPath: string | null = null;
// Helper functions
function addAllCodingContracts(): void {
for (const cct of currServ.contracts) {
allPos.push(cct.fn);
}
2021-09-05 01:09:30 +02:00
}
2021-09-05 01:09:30 +02:00
function addAllLitFiles(): void {
for (const file of currServ.messages) {
2021-10-14 08:07:05 +02:00
if (!file.endsWith(".msg")) {
2021-09-05 01:09:30 +02:00
allPos.push(file);
}
}
2021-09-05 01:09:30 +02:00
}
2021-09-05 01:09:30 +02:00
function addAllMessages(): void {
for (const file of currServ.messages) {
2021-10-14 08:07:05 +02:00
if (file.endsWith(".msg")) {
allPos.push(file);
2021-09-05 01:09:30 +02:00
}
}
2021-09-05 01:09:30 +02:00
}
2021-09-05 01:09:30 +02:00
function addAllPrograms(): void {
for (const program of homeComputer.programs) {
allPos.push(program);
}
2021-09-05 01:09:30 +02:00
}
function addAllScripts(): void {
for (const script of currServ.scripts) {
const res = processFilepath(script.filename);
if (res) {
allPos.push(res);
}
}
2021-09-05 01:09:30 +02:00
}
function addAllTextFiles(): void {
for (const txt of currServ.textFiles) {
const res = processFilepath(txt.fn);
if (res) {
allPos.push(res);
}
2019-04-20 07:27:33 +02:00
}
2021-09-05 01:09:30 +02:00
}
function addAllDirectories(): void {
// Directories are based on the currently evaluated path
2021-09-09 05:47:34 +02:00
const subdirs = getSubdirectories(currServ, evaledParentDirPath == null ? "/" : evaledParentDirPath);
2021-09-05 01:09:30 +02:00
for (let i = 0; i < subdirs.length; ++i) {
2021-09-09 05:47:34 +02:00
const assembledDirPath = evaledParentDirPath == null ? subdirs[i] : evaledParentDirPath + subdirs[i];
2021-09-05 01:09:30 +02:00
const res = processFilepath(assembledDirPath);
if (res != null) {
subdirs[i] = res;
}
2019-04-20 07:27:33 +02:00
}
2021-09-05 01:09:30 +02:00
allPos = allPos.concat(subdirs);
}
2019-04-20 07:27:33 +02:00
2021-09-05 01:09:30 +02:00
// Convert from the real absolute path back to the original path used in the input
function convertParentPath(filepath: string): string {
if (parentDirPath == null || evaledParentDirPath == null) {
console.warn(`convertParentPath() called when paths are null`);
return filepath;
2019-04-20 07:27:33 +02:00
}
2021-09-05 01:09:30 +02:00
if (!filepath.startsWith(evaledParentDirPath)) {
console.warn(
`convertParentPath() called for invalid path. (filepath=${filepath}) (evaledParentDirPath=${evaledParentDirPath})`,
);
return filepath;
}
2021-09-05 01:09:30 +02:00
return parentDirPath + filepath.slice(evaledParentDirPath.length);
}
// Given an a full, absolute filepath, converts it to the proper value
// for autocompletion purposes
function processFilepath(filepath: string): string | null {
if (evaledParentDirPath) {
if (filepath.startsWith(evaledParentDirPath)) {
return convertParentPath(filepath);
}
} else if (parentDirPath !== "") {
// If the parent directory is the root directory, but we're not searching
// it from the root directory, we have to add the original path
let t_parentDirPath = parentDirPath;
if (!t_parentDirPath.endsWith("/")) {
t_parentDirPath += "/";
}
return parentDirPath + filepath;
} else {
return filepath;
}
2021-09-05 01:09:30 +02:00
return null;
}
function isCommand(cmd: string): boolean {
let t_cmd = cmd;
if (!t_cmd.endsWith(" ")) {
t_cmd += " ";
}
2021-09-05 01:09:30 +02:00
return input.startsWith(t_cmd);
}
// Autocomplete the command
2022-01-12 00:04:14 +01:00
if (index === -1 && !input.startsWith('./')) {
2021-09-09 05:47:34 +02:00
return commands.concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases));
2021-09-05 01:09:30 +02:00
}
// Since we're autocompleting an argument and not a command, the argument might
// be a file/directory path. We have to account for that when autocompleting
const commandArray = input.split(" ");
if (commandArray.length === 0) {
console.warn(`Tab autocompletion logic reached invalid branch`);
return allPos;
}
const arg = commandArray[commandArray.length - 1];
parentDirPath = getAllParentDirectories(arg);
evaledParentDirPath = evaluateDirectoryPath(parentDirPath, currPath);
if (evaledParentDirPath === "/") {
evaledParentDirPath = null;
} else if (evaledParentDirPath == null) {
return allPos; // Invalid path
} else {
evaledParentDirPath += "/";
}
if (isCommand("buy")) {
const options = [];
2022-01-16 01:45:03 +01:00
for (const i of Object.keys(DarkWebItems)) {
2021-09-05 01:09:30 +02:00
const item = DarkWebItems[i];
options.push(item.program);
2019-04-20 07:27:33 +02:00
}
2021-09-05 01:09:30 +02:00
return options.concat(Object.keys(GlobalAliases));
}
2021-09-05 01:09:30 +02:00
if (isCommand("scp") && index === 1) {
2021-10-07 22:04:04 +02:00
for (const server of GetAllServers()) {
allPos.push(server.hostname);
}
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("scp") && index === 0) {
addAllScripts();
addAllLitFiles();
addAllTextFiles();
addAllDirectories();
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-10-12 04:35:00 +02:00
if (isCommand("cp") && index === 0) {
addAllScripts();
addAllTextFiles();
addAllDirectories();
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("connect")) {
// All network connections
for (let i = 0; i < currServ.serversOnNetwork.length; ++i) {
2021-10-07 22:56:01 +02:00
const serv = GetServer(currServ.serversOnNetwork[i]);
2021-09-05 01:09:30 +02:00
if (serv == null) {
continue;
}
allPos.push(serv.hostname);
}
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-12-17 18:48:34 +01:00
if (isCommand("nano") || isCommand("vim")) {
2021-09-05 01:09:30 +02:00
addAllScripts();
addAllTextFiles();
addAllDirectories();
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("rm")) {
addAllScripts();
addAllPrograms();
addAllLitFiles();
addAllTextFiles();
addAllCodingContracts();
addAllDirectories();
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-10-15 04:36:28 +02:00
async function scriptAutocomplete(): Promise<string[] | undefined> {
if (!isCommand("run") && !isCommand("tail") && !isCommand("kill") && !input.startsWith("./")) return;
2022-01-12 00:04:14 +01:00
let copy = input;
if (input.startsWith("./")) copy = "run " + input.slice(2);
const commands = ParseCommands(copy);
2021-10-15 04:36:28 +02:00
if (commands.length === 0) return;
const command = ParseCommand(commands[commands.length - 1]);
const filename = command[1] + "";
if (!isScriptFilename(filename)) return; // Not a script.
if (filename.endsWith(".script")) return; // Doesn't work with ns1.
2022-01-12 00:04:14 +01:00
// Use regex to remove any leading './', and then check if it matches against
// the output of processFilepath or if it matches with a '/' prepended,
// this way autocomplete works inside of directories
const script = currServ.scripts.find((script) => {
const fn = filename.replace(/^\.\//g, '');
return (processFilepath(script.filename) === fn || script.filename === '/' + fn);
})
2021-10-15 04:36:28 +02:00
if (!script) return; // Doesn't exist.
2021-10-15 18:47:43 +02:00
if (!script.module) {
2022-01-05 01:09:34 +01:00
await compile(p, script, currServ.scripts);
2021-10-15 18:47:43 +02:00
}
2021-10-15 04:36:28 +02:00
const loadedModule = await script.module;
if (!loadedModule.autocomplete) return; // Doesn't have an autocomplete function.
2021-10-15 18:47:43 +02:00
const runArgs = { "--tail": Boolean, "-t": Number };
const flags = libarg(runArgs, {
permissive: true,
argv: command.slice(2),
});
2021-10-15 19:12:18 +02:00
const flagFunc = Flags(flags._);
const autocompleteData: AutocompleteData = {
servers: GetAllServers().map((server) => server.hostname),
scripts: currServ.scripts.map((script) => script.filename),
txts: currServ.textFiles.map((txt) => txt.fn),
flags: (schema: any) => {
pos2 = schema.map((f: any) => {
if (f[0].length === 1) return "-" + f[0];
return "--" + f[0];
});
try {
return flagFunc(schema);
} catch (err) {
return undefined;
}
},
}
2021-10-15 19:12:18 +02:00
let pos: string[] = [];
let pos2: string[] = [];
pos = pos.concat(
loadedModule.autocomplete(
autocompleteData,
2021-10-15 19:12:18 +02:00
flags._,
),
2021-10-15 18:47:43 +02:00
);
2021-10-15 19:12:18 +02:00
return pos.concat(pos2);
2021-10-15 04:36:28 +02:00
}
const pos = await scriptAutocomplete();
if (pos) return pos;
2022-01-12 00:04:14 +01:00
// If input starts with './', essentially treat it as a slimmer
// invocation of `run`.
if (input.startsWith("./")) {
// All programs and scripts
for (const script of currServ.scripts) {
const res = processFilepath(script.filename);
if (res) {
allPos.push(res);
}
}
for (const program of currServ.programs) {
const res = processFilepath(program);
if (res) {
allPos.push(res);
}
}
// All coding contracts
for (const cct of currServ.contracts) {
const res = processFilepath(cct.fn);
if (res) {
allPos.push(res);
}
}
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("run")) {
addAllScripts();
addAllPrograms();
addAllCodingContracts();
addAllDirectories();
}
2021-12-03 20:44:32 +01:00
if (isCommand("kill") || isCommand("tail") || isCommand("mem") || isCommand("check")) {
addAllScripts();
addAllDirectories();
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("cat")) {
addAllMessages();
addAllLitFiles();
addAllTextFiles();
addAllDirectories();
addAllScripts();
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("download") || isCommand("mv")) {
addAllScripts();
addAllTextFiles();
addAllDirectories();
2019-04-20 07:27:33 +02:00
2021-09-05 01:09:30 +02:00
return allPos;
}
2019-04-20 07:27:33 +02:00
2021-09-05 01:09:30 +02:00
if (isCommand("cd")) {
addAllDirectories();
2021-09-05 01:09:30 +02:00
return allPos;
}
2021-09-05 01:09:30 +02:00
if (isCommand("ls") && index === 0) {
addAllDirectories();
}
2019-04-20 07:27:33 +02:00
2022-01-14 22:25:30 +01:00
if (isCommand("help")) {
// Get names from here instead of commands array because some
// undocumented/nonexistent commands are in the array
return Object.keys(HelpTexts);
}
2021-09-05 01:09:30 +02:00
return allPos;
}