Command History

- Up/Down Arrow Navigation: Users can now press ↑/↓ to navigate through previously executed commands
- History Storage: Commands are stored in state and persist during the session
- Duplicate Prevention: Avoids adding the same command multiple times consecutively
- Reset on Typing: Manual typing resets the history navigation position
 Tab Completion
- Auto-complete: Pressing Tab completes partial commands that match available commands
- Single Match: If only one command matches, it completes the full command
- Multiple Matches: If multiple commands match, displays all possible completions in the terminal output
- Case-insensitive: Works regardless of input case
This commit is contained in:
JorySeverijnse 2025-12-21 13:57:47 +01:00
parent 1495a9a5c5
commit cde5f34858

View File

@ -65,6 +65,8 @@ const TerminalCommand = () => {
const [isOpen, setIsOpen] = useState(false);
const [input, setInput] = useState('');
const [output, setOutput] = useState<string[]>(['Type /help for available commands']);
const [commandHistory, setCommandHistory] = useState<string[]>([]);
const [historyIndex, setHistoryIndex] = useState(-1);
const inputRef = useRef<HTMLInputElement>(null);
const outputRef = useRef<HTMLDivElement>(null);
const navigate = useNavigate();
@ -116,37 +118,46 @@ const TerminalCommand = () => {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const trimmedInput = input.trim().toLowerCase();
const trimmedInput = input.trim();
if (!trimmedInput) return;
playSound('click');
setOutput(prev => [...prev, `> ${input}`]);
// Add to command history (avoid duplicates of the last command)
setCommandHistory(prev => {
const newHistory = prev.filter(cmd => cmd !== trimmedInput);
return [...newHistory, trimmedInput];
});
setHistoryIndex(-1);
// Unlock terminal user achievement
unlockAchievement('terminal_user');
if (trimmedInput === '/help' || trimmedInput === '/h') {
const lowerInput = trimmedInput.toLowerCase();
if (lowerInput === '/help' || lowerInput === '/h') {
setOutput(prev => [...prev, helpText, '---HINT---' + helpHint]);
} else if (trimmedInput === '/hint') {
} else if (lowerInput === '/hint') {
unlockAchievement('hint_seeker');
setOutput(prev => [...prev,
setOutput(prev => [...prev,
'Hidden feature detected in system...',
'Hint: Old-school gamers know a certain cheat code.',
'Think NES, 1986, Contra... 30 lives anyone?',
'The sequence uses arrow keys and two letters.'
]);
} else if (trimmedInput === '/clear' || trimmedInput === '/c') {
} else if (lowerInput === '/clear' || lowerInput === '/c') {
setOutput(['Terminal cleared. Type /help for commands.']);
} else if (commands[trimmedInput]) {
setOutput(prev => [...prev, `Navigating to ${trimmedInput.slice(1)}...`]);
} else if (commands[lowerInput]) {
setOutput(prev => [...prev, `Navigating to ${lowerInput.slice(1)}...`]);
playSound('beep');
setTimeout(() => {
navigate(commands[trimmedInput]);
navigate(commands[lowerInput]);
setIsOpen(false);
}, 300);
} else {
setOutput(prev => [...prev, `Command not found: ${trimmedInput}`, 'Type /help for available commands']);
setOutput(prev => [...prev, `Command not found: ${lowerInput}`, 'Type /help for available commands']);
}
setInput('');
@ -209,16 +220,53 @@ const TerminalCommand = () => {
<form onSubmit={handleSubmit} className="border-t border-primary/50">
<div className="flex items-center px-4 py-3">
<span className="text-primary font-mono mr-2">{'>'}</span>
<input
ref={inputRef}
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
className="flex-1 bg-transparent border-none outline-none font-mono text-primary placeholder-primary/40"
placeholder="Enter command..."
autoComplete="off"
spellCheck={false}
/>
<input
ref={inputRef}
type="text"
value={input}
onChange={(e) => {
setInput(e.target.value);
setHistoryIndex(-1); // Reset history navigation when typing
}}
onKeyDown={(e) => {
if (e.key === 'ArrowUp') {
e.preventDefault();
if (commandHistory.length > 0) {
const newIndex = historyIndex === -1 ? commandHistory.length - 1 : Math.max(0, historyIndex - 1);
setHistoryIndex(newIndex);
setInput(commandHistory[newIndex]);
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex >= 0) {
const newIndex = historyIndex + 1;
if (newIndex >= commandHistory.length) {
setHistoryIndex(-1);
setInput('');
} else {
setHistoryIndex(newIndex);
setInput(commandHistory[newIndex]);
}
}
} else if (e.key === 'Tab') {
e.preventDefault();
const currentInput = input.trim().toLowerCase();
if (currentInput) {
// Find commands that start with current input
const matches = Object.keys(commands).filter(cmd => cmd.startsWith(currentInput));
if (matches.length === 1) {
setInput(matches[0]);
} else if (matches.length > 1) {
setOutput(prev => [...prev, `Possible completions: ${matches.join(', ')}`]);
}
}
}
}}
className="flex-1 bg-transparent border-none outline-none font-mono text-primary placeholder-primary/40"
placeholder="Enter command..."
autoComplete="off"
spellCheck={false}
/>
<span className="text-primary animate-pulse">_</span>
</div>
</form>