mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 09:33:49 +01:00
TERMINAL: Add option for partial history search (#736)
This commit is contained in:
parent
7ea0725a39
commit
86b0bd5ac7
@ -30,6 +30,18 @@ export const MiscPage = (): React.ReactElement => {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<OptionSwitch
|
||||||
|
checked={Settings.EnableHistorySearch}
|
||||||
|
onChange={(newValue) => (Settings.EnableHistorySearch = newValue)}
|
||||||
|
text="Enable terminal history search with arrow keys"
|
||||||
|
tooltip={
|
||||||
|
<>
|
||||||
|
If there is user-entered text in the terminal, using the up arrow will search through the terminal history
|
||||||
|
for previous commands that start with the current text, instead of navigating to the most recent history
|
||||||
|
item. Search results can be executed immediately via 'enter', or autofilled into the terminal with 'tab'.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</GameOptionsPage>
|
</GameOptionsPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,8 @@ export const Settings = {
|
|||||||
DisableOverviewProgressBars: false,
|
DisableOverviewProgressBars: false,
|
||||||
/** Whether to enable bash hotkeys */
|
/** Whether to enable bash hotkeys */
|
||||||
EnableBashHotkeys: false,
|
EnableBashHotkeys: false,
|
||||||
|
/** Whether to enable terminal history search */
|
||||||
|
EnableHistorySearch: false,
|
||||||
/** Timestamps format string */
|
/** Timestamps format string */
|
||||||
TimestampsFormat: "",
|
TimestampsFormat: "",
|
||||||
/** Locale used for display numbers. */
|
/** Locale used for display numbers. */
|
||||||
|
@ -32,6 +32,16 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
padding: theme.spacing(0),
|
padding: theme.spacing(0),
|
||||||
height: "100%",
|
height: "100%",
|
||||||
},
|
},
|
||||||
|
absolute: {
|
||||||
|
margin: theme.spacing(0),
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "5px",
|
||||||
|
opacity: "0.75",
|
||||||
|
maxWidth: "100%",
|
||||||
|
"white-space": "nowrap break-spaces",
|
||||||
|
overflow: "hidden",
|
||||||
|
pointerEvents: "none",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -44,6 +54,9 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
const [value, setValue] = useState(command);
|
const [value, setValue] = useState(command);
|
||||||
const [postUpdateValue, setPostUpdateValue] = useState<{ postUpdate: () => void } | null>();
|
const [postUpdateValue, setPostUpdateValue] = useState<{ postUpdate: () => void } | null>();
|
||||||
const [possibilities, setPossibilities] = useState<string[]>([]);
|
const [possibilities, setPossibilities] = useState<string[]>([]);
|
||||||
|
const [searchResults, setSearchResults] = useState<string[]>([]);
|
||||||
|
const [searchResultsIndex, setSearchResultsIndex] = useState(0);
|
||||||
|
const [autofilledValue, setAutofilledValue] = useState(false);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
// If we have no data in the current terminal history, let's initialize it from the player save
|
// If we have no data in the current terminal history, let's initialize it from the player save
|
||||||
@ -73,6 +86,20 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
function handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
function handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
saveValue(event.target.value);
|
saveValue(event.target.value);
|
||||||
setPossibilities([]);
|
setPossibilities([]);
|
||||||
|
setSearchResults([]);
|
||||||
|
setAutofilledValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSearch(isAutofilled = false) {
|
||||||
|
setSearchResults([]);
|
||||||
|
setAutofilledValue(isAutofilled);
|
||||||
|
setSearchResultsIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchSuggestionPrespace() {
|
||||||
|
const currentPrefix = `[${Player.getCurrentServer().hostname} /${Terminal.cwd()}]> `;
|
||||||
|
const prefixLength = `${currentPrefix}${value}`.length;
|
||||||
|
return Array(prefixLength).fill(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function modifyInput(mod: string): void {
|
function modifyInput(mod: string): void {
|
||||||
@ -197,10 +224,12 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
// Run command or insert newline
|
// Run command or insert newline
|
||||||
if (event.key === KEY.ENTER) {
|
if (event.key === KEY.ENTER) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Terminal.print(`[${Player.getCurrentServer().hostname} /${Terminal.cwd()}]> ${value}`);
|
const command = searchResults.length ? searchResults[searchResultsIndex] : value;
|
||||||
if (value) {
|
Terminal.print(`[${Player.getCurrentServer().hostname} /${Terminal.cwd()}]> ${command}`);
|
||||||
Terminal.executeCommands(value);
|
if (command) {
|
||||||
|
Terminal.executeCommands(command);
|
||||||
saveValue("");
|
saveValue("");
|
||||||
|
resetSearch();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -208,8 +237,15 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
// Autocomplete
|
// Autocomplete
|
||||||
if (event.key === KEY.TAB) {
|
if (event.key === KEY.TAB) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
if (searchResults.length) {
|
||||||
|
saveValue(searchResults[searchResultsIndex]);
|
||||||
|
resetSearch(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const possibilities = await getTabCompletionPossibilities(value, Terminal.cwd());
|
const possibilities = await getTabCompletionPossibilities(value, Terminal.cwd());
|
||||||
if (possibilities.length === 0) return;
|
if (possibilities.length === 0) return;
|
||||||
|
|
||||||
|
setSearchResults([]);
|
||||||
if (possibilities.length === 1) {
|
if (possibilities.length === 1) {
|
||||||
saveValue(value.replace(/[^ ]*$/, possibilities[0]) + " ");
|
saveValue(value.replace(/[^ ]*$/, possibilities[0]) + " ");
|
||||||
return;
|
return;
|
||||||
@ -228,7 +264,7 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
|
|
||||||
// Select previous command.
|
// Select previous command.
|
||||||
if (event.key === KEY.UP_ARROW || (Settings.EnableBashHotkeys && event.key === KEY.P && event.ctrlKey)) {
|
if (event.key === KEY.UP_ARROW || (Settings.EnableBashHotkeys && event.key === KEY.P && event.ctrlKey)) {
|
||||||
if (Settings.EnableBashHotkeys) {
|
if (Settings.EnableBashHotkeys || (Settings.EnableHistorySearch && value)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
const i = Terminal.commandHistoryIndex;
|
const i = Terminal.commandHistoryIndex;
|
||||||
@ -237,6 +273,23 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is a partial command in the terminal, hitting "up" will filter the history
|
||||||
|
if (value && !autofilledValue && Settings.EnableHistorySearch) {
|
||||||
|
if (searchResults.length > 0) {
|
||||||
|
setSearchResultsIndex((searchResultsIndex + 1) % searchResults.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newResults = [...new Set(Terminal.commandHistory.filter((item) => item?.startsWith(value)).reverse())];
|
||||||
|
|
||||||
|
if (newResults.length) {
|
||||||
|
setSearchResults(newResults);
|
||||||
|
}
|
||||||
|
// Prevent moving through the history when the user has a search term even if there are
|
||||||
|
// no search results, to be consistent with zsh-type terminal behavior
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (i < 0 || i > len) {
|
if (i < 0 || i > len) {
|
||||||
Terminal.commandHistoryIndex = len;
|
Terminal.commandHistoryIndex = len;
|
||||||
}
|
}
|
||||||
@ -246,6 +299,7 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
}
|
}
|
||||||
const prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
|
const prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
|
||||||
saveValue(prevCommand);
|
saveValue(prevCommand);
|
||||||
|
resetSearch(true);
|
||||||
if (ref) {
|
if (ref) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
ref.selectionStart = ref.selectionEnd = 10000;
|
ref.selectionStart = ref.selectionEnd = 10000;
|
||||||
@ -258,6 +312,11 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
if (Settings.EnableBashHotkeys) {
|
if (Settings.EnableBashHotkeys) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
if (searchResults.length > 0) {
|
||||||
|
setSearchResultsIndex(searchResultsIndex === 0 ? searchResults.length - 1 : searchResultsIndex - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const i = Terminal.commandHistoryIndex;
|
const i = Terminal.commandHistoryIndex;
|
||||||
const len = Terminal.commandHistory.length;
|
const len = Terminal.commandHistory.length;
|
||||||
|
|
||||||
@ -272,14 +331,20 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
if (i == len || i == len - 1) {
|
if (i == len || i == len - 1) {
|
||||||
Terminal.commandHistoryIndex = len;
|
Terminal.commandHistoryIndex = len;
|
||||||
saveValue("");
|
saveValue("");
|
||||||
|
resetSearch();
|
||||||
} else {
|
} else {
|
||||||
++Terminal.commandHistoryIndex;
|
++Terminal.commandHistoryIndex;
|
||||||
const prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
|
const prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
|
||||||
|
|
||||||
saveValue(prevCommand);
|
saveValue(prevCommand);
|
||||||
|
resetSearch(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.key === KEY.ESC && searchResults.length) {
|
||||||
|
resetSearch();
|
||||||
|
}
|
||||||
|
|
||||||
// Extra Bash Emulation Hotkeys, must be enabled through options
|
// Extra Bash Emulation Hotkeys, must be enabled through options
|
||||||
if (Settings.EnableBashHotkeys) {
|
if (Settings.EnableBashHotkeys) {
|
||||||
if (event.code === KEYCODE.C && event.ctrlKey && ref && ref.selectionStart === ref.selectionEnd) {
|
if (event.code === KEYCODE.C && event.ctrlKey && ref && ref.selectionStart === ref.selectionEnd) {
|
||||||
@ -367,7 +432,10 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
),
|
),
|
||||||
spellCheck: false,
|
spellCheck: false,
|
||||||
onBlur: () => setPossibilities([]),
|
onBlur: () => {
|
||||||
|
setPossibilities([]);
|
||||||
|
resetSearch();
|
||||||
|
},
|
||||||
onKeyDown: onKeyDown,
|
onKeyDown: onKeyDown,
|
||||||
}}
|
}}
|
||||||
></TextField>
|
></TextField>
|
||||||
@ -386,6 +454,10 @@ export function TerminalInput(): React.ReactElement {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Popper>
|
</Popper>
|
||||||
|
<Typography classes={{ root: classes.absolute }} color={"primary"} paragraph={false}>
|
||||||
|
{getSearchSuggestionPrespace()}
|
||||||
|
{(searchResults[searchResultsIndex] ?? "").substring(value.length)}
|
||||||
|
</Typography>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user