mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-30 03:23:48 +01:00
refactor temrinal input for more performace
This commit is contained in:
parent
07721e1cc5
commit
22648df857
@ -45,6 +45,11 @@ interface IDefaultSettings {
|
|||||||
*/
|
*/
|
||||||
MaxPortCapacity: number;
|
MaxPortCapacity: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit the number of entries in the terminal.
|
||||||
|
*/
|
||||||
|
MaxTerminalCapacity: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the player should be asked to confirm purchasing each and every augmentation.
|
* Whether the player should be asked to confirm purchasing each and every augmentation.
|
||||||
*/
|
*/
|
||||||
@ -104,6 +109,7 @@ const defaultSettings: IDefaultSettings = {
|
|||||||
Locale: "en",
|
Locale: "en",
|
||||||
MaxLogCapacity: 50,
|
MaxLogCapacity: 50,
|
||||||
MaxPortCapacity: 50,
|
MaxPortCapacity: 50,
|
||||||
|
MaxTerminalCapacity: 200,
|
||||||
SuppressBuyAugmentationConfirmation: false,
|
SuppressBuyAugmentationConfirmation: false,
|
||||||
SuppressFactionInvites: false,
|
SuppressFactionInvites: false,
|
||||||
SuppressHospitalizationPopup: false,
|
SuppressHospitalizationPopup: false,
|
||||||
@ -125,6 +131,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
|
|||||||
Locale: "en",
|
Locale: "en",
|
||||||
MaxLogCapacity: defaultSettings.MaxLogCapacity,
|
MaxLogCapacity: defaultSettings.MaxLogCapacity,
|
||||||
MaxPortCapacity: defaultSettings.MaxPortCapacity,
|
MaxPortCapacity: defaultSettings.MaxPortCapacity,
|
||||||
|
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,
|
||||||
OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime,
|
OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime,
|
||||||
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default,
|
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default,
|
||||||
SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation,
|
SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation,
|
||||||
|
@ -23,6 +23,7 @@ import { TerminalHelpText } from "./HelpText";
|
|||||||
import { GetServerByHostname, getServer, getServerOnNetwork } from "../Server/ServerHelpers";
|
import { GetServerByHostname, getServer, getServerOnNetwork } from "../Server/ServerHelpers";
|
||||||
import { ParseCommand, ParseCommands } from "./Parser";
|
import { ParseCommand, ParseCommands } from "./Parser";
|
||||||
import { SpecialServerIps, SpecialServerNames } from "../Server/SpecialServerIps";
|
import { SpecialServerIps, SpecialServerNames } from "../Server/SpecialServerIps";
|
||||||
|
import { Settings } from "../Settings/Settings";
|
||||||
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
|
||||||
import {
|
import {
|
||||||
calculateHackingChance,
|
calculateHackingChance,
|
||||||
@ -101,6 +102,13 @@ export class Terminal implements ITerminal {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
append(item: Output | Link): void {
|
||||||
|
this.outputHistory.push(item);
|
||||||
|
if (this.outputHistory.length > Settings.MaxTerminalCapacity) {
|
||||||
|
this.outputHistory.slice(this.outputHistory.length - Settings.MaxTerminalCapacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
print(s: string, config?: any): void {
|
print(s: string, config?: any): void {
|
||||||
this.outputHistory.push(new Output(s, "primary"));
|
this.outputHistory.push(new Output(s, "primary"));
|
||||||
this.hasChanges = true;
|
this.hasChanges = true;
|
||||||
|
362
src/Terminal/ui/TerminalInput.tsx
Normal file
362
src/Terminal/ui/TerminalInput.tsx
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import { KEY } from "../../../utils/helpers/keyCodes";
|
||||||
|
import { ITerminal } from "../ITerminal";
|
||||||
|
import { IEngine } from "../../IEngine";
|
||||||
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
|
import { determineAllPossibilitiesForTabCompletion } from "../determineAllPossibilitiesForTabCompletion";
|
||||||
|
import { tabCompletion } from "../tabCompletion";
|
||||||
|
import { FconfSettings } from "../../Fconf/FconfSettings";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
textfield: {
|
||||||
|
margin: theme.spacing(0),
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: "#000",
|
||||||
|
},
|
||||||
|
nopadding: {
|
||||||
|
padding: theme.spacing(0),
|
||||||
|
},
|
||||||
|
preformatted: {
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
margin: theme.spacing(0),
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
padding: theme.spacing(0),
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
terminal: ITerminal;
|
||||||
|
engine: IEngine;
|
||||||
|
player: IPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TerminalInput({ terminal, engine, player }: IProps): React.ReactElement {
|
||||||
|
const terminalInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const [value, setValue] = useState("");
|
||||||
|
const [possibilities, setPossibilities] = useState<string[]>([]);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
function handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
|
setValue(event.target.value);
|
||||||
|
setPossibilities([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function modifyInput(mod: string): void {
|
||||||
|
const ref = terminalInput.current;
|
||||||
|
if (!ref) return;
|
||||||
|
const inputLength = value.length;
|
||||||
|
const start = ref.selectionStart;
|
||||||
|
if (start === null) return;
|
||||||
|
const inputText = ref.value;
|
||||||
|
|
||||||
|
switch (mod.toLowerCase()) {
|
||||||
|
case "backspace":
|
||||||
|
if (start > 0 && start <= inputLength + 1) {
|
||||||
|
setValue(inputText.substr(0, start - 1) + inputText.substr(start));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "deletewordbefore": // Delete rest of word before the cursor
|
||||||
|
for (let delStart = start - 1; delStart > 0; --delStart) {
|
||||||
|
if (inputText.charAt(delStart) === " ") {
|
||||||
|
setValue(inputText.substr(0, delStart) + inputText.substr(start));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "deletewordafter": // Delete rest of word after the cursor
|
||||||
|
for (let delStart = start + 1; delStart <= value.length + 1; ++delStart) {
|
||||||
|
if (inputText.charAt(delStart) === " ") {
|
||||||
|
setValue(inputText.substr(0, start) + inputText.substr(delStart));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "clearafter": // Deletes everything after cursor
|
||||||
|
break;
|
||||||
|
case "clearbefore:": // Deleetes everything before cursor
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveTextCursor(loc: string): void {
|
||||||
|
const ref = terminalInput.current;
|
||||||
|
if (!ref) return;
|
||||||
|
const inputLength = value.length;
|
||||||
|
const start = ref.selectionStart;
|
||||||
|
if (start === null) return;
|
||||||
|
|
||||||
|
switch (loc.toLowerCase()) {
|
||||||
|
case "home":
|
||||||
|
ref.setSelectionRange(0, 0);
|
||||||
|
break;
|
||||||
|
case "end":
|
||||||
|
ref.setSelectionRange(inputLength, inputLength);
|
||||||
|
break;
|
||||||
|
case "prevchar":
|
||||||
|
if (start > 0) {
|
||||||
|
ref.setSelectionRange(start - 1, start - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "prevword":
|
||||||
|
for (let i = start - 2; i >= 0; --i) {
|
||||||
|
if (ref.value.charAt(i) === " ") {
|
||||||
|
ref.setSelectionRange(i + 1, i + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.setSelectionRange(0, 0);
|
||||||
|
break;
|
||||||
|
case "nextchar":
|
||||||
|
ref.setSelectionRange(start + 1, start + 1);
|
||||||
|
break;
|
||||||
|
case "nextword":
|
||||||
|
for (let i = start + 1; i <= inputLength; ++i) {
|
||||||
|
if (ref.value.charAt(i) === " ") {
|
||||||
|
ref.setSelectionRange(i, i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.setSelectionRange(inputLength, inputLength);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn("Invalid loc argument in Terminal.moveTextCursor()");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch all key inputs and redirect them to the terminal.
|
||||||
|
useEffect(() => {
|
||||||
|
function keyDown(this: Document, event: KeyboardEvent): void {
|
||||||
|
if (terminal.contractOpen) return;
|
||||||
|
const ref = terminalInput.current;
|
||||||
|
if (ref) ref.focus();
|
||||||
|
|
||||||
|
// Cancel action
|
||||||
|
if (event.keyCode === KEY.C && event.ctrlKey) {
|
||||||
|
terminal.finishAction(player, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("keydown", keyDown);
|
||||||
|
return () => document.removeEventListener("keydown", keyDown);
|
||||||
|
});
|
||||||
|
|
||||||
|
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||||
|
// Run command.
|
||||||
|
if (event.keyCode === KEY.ENTER && value !== "") {
|
||||||
|
event.preventDefault();
|
||||||
|
terminal.print(`[${player.getCurrentServer().hostname} ~${terminal.cwd()}]> ${value}`);
|
||||||
|
terminal.executeCommands(engine, player, value);
|
||||||
|
setValue("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autocomplete
|
||||||
|
if (event.keyCode === KEY.TAB && value !== "") {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let copy = value;
|
||||||
|
const semiColonIndex = copy.lastIndexOf(";");
|
||||||
|
if (semiColonIndex !== -1) {
|
||||||
|
copy = copy.slice(semiColonIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy = copy.trim();
|
||||||
|
copy = copy.replace(/\s\s+/g, " ");
|
||||||
|
|
||||||
|
const commandArray = copy.split(" ");
|
||||||
|
let index = commandArray.length - 2;
|
||||||
|
if (index < -1) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
const allPos = determineAllPossibilitiesForTabCompletion(player, copy, index, terminal.cwd());
|
||||||
|
if (allPos.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = "";
|
||||||
|
let command = "";
|
||||||
|
if (commandArray.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (commandArray.length == 1) {
|
||||||
|
command = commandArray[0];
|
||||||
|
} else if (commandArray.length == 2) {
|
||||||
|
command = commandArray[0];
|
||||||
|
arg = commandArray[1];
|
||||||
|
} else if (commandArray.length == 3) {
|
||||||
|
command = commandArray[0] + " " + commandArray[1];
|
||||||
|
arg = commandArray[2];
|
||||||
|
} else {
|
||||||
|
arg = commandArray.pop() + "";
|
||||||
|
command = commandArray.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = tabCompletion(command, arg, allPos, value);
|
||||||
|
if (typeof newValue === "string" && newValue !== "") {
|
||||||
|
setValue(newValue);
|
||||||
|
}
|
||||||
|
if (Array.isArray(newValue)) {
|
||||||
|
setPossibilities(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear screen.
|
||||||
|
if (event.keyCode === KEY.L && event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
terminal.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select previous command.
|
||||||
|
if (
|
||||||
|
event.keyCode === KEY.UPARROW ||
|
||||||
|
(FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.P && event.ctrlKey)
|
||||||
|
) {
|
||||||
|
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
const i = terminal.commandHistoryIndex;
|
||||||
|
const len = terminal.commandHistory.length;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i < 0 || i > len) {
|
||||||
|
terminal.commandHistoryIndex = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != 0) {
|
||||||
|
--terminal.commandHistoryIndex;
|
||||||
|
}
|
||||||
|
const prevCommand = terminal.commandHistory[terminal.commandHistoryIndex];
|
||||||
|
setValue(prevCommand);
|
||||||
|
const ref = terminalInput.current;
|
||||||
|
if (ref) {
|
||||||
|
setTimeout(function () {
|
||||||
|
ref.selectionStart = ref.selectionEnd = 10000;
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select next command
|
||||||
|
if (
|
||||||
|
event.keyCode === KEY.DOWNARROW ||
|
||||||
|
(FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.M && event.ctrlKey)
|
||||||
|
) {
|
||||||
|
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
const i = terminal.commandHistoryIndex;
|
||||||
|
const len = terminal.commandHistory.length;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i < 0 || i > len) {
|
||||||
|
terminal.commandHistoryIndex = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Latest command, put nothing
|
||||||
|
if (i == len || i == len - 1) {
|
||||||
|
terminal.commandHistoryIndex = len;
|
||||||
|
setValue("");
|
||||||
|
} else {
|
||||||
|
++terminal.commandHistoryIndex;
|
||||||
|
const prevCommand = terminal.commandHistory[terminal.commandHistoryIndex];
|
||||||
|
setValue(prevCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra Bash Emulation Hotkeys, must be enabled through .fconf
|
||||||
|
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
||||||
|
if (event.keyCode === KEY.A && event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
moveTextCursor("home");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.keyCode === KEY.E && event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
moveTextCursor("end");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.keyCode === KEY.B && event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
moveTextCursor("prevchar");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.keyCode === KEY.B && event.altKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
moveTextCursor("prevword");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.keyCode === KEY.F && event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
moveTextCursor("nextchar");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.keyCode === KEY.F && event.altKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
moveTextCursor("nextword");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((event.keyCode === KEY.H || event.keyCode === KEY.D) && event.ctrlKey) {
|
||||||
|
modifyInput("backspace");
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO AFTER THIS:
|
||||||
|
// alt + d deletes word after cursor
|
||||||
|
// ^w deletes word before cursor
|
||||||
|
// ^k clears line after cursor
|
||||||
|
// ^u clears line before cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{possibilities.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
|
||||||
|
Possible autocomplete candidate:
|
||||||
|
</Typography>
|
||||||
|
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
|
||||||
|
{possibilities.join(" ")}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<TextField
|
||||||
|
color={terminal.action === null ? "primary" : "secondary"}
|
||||||
|
autoFocus
|
||||||
|
disabled={terminal.action !== null}
|
||||||
|
autoComplete="off"
|
||||||
|
classes={{ root: classes.textfield }}
|
||||||
|
value={value}
|
||||||
|
onChange={handleValueChange}
|
||||||
|
inputRef={terminalInput}
|
||||||
|
InputProps={{
|
||||||
|
// for players to hook in
|
||||||
|
id: "terminal-input",
|
||||||
|
className: classes.input,
|
||||||
|
startAdornment: (
|
||||||
|
<>
|
||||||
|
<Typography color={terminal.action === null ? "primary" : "secondary"}>
|
||||||
|
[{player.getCurrentServer().hostname} ~{terminal.cwd()}]>
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
spellCheck: false,
|
||||||
|
onKeyDown: onKeyDown,
|
||||||
|
}}
|
||||||
|
></TextField>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,19 +1,14 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import List from "@material-ui/core/List";
|
import List from "@material-ui/core/List";
|
||||||
import ListItem from "@material-ui/core/ListItem";
|
import ListItem from "@material-ui/core/ListItem";
|
||||||
import { Link as MuiLink } from "@material-ui/core";
|
import { Link as MuiLink } from "@material-ui/core";
|
||||||
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
import { makeStyles, createStyles, Theme } from "@material-ui/core/styles";
|
||||||
import TextField from "@material-ui/core/TextField";
|
|
||||||
import Box from "@material-ui/core/Box";
|
import Box from "@material-ui/core/Box";
|
||||||
import { KEY } from "../../../utils/helpers/keyCodes";
|
import { ITerminal, Output, Link } from "../ITerminal";
|
||||||
import { ITerminal, Output, Link, TTimer } from "../ITerminal";
|
|
||||||
import { IEngine } from "../../IEngine";
|
import { IEngine } from "../../IEngine";
|
||||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||||
import { determineAllPossibilitiesForTabCompletion } from "../determineAllPossibilitiesForTabCompletion";
|
import { TerminalInput } from "./TerminalInput";
|
||||||
import { tabCompletion } from "../tabCompletion";
|
|
||||||
import { FconfSettings } from "../../Fconf/FconfSettings";
|
|
||||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
|
||||||
|
|
||||||
interface IActionTimerProps {
|
interface IActionTimerProps {
|
||||||
terminal: ITerminal;
|
terminal: ITerminal;
|
||||||
@ -29,24 +24,16 @@ function ActionTimer({ terminal }: IActionTimerProps): React.ReactElement {
|
|||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
textfield: {
|
|
||||||
margin: 0,
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
backgroundColor: "#000",
|
|
||||||
},
|
|
||||||
nopadding: {
|
nopadding: {
|
||||||
padding: 0,
|
padding: theme.spacing(0),
|
||||||
},
|
},
|
||||||
preformatted: {
|
preformatted: {
|
||||||
whiteSpace: "pre-wrap",
|
whiteSpace: "pre-wrap",
|
||||||
margin: 0,
|
margin: theme.spacing(0),
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
padding: 0,
|
padding: theme.spacing(0),
|
||||||
height: "100%",
|
height: "100%",
|
||||||
overflowY: "scroll",
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -58,7 +45,6 @@ interface IProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactElement {
|
export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactElement {
|
||||||
const terminalInput = useRef<HTMLInputElement>(null);
|
|
||||||
const setRerender = useState(false)[1];
|
const setRerender = useState(false)[1];
|
||||||
function rerender(): void {
|
function rerender(): void {
|
||||||
setRerender((old) => !old);
|
setRerender((old) => !old);
|
||||||
@ -71,286 +57,7 @@ export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactE
|
|||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [value, setValue] = useState("");
|
|
||||||
const [possibilities, setPossibilities] = useState<string[]>([]);
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
function handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
|
||||||
setValue(event.target.value);
|
|
||||||
setPossibilities([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function modifyInput(mod: string): void {
|
|
||||||
const ref = terminalInput.current;
|
|
||||||
if (!ref) return;
|
|
||||||
const inputLength = value.length;
|
|
||||||
const start = ref.selectionStart;
|
|
||||||
if (start === null) return;
|
|
||||||
const inputText = ref.value;
|
|
||||||
|
|
||||||
switch (mod.toLowerCase()) {
|
|
||||||
case "backspace":
|
|
||||||
if (start > 0 && start <= inputLength + 1) {
|
|
||||||
setValue(inputText.substr(0, start - 1) + inputText.substr(start));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "deletewordbefore": // Delete rest of word before the cursor
|
|
||||||
for (var delStart = start - 1; delStart > 0; --delStart) {
|
|
||||||
if (inputText.charAt(delStart) === " ") {
|
|
||||||
setValue(inputText.substr(0, delStart) + inputText.substr(start));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "deletewordafter": // Delete rest of word after the cursor
|
|
||||||
for (var delStart = start + 1; delStart <= value.length + 1; ++delStart) {
|
|
||||||
if (inputText.charAt(delStart) === " ") {
|
|
||||||
setValue(inputText.substr(0, start) + inputText.substr(delStart));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "clearafter": // Deletes everything after cursor
|
|
||||||
break;
|
|
||||||
case "clearbefore:": // Deleetes everything before cursor
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveTextCursor(loc: string) {
|
|
||||||
const ref = terminalInput.current;
|
|
||||||
if (!ref) return;
|
|
||||||
const inputLength = value.length;
|
|
||||||
const start = ref.selectionStart;
|
|
||||||
if (start === null) return;
|
|
||||||
|
|
||||||
switch (loc.toLowerCase()) {
|
|
||||||
case "home":
|
|
||||||
ref.setSelectionRange(0, 0);
|
|
||||||
break;
|
|
||||||
case "end":
|
|
||||||
ref.setSelectionRange(inputLength, inputLength);
|
|
||||||
break;
|
|
||||||
case "prevchar":
|
|
||||||
if (start > 0) {
|
|
||||||
ref.setSelectionRange(start - 1, start - 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "prevword":
|
|
||||||
for (let i = start - 2; i >= 0; --i) {
|
|
||||||
if (ref.value.charAt(i) === " ") {
|
|
||||||
ref.setSelectionRange(i + 1, i + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref.setSelectionRange(0, 0);
|
|
||||||
break;
|
|
||||||
case "nextchar":
|
|
||||||
ref.setSelectionRange(start + 1, start + 1);
|
|
||||||
break;
|
|
||||||
case "nextword":
|
|
||||||
for (let i = start + 1; i <= inputLength; ++i) {
|
|
||||||
if (ref.value.charAt(i) === " ") {
|
|
||||||
ref.setSelectionRange(i, i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ref.setSelectionRange(inputLength, inputLength);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.warn("Invalid loc argument in Terminal.moveTextCursor()");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catch all key inputs and redirect them to the terminal.
|
|
||||||
useEffect(() => {
|
|
||||||
function keyDown(this: Document, event: KeyboardEvent): void {
|
|
||||||
if (terminal.contractOpen) return;
|
|
||||||
const ref = terminalInput.current;
|
|
||||||
if (ref) ref.focus();
|
|
||||||
|
|
||||||
// Cancel action
|
|
||||||
if (event.keyCode === KEY.C && event.ctrlKey) {
|
|
||||||
terminal.finishAction(player, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener("keydown", keyDown);
|
|
||||||
return () => document.removeEventListener("keydown", keyDown);
|
|
||||||
});
|
|
||||||
|
|
||||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
|
||||||
// Run command.
|
|
||||||
if (event.keyCode === KEY.ENTER && value !== "") {
|
|
||||||
event.preventDefault();
|
|
||||||
terminal.print(`[${player.getCurrentServer().hostname} ~${terminal.cwd()}]> ${value}`);
|
|
||||||
terminal.executeCommands(engine, player, value);
|
|
||||||
setValue("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Autocomplete
|
|
||||||
if (event.keyCode === KEY.TAB && value !== "") {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
let copy = value;
|
|
||||||
const semiColonIndex = copy.lastIndexOf(";");
|
|
||||||
if (semiColonIndex !== -1) {
|
|
||||||
copy = copy.slice(semiColonIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
copy = copy.trim();
|
|
||||||
copy = copy.replace(/\s\s+/g, " ");
|
|
||||||
|
|
||||||
const commandArray = copy.split(" ");
|
|
||||||
let index = commandArray.length - 2;
|
|
||||||
if (index < -1) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
const allPos = determineAllPossibilitiesForTabCompletion(player, copy, index, terminal.cwd());
|
|
||||||
if (allPos.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let arg = "";
|
|
||||||
let command = "";
|
|
||||||
if (commandArray.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (commandArray.length == 1) {
|
|
||||||
command = commandArray[0];
|
|
||||||
} else if (commandArray.length == 2) {
|
|
||||||
command = commandArray[0];
|
|
||||||
arg = commandArray[1];
|
|
||||||
} else if (commandArray.length == 3) {
|
|
||||||
command = commandArray[0] + " " + commandArray[1];
|
|
||||||
arg = commandArray[2];
|
|
||||||
} else {
|
|
||||||
arg = commandArray.pop() + "";
|
|
||||||
command = commandArray.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newValue = tabCompletion(command, arg, allPos, value);
|
|
||||||
if (typeof newValue === "string" && newValue !== "") {
|
|
||||||
setValue(newValue);
|
|
||||||
}
|
|
||||||
if (Array.isArray(newValue)) {
|
|
||||||
setPossibilities(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear screen.
|
|
||||||
if (event.keyCode === KEY.L && event.ctrlKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
terminal.clear();
|
|
||||||
rerender();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select previous command.
|
|
||||||
if (
|
|
||||||
event.keyCode === KEY.UPARROW ||
|
|
||||||
(FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.P && event.ctrlKey)
|
|
||||||
) {
|
|
||||||
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
const i = terminal.commandHistoryIndex;
|
|
||||||
const len = terminal.commandHistory.length;
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (i < 0 || i > len) {
|
|
||||||
terminal.commandHistoryIndex = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i != 0) {
|
|
||||||
--terminal.commandHistoryIndex;
|
|
||||||
}
|
|
||||||
const prevCommand = terminal.commandHistory[terminal.commandHistoryIndex];
|
|
||||||
setValue(prevCommand);
|
|
||||||
const ref = terminalInput.current;
|
|
||||||
if (ref) {
|
|
||||||
setTimeout(function () {
|
|
||||||
ref.selectionStart = ref.selectionEnd = 10000;
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select next command
|
|
||||||
if (
|
|
||||||
event.keyCode === KEY.DOWNARROW ||
|
|
||||||
(FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.M && event.ctrlKey)
|
|
||||||
) {
|
|
||||||
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
const i = terminal.commandHistoryIndex;
|
|
||||||
const len = terminal.commandHistory.length;
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (i < 0 || i > len) {
|
|
||||||
terminal.commandHistoryIndex = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Latest command, put nothing
|
|
||||||
if (i == len || i == len - 1) {
|
|
||||||
terminal.commandHistoryIndex = len;
|
|
||||||
setValue("");
|
|
||||||
} else {
|
|
||||||
++terminal.commandHistoryIndex;
|
|
||||||
const prevCommand = terminal.commandHistory[terminal.commandHistoryIndex];
|
|
||||||
setValue(prevCommand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra Bash Emulation Hotkeys, must be enabled through .fconf
|
|
||||||
if (FconfSettings.ENABLE_BASH_HOTKEYS) {
|
|
||||||
if (event.keyCode === KEY.A && event.ctrlKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
moveTextCursor("home");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.keyCode === KEY.E && event.ctrlKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
moveTextCursor("end");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.keyCode === KEY.B && event.ctrlKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
moveTextCursor("prevchar");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.keyCode === KEY.B && event.altKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
moveTextCursor("prevword");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.keyCode === KEY.F && event.ctrlKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
moveTextCursor("nextchar");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.keyCode === KEY.F && event.altKey) {
|
|
||||||
event.preventDefault();
|
|
||||||
moveTextCursor("nextword");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((event.keyCode === KEY.H || event.keyCode === KEY.D) && event.ctrlKey) {
|
|
||||||
modifyInput("backspace");
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO AFTER THIS:
|
|
||||||
// alt + d deletes word after cursor
|
|
||||||
// ^w deletes word before cursor
|
|
||||||
// ^k clears line after cursor
|
|
||||||
// ^u clears line before cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position="fixed" bottom="0" width="100%" px={1}>
|
<Box position="fixed" bottom="0" width="100%" px={1}>
|
||||||
<List classes={{ root: classes.list }}>
|
<List classes={{ root: classes.list }}>
|
||||||
@ -378,41 +85,8 @@ export function TerminalRoot({ terminal, engine, player }: IProps): React.ReactE
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
{possibilities.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
|
|
||||||
Possible autocomplete candidate:
|
|
||||||
</Typography>
|
|
||||||
<Typography classes={{ root: classes.preformatted }} color={"primary"} paragraph={false}>
|
|
||||||
{possibilities.join(" ")}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{terminal.action !== null && <ActionTimer terminal={terminal} />}
|
{terminal.action !== null && <ActionTimer terminal={terminal} />}
|
||||||
<TextField
|
<TerminalInput player={player} engine={engine} terminal={terminal} />
|
||||||
color={terminal.action === null ? "primary" : "secondary"}
|
|
||||||
autoFocus
|
|
||||||
disabled={terminal.action !== null}
|
|
||||||
autoComplete="off"
|
|
||||||
classes={{ root: classes.textfield }}
|
|
||||||
value={value}
|
|
||||||
onChange={handleValueChange}
|
|
||||||
inputRef={terminalInput}
|
|
||||||
InputProps={{
|
|
||||||
// for players to hook in
|
|
||||||
id: "terminal-input",
|
|
||||||
className: classes.input,
|
|
||||||
startAdornment: (
|
|
||||||
<>
|
|
||||||
<Typography color={terminal.action === null ? "primary" : "secondary"}>
|
|
||||||
[{player.getCurrentServer().hostname} ~{terminal.cwd()}]>
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
spellCheck: false,
|
|
||||||
onKeyDown: onKeyDown,
|
|
||||||
}}
|
|
||||||
></TextField>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
|||||||
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
|
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
|
||||||
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
|
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
|
||||||
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
|
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
|
||||||
|
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
|
||||||
|
|
||||||
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
|
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
|
||||||
|
|
||||||
const [suppressMessages, setSuppressMessages] = useState(Settings.SuppressMessages);
|
const [suppressMessages, setSuppressMessages] = useState(Settings.SuppressMessages);
|
||||||
@ -86,6 +88,11 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
|||||||
Settings.MaxPortCapacity = newValue as number;
|
Settings.MaxPortCapacity = newValue as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleTerminalSizeChange(event: any, newValue: number | number[]): void {
|
||||||
|
setTerminalSize(newValue as number);
|
||||||
|
Settings.MaxTerminalCapacity = newValue as number;
|
||||||
|
}
|
||||||
|
|
||||||
function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {
|
function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {
|
||||||
setAutosaveInterval(newValue as number);
|
setAutosaveInterval(newValue as number);
|
||||||
Settings.AutosaveInterval = newValue as number;
|
Settings.AutosaveInterval = newValue as number;
|
||||||
@ -209,6 +216,27 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
|||||||
valueLabelDisplay="auto"
|
valueLabelDisplay="auto"
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
<Typography>
|
||||||
|
The maximum number of entries that can be written to a the terminal. Setting this too high can cause
|
||||||
|
the game to use a lot of memory.
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Typography>Terminal capacity</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
<Slider
|
||||||
|
value={terminalSize}
|
||||||
|
onChange={handleTerminalSizeChange}
|
||||||
|
step={50}
|
||||||
|
min={50}
|
||||||
|
max={500}
|
||||||
|
valueLabelDisplay="auto"
|
||||||
|
marks
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
|
Loading…
Reference in New Issue
Block a user