diff --git a/src/components/TerminalShell.tsx b/src/components/TerminalShell.tsx index 3752ce7..8d1f19e 100644 --- a/src/components/TerminalShell.tsx +++ b/src/components/TerminalShell.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { Terminal } from 'lucide-react'; import { System } from '../shell/system'; +import '../styles/matrix.css'; const TerminalShell: React.FC = () => { const [history, setHistory] = useState(['Welcome to the terminal! Type "help" for commands.']); @@ -20,12 +21,13 @@ const TerminalShell: React.FC = () => { if (system.knowsCommand(cmdName)) { const output = system.executeCommand(cmdName, args); - const newLines = output.split('\\n').filter(l => l); + const newLines = output.split('\n').filter(l => l); setHistory(prev => [...prev, `$ ${command}`, ...newLines]); } else { setHistory(prev => [...prev, `$ ${command}`, `Command not found: ${cmdName}`]); } + setCurrentCommand(''); } }; @@ -34,8 +36,8 @@ const TerminalShell: React.FC = () => {
{history.map((line, index) => ( -
- {line} +
+ {/* Dangerously set inner HTML */}
))}
diff --git a/src/shell/FileSystem.ts b/src/shell/FileSystem.ts index a95ecd0..02ccdac 100644 --- a/src/shell/FileSystem.ts +++ b/src/shell/FileSystem.ts @@ -106,4 +106,6 @@ export class FileSystem { current.children!.delete(fileName); console.log(`[FileSystem] File deleted: ${path}`); } + + } diff --git a/src/shell/ShellSyscall.ts b/src/shell/ShellSyscall.ts index bf58e54..8023807 100644 --- a/src/shell/ShellSyscall.ts +++ b/src/shell/ShellSyscall.ts @@ -40,4 +40,8 @@ export class ShellSyscall { deleteFile(path: string): void { this.fs.deleteFile(`${this.cwd}/${path}`); } + + createDirectory(dirname: string): void { + this.fs.createDirectory(`${this.cwd}/${dirname}`); + } } diff --git a/src/shell/commands/IShellCommand.ts b/src/shell/commands/IShellCommand.ts index 82a24f9..3e7cf42 100644 --- a/src/shell/commands/IShellCommand.ts +++ b/src/shell/commands/IShellCommand.ts @@ -1,4 +1,5 @@ export interface IShellCommand { getName(): string; + getManPage(): string; execute(input: string | string[]): string; } \ No newline at end of file diff --git a/src/shell/commands/about.ts b/src/shell/commands/about.ts new file mode 100644 index 0000000..af5c71e --- /dev/null +++ b/src/shell/commands/about.ts @@ -0,0 +1,20 @@ +import { IShellCommand } from "./IShellCommand"; + +export class about implements IShellCommand { + getName(): string { + return 'about'; + } + + execute(_: string | string[]): string { + let output = 'Really cool terminal ;)'; + output += '\n\n'; + output += 'Built by ivanch'; + output += '\n'; + + return output; + } + + getManPage(): string { + return 'about - about this terminal'; + } +} \ No newline at end of file diff --git a/src/shell/commands/cat.ts b/src/shell/commands/cat.ts index c5decee..030486f 100644 --- a/src/shell/commands/cat.ts +++ b/src/shell/commands/cat.ts @@ -12,4 +12,8 @@ export class cat implements IShellCommand { if (args.length === 0) return "cat: missing file operand"; return this.syscall.readFile(args[0]); } + + getManPage(): string { + return "cat - concatenate files and print on the standard output"; + } } diff --git a/src/shell/commands/cd.ts b/src/shell/commands/cd.ts index afaf87a..009d31f 100644 --- a/src/shell/commands/cd.ts +++ b/src/shell/commands/cd.ts @@ -13,4 +13,8 @@ export class cd implements IShellCommand { const success = this.syscall.changeDirectory(path); return success ? '' : 'Invalid directory'; } + + getManPage(): string { + return "cd - change the shell working directory"; + } } diff --git a/src/shell/commands/echo.ts b/src/shell/commands/echo.ts index 9128d46..cbf0890 100644 --- a/src/shell/commands/echo.ts +++ b/src/shell/commands/echo.ts @@ -11,4 +11,8 @@ export class echo implements IShellCommand { } return input.trim(); } + + getManPage(): string { + return 'echo - echo a line of text'; + } } \ No newline at end of file diff --git a/src/shell/commands/ls.ts b/src/shell/commands/ls.ts index 86e58c8..8d66490 100644 --- a/src/shell/commands/ls.ts +++ b/src/shell/commands/ls.ts @@ -12,4 +12,8 @@ export class ls implements IShellCommand { const files = this.syscall.listCurrentDirectory(); return files.join('\n'); } + + getManPage(): string { + return 'ls - list directory contents'; + } } diff --git a/src/shell/commands/man.ts b/src/shell/commands/man.ts new file mode 100644 index 0000000..b2117f8 --- /dev/null +++ b/src/shell/commands/man.ts @@ -0,0 +1,27 @@ +import { IShellCommand } from "./IShellCommand"; +import { System } from "../system"; + +export class man implements IShellCommand { + getName(): string { + return 'man'; + } + + getManPage(): string { + return 'man - display system reference manuals\n\nUsage: man command_name'; + } + + execute(args: string[]): string { + if (args.length !== 1) { + return 'Usage: man command_name'; + } + + const system = System.getInstance(); + const command = system['commands'].find(c => c.getName() === args[0]); + + if (!command) { + return `No manual entry for ${args[0]}`; + } + + return command.getManPage(); + } +} diff --git a/src/shell/commands/matrix.ts b/src/shell/commands/matrix.ts new file mode 100644 index 0000000..233fcb4 --- /dev/null +++ b/src/shell/commands/matrix.ts @@ -0,0 +1,33 @@ +import { IShellCommand } from "./IShellCommand"; + +export class matrix implements IShellCommand { + getName(): string { + return 'matrix'; + } + + getManPage(): string { + return 'matrix - displays a matrix-style text animation\n\nUsage: matrix'; + } + + execute(): string { + const width = 40; + const height = 15; + const characters = '日ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ'; + + const matrixHtml = ` +
+ ${Array(height).fill(0).map(() => ` +
+ ${Array(width).fill(0).map(() => ` + + ${characters[Math.floor(Math.random() * characters.length)]} + + `).join('')} +
+ `).join('')} +
+ `; + + return matrixHtml; + } +} diff --git a/src/shell/commands/mkdir.ts b/src/shell/commands/mkdir.ts new file mode 100644 index 0000000..99e557b --- /dev/null +++ b/src/shell/commands/mkdir.ts @@ -0,0 +1,23 @@ +import { IShellCommand } from "./IShellCommand"; +import { ShellSyscall } from "../ShellSyscall"; + +export class mkdir implements IShellCommand { + constructor(private syscall: ShellSyscall) {} + + getName(): string { + return 'mkdir'; + } + + getManPage(): string { + return 'mkdir - make directories\n\nUsage: mkdir directory_name'; + } + + execute(args: string[]): string { + if (args.length !== 1) { + return 'Usage: mkdir directory_name'; + } + + this.syscall.createDirectory(args[0]); + return ''; + } +} diff --git a/src/shell/commands/ping.ts b/src/shell/commands/ping.ts new file mode 100644 index 0000000..0c2a0b2 --- /dev/null +++ b/src/shell/commands/ping.ts @@ -0,0 +1,31 @@ +import { IShellCommand } from "./IShellCommand"; + +export class ping implements IShellCommand { + getName(): string { + return 'ping'; + } + + getManPage(): string { + return 'ping - send ICMP ECHO_REQUEST to network hosts\n\nUsage: ping [ip-address]'; + } + + execute(args: string[]): string { + if (args.length !== 1) { + return 'Usage: ping [ip-address]'; + } + + const ip = args[0]; + let output = `PING ${ip} (${ip}) 56(84) bytes of data.\n`; + + for (let i = 0; i < 4; i++) { + const time = Math.random() * 50 + 20; + output += `64 bytes from ${ip}: icmp_seq=${i + 1} ttl=64 time=${time.toFixed(1)} ms\n`; + } + + output += '\n--- ping statistics ---\n'; + output += `4 packets transmitted, 4 received, 0% packet loss, time 3000ms\n`; + output += `rtt min/avg/max/mdev = 20.0/30.0/40.0/5.0 ms\n`; + + return output; + } +} diff --git a/src/shell/commands/pwd.ts b/src/shell/commands/pwd.ts index 197a2c3..cdfcf56 100644 --- a/src/shell/commands/pwd.ts +++ b/src/shell/commands/pwd.ts @@ -11,4 +11,8 @@ export class pwd implements IShellCommand { execute(_: string[]): string { return this.syscall.getCurrentDirectory(); } + + getManPage(): string { + return "pwd - print name of current/working directory"; + } } diff --git a/src/shell/commands/rm.ts b/src/shell/commands/rm.ts index 0d7cbc1..9c4838d 100644 --- a/src/shell/commands/rm.ts +++ b/src/shell/commands/rm.ts @@ -21,4 +21,8 @@ export class rm implements IShellCommand { this.syscall.deleteFile(filename); return `Removed ${filename}`; } + + getManPage(): string { + return "rm - remove files or directories"; + } } diff --git a/src/shell/commands/sudo.ts b/src/shell/commands/sudo.ts new file mode 100644 index 0000000..732ef5d --- /dev/null +++ b/src/shell/commands/sudo.ts @@ -0,0 +1,15 @@ +import { IShellCommand } from "./IShellCommand"; + +export class sudo implements IShellCommand { + getName(): string { + return 'sudo'; + } + + getManPage(): string { + return 'sudo - execute a command as another user\n\nUsage: sudo command'; + } + + execute(args: string[]): string { + return 'Permission denied'; + } +} diff --git a/src/shell/commands/touch.ts b/src/shell/commands/touch.ts index f785b17..a2eb055 100644 --- a/src/shell/commands/touch.ts +++ b/src/shell/commands/touch.ts @@ -13,4 +13,8 @@ export class touch implements IShellCommand { this.syscall.createFile(args[0]); return ""; } + + getManPage(): string { + return "touch - create an empty file"; + } } diff --git a/src/shell/system.ts b/src/shell/system.ts index b354dc7..ae50fad 100644 --- a/src/shell/system.ts +++ b/src/shell/system.ts @@ -5,9 +5,15 @@ import { cd } from "./commands/cd"; import { touch } from "./commands/touch"; import { cat } from "./commands/cat"; import { rm } from "./commands/rm"; +import { ping } from "./commands/ping"; +import { mkdir } from "./commands/mkdir"; +import { man } from "./commands/man"; +import { sudo } from "./commands/sudo"; import { IShellCommand } from "./commands/IShellCommand"; import { FileSystem } from "./FileSystem"; import { ShellSyscall } from "./ShellSyscall"; +import { about } from "./commands/about"; +import { matrix } from "./commands/matrix"; // System class to manage commands class System { @@ -32,6 +38,12 @@ class System { this.commands.push(new touch(syscall)); this.commands.push(new cat(syscall)); this.commands.push(new rm(syscall)); + this.commands.push(new ping()); + this.commands.push(new mkdir(syscall)); + this.commands.push(new man()); + this.commands.push(new sudo()); + this.commands.push(new about()); + // this.commands.push(new matrix()); } public static getInstance(): System { @@ -42,10 +54,14 @@ class System { } knowsCommand(commandName: string): boolean { - return this.commands.some(c => c.getName() === commandName); + return commandName === 'help' || this.commands.some(c => c.getName() === commandName); } executeCommand(commandName: string, args: string[]): string { + if (commandName === 'help') { + return this.commands.map(c => c.getName()).join('\n'); + } + const command = this.commands.find(c => c.getName() === commandName); if (command) { return command.execute(args); diff --git a/src/styles/matrix.css b/src/styles/matrix.css new file mode 100644 index 0000000..47bcc22 --- /dev/null +++ b/src/styles/matrix.css @@ -0,0 +1,30 @@ +.matrix-text-animation { + color: #0F0; + font-family: monospace; + line-height: 1; + display: inline-block; +} +.matrix-line { + white-space: pre; +} +.matrix-char { + opacity: 0; + display: inline-block; + animation: matrix-fade 3s infinite; +} +@keyframes matrix-fade { + 0%, 100% { opacity: 0; } + 50% { opacity: 1; } +} +.matrix-text-animation::after { + content: "Wake up, Neo..."; + display: block; + color: #0F0; + font-weight: bold; + margin-top: 1em; + animation: text-fade 4s infinite; +} +@keyframes text-fade { + 0%, 100% { opacity: 0; } + 50% { opacity: 1; } +} \ No newline at end of file