diff --git a/src/components/Terminal.tsx b/src/components/Terminal.tsx index bf092bc..5d5b744 100644 --- a/src/components/Terminal.tsx +++ b/src/components/Terminal.tsx @@ -116,6 +116,175 @@ const TerminalComponent: React.FC = ({ xtermRef.current.write(`\r\nls: cannot access '${currentPath.current}': Not a directory`); } break; + + case 'pwd': + xtermRef.current.write('\r\n' + currentPath.current); + break; + + case 'find': + if (args.length === 0) { + xtermRef.current.write('\r\nfind: missing path\r\nUsage: find [-name ]'); + break; + } + + const findPath = args[0]; + const nameIndex = args.indexOf('-name'); + const namePattern = nameIndex !== -1 && args[nameIndex + 1] ? args[nameIndex + 1] : ''; + + const findResults: string[] = []; + + // Simple recursive search through filesystem + const searchInPath = (searchPath: string, depth: number = 0) => { + if (depth > 10) return; // Prevent infinite recursion + + const pathContent = fileSystem[searchPath]; + if (Array.isArray(pathContent)) { + pathContent.forEach(item => { + const itemPath = searchPath === '/' ? '/' + item : searchPath + '/' + item; + + // Check if item matches pattern (if specified) + if (!namePattern || item.includes(namePattern.replace(/\*/g, ''))) { + findResults.push(itemPath); + } + + // Recursively search subdirectories + if (Array.isArray(fileSystem[itemPath])) { + searchInPath(itemPath, depth + 1); + } + }); + } + }; + + if (fileSystem[findPath] !== undefined) { + if (!namePattern) { + findResults.push(findPath); + } + searchInPath(findPath); + + if (findResults.length > 0) { + xtermRef.current.write('\r\n' + findResults.join('\r\n')); + } else { + xtermRef.current.write(`\r\nfind: no files found`); + } + } else { + xtermRef.current.write(`\r\nfind: ${findPath}: No such file or directory`); + } + break; + + case 'head': + const headLines = args[0] === '-n' && args[1] ? parseInt(args[1]) : 10; + const headFile = args[0] === '-n' ? args[2] : args[0]; + + if (!headFile) { + xtermRef.current.write('\r\nhead: missing file operand'); + break; + } + + let headFilePath: string; + if (headFile.startsWith('/')) { + headFilePath = headFile; + } else { + headFilePath = (currentPath.current === '/' ? '' : currentPath.current) + '/' + headFile; + } + + const headContent = fileSystem[headFilePath]; + if (headContent === undefined) { + xtermRef.current.write(`\r\nhead: ${headFile}: No such file or directory`); + } else if (Array.isArray(headContent)) { + xtermRef.current.write(`\r\nhead: ${headFile}: Is a directory`); + } else { + const lines = headContent.split('\n'); + const displayLines = lines.slice(0, headLines); + xtermRef.current.write('\r\n' + displayLines.join('\r\n')); + } + break; + + case 'tail': + const tailLines = args[0] === '-n' && args[1] ? parseInt(args[1]) : 10; + const tailFile = args[0] === '-n' ? args[2] : args[0]; + + if (!tailFile) { + xtermRef.current.write('\r\ntail: missing file operand'); + break; + } + + let tailFilePath: string; + if (tailFile.startsWith('/')) { + tailFilePath = tailFile; + } else { + tailFilePath = (currentPath.current === '/' ? '' : currentPath.current) + '/' + tailFile; + } + + const tailContent = fileSystem[tailFilePath]; + if (tailContent === undefined) { + xtermRef.current.write(`\r\ntail: ${tailFile}: No such file or directory`); + } else if (Array.isArray(tailContent)) { + xtermRef.current.write(`\r\ntail: ${tailFile}: Is a directory`); + } else { + const lines = tailContent.split('\n'); + const displayLines = lines.slice(-tailLines); + xtermRef.current.write('\r\n' + displayLines.join('\r\n')); + } + break; + + case 'sl': + // Easter egg: Steam Locomotive animation + xtermRef.current.write('\r\n'); + const trainFrames = [ + " ==== ________ ___________", + " _D _| |_______/ \\__I_I_____===__|_________|", + " |(_)--- | H\\________/ | | =|___ ___|", + " / | | H | | | | ||_| |_||", + " | | | H |__--------------------| [___] |", + " | ________|___H__/__|_____/[][]~\\_______| |", + " |/ | |-----------I_____I [][] [] D |=======|__", + "__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__|", + " |/-=|___|= || || || |_____/~\\___/", + " \\_/ \\O=====O=====O=====O_/ \\_/" + ]; + + // Calculate train width (length of longest line) + const trainWidth = Math.max(...trainFrames.map(line => line.length)); + + // Start position: completely off-screen to the right + let position = xtermRef.current.cols + 10; + + const animateTrain = () => { + // Stop when train is completely off-screen to the left + if (position < -trainWidth - 5) { + xtermRef.current?.write('\x1b[2J\x1b[H'); // Clear screen one final time + prompt(); + return; + } + + // Clear screen and position cursor at top + xtermRef.current?.write('\x1b[2J\x1b[H'); + + // Draw each line of the train with proper spacing + trainFrames.forEach((line) => { + if (position >= 0) { + // Train is partially or fully on screen from the right + const spaces = ' '.repeat(position); + const visiblePart = line.substring(0, xtermRef.current!.cols - position); + xtermRef.current?.write(spaces + visiblePart + '\r\n'); + } else if (position + line.length > 0) { + // Train is partially off-screen to the left + const startIdx = Math.abs(position); + const visiblePart = line.substring(startIdx, startIdx + xtermRef.current!.cols); + xtermRef.current?.write(visiblePart + '\r\n'); + } else { + // Train is completely off-screen to the left, just write empty line + xtermRef.current?.write('\r\n'); + } + }); + + position -= 3; // Move train left by 3 characters each frame + setTimeout(animateTrain, 120); // Slightly slower for smoother animation + }; + + animateTrain(); + return; // Don't call prompt() immediately + case 'cd': const targetDir = args[0]; if (!targetDir) { @@ -188,8 +357,12 @@ const TerminalComponent: React.FC = ({ const helpText = "Available Commands:\r\n" + " ls List directory contents\r\n" + + " pwd Print working directory\r\n" + " cd Change directory\r\n" + " cat Display file contents\r\n" + + " find [-name ] Search for files\r\n" + + " head [-n ] Show first lines of file\r\n" + + " tail [-n ] Show last lines of file\r\n" + " clear Clear the screen\r\n" + " help Display this help message\r\n\r\n" + "Tips:\r\n" +