Fix tab complete

This commit is contained in:
2025-06-04 10:45:03 -06:00
parent 146eff0a5f
commit ec2c2b8a8f

View File

@ -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>