Fix tab complete
This commit is contained in:
@ -42,11 +42,9 @@ const Terminal = () => {
|
||||
const [currentInput, setCurrentInput] = useState('');
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
const [fileSystem, setFileSystem] = useState<{ [key: string]: FileSystemNode }>({});
|
||||
const [lastTabInput, setLastTabInput] = useState('');
|
||||
const [showCompletions, setShowCompletions] = useState(false);
|
||||
const [completionsList, setCompletionsList] = useState<string[]>([]);
|
||||
const [isTrainRunning, setIsTrainRunning] = useState(false);
|
||||
const [trainPosition, setTrainPosition] = useState(100);
|
||||
const [persistentHistory, setPersistentHistory] = useState<string[]>([]);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const terminalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -62,6 +60,28 @@ const Terminal = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load persistent command history from localStorage
|
||||
useEffect(() => {
|
||||
const savedHistory = localStorage.getItem('terminal-history');
|
||||
if (savedHistory) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedHistory);
|
||||
setPersistentHistory(parsed);
|
||||
} catch (error) {
|
||||
console.error('Error loading command history:', error);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Save command history to localStorage
|
||||
const saveCommandToHistory = (command: string) => {
|
||||
if (command.trim()) {
|
||||
const updatedHistory = [...persistentHistory, command].slice(-100); // Keep last 100 commands
|
||||
setPersistentHistory(updatedHistory);
|
||||
localStorage.setItem('terminal-history', JSON.stringify(updatedHistory));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initializeFileSystem = async () => {
|
||||
try {
|
||||
@ -269,35 +289,13 @@ ${post.content}`;
|
||||
return '/' + currentParts.join('/');
|
||||
};
|
||||
|
||||
const getCommonPrefix = (strings: string[]): string => {
|
||||
if (strings.length === 0) return '';
|
||||
if (strings.length === 1) return strings[0];
|
||||
|
||||
let commonPrefix = strings[0];
|
||||
for (let i = 1; i < strings.length; i++) {
|
||||
let j = 0;
|
||||
while (j < commonPrefix.length && j < strings[i].length && commonPrefix[j] === strings[i][j]) {
|
||||
j++;
|
||||
}
|
||||
commonPrefix = commonPrefix.substring(0, j);
|
||||
if (commonPrefix === '') break;
|
||||
}
|
||||
return commonPrefix;
|
||||
};
|
||||
|
||||
const getCompletions = (input: string): { completions: string[], prefix: string, replaceFrom: number } => {
|
||||
const getCompletions = (input: string): { completion: string | null, replaceFrom: number } => {
|
||||
const parts = input.trim().split(' ');
|
||||
const command = parts[0];
|
||||
const partialPath = parts[parts.length - 1] || '';
|
||||
|
||||
if (parts.length === 1) {
|
||||
// Command completion
|
||||
const commands = ['ls', 'cd', 'cat', 'pwd', 'clear', 'tree', 'whoami', 'open', 'help'];
|
||||
const matches = commands.filter(cmd => cmd.startsWith(command));
|
||||
return { completions: matches, prefix: command, replaceFrom: 0 };
|
||||
}
|
||||
|
||||
if (['ls', 'cd', 'cat', 'open'].includes(command)) {
|
||||
// Only complete paths for these commands, not the commands themselves
|
||||
if (parts.length > 1 && ['ls', 'cd', 'cat', 'open'].includes(command)) {
|
||||
// Path completion
|
||||
const isAbsolute = partialPath.startsWith('/');
|
||||
const pathToComplete = isAbsolute ? partialPath : resolvePath(partialPath);
|
||||
@ -334,26 +332,26 @@ ${post.content}`;
|
||||
if (current?.children && current.children[part] && current.children[part].type === 'directory') {
|
||||
current = current.children[part];
|
||||
} else {
|
||||
return { completions: [], prefix: searchPrefix, replaceFrom };
|
||||
return { completion: null, replaceFrom };
|
||||
}
|
||||
}
|
||||
|
||||
if (!current?.children) {
|
||||
return { completions: [], prefix: searchPrefix, replaceFrom };
|
||||
return { completion: null, replaceFrom };
|
||||
}
|
||||
|
||||
// Get matching items
|
||||
const matches = Object.keys(current.children)
|
||||
.filter(name => name.startsWith(searchPrefix))
|
||||
.map(name => {
|
||||
const item = current.children![name];
|
||||
return item.type === 'directory' ? `${name}/` : name;
|
||||
});
|
||||
// Get first matching item
|
||||
const match = Object.keys(current.children)
|
||||
.find(name => name.startsWith(searchPrefix));
|
||||
|
||||
return { completions: matches, prefix: searchPrefix, replaceFrom };
|
||||
if (match) {
|
||||
const item = current.children[match];
|
||||
const completion = item.type === 'directory' ? `${match}/` : match;
|
||||
return { completion, replaceFrom };
|
||||
}
|
||||
}
|
||||
|
||||
return { completions: [], prefix: '', replaceFrom: input.length };
|
||||
return { completion: null, replaceFrom: input.length };
|
||||
};
|
||||
|
||||
const executeCommand = (input: string): string => {
|
||||
@ -556,6 +554,9 @@ ${post.content}`;
|
||||
timestamp: new Date()
|
||||
};
|
||||
|
||||
// Save command to persistent history
|
||||
saveCommandToHistory(currentInput);
|
||||
|
||||
if (currentInput.trim().toLowerCase() === 'clear') {
|
||||
setCommandHistory([]);
|
||||
} else {
|
||||
@ -564,91 +565,39 @@ ${post.content}`;
|
||||
|
||||
setCurrentInput('');
|
||||
setHistoryIndex(-1);
|
||||
setShowCompletions(false);
|
||||
setLastTabInput('');
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: JSX.TargetedKeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
|
||||
const { completions, prefix, replaceFrom } = getCompletions(currentInput);
|
||||
const { completion, replaceFrom } = getCompletions(currentInput);
|
||||
|
||||
if (completions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (completions.length === 1) {
|
||||
// Single completion - replace from the correct position
|
||||
if (completion) {
|
||||
// Replace from the correct position with the completion
|
||||
const beforeReplacement = currentInput.substring(0, replaceFrom);
|
||||
const completion = completions[0];
|
||||
const newInput = beforeReplacement + completion;
|
||||
|
||||
setCurrentInput(newInput + (completion.endsWith('/') ? '' : ' '));
|
||||
setShowCompletions(false);
|
||||
setLastTabInput('');
|
||||
} else {
|
||||
// Multiple completions - find common prefix
|
||||
const commonPrefix = getCommonPrefix(completions);
|
||||
|
||||
if (commonPrefix.length > prefix.length) {
|
||||
// Complete to common prefix
|
||||
const beforeReplacement = currentInput.substring(0, replaceFrom);
|
||||
setCurrentInput(beforeReplacement + commonPrefix);
|
||||
setShowCompletions(false);
|
||||
setLastTabInput(currentInput);
|
||||
} else {
|
||||
// Show completions if we hit tab twice on the same input
|
||||
if (currentInput === lastTabInput && !showCompletions) {
|
||||
setCompletionsList(completions);
|
||||
setShowCompletions(true);
|
||||
|
||||
// Add completions to command history for display
|
||||
const output = completions.length > 8
|
||||
? completions.slice(0, 8).join(' ') + ` ... (${completions.length - 8} more)`
|
||||
: completions.join(' ');
|
||||
|
||||
const newCommand: Command = {
|
||||
input: '',
|
||||
output,
|
||||
timestamp: new Date()
|
||||
};
|
||||
setCommandHistory((prev: Command[]) => [...prev, newCommand]);
|
||||
} else {
|
||||
setLastTabInput(currentInput);
|
||||
setShowCompletions(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
const inputCommands = commandHistory.filter((cmd: Command) => cmd.input.trim() !== '');
|
||||
if (inputCommands.length > 0) {
|
||||
const newIndex = historyIndex === -1 ? inputCommands.length - 1 : Math.max(0, historyIndex - 1);
|
||||
if (persistentHistory.length > 0) {
|
||||
const newIndex = historyIndex === -1 ? persistentHistory.length - 1 : Math.max(0, historyIndex - 1);
|
||||
setHistoryIndex(newIndex);
|
||||
setCurrentInput(inputCommands[newIndex].input);
|
||||
setCurrentInput(persistentHistory[newIndex]);
|
||||
}
|
||||
setShowCompletions(false);
|
||||
setLastTabInput('');
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
const inputCommands = commandHistory.filter((cmd: Command) => cmd.input.trim() !== '');
|
||||
if (historyIndex !== -1) {
|
||||
const newIndex = Math.min(inputCommands.length - 1, historyIndex + 1);
|
||||
if (newIndex === inputCommands.length - 1 && historyIndex === newIndex) {
|
||||
const newIndex = Math.min(persistentHistory.length - 1, historyIndex + 1);
|
||||
if (newIndex === persistentHistory.length - 1 && historyIndex === newIndex) {
|
||||
setHistoryIndex(-1);
|
||||
setCurrentInput('');
|
||||
} else {
|
||||
setHistoryIndex(newIndex);
|
||||
setCurrentInput(inputCommands[newIndex].input);
|
||||
setCurrentInput(persistentHistory[newIndex]);
|
||||
}
|
||||
}
|
||||
setShowCompletions(false);
|
||||
setLastTabInput('');
|
||||
} else {
|
||||
// Reset completion state on any other key
|
||||
setShowCompletions(false);
|
||||
setLastTabInput('');
|
||||
}
|
||||
};
|
||||
|
||||
@ -718,12 +667,6 @@ __/ =| o |=-O=====O=====O=====O \\ ____Y___________|__|_________________________
|
||||
</div>
|
||||
))}
|
||||
|
||||
{showCompletions && (
|
||||
<div className="text-secondary text-sm mb-2">
|
||||
Tab completions: {completionsList.join(' ')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex items-center">
|
||||
<span className="text-primary font-semibold">guest@atri.dad</span>
|
||||
<span className="text-base-content">:</span>
|
||||
|
Reference in New Issue
Block a user