bitburner-src/src/Terminal/commands/ls.tsx

284 lines
8.6 KiB
TypeScript
Raw Normal View History

import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import { toString } from "lodash";
2021-10-27 02:26:05 +02:00
import React from "react";
2021-09-16 01:50:44 +02:00
import { BaseServer } from "../../Server/BaseServer";
2022-03-06 05:05:55 +01:00
import { evaluateDirectoryPath, getFirstParentDirectory, isValidDirectoryPath } from "../DirectoryHelpers";
2022-09-06 15:07:12 +02:00
import { Router } from "../../ui/GameRoot";
import { Terminal } from "../../Terminal";
2022-07-20 03:53:43 +02:00
import libarg from "arg";
2022-04-14 16:23:45 +02:00
import { showLiterature } from "../../Literature/LiteratureHelpers";
import { MessageFilenames, showMessage } from "../../Message/MessageHelpers";
2021-09-16 01:50:44 +02:00
2022-09-06 15:07:12 +02:00
export function ls(args: (string | number | boolean)[], server: BaseServer): void {
2022-07-20 03:53:43 +02:00
interface LSFlags {
["-l"]: boolean;
["--grep"]: string;
}
let flags: LSFlags;
try {
2022-04-07 01:30:08 +02:00
flags = libarg(
{
"-l": Boolean,
"--grep": String,
"-g": "--grep",
},
{ argv: args },
);
} catch (e) {
// catch passing only -g / --grep with no string to use as the search
2022-04-07 01:30:08 +02:00
incorrectUsage();
return;
}
2022-04-07 01:30:08 +02:00
const filter = flags["--grep"];
2021-09-16 01:50:44 +02:00
const numArgs = args.length;
function incorrectUsage(): void {
2022-09-06 15:07:12 +02:00
Terminal.error("Incorrect usage of ls command. Usage: ls [dir] [-l] [-g, --grep pattern]");
2021-09-16 01:50:44 +02:00
}
2022-02-05 17:05:48 +01:00
if (numArgs > 4) {
2021-09-16 01:50:44 +02:00
return incorrectUsage();
}
// Directory path
2022-09-06 15:07:12 +02:00
let prefix = Terminal.cwd();
2021-09-16 01:50:44 +02:00
if (!prefix.endsWith("/")) {
prefix += "/";
}
2022-02-05 17:05:48 +01:00
// If first arg doesn't contain a - it must be the file/folder
2022-04-07 01:30:08 +02:00
const dir = args[0] && typeof args[0] == "string" && !args[0].startsWith("-") ? args[0] : "";
2022-09-06 15:07:12 +02:00
const newPath = evaluateDirectoryPath(dir + "", Terminal.cwd());
2022-02-05 17:05:48 +01:00
prefix = newPath || "";
if (!prefix.endsWith("/")) {
prefix += "/";
2021-09-16 01:50:44 +02:00
}
2022-02-05 17:05:48 +01:00
if (!isValidDirectoryPath(prefix)) {
return incorrectUsage();
2021-09-16 01:50:44 +02:00
}
// Root directory, which is the same as no 'prefix' at all
if (prefix === "/") {
prefix = "";
}
// Display all programs and scripts
const allPrograms: string[] = [];
const allScripts: string[] = [];
const allTextFiles: string[] = [];
const allContracts: string[] = [];
const allMessages: string[] = [];
const folders: string[] = [];
function handleFn(fn: string, dest: string[]): void {
let parsedFn = fn;
if (prefix) {
if (!fn.startsWith(prefix)) {
return;
} else {
parsedFn = fn.slice(prefix.length, fn.length);
}
}
if (filter && !parsedFn.includes(filter)) {
return;
}
// If the fn includes a forward slash, it must be in a subdirectory.
// Therefore, we only list the "first" directory in its path
if (parsedFn.includes("/")) {
const firstParentDir = getFirstParentDirectory(parsedFn);
if (filter && !firstParentDir.includes(filter)) {
return;
}
if (!folders.includes(firstParentDir)) {
folders.push(firstParentDir);
}
return;
}
dest.push(parsedFn);
}
// Get all of the programs and scripts on the machine into one temporary array
2022-09-06 15:07:12 +02:00
for (const program of server.programs) handleFn(program, allPrograms);
for (const script of server.scripts) handleFn(script.filename, allScripts);
for (const txt of server.textFiles) handleFn(txt.fn, allTextFiles);
for (const contract of server.contracts) handleFn(contract.fn, allContracts);
for (const msgOrLit of server.messages) handleFn(msgOrLit, allMessages);
2021-09-16 01:50:44 +02:00
// Sort the files/folders alphabetically then print each
allPrograms.sort();
allScripts.sort();
allTextFiles.sort();
allContracts.sort();
allMessages.sort();
folders.sort();
2022-04-14 16:23:45 +02:00
interface ClickableRowProps {
row: string;
prefix: string;
2022-01-11 05:00:52 +01:00
hostname: string;
}
2022-04-14 16:23:45 +02:00
function ClickableScriptRow({ row, prefix, hostname }: ClickableRowProps): React.ReactElement {
const classes = makeStyles((theme: Theme) =>
createStyles({
scriptLinksWrap: {
display: "inline-flex",
color: theme.palette.warning.main,
},
scriptLink: {
cursor: "pointer",
textDecorationLine: "underline",
paddingRight: "1.15em",
"&:last-child": { padding: 0 },
},
}),
)();
2022-03-17 17:50:39 +01:00
const rowSplit = row.split("~");
let rowSplitArray = rowSplit.map((x) => [x.trim(), x.replace(x.trim(), "")]);
2022-03-17 22:25:30 +01:00
rowSplitArray = rowSplitArray.filter((x) => !!x[0]);
function onScriptLinkClick(filename: string): void {
2022-09-06 15:07:12 +02:00
if (!server.isConnectedTo) {
return Terminal.error(`File is not on this server, connect to ${hostname} and try again`);
2022-01-11 05:00:52 +01:00
}
if (filename.startsWith("/")) filename = filename.slice(1);
// Terminal.getFilepath needs leading slash to correctly work here
if (prefix === "") filename = `/${filename}`;
2022-09-06 15:07:12 +02:00
const filepath = Terminal.getFilepath(`${prefix}${filename}`);
// Terminal.getScript also calls Terminal.getFilepath and therefore also
// needs the given parameter
const code = toString(Terminal.getScript(`${prefix}${filename}`)?.code);
2022-09-06 15:07:12 +02:00
Router.toScriptEditor({ [filepath]: code });
}
return (
<span className={classes.scriptLinksWrap}>
2022-03-17 17:50:39 +01:00
{rowSplitArray.map((rowItem) => (
2022-04-14 16:23:45 +02:00
<span key={"script_" + rowItem[0]}>
<span className={classes.scriptLink} onClick={() => onScriptLinkClick(rowItem[0])}>
2022-03-17 17:50:39 +01:00
{rowItem[0]}
</span>
2022-04-14 16:23:45 +02:00
<span>{rowItem[1]}</span>
</span>
))}
</span>
);
}
2022-04-14 16:23:45 +02:00
function ClickableMessageRow({ row, prefix, hostname }: ClickableRowProps): React.ReactElement {
const classes = makeStyles((theme: Theme) =>
createStyles({
linksWrap: {
display: "inline-flex",
color: theme.palette.primary.main,
},
link: {
cursor: "pointer",
textDecorationLine: "underline",
paddingRight: "1.15em",
"&:last-child": { padding: 0 },
},
}),
)();
const rowSplit = row.split("~");
let rowSplitArray = rowSplit.map((x) => [x.trim(), x.replace(x.trim(), "")]);
rowSplitArray = rowSplitArray.filter((x) => !!x[0]);
function onMessageLinkClick(filename: string): void {
2022-09-06 15:07:12 +02:00
if (!server.isConnectedTo) {
return Terminal.error(`File is not on this server, connect to ${hostname} and try again`);
2022-04-14 16:23:45 +02:00
}
if (filename.startsWith("/")) filename = filename.slice(1);
2022-09-06 15:07:12 +02:00
const filepath = Terminal.getFilepath(`${prefix}${filename}`);
2022-04-14 16:23:45 +02:00
if (filepath.endsWith(".lit")) {
showLiterature(filepath);
} else if (filepath.endsWith(".msg")) {
showMessage(filepath as MessageFilenames);
}
}
return (
<span className={classes.linksWrap}>
{rowSplitArray.map((rowItem) => (
<span key={"text_" + rowItem[0]}>
<span className={classes.link} onClick={() => onMessageLinkClick(rowItem[0])}>
{rowItem[0]}
</span>
<span>{rowItem[1]}</span>
</span>
))}
</span>
);
}
enum FileType {
Folder,
Message,
TextFile,
Program,
Contract,
Script,
}
interface FileGroup {
type: FileType;
segments: string[];
}
2022-07-20 03:53:43 +02:00
function postSegments(group: FileGroup, flags: LSFlags): void {
2022-04-14 16:23:45 +02:00
const segments = group.segments;
const linked = group.type === FileType.Script || group.type === FileType.Message;
2021-09-16 01:50:44 +02:00
const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
2022-03-17 18:29:18 +01:00
const filesPerRow = flags["-l"] === true ? 1 : Math.ceil(80 / maxLength);
2021-09-16 01:50:44 +02:00
for (let i = 0; i < segments.length; i++) {
let row = "";
for (let col = 0; col < filesPerRow; col++) {
if (!(i < segments.length)) break;
row += segments[i];
row += " ".repeat(maxLength * (col + 1) - row.length);
2022-04-07 01:30:08 +02:00
if (linked) {
2022-03-17 17:50:39 +01:00
row += "~";
}
2021-09-16 01:50:44 +02:00
i++;
}
i--;
2022-04-14 16:23:45 +02:00
switch (group.type) {
case FileType.Folder:
2022-09-06 15:07:12 +02:00
Terminal.printRaw(<span style={{ color: "cyan" }}>{row}</span>);
2022-04-14 16:23:45 +02:00
break;
case FileType.Script:
2022-09-06 15:07:12 +02:00
Terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
2022-04-14 16:23:45 +02:00
break;
case FileType.Message:
2022-09-06 15:07:12 +02:00
Terminal.printRaw(<ClickableMessageRow row={row} prefix={prefix} hostname={server.hostname} />);
2022-04-14 16:23:45 +02:00
break;
default:
2022-09-06 15:07:12 +02:00
Terminal.print(row);
2022-03-17 17:50:39 +01:00
}
2021-09-16 01:50:44 +02:00
}
}
2022-04-14 16:23:45 +02:00
const groups: FileGroup[] = [
{ type: FileType.Folder, segments: folders },
{ type: FileType.Message, segments: allMessages },
{ type: FileType.TextFile, segments: allTextFiles },
{ type: FileType.Program, segments: allPrograms },
{ type: FileType.Contract, segments: allContracts },
{ type: FileType.Script, segments: allScripts },
2021-09-16 01:50:44 +02:00
].filter((g) => g.segments.length > 0);
2022-04-14 16:23:45 +02:00
for (const group of groups) {
postSegments(group, flags);
2021-09-16 01:50:44 +02:00
}
}